From c32b5d7278edd3078ac27505e88684d6a8e2c5ca Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Wed, 21 Jan 2026 18:09:30 +0100 Subject: [PATCH 01/10] refactors ; some new additions to recording which are critical for cctv softwares --- src/main/java/io/swtc/Main.java | 1 + src/main/java/io/swtc/SwingCCTVManager.java | 25 ++- src/main/java/io/swtc/SwingIFrame.java | 3 + .../io/swtc/proccessing/ui/IconSetter.java | 40 ++++ .../io/swtc/proccessing/ui/ShowError.java | 4 +- .../ui/iframe/CameraInternalFrame.java | 98 +++------- .../ui/iframe/EffectsPanelFrame.java | 7 + .../proccessing/ui/iframe/RecordingFrame.java | 178 ++++++++++++++++++ .../proccessing/ui/iframe/RecordingPane.java | 126 ------------- .../io/swtc/recording/RecordingManager.java | 70 +++++++ .../java/io/swtc/recording/VideoRecorder.java | 131 +++++-------- src/main/resources/icons/artwork.ico | Bin 0 -> 49587 bytes src/main/resources/icons/artwork.png | Bin 0 -> 3482 bytes src/main/resources/icons/effectsframe.png | Bin 0 -> 62341 bytes 14 files changed, 405 insertions(+), 278 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/IconSetter.java create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java delete mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java create mode 100644 src/main/java/io/swtc/recording/RecordingManager.java create mode 100644 src/main/resources/icons/artwork.ico create mode 100644 src/main/resources/icons/artwork.png create mode 100644 src/main/resources/icons/effectsframe.png diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java index 1f9211f..253b598 100644 --- a/src/main/java/io/swtc/Main.java +++ b/src/main/java/io/swtc/Main.java @@ -2,6 +2,7 @@ package io.swtc; import javax.swing.*; +import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.ShowError; public class Main { diff --git a/src/main/java/io/swtc/SwingCCTVManager.java b/src/main/java/io/swtc/SwingCCTVManager.java index 89cd90a..64091d8 100644 --- a/src/main/java/io/swtc/SwingCCTVManager.java +++ b/src/main/java/io/swtc/SwingCCTVManager.java @@ -8,6 +8,7 @@ import com.github.sarxos.webcam.ds.ipcam.IpCamDriver; import com.github.sarxos.webcam.ds.ipcam.IpCamMode; import io.swtc.networking.CameraConfig; import io.swtc.networking.CameraSettings; +import io.swtc.proccessing.ui.IconSetter; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; @@ -37,6 +38,7 @@ public class SwingCCTVManager { frame = new JFrame("dashboard"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(1000, 600); + frame.setIconImage(IconSetter.getIcon()); this.IFrame = new SwingIFrame(); this.IFrame.show(); @@ -71,14 +73,33 @@ public class SwingCCTVManager { } }); + JToolBar toolBar = new JToolBar(); + + Image ogIcon = IconSetter.getIcon(); + Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH); + JLabel iconLabel = new JLabel(new ImageIcon(scaledIcon)); + toolBar.add(iconLabel); + + JLabel textLabel = new JLabel("SWT-CCTV"); + toolBar.add(textLabel); + + toolBar.add(Box.createRigidArea(new Dimension(10, 0))); // 20px horizontal padding + + toolBar.addSeparator(new Dimension(10, 32)); // 10px width, 32px height + JButton btnAdd = new JButton("Add IP Cam"); JButton btnLaunch = new JButton("Launch Stream"); + + btnAdd.setPreferredSize(new Dimension(100, 32)); + btnLaunch.setPreferredSize(new Dimension(120, 32)); + + toolBar.add(Box.createRigidArea(new Dimension(5,0))); 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); btnAdd.addActionListener(e -> showAddCameraDialog()); @@ -145,12 +166,14 @@ public class SwingCCTVManager { } JPopupMenu menu = new JPopupMenu(); + JMenuItem ico = new JMenuItem("test"); JMenuItem launch = new JMenuItem("Launch Live Stream"); JMenuItem delete = new JMenuItem("Remove Device"); launch.addActionListener(al -> launchSelected()); delete.addActionListener(al -> deleteSelected()); + menu.add(ico); menu.add(launch); menu.addSeparator(); menu.add(delete); diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index ff1cc03..f830cd1 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -1,6 +1,7 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; +import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.iframe.*; // Your custom frames import javax.swing.*; import java.awt.*; @@ -28,6 +29,8 @@ public class SwingIFrame { mainFrame.setSize(1280, 720); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + mainFrame.setIconImage(IconSetter.getIcon()); + desktopPane = new DesktopPane(cameraToEffects); desktopPane.setBackground(defDesktopBg); mainFrame.add(desktopPane, BorderLayout.CENTER); diff --git a/src/main/java/io/swtc/proccessing/ui/IconSetter.java b/src/main/java/io/swtc/proccessing/ui/IconSetter.java new file mode 100644 index 0000000..afbc5ba --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/IconSetter.java @@ -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; + } +} diff --git a/src/main/java/io/swtc/proccessing/ui/ShowError.java b/src/main/java/io/swtc/proccessing/ui/ShowError.java index 5efca5a..b93c7a7 100644 --- a/src/main/java/io/swtc/proccessing/ui/ShowError.java +++ b/src/main/java/io/swtc/proccessing/ui/ShowError.java @@ -12,8 +12,8 @@ public final class ShowError { public static void error(Component parent, String title, String message) { JOptionPane.showMessageDialog( parent, - message, title, + message, JOptionPane.ERROR_MESSAGE ); } @@ -21,8 +21,8 @@ public final class ShowError { public static void warning(Component parent, String title, String message) { JOptionPane.showMessageDialog( parent, - message, title, + message, JOptionPane.WARNING_MESSAGE ); } diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java index 127789a..4eb03ea 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java @@ -3,12 +3,16 @@ package io.swtc.proccessing.ui.iframe; import com.github.sarxos.webcam.Webcam; import io.swtc.proccessing.WebcamCaptureLoop; 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 javax.imageio.ImageIO; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.image.BufferedImage; +import java.awt.image.RescaleOp; import java.io.File; import java.io.IOException; import java.util.function.Consumer; @@ -16,15 +20,19 @@ import java.util.function.Consumer; public class CameraInternalFrame extends JInternalFrame { private final WebcamCaptureLoop captureLoop; private final CameraPanel cameraPanel; - private final VideoRecorder videoRecorder; // Instance of the recorder - private JButton recordBtn; + private final VideoRecorder videoRecorder; + private final RecordingManager recordingManager; public CameraInternalFrame(Webcam webcam, Consumer onOpenEffects) { 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 -> SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) ); @@ -36,11 +44,15 @@ public class CameraInternalFrame extends JInternalFrame { private void setupUI(Consumer onOpenEffects) { JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("View", cameraPanel); - tabbedPane.addTab("Capture", new RecordingPane(cameraPanel, videoRecorder)); + tabbedPane.addTab("Capture", new JPanel()); tabbedPane.addTab("Effects", new JPanel()); 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); onOpenEffects.accept(this); } @@ -50,79 +62,25 @@ public class CameraInternalFrame extends JInternalFrame { setSize(600, 500); } - private JPanel createCapturePanel() { - JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10)); // Changed to Grid for better button layout - panel.setBorder(new EmptyBorder(15, 15, 15, 15)); - - JButton screenshotBtn = new JButton("Take Screenshot"); - screenshotBtn.addActionListener(e -> saveSnapshot()); - - 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 openRecordingFrame() { + RecordingFrame rf = new RecordingFrame(this.getTitle(),cameraPanel, videoRecorder); + JDesktopPane desktopPane = getDesktopPane(); + if (desktopPane != null) { + desktopPane.add(rf); + rf.setVisible(true); } - } - private void startVideo() { try { - File file = new File(System.getProperty("user.home"), "vid_" + System.currentTimeMillis() + ".mp4"); - videoRecorder.startRecording(cameraPanel, file); - - recordBtn.setText("Stop Recording"); - recordBtn.setForeground(Color.RED); - } catch (IOException ex) { - showError("Failed to start recording", ex); + rf.setSelected(true); + } catch (java.beans.PropertyVetoException veto) { + ShowError.error(null,"VetoException"+veto.getMessage(),"veto"); } } - 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; } @Override public void dispose() { - // Safety check: stop recording if the window is closed if (videoRecorder.isRecording()) { try { videoRecorder.stopRecording(); } catch (IOException ignored) {} } diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java index a6533c1..62ab516 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java @@ -2,11 +2,18 @@ package io.swtc.proccessing.ui.iframe; import io.swtc.proccessing.CameraPanel; import io.swtc.proccessing.FilterPanel; +import io.swtc.proccessing.ui.IconSetter; + import javax.swing.*; +import java.awt.*; public class EffectsPanelFrame extends JInternalFrame { public EffectsPanelFrame(String title, CameraPanel cameraPanel) { super(title, true, true, true, true); + + Image ico = IconSetter.getIcon(); + setFrameIcon(new ImageIcon(IconSetter.getEffectIcon())); + setDefaultCloseOperation(HIDE_ON_CLOSE); add(new FilterPanel(cameraPanel)); setSize(350, 600); diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java new file mode 100644 index 0000000..5fd4947 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java @@ -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"); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java deleted file mode 100644 index 22b7d93..0000000 --- a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/RecordingManager.java b/src/main/java/io/swtc/recording/RecordingManager.java new file mode 100644 index 0000000..906e78a --- /dev/null +++ b/src/main/java/io/swtc/recording/RecordingManager.java @@ -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 onStop, Consumer onError) { + if (!recorder.isRecording()) { + new SwingWorker() { + @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() { + @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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/VideoRecorder.java b/src/main/java/io/swtc/recording/VideoRecorder.java index de09f07..352bfec 100644 --- a/src/main/java/io/swtc/recording/VideoRecorder.java +++ b/src/main/java/io/swtc/recording/VideoRecorder.java @@ -6,109 +6,82 @@ import org.jcodec.common.io.NIOUtils; import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.common.model.Rational; -import javax.swing.*; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; - -/* -* -* 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. -* -* */ +import java.util.concurrent.*; public class VideoRecorder { - private boolean recording = false; - private Timer captureTimer; + private volatile boolean recording = false; private File outputFile; private AWTSequenceEncoder encoder; private SeekableByteChannel channel; private static final int FPS = 30; + private LinkedBlockingQueue frameQueue; + private ExecutorService workerThread; + public void startRecording(CameraPanel panel, File output) throws IOException { 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; - channel = NIOUtils.writableFileChannel(String.valueOf(outputFile)); - encoder = new AWTSequenceEncoder(channel, Rational.R(FPS, 1)); + workerThread = Executors.newSingleThreadExecutor(); + workerThread.submit(this::processQueue); - captureTimer = new Timer(1000 / FPS, e -> { - try { - 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 - ); + new Thread(() -> { + while (recording) { try { - stopRecording(); - } catch (IOException stopEx) { - JOptionPane.showMessageDialog( - null, - "IOException@stopEx\n" + stopEx, - "IOException in Recorder@stopEx", - JOptionPane.ERROR_MESSAGE - ); - + BufferedImage img = panel.getCurrentProcessedImage(); + if (img != null) { + BufferedImage copy = snapshotToRGB(img); + frameQueue.offer(copy); + } + Thread.sleep(1000 / FPS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } - }); - captureTimer.start(); + }).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 { recording = false; - - /* some helper methods, i swear its always the same? */ - if (captureTimer != null) { - captureTimer.stop(); + workerThread.shutdown(); + try { + workerThread.awaitTermination(5, TimeUnit.SECONDS); + 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; } - public boolean isRecording() { - 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; - } + public boolean isRecording() { return recording; } } \ No newline at end of file diff --git a/src/main/resources/icons/artwork.ico b/src/main/resources/icons/artwork.ico new file mode 100644 index 0000000000000000000000000000000000000000..2bb9a784af8dd689a7f4cce8296e89b2a9f07eba GIT binary patch literal 49587 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 J!>~R9_&3|`gr5Ka literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/artwork.png b/src/main/resources/icons/artwork.png new file mode 100644 index 0000000000000000000000000000000000000000..0bbf48e6656eb0001272179ce1ba2c6781b1cc60 GIT binary patch literal 3482 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 IM6N<$f*Wjk8UO$Q literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/effectsframe.png b/src/main/resources/icons/effectsframe.png new file mode 100644 index 0000000000000000000000000000000000000000..20daf030d1abb09e189c6945c3a3e6dc91200c71 GIT binary patch literal 62341 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

3ZCv`LO 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 mfBEMkk~N}O=v~l(EkJCn7MEO literal 0 HcmV?d00001 -- 2.49.1 From 40a61835298e17d450fb003cf70004068f252ccf Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 22 Jan 2026 15:33:59 +0100 Subject: [PATCH 02/10] some fixes to config import --- src/main/java/io/swtc/SwingCCTVManager.java | 373 +++++++++--------- .../java/io/swtc/networking/CameraConfig.java | 1 + .../io/swtc/networking/CameraSettings.java | 24 +- 3 files changed, 214 insertions(+), 184 deletions(-) diff --git a/src/main/java/io/swtc/SwingCCTVManager.java b/src/main/java/io/swtc/SwingCCTVManager.java index 64091d8..716786d 100644 --- a/src/main/java/io/swtc/SwingCCTVManager.java +++ b/src/main/java/io/swtc/SwingCCTVManager.java @@ -2,26 +2,37 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; 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.ipcam.IpCamDeviceRegistry; -import com.github.sarxos.webcam.ds.ipcam.IpCamDriver; -import com.github.sarxos.webcam.ds.ipcam.IpCamMode; +import com.github.sarxos.webcam.ds.ipcam.*; import io.swtc.networking.CameraConfig; import io.swtc.networking.CameraSettings; import io.swtc.proccessing.ui.IconSetter; +import io.swtc.proccessing.ui.ShowError; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import java.awt.*; +import java.awt.datatransfer.DataFlavor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.File; +import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URL; import java.util.List; 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 { + System.setProperty("ipcam.connection.timeout", TIMEOUT_MS); Webcam.setDriver(new WebcamCompositeDriver() {{ add(new WebcamDefaultDriver()); add(new IpCamDriver()); @@ -33,228 +44,238 @@ public class SwingCCTVManager { private final JTable deviceTable; private final DefaultTableModel tableModel; private final SwingIFrame IFrame; + private boolean isRefreshing = false; public SwingCCTVManager() { - frame = new JFrame("dashboard"); + frame = new JFrame("Dashboard"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(1000, 600); + frame.setSize(1100, 600); frame.setIconImage(IconSetter.getIcon()); this.IFrame = new SwingIFrame(); this.IFrame.show(); - String[] columns = {"Status", "Device Name", "Type", "Resolution", "Address"}; - tableModel = new DefaultTableModel(columns, 0) { + tableModel = new DefaultTableModel(new String[]{"Status", "Device Name", "Type", "Resolution", "Address"}, 0) { @Override - public boolean isCellEditable(int r, int c) { - return false; - } + public boolean isCellEditable(int r, int c) { return false; } }; deviceTable = new JTable(tableModel); - - deviceTable.setRowSelectionAllowed(true); - deviceTable.setFocusable(true); - deviceTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - setupTableAppearance(); - - deviceTable.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - deviceTable.requestFocusInWindow(); - - if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) { - launchSelected(); - } - if (SwingUtilities.isRightMouseButton(e)) { - showContextMenu(e); - } - } - }); - + setupDragAndDrop(); // Initialize DND + setupListeners(); JToolBar toolBar = new JToolBar(); - - Image ogIcon = IconSetter.getIcon(); - Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH); - JLabel iconLabel = new JLabel(new ImageIcon(scaledIcon)); - toolBar.add(iconLabel); - - JLabel textLabel = new JLabel("SWT-CCTV"); - toolBar.add(textLabel); - - toolBar.add(Box.createRigidArea(new Dimension(10, 0))); // 20px horizontal padding - - toolBar.addSeparator(new Dimension(10, 32)); // 10px width, 32px height - - JButton btnAdd = new JButton("Add IP Cam"); - JButton btnLaunch = new JButton("Launch Stream"); - - btnAdd.setPreferredSize(new Dimension(100, 32)); - btnLaunch.setPreferredSize(new Dimension(120, 32)); - - toolBar.add(Box.createRigidArea(new Dimension(5,0))); - toolBar.add(btnAdd); - toolBar.addSeparator(); - toolBar.add(btnLaunch); + setupBrandedToolbar(toolBar); frame.add(toolBar, BorderLayout.SOUTH); frame.add(new JScrollPane(deviceTable), BorderLayout.CENTER); - btnAdd.addActionListener(e -> showAddCameraDialog()); - btnLaunch.addActionListener(e -> launchSelected()); - startAutoRefresh(); refreshTable(); } - private void setupTableAppearance() { - deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); - deviceTable.setRowHeight(30); - - 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("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 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" - }); + private void setupBrandedToolbar(JToolBar toolBar) { + Image ogIcon = IconSetter.getIcon(); + if (ogIcon != null) { + Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH); + toolBar.add(new JLabel(new ImageIcon(scaledIcon))); } - for (int r : selectedRows) { - if (r < tableModel.getRowCount()) { - deviceTable.addRowSelectionInterval(r, r); + toolBar.add(new JLabel("SWT-CCTV")); + toolBar.add(Box.createRigidArea(new Dimension(10, 0))); + 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 files = (List) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.getName().endsWith(".json")) { + List 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 configs = CameraSettings.loadFromFile(file); + importCameras(configs); } } - private void showContextMenu(MouseEvent e) { - int row = deviceTable.rowAtPoint(e.getPoint()); - if (row >= 0) { - deviceTable.setRowSelectionInterval(row, row); + private void importCameras(List configs) { + if (configs.isEmpty()) { + ShowError.warning(frame, "Import Empty", "No valid camera configurations found in file."); + 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(); - JMenuItem ico = new JMenuItem("test"); - JMenuItem launch = new JMenuItem("Launch Live Stream"); - JMenuItem delete = new JMenuItem("Remove Device"); + private void setupTableAppearance() { + deviceTable.setRowHeight(30); + 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()); - delete.addActionListener(al -> deleteSelected()); + private synchronized void refreshTable() { + if (isRefreshing) return; + isRefreshing = true; + final int selectedRow = deviceTable.getSelectedRow(); + new SwingWorker() { + @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 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(ico); - menu.add(launch); - menu.addSeparator(); - menu.add(delete); - menu.show(deviceTable, e.getX(), e.getY()); + private Object[] probeIpCamera(IpCamDevice ipDevice) { + String status = STATUS_OFFLINE; String resText = "N/A"; + try { + 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() { int row = deviceTable.getSelectedRow(); if (row == -1) return; - String name = (String) tableModel.getValueAt(row, 1); - Webcam.getWebcams().stream() - .filter(w -> w.getName().equals(name)) - .findFirst() - .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 - ); - } + if (STATUS_OFFLINE.equals(tableModel.getValueAt(row, 0))) { + ShowError.warning(frame, "Device Offline", "Cannot connect to '" + name + "'."); + return; } + 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() { JPanel p = new JPanel(new GridLayout(2, 2, 5, 5)); - JTextField n = new JTextField(); - JTextField u = new JTextField(); - - p.add(new JLabel("Name:")); - 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()); - } + JTextField n = 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) { + importCameras(List.of(new CameraConfig(n.getText(), u.getText()))); } } - public void open() { - frame.setLocationRelativeTo(null); - frame.setVisible(true); + private void setupListeners() { + deviceTable.addMouseListener(new MouseAdapter() { + @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) { SwingUtilities.invokeLater(() -> new SwingCCTVManager().open()); } -} +} \ No newline at end of file diff --git a/src/main/java/io/swtc/networking/CameraConfig.java b/src/main/java/io/swtc/networking/CameraConfig.java index d3814a0..1716f54 100644 --- a/src/main/java/io/swtc/networking/CameraConfig.java +++ b/src/main/java/io/swtc/networking/CameraConfig.java @@ -5,6 +5,7 @@ public class CameraConfig { public String url; // Default constructor for Jackson + public CameraConfig() {} public CameraConfig(String name, String url) { this.name = name; diff --git a/src/main/java/io/swtc/networking/CameraSettings.java b/src/main/java/io/swtc/networking/CameraSettings.java index 15e6268..e8541fa 100644 --- a/src/main/java/io/swtc/networking/CameraSettings.java +++ b/src/main/java/io/swtc/networking/CameraSettings.java @@ -7,24 +7,32 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -/* -* Some JSON stuff for camera config saving -* */ + public class CameraSettings { - private static final File storage_file = new File("network_cameras.json"); - private static final ObjectMapper mapper = new ObjectMapper(); + private static final File STORAGE_FILE = new File("network_cameras.json"); + private static final ObjectMapper MAPPER = new ObjectMapper(); public static List load() { - if (!storage_file.exists() || storage_file.length() == 0) return new ArrayList<>(); + return loadFromFile(STORAGE_FILE); + } + + public static List loadFromFile(File file) { + if (!file.exists() || file.length() == 0) return new ArrayList<>(); try { - return mapper.readValue(storage_file, new TypeReference>() {}); + return MAPPER.readValue(file, new TypeReference>() {}); } catch (IOException e) { + System.err.println("Error reading camera config: " + e.getMessage()); return new ArrayList<>(); } } + /** + * Saves a single camera. Prevents duplicates by name. + */ public static void save(CameraConfig newCam) { List current = load(); + // Prevent duplicate names in the local storage + current.removeIf(cam -> cam.getName().equalsIgnoreCase(newCam.getName())); current.add(newCam); write(current); } @@ -37,7 +45,7 @@ public class CameraSettings { private static void write(List list) { try { - mapper.writerWithDefaultPrettyPrinter().writeValue(storage_file, list); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(STORAGE_FILE, list); } catch (IOException e) { e.printStackTrace(); } -- 2.49.1 From 98ff3b9b769646af31357acf8720356d6a34b457 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Fri, 23 Jan 2026 19:18:37 +0100 Subject: [PATCH 03/10] zooming in CameraPanel.java with new methods and more efficient rendering! --- .gitignore | 5 +- .../java/io/swtc/proccessing/CameraPanel.java | 179 ++++++++++++++++-- 2 files changed, 163 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 7568db7..d1c36b7 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ dependency-reduced-pom.xml .idea ## This is for our app, cause it likes to store stuff ## -network_cameras.json \ No newline at end of file +network_cameras.json + +## exec launch4j config ## +execfg.xml \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java index e27851b..1657e8e 100644 --- a/src/main/java/io/swtc/proccessing/CameraPanel.java +++ b/src/main/java/io/swtc/proccessing/CameraPanel.java @@ -1,30 +1,106 @@ package io.swtc.proccessing; +import io.swtc.proccessing.ui.ShowError; + import javax.swing.*; import java.awt.*; +import java.awt.event.*; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; -/** - * Enhanced CameraPanel with support for custom image processors - */ public class CameraPanel extends JPanel { private volatile BufferedImage sourceImage; private volatile BufferedImage processedImage; - private Function imageProcessor; private final AtomicBoolean repaintScheduled = new AtomicBoolean(false); + private double zoomLevel = 1.0; + private double xOffset = 0; + private double yOffset = 0; + private Point dragStartPoint; + + private static final double MIN_ZOOM = 1.0; + private static final double MAX_ZOOM = 10.0; // ten is enough if ur using 640x480 + private static final double ZOOM_MULTIPLIER = 1.1; + public CameraPanel() { setBackground(Color.BLACK); setPreferredSize(new Dimension(640, 480)); + + initInteractionListeners(); + } + + private void initInteractionListeners() { + MouseAdapter mouseHandler = new MouseAdapter() { + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + handleZoom(e); + } + + @Override + public void mousePressed(MouseEvent e) { + dragStartPoint = e.getPoint(); + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) + resetView(); + } + + @Override + public void mouseReleased(MouseEvent e) { setCursor(Cursor.getDefaultCursor()); } + + @Override + public void mouseDragged(MouseEvent e) { + handlePan(e); + } + + @Override + public void mouseExited(MouseEvent e) { setCursor(Cursor.getDefaultCursor()); } + }; + + addMouseWheelListener(mouseHandler); + addMouseListener(mouseHandler); + addMouseMotionListener(mouseHandler); } public void setImage(BufferedImage img) { sourceImage = img; + updateProcessedImage(); + scheduleRepaint(); + } + + public void setImageProcessor(Function processor) { + this.imageProcessor = processor; + if (sourceImage != null) { + updateProcessedImage(); + scheduleRepaint(); + } + } + + private void updateProcessedImage() { + if (sourceImage == null) return; + + if (imageProcessor != null) { + try { + processedImage = imageProcessor.apply(sourceImage); + } catch (Exception e) { + ShowError.error(null,"Fucked up in rendering \n" + Arrays.toString(e.getStackTrace()),"Problem"); + processedImage = sourceImage; // Fallback + } + } else { + processedImage = sourceImage; + } + } + + private void scheduleRepaint() { if (repaintScheduled.compareAndSet(false, true)) { SwingUtilities.invokeLater(() -> { repaintScheduled.set(false); @@ -33,37 +109,100 @@ public class CameraPanel extends JPanel { } } - public void setImageProcessor(Function processor) { - this.imageProcessor = processor; + private void handleZoom(MouseWheelEvent e) { + if (processedImage == null) return; + + double oldZoom = zoomLevel; + + if (e.getWheelRotation() < 0) { + zoomLevel *= ZOOM_MULTIPLIER; // Zoom In + } else { + zoomLevel /= ZOOM_MULTIPLIER; // Zoom Out + } + + // clamp shit + zoomLevel = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoomLevel)); + + if (oldZoom != zoomLevel) { + double xRel = e.getX() - xOffset; + double yRel = e.getY() - yOffset; + + double zoomFactor = zoomLevel / oldZoom; + + xOffset = e.getX() - (xRel * zoomFactor); + yOffset = e.getY() - (yRel * zoomFactor); + + checkBounds(); + repaint(); + } + } + + private void handlePan(MouseEvent e) { + if (processedImage == null || dragStartPoint == null) return; + + // Calculate delta + int dx = e.getX() - dragStartPoint.x; + int dy = e.getY() - dragStartPoint.y; + + xOffset += dx; + yOffset += dy; + + dragStartPoint = e.getPoint(); + + checkBounds(); + repaint(); + } + + private void checkBounds() { + double viewedWidth = getWidth() * zoomLevel; + double viewedHeight = getHeight() * zoomLevel; + + if (xOffset > 0) xOffset = 0; + if (yOffset > 0) yOffset = 0; + + if (xOffset + viewedWidth < getWidth()) { + xOffset = getWidth() - viewedWidth; + } + if (yOffset + viewedHeight < getHeight()) { + yOffset = getHeight() - viewedHeight; + } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); - BufferedImage src = sourceImage; - if (src == null) return; - - BufferedImage img = src; - - 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; + if (processedImage == null) return; Graphics2D g2d = (Graphics2D) g; + + AffineTransform originalTransform = g2d.getTransform(); + g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ); - g2d.drawImage(img, 0, 0, getWidth(), getHeight(), null); + g2d.translate(xOffset, yOffset); + + double scaleX = (double) getWidth() / processedImage.getWidth(); + double scaleY = (double) getHeight() / processedImage.getHeight(); + + g2d.scale(scaleX * zoomLevel, scaleY * zoomLevel); + + g2d.drawImage(processedImage, 0, 0, null); + + g2d.setTransform(originalTransform); } public BufferedImage getCurrentProcessedImage() { return processedImage; } -} + + public void resetView() { + zoomLevel = 1.0; + xOffset = 0; + yOffset = 0; + repaint(); + } +} \ No newline at end of file -- 2.49.1 From c0aa3421a44d8737de41ba6dc65311dbf2ed74b2 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sun, 25 Jan 2026 19:39:03 +0100 Subject: [PATCH 04/10] some UI Updates for Picking a custom Background color, and having Multiple Monitor Fullscreen! --- src/main/java/io/swtc/SwingIFrame.java | 64 +++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index f830cd1..033ea8b 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -2,7 +2,7 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; import io.swtc.proccessing.ui.IconSetter; -import io.swtc.proccessing.ui.iframe.*; // Your custom frames +import io.swtc.proccessing.ui.iframe.*; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; @@ -10,6 +10,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class SwingIFrame { private final JFrame mainFrame; @@ -109,7 +110,14 @@ public class SwingIFrame { popupMenu.removeAll(); // clean slate JCheckBoxMenuItem fullscreenItem = new JCheckBoxMenuItem("Fullscreen"); + JCheckBoxMenuItem mmfullscreenItem = new JCheckBoxMenuItem("Multi Monitor Fullscreen"); + JCheckBoxMenuItem backgroundcolor = new JCheckBoxMenuItem("Calmer Background"); + JMenuItem colorpicker = new JMenuItem("Set background color"); + fullscreenItem.addActionListener(e -> toggleFullscreen()); + mmfullscreenItem.addActionListener(e -> toggleMMFullscreen()); + backgroundcolor.addActionListener(e -> toggleBackground()); + colorpicker.addActionListener(e -> chooseBgColor()); popupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { @Override @@ -121,6 +129,22 @@ public class SwingIFrame { }); popupMenu.add(fullscreenItem); + popupMenu.add(mmfullscreenItem); + popupMenu.add(backgroundcolor); + popupMenu.add(colorpicker); + } + + private void chooseBgColor() { + Color selected = JColorChooser.showDialog( + mainFrame, + "Select Background Color", + desktopPane.getBackground() + ); + + if (!Objects.isNull(selected)) { + desktopPane.setBackground(selected); + desktopPane.repaint(); + } } private void setupBlackBg() { @@ -137,6 +161,7 @@ public class SwingIFrame { private void setupFullscreenToggle() { JRootPane root = mainFrame.getRootPane(); + // One Monitor FS root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) .put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen"); root.getActionMap().put("toggleFullscreen", new AbstractAction() { @@ -145,6 +170,16 @@ public class SwingIFrame { toggleFullscreen(); } }); + + // Multi Monitor FS + root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke("F12"), "toggleMMFullscreen"); + root.getActionMap().put("toggleMMFullscreen", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + toggleMMFullscreen(); + } + }); } private void toggleBackground() { @@ -153,6 +188,33 @@ public class SwingIFrame { desktopPane.repaint(); } + private void toggleMMFullscreen() { + if (!fullscreen) { + windowedBounds = mainFrame.getBounds(); + + Rectangle virtualBounds = new Rectangle(); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] gs = ge.getScreenDevices(); + + for (GraphicsDevice gd : gs) { + GraphicsConfiguration[] gc = gd.getConfigurations(); + for (GraphicsConfiguration configuration : gc) { + virtualBounds = virtualBounds.union(configuration.getBounds()); + } + } + + mainFrame.dispose(); + mainFrame.setUndecorated(true); + + mainFrame.setBounds(virtualBounds); + mainFrame.setVisible(true); + + fullscreen = true; + } else { + toggleFullscreen(); + } + } + /** Toggle fullscreen mode */ private void toggleFullscreen() { if (!fullscreen) { -- 2.49.1 From d775a33107a767f2e36057613096f06e7478b616 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 29 Jan 2026 16:40:36 +0100 Subject: [PATCH 05/10] some more performance update: Changed: + FFmpeg with JavaCV + Exporting to a USB Removed: - JCodec! --- pom.xml | 128 +++++++++---- src/main/java/io/swtc/Main.java | 10 +- .../java/io/swtc/proccessing/CameraPanel.java | 148 ++++++++------- .../ui/iframe/CameraInternalFrame.java | 34 +--- .../proccessing/ui/iframe/RecordingFrame.java | 176 ++++++++++++++---- .../ui/sections/recording/ExportSection.java | 135 ++++++++++++++ .../io/swtc/recording/RecordingManager.java | 70 ------- .../java/io/swtc/recording/VideoRecorder.java | 87 --------- .../java/io/swtc/recording/cv/AVRecorder.java | 60 ++++++ .../io/swtc/recording/cv/FrameProccessor.java | 32 ++++ .../java/io/swtc/recording/cv/MediaSink.java | 45 +++++ .../io/swtc/recording/cv/RecorderConfig.java | 12 ++ .../swtc/recording/evidence/ExportStats.java | 42 +++++ .../recording/evidence/USBExportManager.java | 114 ++++++++++++ 14 files changed, 766 insertions(+), 327 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java delete mode 100644 src/main/java/io/swtc/recording/RecordingManager.java delete mode 100644 src/main/java/io/swtc/recording/VideoRecorder.java create mode 100644 src/main/java/io/swtc/recording/cv/AVRecorder.java create mode 100644 src/main/java/io/swtc/recording/cv/FrameProccessor.java create mode 100644 src/main/java/io/swtc/recording/cv/MediaSink.java create mode 100644 src/main/java/io/swtc/recording/cv/RecorderConfig.java create mode 100644 src/main/java/io/swtc/recording/evidence/ExportStats.java create mode 100644 src/main/java/io/swtc/recording/evidence/USBExportManager.java diff --git a/pom.xml b/pom.xml index 5e4fbc7..4bccba9 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.swtc swtc 1.0-SNAPSHOT - + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.1 + + + package + + shade + + + + + + io.swtc.Main + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + 17 @@ -63,30 +100,30 @@ 0.3.12 - - - org.lwjgl - lwjgl - 3.3.3 - - - org.lwjgl - lwjgl-opengl - 3.3.3 - + + + + + + + + + + + - - org.lwjgl - lwjgl - 3.3.3 - natives-windows - - - org.lwjgl - lwjgl-opengl - 3.3.3 - natives-windows - + + + + + + + + + + + + @@ -106,22 +143,35 @@ 2.20.1 - - - - org.jcodec - jcodec - 0.2.5 - + + + + + + + + + - - - org.jcodec - jcodec-javase - 0.2.5 - + + + + + + + + + org.bytedeco + javacv + 1.5.10 + + + + org.bytedeco + ffmpeg + 6.1.1-1.5.10 + windows-x86_64 + \ No newline at end of file diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java index 253b598..c62e48e 100644 --- a/src/main/java/io/swtc/Main.java +++ b/src/main/java/io/swtc/Main.java @@ -8,15 +8,13 @@ import io.swtc.proccessing.ui.ShowError; public class Main { public static void main(String[] args) { - for (int i = 0; i < args.length; i++) { - System.out.println("Arg " + i + ": " + args[i]); - } +// for (int i = 0; i < args.length; i++) { +// System.out.println("Arg " + i + ": " + args[i]); +// } try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - ShowError.warning(null,"LaF Warn","LaF"); - } + } catch (Exception e) { /* Do nothing */ } SwingCCTVManager.main(null); } diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java index 1657e8e..bf24ebf 100644 --- a/src/main/java/io/swtc/proccessing/CameraPanel.java +++ b/src/main/java/io/swtc/proccessing/CameraPanel.java @@ -1,11 +1,11 @@ package io.swtc.proccessing; import io.swtc.proccessing.ui.ShowError; +import io.swtc.recording.cv.AVRecorder; import javax.swing.*; import java.awt.*; import java.awt.event.*; -import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; @@ -16,6 +16,8 @@ public class CameraPanel extends JPanel { private volatile BufferedImage sourceImage; private volatile BufferedImage processedImage; private Function imageProcessor; + + private volatile AVRecorder recorder; // this is now javacv, jcodec is stoopid private final AtomicBoolean repaintScheduled = new AtomicBoolean(false); private double zoomLevel = 1.0; @@ -24,22 +26,25 @@ public class CameraPanel extends JPanel { private Point dragStartPoint; private static final double MIN_ZOOM = 1.0; - private static final double MAX_ZOOM = 10.0; // ten is enough if ur using 640x480 + private static final double MAX_ZOOM = 10.0; private static final double ZOOM_MULTIPLIER = 1.1; + private final GraphicsConfiguration graphicsConfig; + public CameraPanel() { setBackground(Color.BLACK); setPreferredSize(new Dimension(640, 480)); + graphicsConfig = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration(); + initInteractionListeners(); } private void initInteractionListeners() { MouseAdapter mouseHandler = new MouseAdapter() { @Override - public void mouseWheelMoved(MouseWheelEvent e) { - handleZoom(e); - } + public void mouseWheelMoved(MouseWheelEvent e) { handleZoom(e); } @Override public void mousePressed(MouseEvent e) { @@ -49,17 +54,14 @@ public class CameraPanel extends JPanel { @Override public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) - resetView(); + if (e.getClickCount() == 2) resetView(); } @Override public void mouseReleased(MouseEvent e) { setCursor(Cursor.getDefaultCursor()); } @Override - public void mouseDragged(MouseEvent e) { - handlePan(e); - } + public void mouseDragged(MouseEvent e) { handlePan(e); } @Override public void mouseExited(MouseEvent e) { setCursor(Cursor.getDefaultCursor()); } @@ -70,10 +72,20 @@ public class CameraPanel extends JPanel { addMouseMotionListener(mouseHandler); } + public void setExternalRecorder(AVRecorder recorder) { + this.recorder = recorder; + } + public void setImage(BufferedImage img) { - sourceImage = img; + this.sourceImage = img; updateProcessedImage(); + // Feed the AVRecorder using its 'accept' method if active + AVRecorder currentRecorder = this.recorder; + if (currentRecorder != null && currentRecorder.isRecording() && processedImage != null) { + currentRecorder.accept(processedImage); + } + scheduleRepaint(); } @@ -85,19 +97,46 @@ public class CameraPanel extends JPanel { } } + public BufferedImage getCurrentProcessedImage() { + return processedImage; + } + + public void resetView() { + zoomLevel = 1.0; + xOffset = 0; + yOffset = 0; + repaint(); + } + private void updateProcessedImage() { if (sourceImage == null) return; + BufferedImage temp; if (imageProcessor != null) { try { - processedImage = imageProcessor.apply(sourceImage); + temp = imageProcessor.apply(sourceImage); } catch (Exception e) { - ShowError.error(null,"Fucked up in rendering \n" + Arrays.toString(e.getStackTrace()),"Problem"); - processedImage = sourceImage; // Fallback + ShowError.error(null, "Error in image processing: \n" + Arrays.toString(e.getStackTrace()), "Processing Error"); + temp = sourceImage; } } else { - processedImage = sourceImage; + temp = sourceImage; } + + if (processedImage == null || + processedImage.getWidth() != temp.getWidth() || + processedImage.getHeight() != temp.getHeight()) { + + processedImage = graphicsConfig.createCompatibleImage( + temp.getWidth(), + temp.getHeight(), + temp.getTransparency() + ); + } + + Graphics2D g2d = processedImage.createGraphics(); + g2d.drawImage(temp, 0, 0, null); + g2d.dispose(); } private void scheduleRepaint() { @@ -109,24 +148,46 @@ public class CameraPanel extends JPanel { } } + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + if (processedImage == null) return; + + Graphics2D g2d = (Graphics2D) g.create(); + try { + g2d.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR + ); + + g2d.translate((int)xOffset, (int)yOffset); + + double scaleX = (double) getWidth() / processedImage.getWidth(); + double scaleY = (double) getHeight() / processedImage.getHeight(); + + g2d.scale(scaleX * zoomLevel, scaleY * zoomLevel); + g2d.drawImage(processedImage, 0, 0, null); + + } finally { + g2d.dispose(); + } + } + private void handleZoom(MouseWheelEvent e) { if (processedImage == null) return; double oldZoom = zoomLevel; - if (e.getWheelRotation() < 0) { - zoomLevel *= ZOOM_MULTIPLIER; // Zoom In + zoomLevel *= ZOOM_MULTIPLIER; } else { - zoomLevel /= ZOOM_MULTIPLIER; // Zoom Out + zoomLevel /= ZOOM_MULTIPLIER; } - // clamp shit zoomLevel = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoomLevel)); if (oldZoom != zoomLevel) { double xRel = e.getX() - xOffset; double yRel = e.getY() - yOffset; - double zoomFactor = zoomLevel / oldZoom; xOffset = e.getX() - (xRel * zoomFactor); @@ -140,15 +201,10 @@ public class CameraPanel extends JPanel { private void handlePan(MouseEvent e) { if (processedImage == null || dragStartPoint == null) return; - // Calculate delta - int dx = e.getX() - dragStartPoint.x; - int dy = e.getY() - dragStartPoint.y; - - xOffset += dx; - yOffset += dy; + xOffset += e.getX() - dragStartPoint.x; + yOffset += e.getY() - dragStartPoint.y; dragStartPoint = e.getPoint(); - checkBounds(); repaint(); } @@ -167,42 +223,4 @@ public class CameraPanel extends JPanel { yOffset = getHeight() - viewedHeight; } } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - if (processedImage == null) return; - - Graphics2D g2d = (Graphics2D) g; - - AffineTransform originalTransform = g2d.getTransform(); - - g2d.setRenderingHint( - RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR - ); - - g2d.translate(xOffset, yOffset); - - double scaleX = (double) getWidth() / processedImage.getWidth(); - double scaleY = (double) getHeight() / processedImage.getHeight(); - - g2d.scale(scaleX * zoomLevel, scaleY * zoomLevel); - - g2d.drawImage(processedImage, 0, 0, null); - - g2d.setTransform(originalTransform); - } - - public BufferedImage getCurrentProcessedImage() { - return processedImage; - } - - public void resetView() { - zoomLevel = 1.0; - xOffset = 0; - yOffset = 0; - repaint(); - } } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java index 4eb03ea..16a0349 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java @@ -5,23 +5,13 @@ import io.swtc.proccessing.WebcamCaptureLoop; 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 javax.imageio.ImageIO; import javax.swing.*; -import javax.swing.border.EmptyBorder; import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.RescaleOp; -import java.io.File; -import java.io.IOException; import java.util.function.Consumer; public class CameraInternalFrame extends JInternalFrame { private final WebcamCaptureLoop captureLoop; private final CameraPanel cameraPanel; - private final VideoRecorder videoRecorder; - private final RecordingManager recordingManager; public CameraInternalFrame(Webcam webcam, Consumer onOpenEffects) { super(webcam.getName(), true, true, true, true); @@ -30,12 +20,8 @@ public class CameraInternalFrame extends JInternalFrame { 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 -> - SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) - ); + this.captureLoop = new WebcamCaptureLoop(webcam, cameraPanel::setImage); setupUI(onOpenEffects); captureLoop.start(); @@ -49,7 +35,7 @@ public class CameraInternalFrame extends JInternalFrame { tabbedPane.addChangeListener(e -> { int index = tabbedPane.getSelectedIndex(); - if (index == 1) { // the tab index for capture is 1 + if (index == 1) { tabbedPane.setSelectedIndex(0); openRecordingFrame(); } else if (index == 2) { @@ -63,17 +49,16 @@ public class CameraInternalFrame extends JInternalFrame { } private void openRecordingFrame() { - RecordingFrame rf = new RecordingFrame(this.getTitle(),cameraPanel, videoRecorder); + RecordingFrame rf = new RecordingFrame(this.getTitle(), cameraPanel); JDesktopPane desktopPane = getDesktopPane(); if (desktopPane != null) { desktopPane.add(rf); rf.setVisible(true); - } - - try { - rf.setSelected(true); - } catch (java.beans.PropertyVetoException veto) { - ShowError.error(null,"VetoException"+veto.getMessage(),"veto"); + try { + rf.setSelected(true); + } catch (java.beans.PropertyVetoException veto) { + ShowError.error(null, "Focus Error: " + veto.getMessage(), "Error"); + } } } @@ -81,9 +66,6 @@ public class CameraInternalFrame extends JInternalFrame { @Override public void dispose() { - if (videoRecorder.isRecording()) { - try { videoRecorder.stopRecording(); } catch (IOException ignored) {} - } captureLoop.stop(); super.dispose(); } diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java index 5fd4947..53cc3ce 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java @@ -3,7 +3,10 @@ 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 io.swtc.proccessing.ui.sections.recording.ExportSection; +import io.swtc.recording.cv.AVRecorder; +import io.swtc.recording.cv.RecorderConfig; +import io.swtc.recording.evidence.USBExportManager; import javax.swing.*; import java.awt.*; @@ -11,51 +14,72 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; +import java.nio.file.Path; public class RecordingFrame extends JInternalFrame { - private final VideoRecorder videoRecorder; + private AVRecorder avRecorder; + private ExportSection exportSection; private final CameraPanel cameraPanel; private JButton recordBtn; private JLabel statusLabel; private JLabel statsLabel; - private File outputDirectory = new File(System.getProperty("user.home")); + private File outputDirectory; 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) { + public RecordingFrame(String cameraName, CameraPanel cameraPanel) { super(cameraName + " Capture", true, true, false, true); + setupDirectory(); + Image ico = IconSetter.getIcon(); setFrameIcon(new ImageIcon(ico)); this.cameraPanel = cameraPanel; - this.videoRecorder = videoRecorder; initializeUI(); + // Timer for UI updates only (1 FPS is enough for the clock) this.statsTimer = new Timer(1000, e -> updateStats()); this.statsTimer.setCoalesce(true); pack(); } + private void setupDirectory() { + File videoDir = new File(System.getProperty("user.home"), "Videos"); + outputDirectory = new File(videoDir, "swtcctv-rec"); + + if (!outputDirectory.exists()) { + boolean created = outputDirectory.mkdirs(); + if (!created) { + System.err.println("Could not create recording directory: " + outputDirectory.getAbsolutePath()); + } + } + } + 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()); + exportSection = new ExportSection(this, outputDirectory, statusLabel); + + JPanel actionPanel = createActionPanel(); + + JPanel statsPanel = createStatsPanel(); + + mainContent.add(exportSection); mainContent.add(Box.createVerticalStrut(10)); - mainContent.add(createActionPanel()); + mainContent.add(statsPanel); mainContent.add(Box.createVerticalStrut(10)); - mainContent.add(createStatsPanel()); + mainContent.add(actionPanel); + getContentPane().add(mainContent); } @@ -65,11 +89,9 @@ public class RecordingFrame extends JInternalFrame { 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)); + statsLabel.setPreferredSize(new Dimension(240, 20)); panel.add(statusLabel); panel.add(statsLabel); @@ -77,7 +99,7 @@ public class RecordingFrame extends JInternalFrame { } private void updateStats() { - if (!videoRecorder.isRecording() || currentFile == null) return; + if (avRecorder == null || !avRecorder.isRecording() || currentFile == null) return; long elapsedSecs = (System.currentTimeMillis() - startTime) / 1000; long minutes = elapsedSecs / 60; @@ -90,14 +112,15 @@ public class RecordingFrame extends JInternalFrame { .append(minutes < 10 ? "0" : "").append(minutes).append(":") .append(seconds < 10 ? "0" : "").append(seconds) .append(" | Size: ") - .append(Math.round(sizeInMb * 100.0) / 100.0) + .append(String.format("%.2f", sizeInMb)) .append(" MB"); statsLabel.setText(sb.toString()); } private void toggleRecording() { - if (!videoRecorder.isRecording()) { + // Updated check for the new recorder state + if (avRecorder == null || !avRecorder.isRecording()) { startRec(); } else { stopRec(); @@ -105,38 +128,70 @@ public class RecordingFrame extends JInternalFrame { } private void startRec() { + BufferedImage sample = cameraPanel.getCurrentProcessedImage(); + if (sample == null) { + ShowError.warning(this, "No camera feed detected. Start camera first.", "Warning"); + return; + } + try { + if (!outputDirectory.exists() && !outputDirectory.mkdirs()) { + throw new IOException("Failed to create directory: " + outputDirectory); + } + currentFile = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4"); - videoRecorder.startRecording(cameraPanel, currentFile); + + // 1. Define the production config + RecorderConfig config = new RecorderConfig( + currentFile, + sample.getWidth(), + sample.getHeight(), + 20, // Frame Rate + 25, // CRF (Quality) + "ultrafast" + ); + + // 2. Initialize the HA Recorder + avRecorder = new AVRecorder(config); + avRecorder.start(); + + // 3. Link to CameraPanel (Ensure CameraPanel calls .accept(img)) + cameraPanel.setExternalRecorder(avRecorder); startTime = System.currentTimeMillis(); - statsTimer.start(); // Only run timer when needed + statsTimer.start(); recordBtn.setText("Stop Recording"); - statusLabel.setText("Currently Recording"); - } catch (IOException ex) { - ShowError.error(null,"Error starting Recording" + ex.getMessage(), "Error"); + recordBtn.setForeground(Color.RED); + statusLabel.setText("Recording..."); + } catch (Exception ex) { + ShowError.error(this, "Failed to start Recorder: " + ex.getMessage(), "Error"); + ex.printStackTrace(); } } 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"); + if (avRecorder != null) { + statusLabel.setText("Finalizing file..."); + avRecorder.stop(); + cameraPanel.setExternalRecorder(null); } + + statsTimer.stop(); + recordBtn.setText("Start Recording"); + recordBtn.setForeground(null); + + String fileName = (currentFile != null) ? currentFile.getName() : "N/A"; + statusLabel.setText("File saved: " + fileName); } private JPanel createStoragePanel() { JPanel panel = new JPanel(new BorderLayout(5, 5)); - panel.setBorder(BorderFactory.createTitledBorder("Storage")); + panel.setBorder(BorderFactory.createTitledBorder("Storage Folder")); + JTextField pathField = new JTextField(outputDirectory.getAbsolutePath()); pathField.setEditable(false); + JButton browseBtn = new JButton("..."); browseBtn.addActionListener(e -> { JFileChooser chooser = new JFileChooser(outputDirectory); @@ -146,17 +201,70 @@ public class RecordingFrame extends JInternalFrame { pathField.setText(outputDirectory.getAbsolutePath()); } }); + panel.add(pathField, BorderLayout.CENTER); panel.add(browseBtn, BorderLayout.EAST); return panel; } + private void exportToUSB() { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setDialogTitle("Select USB destination"); + + if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return; + + Path usbRoot = chooser.getSelectedFile().toPath(); + Path exportTarget = usbRoot.resolve("CCTV_EXPORT"); + + try { + long size = USBExportManager.calculateDirectorySize(outputDirectory.toPath()); + + if (!USBExportManager.hasEnoughSpace(usbRoot, size)) { + ShowError.error(this, "Not enough space on USB device.", "Export failed"); + return; + } + + JProgressBar bar = new JProgressBar(0, 100); + JOptionPane pane = new JOptionPane(bar, + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.DEFAULT_OPTION, + null, + new Object[]{}); + + JDialog dialog = pane.createDialog(this, "Exporting..."); + dialog.setModal(false); + dialog.setVisible(true); + + USBExportManager.exportAsync( + outputDirectory.toPath(), + exportTarget, + stats -> bar.setValue(stats.percent()), + () -> { + dialog.dispose(); + statusLabel.setText("Export completed"); + }, + ex -> { + dialog.dispose(); + ShowError.error(this, ex.getMessage(), "Export error"); + } + ); + + } catch (IOException ex) { + ShowError.error(this, ex.getMessage(), "Export error"); + } + } + + 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()); + + // Export is now handled by the ExportSection component panel.add(recordBtn); panel.add(snapBtn); return panel; @@ -166,13 +274,13 @@ public class RecordingFrame extends JInternalFrame { BufferedImage img = cameraPanel.getCurrentProcessedImage(); if (img != null) { try { + if (!outputDirectory.exists()) outputDirectory.mkdirs(); File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png"); ImageIO.write(img, "PNG", file); - statusLabel.setText("Saved a Snapshot"); + statusLabel.setText("Snapshot: " + file.getName()); } catch (IOException ex) { - ShowError.error(null,"Snapshot failed"+ex.getMessage(),"Snapshot Error"); + ShowError.error(this, "Snapshot failed: " + ex.getMessage(), "Error"); } } } - } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java b/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java new file mode 100644 index 0000000..fe149d1 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java @@ -0,0 +1,135 @@ +package io.swtc.proccessing.ui.sections.recording; + +import io.swtc.proccessing.ui.ShowError; +import io.swtc.recording.evidence.USBExportManager; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +public class ExportSection extends JPanel { + private final Component parent; + private File sourceDirectory; + private final JLabel statusLabel; + private final JTextField pathField; + + public ExportSection(Component parent, File initialSource, JLabel statusLabel) { + this.parent = parent; + this.sourceDirectory = initialSource; + this.statusLabel = new JLabel("", SwingConstants.CENTER); + + + setLayout(new BorderLayout()); + + this.pathField = new JTextField(sourceDirectory.getAbsolutePath()); + this.pathField.setEditable(false); + + JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane.addTab("Evidence-Export", createTransferTab()); + tabbedPane.addTab("Storage Settings", createSettingsTab()); + + add(tabbedPane, BorderLayout.CENTER); + } + + private static JLabel getStatusLabel(JLabel statusLabel) { + return statusLabel; + } + + private JPanel createTransferTab() { + JPanel panel = new JPanel(new GridBagLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + + JButton exportBtn = new JButton("Export all Evidence"); + exportBtn.addActionListener(e -> startUsbExport()); + + JLabel infoLabel = new JLabel("" + + "Export Evidence to a USB Drive!" + + ""); + + gbc.gridy = 0; + panel.add(exportBtn, gbc); + gbc.gridy = 1; + gbc.insets = new Insets(10, 0, 0, 0); + panel.add(infoLabel, gbc); + + return panel; + } + + private JPanel createSettingsTab() { + JPanel panel = new JPanel(new BorderLayout(5, 5)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JButton browseBtn = new JButton("Change Folder"); + browseBtn.addActionListener(e -> changeSourceDirectory()); + + panel.add(new JLabel("Recording Path:"), BorderLayout.NORTH); + panel.add(pathField, BorderLayout.CENTER); + panel.add(browseBtn, BorderLayout.SOUTH); + + return panel; + } + + private void changeSourceDirectory() { + JFileChooser chooser = new JFileChooser(sourceDirectory); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { + this.sourceDirectory = chooser.getSelectedFile(); + this.pathField.setText(sourceDirectory.getAbsolutePath()); + } + } + + private void startUsbExport() { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setDialogTitle("Select USB Destination"); + + if (chooser.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) return; + + Path usbRoot = chooser.getSelectedFile().toPath(); + Path exportTarget = usbRoot.resolve(""); + + try { + long size = USBExportManager.calculateDirectorySize(sourceDirectory.toPath()); + + if (!USBExportManager.hasEnoughSpace(usbRoot, size)) { + ShowError.error(parent, "Not enough space on USB device.", "Export failed"); + return; + } + + JProgressBar bar = new JProgressBar(0, 100); + bar.setStringPainted(true); + + JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor(parent), "Exporting Data..."); + dialog.setLayout(new BorderLayout()); + dialog.add(new JLabel("Copying files ...", JLabel.CENTER), BorderLayout.NORTH); + dialog.add(bar, BorderLayout.CENTER); + dialog.setSize(300, 100); + dialog.setLocationRelativeTo(parent); + dialog.setModal(true); + + USBExportManager.exportAsync( + sourceDirectory.toPath(), + exportTarget, + stats -> SwingUtilities.invokeLater(() -> bar.setValue(stats.percent())), + () -> { + dialog.dispose(); + statusLabel.setText("Export completed successfully."); + }, + ex -> { + dialog.dispose(); + ShowError.error(parent, "Export error: " + ex.getMessage(), "Error"); + } + ); + + dialog.setVisible(true); + + } catch (IOException ex) { + ShowError.error(parent, "IO Error: " + ex.getMessage(), "Export error"); + } + } +} diff --git a/src/main/java/io/swtc/recording/RecordingManager.java b/src/main/java/io/swtc/recording/RecordingManager.java deleted file mode 100644 index 906e78a..0000000 --- a/src/main/java/io/swtc/recording/RecordingManager.java +++ /dev/null @@ -1,70 +0,0 @@ -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 onStop, Consumer onError) { - if (!recorder.isRecording()) { - new SwingWorker() { - @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() { - @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(); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/VideoRecorder.java b/src/main/java/io/swtc/recording/VideoRecorder.java deleted file mode 100644 index 352bfec..0000000 --- a/src/main/java/io/swtc/recording/VideoRecorder.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.swtc.recording; - -import io.swtc.proccessing.CameraPanel; -import org.jcodec.api.awt.AWTSequenceEncoder; -import org.jcodec.common.io.NIOUtils; -import org.jcodec.common.io.SeekableByteChannel; -import org.jcodec.common.model.Rational; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.util.concurrent.*; - -public class VideoRecorder { - private volatile boolean recording = false; - private File outputFile; - private AWTSequenceEncoder encoder; - private SeekableByteChannel channel; - private static final int FPS = 30; - - private LinkedBlockingQueue frameQueue; - private ExecutorService workerThread; - - public void startRecording(CameraPanel panel, File output) throws IOException { - 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; - - workerThread = Executors.newSingleThreadExecutor(); - workerThread.submit(this::processQueue); - - new Thread(() -> { - while (recording) { - try { - BufferedImage img = panel.getCurrentProcessedImage(); - if (img != null) { - BufferedImage copy = snapshotToRGB(img); - frameQueue.offer(copy); - } - Thread.sleep(1000 / FPS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - }).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 { - recording = false; - workerThread.shutdown(); - try { - workerThread.awaitTermination(5, TimeUnit.SECONDS); - if (encoder != null) encoder.finish(); - if (channel != null) channel.close(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - return outputFile; - } - - public boolean isRecording() { return recording; } -} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/cv/AVRecorder.java b/src/main/java/io/swtc/recording/cv/AVRecorder.java new file mode 100644 index 0000000..f19d45c --- /dev/null +++ b/src/main/java/io/swtc/recording/cv/AVRecorder.java @@ -0,0 +1,60 @@ +package io.swtc.recording.cv; + +import io.swtc.proccessing.ui.ShowError; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class AVRecorder { + private final BlockingQueue queue = new LinkedBlockingQueue<>(120); + private final RecorderConfig config; + private volatile boolean running = false; + + public AVRecorder(RecorderConfig config) { + this.config = config; + } + + public void start() { + running = true; + Thread worker = new Thread(this::runLoop, "Recording-Worker"); + worker.start(); + } + + private void runLoop() { + FrameProccessor processor = new FrameProccessor(); + MediaSink sink = null; + + try { + while (running || !queue.isEmpty()) { + BufferedImage img = queue.poll(500, TimeUnit.MILLISECONDS); + if (img == null) continue; + + if (sink == null) { + sink = new MediaSink(config); + } + + sink.write(processor.convert(img)); + } + } catch (Exception e) { + ShowError.error(null,"Error in AVRecorder", "AVRecorder isn't responding!"); + } finally { + if (sink != null) sink.stop(); + processor.close(); + } + } + + public void accept(BufferedImage img) { + if (!queue.offer(img)) { + System.err.println("Recorder lag: Frame dropped"); + } + } + + public boolean isRecording() { return running; } + + public void stop() { + running = false; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/cv/FrameProccessor.java b/src/main/java/io/swtc/recording/cv/FrameProccessor.java new file mode 100644 index 0000000..b4e0a87 --- /dev/null +++ b/src/main/java/io/swtc/recording/cv/FrameProccessor.java @@ -0,0 +1,32 @@ +package io.swtc.recording.cv; + +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.Java2DFrameConverter; + +import java.awt.image.BufferedImage; +import java.util.Objects; + +public class FrameProccessor { + private final Java2DFrameConverter converter = new Java2DFrameConverter(); + private BufferedImage reuseImg; + + public Frame convert(BufferedImage rawImg) { + if (Objects.isNull(reuseImg)) { + reuseImg = new BufferedImage( + rawImg.getWidth(), + rawImg.getHeight(), + BufferedImage.TYPE_3BYTE_BGR // default java BufferedImage Type + ); + + var g = reuseImg.createGraphics(); + g.drawImage(rawImg, 0, 0, null); + g.dispose(); + } + return converter.getFrame(reuseImg); + } + + public void close() { + converter.close(); + if (!Objects.isNull(reuseImg)) { reuseImg.flush(); } + } +} diff --git a/src/main/java/io/swtc/recording/cv/MediaSink.java b/src/main/java/io/swtc/recording/cv/MediaSink.java new file mode 100644 index 0000000..bf440d2 --- /dev/null +++ b/src/main/java/io/swtc/recording/cv/MediaSink.java @@ -0,0 +1,45 @@ +package io.swtc.recording.cv; + +import org.bytedeco.ffmpeg.global.avcodec; +import org.bytedeco.javacv.FFmpegFrameRecorder; + +import org.bytedeco.ffmpeg.global.avutil; +import org.bytedeco.javacv.Frame; + +public class MediaSink { + private final FFmpegFrameRecorder recorder; + private final long startNanos; + + public MediaSink(RecorderConfig config) throws Exception { + this.recorder = new FFmpegFrameRecorder(config.outputFile(), config.width(), config.height()); + this.startNanos = System.nanoTime(); + + avutil.av_log_set_level(avutil.AV_LOG_ERROR); + + recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); + recorder.setFormat("mp4"); + recorder.setPixelFormat(avutil.AV_PIX_FMT_BGR24); + recorder.setFrameRate(config.fps()); + recorder.setVideoOption("pixel_format", "yuv420p"); + recorder.setVideoOption("preset", config.preset()); + recorder.setVideoOption("crf", String.valueOf(config.crf())); + recorder.setGopSize(config.fps() * 2); + + recorder.start(); + } + + public void write(Frame frame) throws Exception { + long pts = (System.nanoTime() - startNanos) / 1000; + if (pts > recorder.getTimestamp()) { + recorder.setTimestamp(pts); + recorder.record(frame); + } + } + + public void stop() { + try { + recorder.stop(); + recorder.release(); + } catch (Exception ignored) { /* Do absolutley nothing ;) */ } + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/cv/RecorderConfig.java b/src/main/java/io/swtc/recording/cv/RecorderConfig.java new file mode 100644 index 0000000..e41619c --- /dev/null +++ b/src/main/java/io/swtc/recording/cv/RecorderConfig.java @@ -0,0 +1,12 @@ +package io.swtc.recording.cv; + +import java.io.File; + +public record RecorderConfig( + File outputFile, + int width, + int height, + int fps, + int crf, // Quality, 18 is high and 28 is low + String preset // ultrafast +) { /* Record */ } diff --git a/src/main/java/io/swtc/recording/evidence/ExportStats.java b/src/main/java/io/swtc/recording/evidence/ExportStats.java new file mode 100644 index 0000000..cf5c321 --- /dev/null +++ b/src/main/java/io/swtc/recording/evidence/ExportStats.java @@ -0,0 +1,42 @@ +package io.swtc.recording.evidence; + +import java.util.concurrent.TimeUnit; + +public record ExportStats( + long totalBytes, + long copiedBytes, + long startTimeNanos +) { + + public int percent() { + return totalBytes == 0 ? 0 : + (int) ((copiedBytes * 100) / totalBytes); + } + + /* timeLeft, ill implement this in the future (in the UI) i think + * this is the proper way to do this? idk + * */ + public String timeLeft() { + if (copiedBytes == 0) return "0"; + + long elapsedNanos = System.nanoTime() - startTimeNanos; + + double bytesPerNano = (double) copiedBytes / elapsedNanos; + long remainingBytes = totalBytes - copiedBytes; + + long remainingNanos = (long) (remainingBytes / bytesPerNano); + + return formatDuration(remainingNanos); + } + + private static String formatDuration(long nanos) { + long seconds = TimeUnit.NANOSECONDS.toSeconds(nanos); + long mins = seconds / 60; + long secs = seconds % 60; + + if (mins > 0) { + return mins + "m " + secs + "s"; + } + return secs + "s"; + } +} diff --git a/src/main/java/io/swtc/recording/evidence/USBExportManager.java b/src/main/java/io/swtc/recording/evidence/USBExportManager.java new file mode 100644 index 0000000..a24d220 --- /dev/null +++ b/src/main/java/io/swtc/recording/evidence/USBExportManager.java @@ -0,0 +1,114 @@ +package io.swtc.recording.evidence; + +import javax.swing.*; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + + +public class USBExportManager { + + public static long calculateDirectorySize(Path dir) throws IOException { + AtomicLong size = new AtomicLong(); + + Files.walkFileTree(dir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + size.addAndGet(attrs.size()); + return FileVisitResult.CONTINUE; + } + }); + + return size.get(); + } + + public static boolean hasEnoughSpace(Path target, long requiredBytes) throws IOException { + FileStore store = Files.getFileStore(target); + return store.getUsableSpace() >= requiredBytes; + } + + public static void exportDirectory( + Path sourceDir, + Path usbTargetDir, + Consumer progressCallback + ) throws IOException { + + if (!Files.isDirectory(sourceDir)) { + throw new IOException("Source is not a directory: " + sourceDir); + } + + Files.createDirectories(usbTargetDir); + + long totalBytes = calculateDirectorySize(sourceDir); + AtomicLong copiedBytes = new AtomicLong(); + + Files.walkFileTree(sourceDir, new SimpleFileVisitor<>() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + + Path targetDir = usbTargetDir.resolve(sourceDir.relativize(dir)); + Files.createDirectories(targetDir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + + Path targetFile = usbTargetDir.resolve(sourceDir.relativize(file)); + + Files.copy( + file, + targetFile, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES + ); + + copiedBytes.addAndGet(attrs.size()); + + long startTime = System.nanoTime(); + + if (progressCallback != null) { + progressCallback.accept( + new ExportStats(totalBytes, copiedBytes.get(), startTime) + ); + } + + return FileVisitResult.CONTINUE; + } + }); + } + + + + public static void exportAsync( + Path sourceDir, + Path usbTargetDir, + Consumer progressCallback, + Runnable onDone, + Consumer onError + ) { + + new Thread(() -> { + try { + exportDirectory(sourceDir, usbTargetDir, stats -> + SwingUtilities.invokeLater(() -> + progressCallback.accept(stats)) + ); + + if (onDone != null) { + SwingUtilities.invokeLater(onDone); + } + + } catch (Exception ex) { + if (onError != null) { + SwingUtilities.invokeLater(() -> onError.accept(ex)); + } + } + }, "USB-Export-Thread").start(); + } +} -- 2.49.1 From 8239b910fe8688350a0562e375827b8d1f835a73 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 29 Jan 2026 17:02:04 +0100 Subject: [PATCH 06/10] recording was fucked up. FIX! mainly in RecordingFrame --- .../io/swtc/proccessing/ui/iframe/RecordingFrame.java | 4 ++-- .../java/io/swtc/recording/cv/FrameProccessor.java | 11 ++++++----- src/main/java/io/swtc/recording/cv/MediaSink.java | 4 ++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java index 53cc3ce..1d6848d 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java @@ -147,8 +147,8 @@ public class RecordingFrame extends JInternalFrame { sample.getWidth(), sample.getHeight(), 20, // Frame Rate - 25, // CRF (Quality) - "ultrafast" + 18, // CRF (Quality) + "superfast" ); // 2. Initialize the HA Recorder diff --git a/src/main/java/io/swtc/recording/cv/FrameProccessor.java b/src/main/java/io/swtc/recording/cv/FrameProccessor.java index b4e0a87..6bf0f5e 100644 --- a/src/main/java/io/swtc/recording/cv/FrameProccessor.java +++ b/src/main/java/io/swtc/recording/cv/FrameProccessor.java @@ -15,13 +15,14 @@ public class FrameProccessor { reuseImg = new BufferedImage( rawImg.getWidth(), rawImg.getHeight(), - BufferedImage.TYPE_3BYTE_BGR // default java BufferedImage Type + BufferedImage.TYPE_3BYTE_BGR ); - - var g = reuseImg.createGraphics(); - g.drawImage(rawImg, 0, 0, null); - g.dispose(); } + + var g = reuseImg.createGraphics(); + g.drawImage(rawImg, 0, 0, null); + g.dispose(); + return converter.getFrame(reuseImg); } diff --git a/src/main/java/io/swtc/recording/cv/MediaSink.java b/src/main/java/io/swtc/recording/cv/MediaSink.java index bf440d2..c9ea6f7 100644 --- a/src/main/java/io/swtc/recording/cv/MediaSink.java +++ b/src/main/java/io/swtc/recording/cv/MediaSink.java @@ -20,9 +20,13 @@ public class MediaSink { recorder.setFormat("mp4"); recorder.setPixelFormat(avutil.AV_PIX_FMT_BGR24); recorder.setFrameRate(config.fps()); + /* this is essentially just building FFmpeg? Would've used ProccessBuilder for this lol */ recorder.setVideoOption("pixel_format", "yuv420p"); recorder.setVideoOption("preset", config.preset()); recorder.setVideoOption("crf", String.valueOf(config.crf())); + recorder.setVideoOption("tune", "zerolatency"); + recorder.setVideoOption("x264opts", "keyint=40:min-keyint=20"); + recorder.setVideoBitrate(0); // 0 tells the recorder to respect CRF strictly recorder.setGopSize(config.fps() * 2); recorder.start(); -- 2.49.1 From e1003c20ff00c637d963ce21fd685fed6460602a Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 31 Jan 2026 21:40:34 +0100 Subject: [PATCH 07/10] A LOT of changes! Firstly a desktop icon system, secondly in the cameras ui you can now see time, thirdly you can now set the recording quality, lastly you have a desktop icon specifically for exporting! --- src/main/java/io/swtc/SwingIFrame.java | 16 ++ .../java/io/swtc/proccessing/CameraPanel.java | 72 ++++- .../io/swtc/proccessing/ui/IconSetter.java | 10 + .../io/swtc/proccessing/ui/desktop/DIM.java | 50 ++++ .../proccessing/ui/desktop/DesktopIcon.java | 107 +++++++ .../proccessing/ui/desktop/ShadowLabel.java | 32 +++ .../desktop/evidence/EvidenceExportFrame.java | 110 ++++++++ .../proccessing/ui/iframe/DesktopPane.java | 22 -- .../proccessing/ui/iframe/RecordingFrame.java | 267 +++++++----------- .../ui/sections/recording/ExportSection.java | 9 +- .../java/io/swtc/recording/cv/Quality.java | 25 ++ .../swtc/recording/evidence/ExportStats.java | 40 ++- .../recording/evidence/USBExportManager.java | 161 +++++------ src/main/resources/font/OverlayFont.ttf | Bin 0 -> 116008 bytes src/main/resources/icons/save.png | Bin 0 -> 35371 bytes 15 files changed, 617 insertions(+), 304 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/DIM.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java create mode 100644 src/main/java/io/swtc/recording/cv/Quality.java create mode 100644 src/main/resources/font/OverlayFont.ttf create mode 100644 src/main/resources/icons/save.png diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index 033ea8b..7f0de41 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -2,6 +2,8 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; import io.swtc.proccessing.ui.IconSetter; +import io.swtc.proccessing.ui.desktop.DIM; +import io.swtc.proccessing.ui.desktop.evidence.EvidenceExportFrame; import io.swtc.proccessing.ui.iframe.*; import javax.swing.*; import java.awt.*; @@ -15,6 +17,7 @@ import java.util.Objects; public class SwingIFrame { private final JFrame mainFrame; private final DesktopPane desktopPane; + private final DIM desktopIconManager; private final Map cameraToEffects = new HashMap<>(); private boolean fullscreen = false; @@ -25,6 +28,7 @@ public class SwingIFrame { private final JPopupMenu popupMenu = new JPopupMenu(); + public SwingIFrame() { mainFrame = new JFrame("Viewer"); mainFrame.setSize(1280, 720); @@ -36,6 +40,10 @@ public class SwingIFrame { desktopPane.setBackground(defDesktopBg); mainFrame.add(desktopPane, BorderLayout.CENTER); + desktopIconManager = new DIM(desktopPane); + + setupDesktopExportFrame(); + setupFullscreenToggle(); setupBlackBg(); initPopupMenu(); @@ -43,6 +51,14 @@ public class SwingIFrame { desktopPane.addMouseListener(popupListener()); } + private void setupDesktopExportFrame() { + desktopIconManager.addIcon( + "Export Evidence", + IconSetter.getSaveIconAsImageIcon(), + EvidenceExportFrame::showExport + ); + } + public void addCameraInternalFrame(Webcam webcam) { CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest); diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java index bf24ebf..2259704 100644 --- a/src/main/java/io/swtc/proccessing/CameraPanel.java +++ b/src/main/java/io/swtc/proccessing/CameraPanel.java @@ -1,12 +1,19 @@ package io.swtc.proccessing; +import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.ShowError; import io.swtc.recording.cv.AVRecorder; import javax.swing.*; import java.awt.*; import java.awt.event.*; +import java.awt.font.GlyphVector; import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; @@ -17,6 +24,21 @@ public class CameraPanel extends JPanel { private volatile BufferedImage processedImage; private Function imageProcessor; + private Font overlayFont; + private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + + // Cached glyph vectors for performance + private GlyphVector dateGlyphs; + private GlyphVector timeGlyphs; + private String lastDate = ""; + private String lastTime = ""; + + // Pre-calculated positions to avoid repeated calculations + private static final int OVERLAY_X = 8; + private static final int OVERLAY_Y = 20; + private static final int TIME_Y_OFFSET = 19; + private volatile AVRecorder recorder; // this is now javacv, jcodec is stoopid private final AtomicBoolean repaintScheduled = new AtomicBoolean(false); @@ -35,12 +57,26 @@ public class CameraPanel extends JPanel { setBackground(Color.BLACK); setPreferredSize(new Dimension(640, 480)); + loadFont(); + graphicsConfig = GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration(); initInteractionListeners(); } + private void loadFont() { + try (InputStream is = getClass().getResourceAsStream("/font/OverlayFont.ttf")) { + if (is != null) { + overlayFont = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(15f); + } else { + overlayFont = new Font(Font.MONOSPACED, Font.PLAIN, 15); + } + } catch (Exception e) { + overlayFont = new Font(Font.MONOSPACED, Font.PLAIN, 15); + } + } + private void initInteractionListeners() { MouseAdapter mouseHandler = new MouseAdapter() { @Override @@ -135,8 +171,40 @@ public class CameraPanel extends JPanel { } Graphics2D g2d = processedImage.createGraphics(); - g2d.drawImage(temp, 0, 0, null); - g2d.dispose(); + try { + g2d.drawImage(temp, 0, 0, null); // this is fucking expensive + drawTimeOverlay(g2d); + } finally { + g2d.dispose(); + } + } + + + private void drawTimeOverlay(Graphics2D g) { + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g.setFont(overlayFont); + + LocalDateTime now = LocalDateTime.now(); + String dateStr = now.format(dateFormatter); + String timeStr = now.format(timeFormatter); + + if (!dateStr.equals(lastDate)) { + dateGlyphs = overlayFont.createGlyphVector(g.getFontRenderContext(), dateStr); + lastDate = dateStr; + } + + if (!timeStr.equals(lastTime)) { + timeGlyphs = overlayFont.createGlyphVector(g.getFontRenderContext(), timeStr); + lastTime = timeStr; + } + + g.setColor(Color.BLACK); + g.drawGlyphVector(dateGlyphs, OVERLAY_X + 2, OVERLAY_Y + 2); + g.drawGlyphVector(timeGlyphs, OVERLAY_X + 2, OVERLAY_Y + TIME_Y_OFFSET + 2); + + g.setColor(Color.WHITE); + g.drawGlyphVector(dateGlyphs, OVERLAY_X, OVERLAY_Y); + g.drawGlyphVector(timeGlyphs, OVERLAY_X, OVERLAY_Y + TIME_Y_OFFSET); } private void scheduleRepaint() { diff --git a/src/main/java/io/swtc/proccessing/ui/IconSetter.java b/src/main/java/io/swtc/proccessing/ui/IconSetter.java index afbc5ba..57f741b 100644 --- a/src/main/java/io/swtc/proccessing/ui/IconSetter.java +++ b/src/main/java/io/swtc/proccessing/ui/IconSetter.java @@ -2,6 +2,7 @@ package io.swtc.proccessing.ui; import javax.swing.*; import java.awt.*; +import java.io.InputStream; import java.net.URL; import java.util.Objects; @@ -37,4 +38,13 @@ public class IconSetter { } return ICON_ICON; } + + public static ImageIcon getSaveIconAsImageIcon() { + if (Objects.isNull(ICON_ICON)) { + URL url = IconSetter.class.getResource("/icons/save.png"); + if (Objects.isNull(url)) throw new RuntimeException("Icon not found: /icons/save.ico"); + ICON_ICON = new ImageIcon(url); + } + return ICON_ICON; + } } diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/DIM.java b/src/main/java/io/swtc/proccessing/ui/desktop/DIM.java new file mode 100644 index 0000000..c7435d1 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/DIM.java @@ -0,0 +1,50 @@ +package io.swtc.proccessing.ui.desktop; + +import javax.swing.*; +import java.awt.*; + +/* DesktopIconManager */ +public class DIM { + + private final JDesktopPane desktop; + + private int cX; + private int cY; + + private static final int PAD = 6; + + public DIM(JDesktopPane desktop) { + this.desktop = desktop; + + Insets insets = desktop.getInsets(); + this.cX = insets.left + PAD; + this.cY = insets.top + PAD; + } + + public void addIcon(String label, Icon icon, Runnable runaction) { + DesktopIcon desktopIcon = new DesktopIcon(label, icon, runaction); + + Dimension pref = desktopIcon.getPreferredSize(); + int w = pref.width; + int h = pref.height; + + Insets insets = desktop.getInsets(); + int usableHeight = desktop.getHeight() - insets.top - insets.bottom; + + if (usableHeight <= 0) { + usableHeight = Integer.MAX_VALUE; + } + + if (cY + h > usableHeight) { + cY = insets.top + PAD; + cX += w + PAD; + } + + desktopIcon.setBounds(cX, cY, w, h); + + desktop.add(desktopIcon, JLayeredPane.DEFAULT_LAYER); + + cY += h + PAD; + desktop.repaint(); + } +} diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java b/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java new file mode 100644 index 0000000..8ac62e8 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java @@ -0,0 +1,107 @@ +package io.swtc.proccessing.ui.desktop; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class DesktopIcon extends JPanel { + + private boolean hovered = false; + + private final JLabel iconLabel; + private final JLabel textLabel; + + public DesktopIcon(String label, Icon icon, Runnable action) { + + setLayout(new BorderLayout(4, 4)); + setOpaque(false); + + if (icon instanceof ImageIcon) { + Image img = ((ImageIcon) icon).getImage(); + Image scaled = img.getScaledInstance(64, 64, Image.SCALE_SMOOTH); + icon = new ImageIcon(scaled); + } + + iconLabel = new JLabel(icon, SwingConstants.CENTER); + textLabel = new ShadowLabel(label); + textLabel.setHorizontalAlignment(SwingConstants.CENTER); + + add(iconLabel, BorderLayout.CENTER); + add(textLabel, BorderLayout.SOUTH); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) { + if (action != null) action.run(); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + hovered = true; + repaint(); + } + + @Override + public void mouseExited(MouseEvent e) { + hovered = false; + repaint(); + } + }); + } + + @Override + public Dimension getPreferredSize() { + Dimension icon = iconLabel.getPreferredSize(); + Dimension text = textLabel.getPreferredSize(); + + int w = Math.max(icon.width, text.width) + 12; + int h = icon.height + text.height + 12; + + return new Dimension(w, h); + } + + @Override + protected void paintComponent(Graphics g) { + if (hovered) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + boolean lightBg = isBackgroundLight(); + + Color fill = lightBg + ? new Color(0, 0, 0, 30) + : new Color(255, 255, 255, 40); + + Color border = lightBg + ? new Color(0, 0, 0, 80) + : new Color(255, 255, 255, 100); + + g2.setColor(fill); + g2.fillRoundRect(2, 2, getWidth() - 4, getHeight() - 4, 10, 10); + + g2.setColor(border); + g2.drawRoundRect(2, 2, getWidth() - 5, getHeight() - 5, 10, 10); + + g2.dispose(); + } + + super.paintComponent(g); + } + + private boolean isBackgroundLight() { + Container p = getParent(); + if (p == null) return true; + + Color bg = p.getBackground(); + int luminance = (int) ( + bg.getRed() * 0.299 + + bg.getGreen() * 0.587 + + bg.getBlue() * 0.114 + ); + return luminance > 180; + } +} diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java b/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java new file mode 100644 index 0000000..6e5075f --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java @@ -0,0 +1,32 @@ +package io.swtc.proccessing.ui.desktop; + +import javax.swing.*; +import java.awt.*; + +public class ShadowLabel extends JLabel { + public ShadowLabel(String text) { + super(text, SwingConstants.CENTER); + setForeground(Color.WHITE); + setPreferredSize(new Dimension(15, 20)); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + FontMetrics fm = g2d.getFontMetrics(); + String text = getText(); + + int x = (getWidth() - fm.stringWidth(text)) / 2; + int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent(); + + g2d.setColor(new Color(0, 0, 0, 200)); + g2d.drawString(text, x + 1, y + 1); + + g2d.setColor(getForeground()); + g2d.drawString(text, x, y); + + g2d.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java b/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java new file mode 100644 index 0000000..ff1f891 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java @@ -0,0 +1,110 @@ +package io.swtc.proccessing.ui.desktop.evidence; + +import io.swtc.proccessing.ui.ShowError; +import io.swtc.recording.evidence.USBExportManager; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.nio.file.Path; +import java.io.File; + +public class EvidenceExportFrame extends JFrame { + + private final JProgressBar progressBar; + private final JLabel statusLabel; + private final JLabel detailLabel; + private final JButton actionBtn; + + private EvidenceExportFrame(Path sourceDir, Path usbTargetDir) { + setTitle("Export"); + setSize(400, 220); + setLocationRelativeTo(null); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + JPanel contentPane = new JPanel(); + contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); + contentPane.setBorder(new EmptyBorder(25, 25, 25, 25)); + + statusLabel = new JLabel("Starting export"); + statusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + statusLabel.setFont(new Font(statusLabel.getFont().getName(), Font.BOLD, 14)); + detailLabel = new JLabel("Initializing"); + detailLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + detailLabel.setForeground(Color.GRAY); + + progressBar = new JProgressBar(0, 100); + progressBar.setAlignmentX(Component.LEFT_ALIGNMENT); + progressBar.setMaximumSize(new Dimension(Integer.MAX_VALUE, 20)); + + actionBtn = new JButton("Cancel"); + actionBtn.setAlignmentX(Component.LEFT_ALIGNMENT); + contentPane.add(statusLabel); + contentPane.add(Box.createRigidArea(new Dimension(0, 10))); + contentPane.add(detailLabel); + contentPane.add(Box.createRigidArea(new Dimension(0, 20))); + contentPane.add(progressBar); + contentPane.add(Box.createVerticalGlue()); + contentPane.add(actionBtn); + + add(contentPane); + + actionBtn.addActionListener(e -> handleAction()); + + setVisible(true); + startExport(sourceDir, usbTargetDir); + } + + private void handleAction() { + if (actionBtn.getText().equals("Close")) { + dispose(); + return; + } + + int confirm = JOptionPane.showConfirmDialog(this, + "Stop export?", "Confirm", JOptionPane.YES_NO_OPTION); + if (confirm == JOptionPane.YES_OPTION) dispose(); + } + + private void startExport(Path sourceDir, Path usbTargetDir) { + USBExportManager.exportAsync( + sourceDir, + usbTargetDir, + stats -> SwingUtilities.invokeLater(() -> { + progressBar.setValue(stats.percent()); + statusLabel.setText("Exporting " + stats.percent() + "%"); + detailLabel.setText(String.format("%s | %s remaining", + stats.getSpeedMBps(), stats.timeLeft())); + }), + () -> SwingUtilities.invokeLater(() -> { + progressBar.setValue(100); + statusLabel.setText("Export Complete"); + detailLabel.setText("Files saved"); + actionBtn.setText("Close"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + }), + ex -> SwingUtilities.invokeLater(() -> { + statusLabel.setText("Export Failed"); + detailLabel.setText(ex.getMessage()); + ShowError.error(this, ex.getMessage(), "Error"); + }) + ); + } + + public static void showExport() { + SwingUtilities.invokeLater(() -> { + File videoDir = new File(System.getProperty("user.home"), "Videos/swtcctv-rec"); + if (!videoDir.exists()) { + ShowError.warning(null, "No recordings found.", "Not Found"); + return; + } + + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + Path target = chooser.getSelectedFile().toPath().resolve("swtcctv-rec_" + System.currentTimeMillis() / 1000); + new EvidenceExportFrame(videoDir.toPath(), target); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java index 9fc933d..e2b5267 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java @@ -2,7 +2,6 @@ package io.swtc.proccessing.ui.iframe; import javax.swing.*; import java.awt.*; -import java.awt.geom.CubicCurve2D; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -38,29 +37,8 @@ public class DesktopPane extends JDesktopPane { if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) { g2d.setColor(getConnectionColor(camera)); - drawBezierConnection(g2d, camera, effects); } } g2d.dispose(); } - - private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) { - Rectangle f = from.getBounds(); - Rectangle t = to.getBounds(); - - int x1 = f.x + f.width; - int y1 = f.y + (f.height / 2); - int x2 = t.x; - int y2 = t.y + (t.height / 2); - - 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); - - //g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - //g2d.draw(curve); - - // Terminals - //g2d.fillOval(x1 - 5, y1 - 5, 10, 10); - //g2d.fillOval(x2 - 5, y2 - 5, 10, 10); - } } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java index 1d6848d..234ebc6 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java @@ -5,8 +5,8 @@ import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.ShowError; import io.swtc.proccessing.ui.sections.recording.ExportSection; import io.swtc.recording.cv.AVRecorder; +import io.swtc.recording.cv.Quality; import io.swtc.recording.cv.RecorderConfig; -import io.swtc.recording.evidence.USBExportManager; import javax.swing.*; import java.awt.*; @@ -14,7 +14,6 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; -import java.nio.file.Path; public class RecordingFrame extends JInternalFrame { private AVRecorder avRecorder; @@ -24,6 +23,7 @@ public class RecordingFrame extends JInternalFrame { private JButton recordBtn; private JLabel statusLabel; private JLabel statsLabel; + private JComboBox presetCombo; private File outputDirectory; private File currentFile; @@ -44,7 +44,6 @@ public class RecordingFrame extends JInternalFrame { initializeUI(); - // Timer for UI updates only (1 FPS is enough for the clock) this.statsTimer = new Timer(1000, e -> updateStats()); this.statsTimer.setCoalesce(true); @@ -56,10 +55,7 @@ public class RecordingFrame extends JInternalFrame { outputDirectory = new File(videoDir, "swtcctv-rec"); if (!outputDirectory.exists()) { - boolean created = outputDirectory.mkdirs(); - if (!created) { - System.err.println("Could not create recording directory: " + outputDirectory.getAbsolutePath()); - } + outputDirectory.mkdirs(); } } @@ -69,21 +65,38 @@ public class RecordingFrame extends JInternalFrame { mainContent.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); exportSection = new ExportSection(this, outputDirectory, statusLabel); - - JPanel actionPanel = createActionPanel(); - + JPanel settingsPanel = createSettingsPanel(); JPanel statsPanel = createStatsPanel(); + JPanel actionPanel = createActionPanel(); mainContent.add(exportSection); mainContent.add(Box.createVerticalStrut(10)); + + mainContent.add(settingsPanel); + mainContent.add(Box.createVerticalStrut(10)); + mainContent.add(statsPanel); mainContent.add(Box.createVerticalStrut(10)); + mainContent.add(actionPanel); - getContentPane().add(mainContent); } + private JPanel createSettingsPanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + panel.setBorder(BorderFactory.createTitledBorder("Encoding Settings")); + + presetCombo = new JComboBox<>(Quality.values()); + presetCombo.setSelectedItem(Quality.SUPERFAST); // Default + + panel.add(new JLabel("CPU Preset:")); + panel.add(presetCombo); + + // Disable combo box during recording to prevent mid-stream config changes + return panel; + } + private JPanel createStatsPanel() { JPanel panel = new JPanel(new GridLayout(2, 1)); panel.setBorder(BorderFactory.createTitledBorder("Session Info")); @@ -91,20 +104,95 @@ public class RecordingFrame extends JInternalFrame { 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(240, 20)); panel.add(statusLabel); panel.add(statsLabel); 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 startRec() { + BufferedImage sample = cameraPanel.getCurrentProcessedImage(); + if (sample == null) { + ShowError.warning(this, "No camera feed detected.", "Warning"); + return; + } + + try { + currentFile = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4"); + + Quality selected = (Quality) presetCombo.getSelectedItem(); + String preset = (selected != null) ? selected.getFFmpegValue() : "superfast"; + + RecorderConfig config = new RecorderConfig( + currentFile, + sample.getWidth(), + sample.getHeight(), + 20, + 18, + preset + ); + + avRecorder = new AVRecorder(config); + avRecorder.start(); + + cameraPanel.setExternalRecorder(avRecorder); + + startTime = System.currentTimeMillis(); + statsTimer.start(); + + // UI Feedback + presetCombo.setEnabled(false); // Lock settings during recording + recordBtn.setText("Stop Recording"); + recordBtn.setForeground(Color.RED); + statusLabel.setText("Recording..."); + } catch (Exception ex) { + ShowError.error(this, "Failed to start: " + ex.getMessage(), "Error"); + } + } + + private void stopRec() { + if (avRecorder != null) { + statusLabel.setText("Finalizing file..."); + avRecorder.stop(); + cameraPanel.setExternalRecorder(null); + } + + statsTimer.stop(); + presetCombo.setEnabled(true); // Unlock settings + recordBtn.setText("Start Recording"); + recordBtn.setForeground(null); + + String fileName = (currentFile != null) ? currentFile.getName() : "N/A"; + statusLabel.setText("File saved: " + fileName); + } + + private void toggleRecording() { + if (avRecorder == null || !avRecorder.isRecording()) { + startRec(); + } else { + stopRec(); + } + } + private void updateStats() { if (avRecorder == null || !avRecorder.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); @@ -118,163 +206,10 @@ public class RecordingFrame extends JInternalFrame { statsLabel.setText(sb.toString()); } - private void toggleRecording() { - // Updated check for the new recorder state - if (avRecorder == null || !avRecorder.isRecording()) { - startRec(); - } else { - stopRec(); - } - } - - private void startRec() { - BufferedImage sample = cameraPanel.getCurrentProcessedImage(); - if (sample == null) { - ShowError.warning(this, "No camera feed detected. Start camera first.", "Warning"); - return; - } - - try { - if (!outputDirectory.exists() && !outputDirectory.mkdirs()) { - throw new IOException("Failed to create directory: " + outputDirectory); - } - - currentFile = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4"); - - // 1. Define the production config - RecorderConfig config = new RecorderConfig( - currentFile, - sample.getWidth(), - sample.getHeight(), - 20, // Frame Rate - 18, // CRF (Quality) - "superfast" - ); - - // 2. Initialize the HA Recorder - avRecorder = new AVRecorder(config); - avRecorder.start(); - - // 3. Link to CameraPanel (Ensure CameraPanel calls .accept(img)) - cameraPanel.setExternalRecorder(avRecorder); - - startTime = System.currentTimeMillis(); - statsTimer.start(); - - recordBtn.setText("Stop Recording"); - recordBtn.setForeground(Color.RED); - statusLabel.setText("Recording..."); - } catch (Exception ex) { - ShowError.error(this, "Failed to start Recorder: " + ex.getMessage(), "Error"); - ex.printStackTrace(); - } - } - - private void stopRec() { - if (avRecorder != null) { - statusLabel.setText("Finalizing file..."); - avRecorder.stop(); - cameraPanel.setExternalRecorder(null); - } - - statsTimer.stop(); - recordBtn.setText("Start Recording"); - recordBtn.setForeground(null); - - String fileName = (currentFile != null) ? currentFile.getName() : "N/A"; - statusLabel.setText("File saved: " + fileName); - } - - private JPanel createStoragePanel() { - JPanel panel = new JPanel(new BorderLayout(5, 5)); - panel.setBorder(BorderFactory.createTitledBorder("Storage Folder")); - - 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 void exportToUSB() { - JFileChooser chooser = new JFileChooser(); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.setDialogTitle("Select USB destination"); - - if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return; - - Path usbRoot = chooser.getSelectedFile().toPath(); - Path exportTarget = usbRoot.resolve("CCTV_EXPORT"); - - try { - long size = USBExportManager.calculateDirectorySize(outputDirectory.toPath()); - - if (!USBExportManager.hasEnoughSpace(usbRoot, size)) { - ShowError.error(this, "Not enough space on USB device.", "Export failed"); - return; - } - - JProgressBar bar = new JProgressBar(0, 100); - JOptionPane pane = new JOptionPane(bar, - JOptionPane.INFORMATION_MESSAGE, - JOptionPane.DEFAULT_OPTION, - null, - new Object[]{}); - - JDialog dialog = pane.createDialog(this, "Exporting..."); - dialog.setModal(false); - dialog.setVisible(true); - - USBExportManager.exportAsync( - outputDirectory.toPath(), - exportTarget, - stats -> bar.setValue(stats.percent()), - () -> { - dialog.dispose(); - statusLabel.setText("Export completed"); - }, - ex -> { - dialog.dispose(); - ShowError.error(this, ex.getMessage(), "Export error"); - } - ); - - } catch (IOException ex) { - ShowError.error(this, ex.getMessage(), "Export error"); - } - } - - - 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()); - - // Export is now handled by the ExportSection component - panel.add(recordBtn); - panel.add(snapBtn); - return panel; - } - private void takeSnapshot() { BufferedImage img = cameraPanel.getCurrentProcessedImage(); if (img != null) { try { - if (!outputDirectory.exists()) outputDirectory.mkdirs(); File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png"); ImageIO.write(img, "PNG", file); statusLabel.setText("Snapshot: " + file.getName()); diff --git a/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java b/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java index fe149d1..2656fb7 100644 --- a/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java +++ b/src/main/java/io/swtc/proccessing/ui/sections/recording/ExportSection.java @@ -27,16 +27,13 @@ public class ExportSection extends JPanel { this.pathField.setEditable(false); JTabbedPane tabbedPane = new JTabbedPane(); - tabbedPane.addTab("Evidence-Export", createTransferTab()); tabbedPane.addTab("Storage Settings", createSettingsTab()); + tabbedPane.addTab("Evidence-Export", createTransferTab()); + add(tabbedPane, BorderLayout.CENTER); } - private static JLabel getStatusLabel(JLabel statusLabel) { - return statusLabel; - } - private JPanel createTransferTab() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); @@ -48,7 +45,7 @@ public class ExportSection extends JPanel { exportBtn.addActionListener(e -> startUsbExport()); JLabel infoLabel = new JLabel("" + - "Export Evidence to a USB Drive!" + + "Export Evidence (Can also be done via Desktop)" + ""); gbc.gridy = 0; diff --git a/src/main/java/io/swtc/recording/cv/Quality.java b/src/main/java/io/swtc/recording/cv/Quality.java new file mode 100644 index 0000000..b102727 --- /dev/null +++ b/src/main/java/io/swtc/recording/cv/Quality.java @@ -0,0 +1,25 @@ +package io.swtc.recording.cv; + +public enum Quality { + ULTRAFAST("Ultrafast (Lowest CPU)"), + SUPERFAST("Superfast"), + VERYFAST("Very Fast"), + FASTER("Faster"), + FAST("Fast"), + MEDIUM("Medium (Best Quality/Size)"); + + private final String label; + + Quality(String label) { + this.label = label; + } + + public String getFFmpegValue() { + return this.name().toLowerCase(); + } + + @Override + public String toString() { + return label; + } +} diff --git a/src/main/java/io/swtc/recording/evidence/ExportStats.java b/src/main/java/io/swtc/recording/evidence/ExportStats.java index cf5c321..9804f0d 100644 --- a/src/main/java/io/swtc/recording/evidence/ExportStats.java +++ b/src/main/java/io/swtc/recording/evidence/ExportStats.java @@ -5,38 +5,50 @@ import java.util.concurrent.TimeUnit; public record ExportStats( long totalBytes, long copiedBytes, - long startTimeNanos + long startTimeNanos, + String currentFileName ) { public int percent() { - return totalBytes == 0 ? 0 : - (int) ((copiedBytes * 100) / totalBytes); + if (totalBytes <= 0) return 0; + return (int) Math.min(100, (copiedBytes * 100) / totalBytes); } - /* timeLeft, ill implement this in the future (in the UI) i think - * this is the proper way to do this? idk - * */ public String timeLeft() { - if (copiedBytes == 0) return "0"; + if (copiedBytes <= 0) return "Calculating..."; long elapsedNanos = System.nanoTime() - startTimeNanos; + if (elapsedNanos <= 0) return "Calculating..."; - double bytesPerNano = (double) copiedBytes / elapsedNanos; + // Bytes per nanosecond + double bps = (double) copiedBytes / elapsedNanos; long remainingBytes = totalBytes - copiedBytes; - long remainingNanos = (long) (remainingBytes / bytesPerNano); + if (remainingBytes <= 0) return "Done"; + long remainingNanos = (long) (remainingBytes / bps); return formatDuration(remainingNanos); } + public String getSpeedMBps() { + long elapsedNanos = System.nanoTime() - startTimeNanos; + if (elapsedNanos <= 0 || copiedBytes <= 0) return "0 MB/s"; + + double seconds = elapsedNanos / 1_000_000_000.0; + double megabytes = copiedBytes / (1024.0 * 1024.0); + return String.format("%.1f MB/s", megabytes / seconds); + } + private static String formatDuration(long nanos) { - long seconds = TimeUnit.NANOSECONDS.toSeconds(nanos); - long mins = seconds / 60; - long secs = seconds % 60; + long totalSeconds = TimeUnit.NANOSECONDS.toSeconds(nanos); + if (totalSeconds < 1) return "less than a sec"; + + long mins = totalSeconds / 60; + long secs = totalSeconds % 60; if (mins > 0) { - return mins + "m " + secs + "s"; + return String.format("%dm %ds", mins, secs); } return secs + "s"; } -} +} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/evidence/USBExportManager.java b/src/main/java/io/swtc/recording/evidence/USBExportManager.java index a24d220..3c2c45d 100644 --- a/src/main/java/io/swtc/recording/evidence/USBExportManager.java +++ b/src/main/java/io/swtc/recording/evidence/USBExportManager.java @@ -1,114 +1,87 @@ package io.swtc.recording.evidence; -import javax.swing.*; import java.io.IOException; import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.concurrent.atomic.AtomicLong; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; import java.util.function.Consumer; - public class USBExportManager { - public static long calculateDirectorySize(Path dir) throws IOException { - AtomicLong size = new AtomicLong(); - - Files.walkFileTree(dir, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - size.addAndGet(attrs.size()); - return FileVisitResult.CONTINUE; + public static void exportAsync(Path source, Path target, Consumer progress, Runnable onDone, Consumer onError) { + CompletableFuture.runAsync(() -> { + try { + performExport(source, target, progress); + onDone.run(); + } catch (Exception e) { + onError.accept(e); } }); + } - return size.get(); + private static void performExport(Path source, Path target, Consumer progress) throws IOException { + List allFiles; + try (Stream stream = Files.walk(source)) { + allFiles = stream.filter(Files::isRegularFile).toList(); + } + + long totalBytes = allFiles.stream().mapToLong(p -> p.toFile().length()).sum(); + + if (!hasEnoughSpace(target, totalBytes)) { + throw new IOException("Not enough space on target drive. Required: " + (totalBytes / 1024 / 1024) + " MB"); + } + + long totalCopied = 0; + long startTimeNanos = System.nanoTime(); + + for (Path file : allFiles) { + String fileName = file.getFileName().toString(); + + if (progress != null) { + progress.accept(new ExportStats(totalBytes, totalCopied, startTimeNanos, fileName)); + } + + Path relativePath = source.relativize(file); + Path destination = target.resolve(relativePath); + + Files.createDirectories(destination.getParent()); + + Files.copy(file, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + + totalCopied += Files.size(file); + + if (progress != null) { + progress.accept(new ExportStats(totalBytes, totalCopied, startTimeNanos, fileName)); + } + } } public static boolean hasEnoughSpace(Path target, long requiredBytes) throws IOException { - FileStore store = Files.getFileStore(target); + Path root = target; + while (root != null && !Files.exists(root)) { + root = root.getParent(); + } + if (root == null) return true; + + FileStore store = Files.getFileStore(root); return store.getUsableSpace() >= requiredBytes; } - public static void exportDirectory( - Path sourceDir, - Path usbTargetDir, - Consumer progressCallback - ) throws IOException { - if (!Files.isDirectory(sourceDir)) { - throw new IOException("Source is not a directory: " + sourceDir); + public static long calculateDirectorySize(Path dir) throws IOException { + if (dir == null || !Files.exists(dir)) return 0L; + + try (Stream walk = Files.walk(dir)) { + return walk.filter(Files::isRegularFile) + .mapToLong(p -> { + try { + return Files.size(p); + } catch (IOException e) { + return 0L; // Skip files that can't be read + } + }) + .sum(); } - - Files.createDirectories(usbTargetDir); - - long totalBytes = calculateDirectorySize(sourceDir); - AtomicLong copiedBytes = new AtomicLong(); - - Files.walkFileTree(sourceDir, new SimpleFileVisitor<>() { - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) - throws IOException { - - Path targetDir = usbTargetDir.resolve(sourceDir.relativize(dir)); - Files.createDirectories(targetDir); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - - Path targetFile = usbTargetDir.resolve(sourceDir.relativize(file)); - - Files.copy( - file, - targetFile, - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.COPY_ATTRIBUTES - ); - - copiedBytes.addAndGet(attrs.size()); - - long startTime = System.nanoTime(); - - if (progressCallback != null) { - progressCallback.accept( - new ExportStats(totalBytes, copiedBytes.get(), startTime) - ); - } - - return FileVisitResult.CONTINUE; - } - }); } - - - - public static void exportAsync( - Path sourceDir, - Path usbTargetDir, - Consumer progressCallback, - Runnable onDone, - Consumer onError - ) { - - new Thread(() -> { - try { - exportDirectory(sourceDir, usbTargetDir, stats -> - SwingUtilities.invokeLater(() -> - progressCallback.accept(stats)) - ); - - if (onDone != null) { - SwingUtilities.invokeLater(onDone); - } - - } catch (Exception ex) { - if (onError != null) { - SwingUtilities.invokeLater(() -> onError.accept(ex)); - } - } - }, "USB-Export-Thread").start(); - } -} +} \ No newline at end of file diff --git a/src/main/resources/font/OverlayFont.ttf b/src/main/resources/font/OverlayFont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2442affbf6570d23c0b2b270cca8d49535df27bf GIT binary patch literal 116008 zcmeFa37k~bl?Qz9d$0DYUTB)F1*&Oqqe1AV1r)XD1!NJCMQOE>1{!GDT4>w?s4*^} zaf?P{HWM{2F|lxoOI$OPWU@?>nPjq>iOFOVlT4gU#%xT~@Bcsd-l|tcH;c)9zu)&w z3cBi5z4z`t_uR9ebMI4HDOH8HK&iRy9do?B-cF_TQ2aVpb2$H#b$#nMU%d88 zw3q#bo=NDV1BO^m|^}(9^v(zGLpc;ykZAaRYt` z9*C~P_l5XAe#7SeOFvb;>{)>J4@xDE+O%~|_jTWX?I9e0NU6y9&E1#w1;=H+fa@D^ zeRfOt=AN$mu4q##`ZVDGY2ViE{pY{u`hQev5?~y1X5Y4+zVD3u_Adc%9M|ttTIsjn zR+acviV*VUDeYbAy-WEx#_!&(Y6i}YS23K)sk77#`2Fe3Fy71a{%gF+f9=oTmSeUQ zdU?Be4ci*F>v!i~P&w;&$~Sj>>zM5CdEvJ%{Z3g8y)M6H0nvh|)jup* zu}|xpI`jIOxQeSaefw0b^~p`kW{nTj@jT8YyOJHrNy*`fAZ~x?jh+>q866jqA5yK) z55Kbd%G8xn(o+JLRJJ~kzRf@R#aHe7#_Q|PT9MCPzha-iwte5x{Pt}8N~MF`^=p>l zN7demS1#EdpBJARuZsog9*O+ zvN)$ev!s&<<}3x;tW-nfw$}zUCGT6;Y*O#ux^3+ib$R%G`;NZtsz3Y=Ftl(JAqJt~8);kv zQoa*T;Uv5(mC`@e_vrI=yN-j3KB6vG8`Mc^gq+hDuZOV(>JRFV^2fUaHBY@uy=>cw z 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∓eYz|5P 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 Date: Mon, 2 Feb 2026 12:31:02 +0100 Subject: [PATCH 08/10] fix for icon change. e1003c20ff00c637d963ce21fd685fed6460602a Signed-off-by: rattatwinko --- src/main/java/io/swtc/SwingIFrame.java | 4 ++-- .../desktop/evidence/EvidenceExportFrame.java | 21 +++++++++++++------ .../java/io/swtc/recording/cv/MediaSink.java | 3 +-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index 7f0de41..0516899 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -25,7 +25,6 @@ public class SwingIFrame { private boolean blackbg = false; private final Color bgcolor = Color.decode("#336B6A"); private final Color defDesktopBg = Color.WHITE; - private final JPopupMenu popupMenu = new JPopupMenu(); @@ -55,7 +54,8 @@ public class SwingIFrame { desktopIconManager.addIcon( "Export Evidence", IconSetter.getSaveIconAsImageIcon(), - EvidenceExportFrame::showExport + /* e1003c20ff00c637d963ce21fd685fed6460602a: Fix to icon, need to pass parent! Or Override method which is dumb */ + () -> EvidenceExportFrame.showExport(mainFrame) ); } diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java b/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java index ff1f891..1564e1c 100644 --- a/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/desktop/evidence/EvidenceExportFrame.java @@ -1,5 +1,6 @@ package io.swtc.proccessing.ui.desktop.evidence; +import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.ShowError; import io.swtc.recording.evidence.USBExportManager; @@ -15,17 +16,25 @@ public class EvidenceExportFrame extends JFrame { private final JLabel statusLabel; private final JLabel detailLabel; private final JButton actionBtn; + private final JFrame parent; /* neccessary to get icon working, inheritance is a bitch */ + + private EvidenceExportFrame(Path sourceDir, Path usbTargetDir, JFrame parent) { + + this.parent = parent; - private EvidenceExportFrame(Path sourceDir, Path usbTargetDir) { setTitle("Export"); setSize(400, 220); - setLocationRelativeTo(null); + setLocationRelativeTo(this.parent); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); JPanel contentPane = new JPanel(); + contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); contentPane.setBorder(new EmptyBorder(25, 25, 25, 25)); + ImageIcon ico = IconSetter.getSaveIconAsImageIcon(); + this.setIconImage(ico.getImage()); + statusLabel = new JLabel("Starting export"); statusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); statusLabel.setFont(new Font(statusLabel.getFont().getName(), Font.BOLD, 14)); @@ -61,7 +70,7 @@ public class EvidenceExportFrame extends JFrame { return; } - int confirm = JOptionPane.showConfirmDialog(this, + int confirm = JOptionPane.showConfirmDialog(this.parent, "Stop export?", "Confirm", JOptionPane.YES_NO_OPTION); if (confirm == JOptionPane.YES_OPTION) dispose(); } @@ -91,7 +100,7 @@ public class EvidenceExportFrame extends JFrame { ); } - public static void showExport() { + public static void showExport(JFrame parent) { SwingUtilities.invokeLater(() -> { File videoDir = new File(System.getProperty("user.home"), "Videos/swtcctv-rec"); if (!videoDir.exists()) { @@ -101,9 +110,9 @@ public class EvidenceExportFrame extends JFrame { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { Path target = chooser.getSelectedFile().toPath().resolve("swtcctv-rec_" + System.currentTimeMillis() / 1000); - new EvidenceExportFrame(videoDir.toPath(), target); + new EvidenceExportFrame(videoDir.toPath(), target, parent); } }); } diff --git a/src/main/java/io/swtc/recording/cv/MediaSink.java b/src/main/java/io/swtc/recording/cv/MediaSink.java index c9ea6f7..5f4f4d7 100644 --- a/src/main/java/io/swtc/recording/cv/MediaSink.java +++ b/src/main/java/io/swtc/recording/cv/MediaSink.java @@ -18,7 +18,6 @@ public class MediaSink { recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); recorder.setFormat("mp4"); - recorder.setPixelFormat(avutil.AV_PIX_FMT_BGR24); recorder.setFrameRate(config.fps()); /* this is essentially just building FFmpeg? Would've used ProccessBuilder for this lol */ recorder.setVideoOption("pixel_format", "yuv420p"); @@ -26,7 +25,7 @@ public class MediaSink { recorder.setVideoOption("crf", String.valueOf(config.crf())); recorder.setVideoOption("tune", "zerolatency"); recorder.setVideoOption("x264opts", "keyint=40:min-keyint=20"); - recorder.setVideoBitrate(0); // 0 tells the recorder to respect CRF strictly + recorder.setVideoBitrate(0); // javacv respects bitrate already ; this is for my own safety recorder.setGopSize(config.fps() * 2); recorder.start(); -- 2.49.1 From e225d8f0bc5e7c04141b94a239ebd535b1d1f2a8 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sun, 8 Feb 2026 17:00:05 +0100 Subject: [PATCH 09/10] Added Profiler + Some Fixes + Profiler.java new readme Signed-off-by: rattatwinko --- readme.md | 33 +++- src/main/java/io/swtc/Main.java | 7 - src/main/java/io/swtc/SwingCCTVManager.java | 1 - src/main/java/io/swtc/SwingIFrame.java | 13 ++ .../io/swtc/proccessing/ui/IconSetter.java | 36 +++- .../ui/desktop/debug/Profiler.java | 169 ++++++++++++++++++ src/main/resources/icons/icondbg-7.png | Bin 0 -> 3148 bytes 7 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/debug/Profiler.java create mode 100644 src/main/resources/icons/icondbg-7.png diff --git a/readme.md b/readme.md index b8e459f..19d6bf7 100644 --- a/readme.md +++ b/readme.md @@ -1,26 +1,47 @@ -# SWT-CCTV +# SWT-CCTV (Simple Watch Tool) A rather simple CCTV software which operates with Java. If you want to build this project on yourself, you will need IntelliJ (or any other IDE) and Maven! +If you are looking for a desktop like experience this is the software, it has its own windowing system! + +## Downloads: + +If you are looking for downloads then you are in luck! Currently there are Windows Executables and portable Jar Files in place! +Take a look at the [releases](https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/swt-cctv/releases) page for the newest software releases! + +[Releases Page](https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/swt-cctv/releases) ## Dependencies: - Webcam by Sarxos - Swing (AWT) -- _lwjgl (with opengl)_ → This is important for our goals of rendering on the GPU. - junit for testing stuff -- jcodec, in the future we will be recording using this - Jackson (fasterxml) → serializing the config for network cams +- JavaCV + - FFmpeg ### Future Plans: -They arent too big, i want one thing more and that is some more utilities in the camera window. +Implement some more JavaCV cause of performance. -Protable Jar which can be run with JRE 17 (already done but not too good!) +## Requirements: + +- JRE/JDK 1.8.00 - 25 ([Reccomended](https://adoptium.net/de/download?link=https%3A%2F%2Fgithub.com%2Fadoptium%2Ftemurin17-binaries%2Freleases%2Fdownload%2Fjdk-17.0.17%252B10%2FOpenJDK17U-jre_x64_windows_hotspot_17.0.17_10.msi&vendor=Adoptium)) + +| System Requirements | Minimum Requirements | Reccomended Requirements | +|--------------------- |---------------------------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **_CPU_** | [Intel(R) Celeron(R) CPU G550 @ 2.60GHz](https://www.techpowerup.com/cpu-specs/celeron-g550.c1339) **_Or any Dual Core CPU_** | [Intel® Coreâ„¢ i5-3470](https://www.intel.de/content/www/de/de/products/sku/68316/intel-core-i53470-processor-6m-cache-up-to-3-60-ghz/specifications.html) Or any **_Quad (or more) Core CPU_** | +| **_RAM_** | **2GB DDR3** | **4/8GB DDR3/4/5** (You can have **_more_** than this, _Java likes it_) | +| **_JRE_** | Java Runtime Enviroment: [1.8.000](https://javadl.oracle.com/webapps/download/AutoDL?BundleId=252905_0d06828d282343ea81775b28020a7cd3) | Java Runtime Enviroment _(or JDK)_: [17](https://adoptium.net/download?link=https%3A%2F%2Fgithub.com%2Fadoptium%2Ftemurin17-binaries%2Freleases%2Fdownload%2Fjdk-17.0.17%252B10%2FOpenJDK17U-jre_x64_windows_hotspot_17.0.17_10.msi&vendor=Adoptium) | +| **_Disk Space_** | **_100Mb of HDD/SSD_** Space for the Program (currently **40.3Mb**) ; _For Recording more is needed_ | _100Mb SSD Space_ ; For Recording more than 100Mb , this depends on how many cameras you have | + +**Note: This was tested on these CPU'S!** ### Future Plans: - [x] basic network cam interfacing -- [ ] better multiplexer (or whatever the viewport is called in cctv) +- [x] better multiplexer (or whatever the viewport is called in cctv) - [x] Protable .jar which can be run with **JRE 17** +- [ / ] Performance stabilisation (currently it is in place, and this app can be run on shitty hardware, but it can be better (current low is a celeron g550)) +- [ ] JavaCV integration for cameras ### Author(s): diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java index c62e48e..4eb4f76 100644 --- a/src/main/java/io/swtc/Main.java +++ b/src/main/java/io/swtc/Main.java @@ -2,16 +2,9 @@ package io.swtc; import javax.swing.*; -import io.swtc.proccessing.ui.IconSetter; -import io.swtc.proccessing.ui.ShowError; - public class Main { public static void main(String[] args) { -// for (int i = 0; i < args.length; i++) { -// System.out.println("Arg " + i + ": " + args[i]); -// } - try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { /* Do nothing */ } diff --git a/src/main/java/io/swtc/SwingCCTVManager.java b/src/main/java/io/swtc/SwingCCTVManager.java index 716786d..9327aa5 100644 --- a/src/main/java/io/swtc/SwingCCTVManager.java +++ b/src/main/java/io/swtc/SwingCCTVManager.java @@ -45,7 +45,6 @@ public class SwingCCTVManager { private final DefaultTableModel tableModel; private final SwingIFrame IFrame; private boolean isRefreshing = false; - public SwingCCTVManager() { frame = new JFrame("Dashboard"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index 0516899..bd41aeb 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -3,6 +3,7 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; import io.swtc.proccessing.ui.IconSetter; import io.swtc.proccessing.ui.desktop.DIM; +import io.swtc.proccessing.ui.desktop.debug.Profiler; import io.swtc.proccessing.ui.desktop.evidence.EvidenceExportFrame; import io.swtc.proccessing.ui.iframe.*; import javax.swing.*; @@ -42,6 +43,7 @@ public class SwingIFrame { desktopIconManager = new DIM(desktopPane); setupDesktopExportFrame(); + setupProfiler(); setupFullscreenToggle(); setupBlackBg(); @@ -59,6 +61,17 @@ public class SwingIFrame { ); } + private void setupProfiler() { + desktopIconManager.addIcon( + "Profiler", + IconSetter.getDbg_icon(), + () -> { + SwingUtilities.invokeLater(() -> { + Profiler.showFrame(new Profiler(mainFrame)); + }); + }); + } + public void addCameraInternalFrame(Webcam webcam) { CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest); diff --git a/src/main/java/io/swtc/proccessing/ui/IconSetter.java b/src/main/java/io/swtc/proccessing/ui/IconSetter.java index 57f741b..7347f2a 100644 --- a/src/main/java/io/swtc/proccessing/ui/IconSetter.java +++ b/src/main/java/io/swtc/proccessing/ui/IconSetter.java @@ -2,20 +2,25 @@ package io.swtc.proccessing.ui; import javax.swing.*; import java.awt.*; -import java.io.InputStream; import java.net.URL; import java.util.Objects; +/* vital boilerplate class, shouldve made it better but idk. */ public class IconSetter { private static Image ICON_IMAGE; private static ImageIcon ICON_ICON; private static Image effects_icon; + private static ImageIcon dbg_icon; + /* this is used for the app icon itself (the one in tb) */ 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"); + if (Objects.isNull(url)) { + ShowError.error(null,"Icon","Icon (Type: Image) failed, NULL!"); + throw new RuntimeException("NULL!"); + } ICON_IMAGE = Toolkit.getDefaultToolkit().getImage(url); } return ICON_IMAGE; @@ -24,7 +29,10 @@ public class IconSetter { 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"); + if (Objects.isNull(url)) { + ShowError.error(null,"Icon","Effects icon was Null! (Type Image)"); + throw new RuntimeException("NULL!"); + } effects_icon = Toolkit.getDefaultToolkit().getImage(url); } return effects_icon; @@ -33,7 +41,10 @@ public class IconSetter { 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"); + if (Objects.isNull(url)) { + ShowError.error(null,"Icon","Icon not found!, NULL! (Type ImageIcon)"); + throw new RuntimeException("NULL!"); + } ICON_ICON = new ImageIcon(url); // separate variable for ImageIcon } return ICON_ICON; @@ -42,9 +53,24 @@ public class IconSetter { public static ImageIcon getSaveIconAsImageIcon() { if (Objects.isNull(ICON_ICON)) { URL url = IconSetter.class.getResource("/icons/save.png"); - if (Objects.isNull(url)) throw new RuntimeException("Icon not found: /icons/save.ico"); + if (Objects.isNull(url)) { + ShowError.error(null,"Icon","getSaveIconAsImageIcon failed, NULL! (Type ImageIcon)"); + throw new RuntimeException("NULL!"); + } ICON_ICON = new ImageIcon(url); } return ICON_ICON; } + + public static ImageIcon getDbg_icon() { + if (Objects.isNull(dbg_icon)) { + URL url = IconSetter.class.getResource("/icons/icondbg-7.png"); + if (Objects.isNull(url)) { + ShowError.error(null, "Icon", "getDbg_icon, object url was null (Type ImageIcon)"); + throw new RuntimeException("NULL!"); + } + dbg_icon = new ImageIcon(url); + } + return dbg_icon; + } } diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/debug/Profiler.java b/src/main/java/io/swtc/proccessing/ui/desktop/debug/Profiler.java new file mode 100644 index 0000000..3f6ec7b --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/debug/Profiler.java @@ -0,0 +1,169 @@ +package io.swtc.proccessing.ui.desktop.debug; + +import io.swtc.proccessing.ui.IconSetter; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.lang.management.*; +import java.util.List; + +/* simple profiler to see memory usage, this isnt too important but certainly useful */ +public class Profiler extends JFrame { + + private final MemoryMXBean memoryMXBean = + ManagementFactory.getMemoryMXBean(); + private final ThreadMXBean threadMXBean = + ManagementFactory.getThreadMXBean(); + private final List pools = + ManagementFactory.getMemoryPoolMXBeans(); + private final List gcs = + ManagementFactory.getGarbageCollectorMXBeans(); + private final List buffers = + ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); + + private final JLabel heapLabel = new JLabel(); + private final JLabel nonHeapLabel = new JLabel(); + private final JLabel threadLabel = new JLabel(); + + private final DefaultTableModel poolModel = + new DefaultTableModel( + new String[]{"Pool", "Type", "Used (MB)", "Committed (MB)", "Max (MB)"}, + 0 + ); + + private final DefaultTableModel gcModel = + new DefaultTableModel( + new String[]{"GC", "Collections", "Time (ms)"}, + 0 + ); + + private final DefaultTableModel bufferModel = + new DefaultTableModel( + new String[]{"Buffer", "Used (MB)", "Count"}, + 0 + ); + + public Profiler(JFrame parent) { + setTitle("Profiler"); + setSize(750, 400); + setLocationRelativeTo(parent); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + + ImageIcon ico = IconSetter.getDbg_icon(); + setIconImage(ico.getImage()); + + JPanel root = new JPanel(new BorderLayout(10, 10)); + root.setBorder(new EmptyBorder(10, 10, 10, 10)); + setContentPane(root); + + JPanel summary = new JPanel(); + summary.setLayout(new BoxLayout(summary, BoxLayout.Y_AXIS)); + + summary.add(heapLabel); + summary.add(nonHeapLabel); + summary.add(threadLabel); + + root.add(summary, BorderLayout.NORTH); + + JTabbedPane tabs = new JTabbedPane(); + + tabs.add("Memory Pools", new JScrollPane(new JTable(poolModel))); + tabs.add("GC", new JScrollPane(new JTable(gcModel))); + tabs.add("Buffers", new JScrollPane(new JTable(bufferModel))); + + root.add(tabs, BorderLayout.CENTER); + + Timer timer = new Timer(1000, e -> update()); + timer.start(); + + update(); + } + + private void update() { + updateSummary(); + updatePools(); + updateGC(); + updateBuffers(); + } + + private void updateSummary() { + MemoryUsage heap = memoryMXBean.getHeapMemoryUsage(); + MemoryUsage nonHeap = memoryMXBean.getNonHeapMemoryUsage(); + + heapLabel.setText(String.format( + "Heap: used %d MB / committed %d MB / max %d MB", + mb(heap.getUsed()), + mb(heap.getCommitted()), + mb(heap.getMax()) + )); + + nonHeapLabel.setText(String.format( + "Non-Heap: used %d MB / committed %d MB", + mb(nonHeap.getUsed()), + mb(nonHeap.getCommitted()) + )); + + int threads = threadMXBean.getThreadCount(); + int peak = threadMXBean.getPeakThreadCount(); + int daemons = threadMXBean.getDaemonThreadCount(); + + threadLabel.setText(String.format( + "Threads: %d live (%d daemon, peak %d)", + threads, daemons, peak + )); + } + + private void updatePools() { + poolModel.setRowCount(0); + + for (MemoryPoolMXBean pool : pools) { + MemoryUsage u = pool.getUsage(); + if (u == null) continue; + + poolModel.addRow(new Object[]{ + pool.getName(), + pool.getType(), + mb(u.getUsed()), + mb(u.getCommitted()), + mb(u.getMax()) + }); + } + } + + private void updateGC() { + gcModel.setRowCount(0); + + for (GarbageCollectorMXBean gc : gcs) { + gcModel.addRow(new Object[]{ + gc.getName(), + gc.getCollectionCount(), + gc.getCollectionTime() + }); + } + } + + private void updateBuffers() { + bufferModel.setRowCount(0); + + for (BufferPoolMXBean b : buffers) { + bufferModel.addRow(new Object[]{ + b.getName(), + mb(b.getMemoryUsed()), + b.getCount() + }); + } + } + + /* Conversion logic for byte -> mb */ + public long mb(long bytes) { + return bytes < 0 ? -1 : bytes / 1024 / 1024; + } + + public static void showFrame(JFrame parent) { + SwingUtilities.invokeLater(() -> + new Profiler(parent).setVisible(true) + ); + } +} diff --git a/src/main/resources/icons/icondbg-7.png b/src/main/resources/icons/icondbg-7.png new file mode 100644 index 0000000000000000000000000000000000000000..72f975b7c86b363c0660187f3821af3b4e6e8479 GIT binary patch literal 3148 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 UVc-3BYQF)Pp}9e|o=fz90Aj}FlmGw# literal 0 HcmV?d00001 -- 2.49.1 From 57ee4d9a92e24862211254fc5241a23b5ebf0198 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Mon, 9 Feb 2026 12:23:37 +0100 Subject: [PATCH 10/10] New Desktop Icons Some new Functionality to record more than 1 camera using the desktop icon. open local files using desktop, open profiler (nothing changed) Signed-off-by: rattatwinko --- src/main/java/io/swtc/SwingIFrame.java | 48 +++++ .../io/swtc/proccessing/ui/IconSetter.java | 39 ++++ .../proccessing/ui/desktop/DesktopIcon.java | 107 +++++----- .../proccessing/ui/desktop/ShadowLabel.java | 42 ++-- .../proccessing/ui/desktop/SmoothIcon.java | 28 +++ .../ui/desktop/recording/CameraCheckItem.java | 13 ++ .../recording/CheckBoxListRenderer.java | 17 ++ .../recording/MultiRecordingFrame.java | 183 ++++++++++++++++++ .../proccessing/ui/iframe/RecordingFrame.java | 4 +- src/main/resources/icons/explorer.png | Bin 0 -> 34094 bytes src/main/resources/icons/rec.png | Bin 0 -> 54712 bytes 11 files changed, 411 insertions(+), 70 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/SmoothIcon.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/recording/CameraCheckItem.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/recording/CheckBoxListRenderer.java create mode 100644 src/main/java/io/swtc/proccessing/ui/desktop/recording/MultiRecordingFrame.java create mode 100644 src/main/resources/icons/explorer.png create mode 100644 src/main/resources/icons/rec.png diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index bd41aeb..b3f1065 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -2,19 +2,25 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; import io.swtc.proccessing.ui.IconSetter; +import io.swtc.proccessing.ui.ShowError; import io.swtc.proccessing.ui.desktop.DIM; import io.swtc.proccessing.ui.desktop.debug.Profiler; import io.swtc.proccessing.ui.desktop.evidence.EvidenceExportFrame; +import io.swtc.proccessing.ui.desktop.recording.MultiRecordingFrame; import io.swtc.proccessing.ui.iframe.*; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import static java.awt.SystemColor.desktop; + public class SwingIFrame { private final JFrame mainFrame; private final DesktopPane desktopPane; @@ -43,6 +49,8 @@ public class SwingIFrame { desktopIconManager = new DIM(desktopPane); setupDesktopExportFrame(); + setupRecordingFrame(); + setupFileEx(); setupProfiler(); setupFullscreenToggle(); @@ -61,6 +69,45 @@ public class SwingIFrame { ); } + private void setupRecordingFrame() { + desktopIconManager.addIcon( + "Record Batch", + IconSetter.getCamerarec_ImageIcon(), + () -> { + MultiRecordingFrame mrf = new MultiRecordingFrame(); + desktopPane.add(mrf); + mrf.show(); + try { + mrf.setSelected(true); + } catch (java.beans.PropertyVetoException e) { + ShowError.error(null,"Exception", "" + e.getStackTrace()); + } + } + ); + } + + private void setupFileEx() { + desktopIconManager.addIcon( + "Open Recordings", + IconSetter.getExplorerIcon(), + () -> { + String userHome = System.getProperty("user.home"); + File folder = new File(userHome,"Videos/swtcctv-rec"); + if (Desktop.isDesktopSupported() && folder.exists()) { + try { + Desktop.getDesktop().open(folder); + } catch (IOException e) { + ShowError.error(null, + "Failed to open Folder", + "Failed" + e.getMessage() + ); + } + } + } + + ); + } + private void setupProfiler() { desktopIconManager.addIcon( "Profiler", @@ -72,6 +119,7 @@ public class SwingIFrame { }); } + public void addCameraInternalFrame(Webcam webcam) { CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest); diff --git a/src/main/java/io/swtc/proccessing/ui/IconSetter.java b/src/main/java/io/swtc/proccessing/ui/IconSetter.java index 7347f2a..c985e26 100644 --- a/src/main/java/io/swtc/proccessing/ui/IconSetter.java +++ b/src/main/java/io/swtc/proccessing/ui/IconSetter.java @@ -12,6 +12,9 @@ public class IconSetter { private static ImageIcon ICON_ICON; private static Image effects_icon; private static ImageIcon dbg_icon; + private static Image camerarec; + private static ImageIcon camerarec_imgico; + private static ImageIcon expIcon; /* this is used for the app icon itself (the one in tb) */ public static Image getIcon() { @@ -73,4 +76,40 @@ public class IconSetter { } return dbg_icon; } + + public static Image getCamerarec_img() { + if (Objects.isNull(camerarec)) { + URL url = IconSetter.class.getResource("/icons/rec.png"); + if (Objects.isNull(url)) { + ShowError.error(null,"icon","recicon was null Type Image"); + throw new RuntimeException("NULL!"); + } + camerarec = Toolkit.getDefaultToolkit().getImage(url); + } + return camerarec; + } + + public static ImageIcon getCamerarec_ImageIcon() { + if (Objects.isNull(camerarec_imgico)) { + URL url = IconSetter.class.getResource("/icons/rec.png"); + if (Objects.isNull(url)) { + ShowError.error(null,"icon","getCamerarec_ImageIcon failed, Type Image"); + throw new RuntimeException("NULL!"); + } + camerarec_imgico = new ImageIcon(url); + } + return camerarec_imgico; + } + + public static ImageIcon getExplorerIcon() { + if (Objects.isNull(expIcon)) { + URL url = IconSetter.class.getResource("/icons/explorer.png"); + if (Objects.isNull(url)) { + ShowError.error(null,"icon","getExplorerIcon failed, Type Image"); + throw new RuntimeException("NULL!"); + } + expIcon = new ImageIcon(url); + } + return expIcon; + } } diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java b/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java index 8ac62e8..5a0265c 100644 --- a/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java +++ b/src/main/java/io/swtc/proccessing/ui/desktop/DesktopIcon.java @@ -1,35 +1,51 @@ package io.swtc.proccessing.ui.desktop; import javax.swing.*; +import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.geom.RoundRectangle2D; public class DesktopIcon extends JPanel { + private static final int ICON_SIZE = 64; + private static final int TOTAL_WIDTH = 100; + private static final int TOTAL_HEIGHT = 85; + private boolean hovered = false; - private final JLabel iconLabel; - private final JLabel textLabel; - public DesktopIcon(String label, Icon icon, Runnable action) { - - setLayout(new BorderLayout(4, 4)); + setLayout(new BorderLayout(0, 2)); setOpaque(false); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - if (icon instanceof ImageIcon) { - Image img = ((ImageIcon) icon).getImage(); - Image scaled = img.getScaledInstance(64, 64, Image.SCALE_SMOOTH); - icon = new ImageIcon(scaled); - } - iconLabel = new JLabel(icon, SwingConstants.CENTER); - textLabel = new ShadowLabel(label); + Dimension fixedSize = new Dimension(TOTAL_WIDTH, TOTAL_HEIGHT); + setPreferredSize(fixedSize); + setMinimumSize(fixedSize); + setMaximumSize(fixedSize); + + setBorder(new EmptyBorder(4, 4, 4, 4)); + + Icon scaledIcon = (icon instanceof ImageIcon) + ? new SmoothIcon(((ImageIcon) icon).getImage(), ICON_SIZE, ICON_SIZE) + : icon; + + JLabel iconLabel = new JLabel(scaledIcon, SwingConstants.CENTER); + iconLabel.setVerticalAlignment(SwingConstants.BOTTOM); + + JLabel textLabel = new ShadowLabel(label); textLabel.setHorizontalAlignment(SwingConstants.CENTER); + textLabel.setVerticalAlignment(SwingConstants.TOP); add(iconLabel, BorderLayout.CENTER); add(textLabel, BorderLayout.SOUTH); + setupMouseListeners(action); + } + + private void setupMouseListeners(Runnable action) { addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -52,56 +68,35 @@ public class DesktopIcon extends JPanel { }); } - @Override - public Dimension getPreferredSize() { - Dimension icon = iconLabel.getPreferredSize(); - Dimension text = textLabel.getPreferredSize(); - - int w = Math.max(icon.width, text.width) + 12; - int h = icon.height + text.height + 12; - - return new Dimension(w, h); - } - @Override protected void paintComponent(Graphics g) { if (hovered) { - Graphics2D g2 = (Graphics2D) g.create(); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - - boolean lightBg = isBackgroundLight(); - - Color fill = lightBg - ? new Color(0, 0, 0, 30) - : new Color(255, 255, 255, 40); - - Color border = lightBg - ? new Color(0, 0, 0, 80) - : new Color(255, 255, 255, 100); - - g2.setColor(fill); - g2.fillRoundRect(2, 2, getWidth() - 4, getHeight() - 4, 10, 10); - - g2.setColor(border); - g2.drawRoundRect(2, 2, getWidth() - 5, getHeight() - 5, 10, 10); - - g2.dispose(); + paintHoverEffect(g); } - super.paintComponent(g); } - private boolean isBackgroundLight() { - Container p = getParent(); - if (p == null) return true; + private void paintHoverEffect(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - Color bg = p.getBackground(); - int luminance = (int) ( - bg.getRed() * 0.299 + - bg.getGreen() * 0.587 + - bg.getBlue() * 0.114 - ); - return luminance > 180; + boolean lightBg = isBackgroundLight(); + Color fill = lightBg ? new Color(0, 0, 0, 20) : new Color(255, 255, 255, 30); + Color border = lightBg ? new Color(0, 0, 0, 50) : new Color(255, 255, 255, 70); + + g2.setColor(fill); + g2.fill(new RoundRectangle2D.Float(2, 2, getWidth() - 4, getHeight() - 4, 10, 10)); + + g2.setColor(border); + g2.draw(new RoundRectangle2D.Float(2, 2, getWidth() - 4, getHeight() - 4, 10, 10)); + + g2.dispose(); } -} + + public boolean isBackgroundLight() { + Container p = getParent(); + Color bg = (p != null) ? p.getBackground() : Color.WHITE; + double luminance = (0.299 * bg.getRed() + 0.587 * bg.getGreen() + 0.114 * bg.getBlue()) / 255; + return luminance > 0.6; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java b/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java index 6e5075f..ca9f5e0 100644 --- a/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java +++ b/src/main/java/io/swtc/proccessing/ui/desktop/ShadowLabel.java @@ -5,28 +5,44 @@ import java.awt.*; public class ShadowLabel extends JLabel { public ShadowLabel(String text) { - super(text, SwingConstants.CENTER); + super(text); setForeground(Color.WHITE); - setPreferredSize(new Dimension(15, 20)); } @Override protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g.create(); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - - FontMetrics fm = g2d.getFontMetrics(); String text = getText(); + if (text == null || text.isEmpty()) return; - int x = (getWidth() - fm.stringWidth(text)) / 2; - int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent(); + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2d.setColor(new Color(0, 0, 0, 200)); - g2d.drawString(text, x + 1, y + 1); + FontMetrics fm = g2.getFontMetrics(); + int availableWidth = getWidth(); - g2d.setColor(getForeground()); - g2d.drawString(text, x, y); + String drawText = text; + if (fm.stringWidth(text) > availableWidth) { + for (int i = text.length(); i > 0; i--) { + String temp = text.substring(0, i) + "..."; + if (fm.stringWidth(temp) <= availableWidth) { + drawText = temp; + break; + } + } + } + DesktopIcon parent = (DesktopIcon) getParent(); + boolean isLightBg = (parent != null) && parent.isBackgroundLight(); - g2d.dispose(); + Color textColor = isLightBg ? Color.BLACK : Color.WHITE; + Color shadowColor = isLightBg ? new Color(255, 255, 255, 200) : new Color(0, 0, 0, 180); + + int x = (availableWidth - fm.stringWidth(drawText)) / 2; + int y = fm.getAscent(); + + g2.setColor(shadowColor); + g2.drawString(drawText, x + 1, y + 1); + + g2.setColor(textColor); + g2.drawString(drawText, x, y); } } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/SmoothIcon.java b/src/main/java/io/swtc/proccessing/ui/desktop/SmoothIcon.java new file mode 100644 index 0000000..8ba9188 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/SmoothIcon.java @@ -0,0 +1,28 @@ +package io.swtc.proccessing.ui.desktop; + +import javax.swing.*; +import java.awt.*; + +public record SmoothIcon(Image image, int width, int height) implements Icon { + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2.drawImage(image, x, y, width, height, null); + g2.dispose(); + } + + @Override + public int getIconWidth() { + return width; + } + + @Override + public int getIconHeight() { + return height; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/recording/CameraCheckItem.java b/src/main/java/io/swtc/proccessing/ui/desktop/recording/CameraCheckItem.java new file mode 100644 index 0000000..647522c --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/recording/CameraCheckItem.java @@ -0,0 +1,13 @@ +package io.swtc.proccessing.ui.desktop.recording; + +import io.swtc.proccessing.ui.iframe.CameraInternalFrame; + +public class CameraCheckItem { + private final CameraInternalFrame frame; + private boolean selected = true; + public CameraCheckItem(CameraInternalFrame frame) { this.frame = frame; } + public CameraInternalFrame getFrame() { return frame; } + public boolean isSelected() { return selected; } + public void setSelected(boolean s) { this.selected = s; } + @Override public String toString() { return frame.getTitle(); } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/recording/CheckBoxListRenderer.java b/src/main/java/io/swtc/proccessing/ui/desktop/recording/CheckBoxListRenderer.java new file mode 100644 index 0000000..f661fdf --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/recording/CheckBoxListRenderer.java @@ -0,0 +1,17 @@ +package io.swtc.proccessing.ui.desktop.recording; + +import javax.swing.*; +import java.awt.*; + +public class CheckBoxListRenderer extends JCheckBox implements ListCellRenderer { + public CheckBoxListRenderer() { setOpaque(true); } + @Override + public Component getListCellRendererComponent(JList list, CameraCheckItem value, int index, boolean isSel, boolean cellHasFocus) { + setSelected(value.isSelected()); + setText(value.toString()); + setBackground(isSel ? list.getSelectionBackground() : list.getBackground()); + setForeground(isSel ? list.getSelectionForeground() : list.getForeground()); + setEnabled(list.isEnabled()); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/desktop/recording/MultiRecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/desktop/recording/MultiRecordingFrame.java new file mode 100644 index 0000000..8ce808d --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/desktop/recording/MultiRecordingFrame.java @@ -0,0 +1,183 @@ +package io.swtc.proccessing.ui.desktop.recording; + +import io.swtc.proccessing.CameraPanel; +import io.swtc.proccessing.ui.IconSetter; +import io.swtc.proccessing.ui.ShowError; +import io.swtc.proccessing.ui.iframe.CameraInternalFrame; +import io.swtc.recording.cv.AVRecorder; +import io.swtc.recording.cv.Quality; +import io.swtc.recording.cv.RecorderConfig; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MultiRecordingFrame extends JInternalFrame { + private final DefaultListModel listModel = new DefaultListModel<>(); + private final JList cameraList = new JList<>(listModel); + + private final List activeRecorders = new ArrayList<>(); + private boolean isRecording = false; + + private JButton toggleBtn; + private JComboBox globalQualityCombo; + private JLabel statusSummaryLabel; + + + public MultiRecordingFrame() { + super("Record Batch", true, true, false, true); + + Image ico = IconSetter.getCamerarec_img(); + setFrameIcon(new ImageIcon(ico)); + + setupUI(); + setSize(350, 400); + } + + private void setupUI() { + setLayout(new BorderLayout(10, 10)); + ((JPanel)getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + statusSummaryLabel = new JLabel("Ready", SwingConstants.CENTER); + + JPanel settingsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + settingsPanel.setBorder(BorderFactory.createTitledBorder("Global Encoding")); + globalQualityCombo = new JComboBox<>(Quality.values()); + globalQualityCombo.setSelectedItem(Quality.VERYFAST); + settingsPanel.add(new JLabel("CPU Preset:")); + settingsPanel.add(globalQualityCombo); + + cameraList.setCellRenderer(new CheckBoxListRenderer()); + cameraList.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent e) { + if (isRecording) return; + int index = cameraList.locationToIndex(e.getPoint()); + if (index != -1) { + CameraCheckItem item = listModel.getElementAt(index); + item.setSelected(!item.isSelected()); + cameraList.repaint(cameraList.getCellBounds(index, index)); + } + } + }); + + toggleBtn = new JButton("Start Batch Recording"); + toggleBtn.addActionListener(e -> handleToggleAction()); + + JButton refreshBtn = new JButton("Refresh List"); + refreshBtn.addActionListener(e -> refreshCameraList()); + + JPanel actionPanel = new JPanel(new GridLayout(2, 1, 5, 5)); + actionPanel.add(refreshBtn); + actionPanel.add(toggleBtn); + + JPanel southPanel = new JPanel(new BorderLayout(5, 5)); + southPanel.add(statusSummaryLabel, BorderLayout.NORTH); + southPanel.add(actionPanel, BorderLayout.SOUTH); + + // Add components to frame + add(settingsPanel, BorderLayout.NORTH); + add(new JScrollPane(cameraList), BorderLayout.CENTER); + add(southPanel, BorderLayout.SOUTH); + } + + + private void startRecordingProcess() { + List selectedFrames = new ArrayList<>(); + for (int i = 0; i < listModel.size(); i++) { + CameraCheckItem item = listModel.get(i); + if (item.isSelected()) selectedFrames.add(item.getFrame()); + } + + if (selectedFrames.isEmpty()) { + ShowError.warning(this,"Select 1 Camera at minimum","Selection"); + return; + } + + Quality quality = (Quality) globalQualityCombo.getSelectedItem(); + String preset = (quality != null) ? quality.getFFmpegValue() : "superfast"; + File videoDir = new File(System.getProperty("user.home"), "Videos/swtcctv-rec"); + if (!videoDir.exists()) videoDir.mkdirs(); + + for (CameraInternalFrame frame : selectedFrames) { + try { + CameraPanel panel = frame.getCameraPanel(); + BufferedImage sample = panel.getCurrentProcessedImage(); + if (sample == null) continue; + + File outputFile = new File(videoDir, "(" + frame.getTitle() + ") batch " + System.currentTimeMillis() + ".mp4"); + RecorderConfig config = new RecorderConfig(outputFile, sample.getWidth(), sample.getHeight(), 20, 18, preset); + + AVRecorder recorder = new AVRecorder(config); + recorder.start(); + panel.setExternalRecorder(recorder); + activeRecorders.add(recorder); + } catch (Exception e) { + System.err.println("Failed to start recorder for: " + frame.getTitle()); + } + } + isRecording = true; + updateUIState(true); + } + + private void stopRecordingProcess() { + for (AVRecorder recorder : activeRecorders) { + recorder.stop(); + } + activeRecorders.clear(); + + // Clear references from panels + for (int i = 0; i < listModel.size(); i++) { + listModel.get(i).getFrame().getCameraPanel().setExternalRecorder(null); + } + + isRecording = false; + updateUIState(false); + } + + private void handleToggleAction() { + if (!isRecording) startRecordingProcess(); + else stopRecordingProcess(); + } + + private void refreshCameraList() { + if (isRecording) return; + listModel.clear(); + JDesktopPane desktop = getDesktopPane(); + if (desktop == null) return; + + for (JInternalFrame f : desktop.getAllFrames()) { + if (f instanceof CameraInternalFrame camFrame) { + listModel.addElement(new CameraCheckItem(camFrame)); + } + } + + if (statusSummaryLabel != null) { + statusSummaryLabel.setText("Total" + listModel.size() + " Cameras"); + } + } + + private void updateUIState(boolean recordingActive) { + globalQualityCombo.setEnabled(!recordingActive); + cameraList.setEnabled(!recordingActive); + + if (recordingActive) { + toggleBtn.setText("Stop all"); + statusSummaryLabel.setText("Active"); + } else { + toggleBtn.setText("Start Batch Recording"); + toggleBtn.setBackground(null); + toggleBtn.setForeground(null); + statusSummaryLabel.setText("Status: Ready"); + statusSummaryLabel.setForeground(null); + } + } + + @Override + public void addNotify() { + super.addNotify(); + refreshCameraList(); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java index 234ebc6..6e822be 100644 --- a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingFrame.java @@ -29,6 +29,7 @@ public class RecordingFrame extends JInternalFrame { private File currentFile; private final Timer statsTimer; private long startTime; + private String camName; private final StringBuilder sb = new StringBuilder(32); @@ -41,6 +42,7 @@ public class RecordingFrame extends JInternalFrame { setFrameIcon(new ImageIcon(ico)); this.cameraPanel = cameraPanel; + this.camName = cameraName; initializeUI(); @@ -131,7 +133,7 @@ public class RecordingFrame extends JInternalFrame { } try { - currentFile = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4"); + currentFile = new File(outputDirectory, "(" + this.camName + ") " + "vid_" + System.currentTimeMillis() + ".mp4"); Quality selected = (Quality) presetCombo.getSelectedItem(); String preset = (selected != null) ? selected.getFFmpegValue() : "superfast"; diff --git a/src/main/resources/icons/explorer.png b/src/main/resources/icons/explorer.png new file mode 100644 index 0000000000000000000000000000000000000000..c16f50b71ed0907612d68fbf9ef7920bfeca3c25 GIT binary patch literal 34094 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


^fbpHlH>V0seOHeE36(C#0S+sD_ z3^#^V81P&vys9zny9^?D*Y`%ZM|^AgSwW^Fnc|^;+jRD8t~Ci2x~>9wY!Toz1$c36 zzI8WX*;YO-SfkAPV4;0;LC&^T7deu*WcU!oHIR|`f3E2vs1$YoP*xTTC%<`cN07>J zVNXs@`a*_;j+Ck8-x=*4B3*nFgC%S@r zM6T$B!3fr^$d?E{qwv{QTdMx|e?~Rm)bb?yCkha`p8F>VazdAq zKc|5wSk?38#H=6->z1twG*z7IYr$MTbeWuSG(>o3{z|p#6Qr-z73vI%P0LxLh7ye3 zXGW<48cK`n7_6`p&V@=kKHUcI_^DZts$z}1snK(UewU{&s_W&fiCa~%JS)Z2b^&&w z2>+l#GFLxOa9cDA6nSF);K&$YnxGjWKcvqj>`D zt;xsPZY%`Hk!)m&FC-bMA;aa*Ssj z%Nx{wz|y1&om3mcxVc(U`a4^$ag)hUVn;H`4$#z)kei^)RF6UN!n2*vbD!JlxHg`D zP%9K2A7f!DRx8x`1m)Mzc5#4)=;1fv-a$Cpgh>}eM*qGyp}h52*i51c&EYA3?1K&( z24D5BvPS}ITAFb_O+-0O6u+eRX!eZ~CAT132_WZ^MbGi&z3#V=+!FNyQDBZ*&2==} zT-_cD+{1tP(DO0)(gzH7ty~A$cp(wXbG8y+)IM-BN4@6Kta-r zRz2M?LOj2&<@)Nn&fN(^`+k+ImRaYCdx^pqo-lrI?30Wz!$``?PcTT!C8XunwN~t6 z^J3t+AVA#EfCTo-`2z%!+i^LF-&$?j0o-iRaR0!fy_LVdX3E8ck14aWdW!+!IF@ND zb?59|E*5>%$5?*DH5#nqLiRVmW@c~LMf;Bl`)%8op*n4<8G{$m-R`-5geH!=iBePQ zP_M-Jfk|=QJN*d)H5dH2-?ukZs4#S4%$$l&c%``+n4n!Dli zJ^GY8iSdJ)JJcI0Ot%dfU``pX6o@L>)fGgW>yntFRJV_%BA^iJ3E z-(ktJ=_jSzK=a#y#Mr(?RZy&VPr`!?{qCrb`x%0D^dka)({&S)Jk!v6?f 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 literal 0 HcmV?d00001 -- 2.49.1