From fc498b090e24651069fcceb4812831fbab370cf1 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Tue, 27 Oct 2026 06:46:09 +0000 Subject: [PATCH 1/2] solarcam: run ffmpeg without a shell; camera-supplied file names reached it convertFile() built the ffmpeg command as one f-string and ran it with shell=True. Its paths come from generateTargetFilePath(), which takes segments of the FileName the camera returns from OPFileQuery and only replaces "." with "-". A camera returning /mnt/sd/x;touch${IFS}PWNED;/001/... therefore ran commands on the downloading host whenever target_filetype_video was set. ffmpeg now gets an argv list, and generateTargetFilePath() keeps only [A-Za-z0-9_-] in the name it derives. Real names (2024-01-15_12-00-00) are unchanged, so existing blacklists and .dmp entries still match. convertFile() also logged success and deleted the downloaded recording even when ffmpeg failed. It now keeps the source on failure. Reported by @waseem-cve. --- solarcam.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/solarcam.py b/solarcam.py index c4594bc..d639cdc 100644 --- a/solarcam.py +++ b/solarcam.py @@ -3,6 +3,7 @@ from pathlib import Path import subprocess import json +import re from datetime import datetime @@ -73,6 +74,8 @@ def generateTargetFilePath(self, filename, downloadDir, extention=""): fileExtention = Path(filename).suffix filenameSplit = filename.split("/") filenameDisk = f"{filenameSplit[3]}_{filenameSplit[5][:8]}".replace(".", "-") + # the segments come from the camera; keep anything else out of the path + filenameDisk = re.sub(r"[^A-Za-z0-9_-]", "_", filenameDisk) targetPathClean = f"{downloadDir}/{filenameDisk}" if extention != "": @@ -81,16 +84,25 @@ def generateTargetFilePath(self, filename, downloadDir, extention=""): return f"{targetPathClean}{fileExtention}" def convertFile(self, sourceFile, targetFile): + # an argv list, not a shell string: the paths carry camera-supplied names if ( subprocess.run( - f"ffmpeg -framerate 15 -i {sourceFile} -b:v 1M -c:v libvpx-vp9 -c:a libopus {targetFile}", + [ + "ffmpeg", + "-framerate", "15", + "-i", sourceFile, + "-b:v", "1M", + "-c:v", "libvpx-vp9", + "-c:a", "libopus", + targetFile, + ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - shell=True, ).returncode != 0 ): self.logger.debug(f"Error converting video. Check {sourceFile}") + return self.logger.debug(f"File successfully converted: {targetFile}") Path(sourceFile).unlink() From 62c8e4705c47d5de2a815cc90ebc33402a0a3df2 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Tue, 27 Oct 2026 06:59:22 +0000 Subject: [PATCH 2/2] solarcam: retry a failed conversion from the kept source Keeping the source after a failed ffmpeg run left save_files() treating it as done, so the conversion was never retried. With conversion on, an existing source is now converted again without a second download, and a partial output from the failed run is removed so it is not mistaken for a finished conversion. --- solarcam.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/solarcam.py b/solarcam.py index d639cdc..e6bf722 100644 --- a/solarcam.py +++ b/solarcam.py @@ -102,6 +102,8 @@ def convertFile(self, sourceFile, targetFile): != 0 ): self.logger.debug(f"Error converting video. Check {sourceFile}") + # a partial output would be taken as converted and stop the retry + Path(targetFile).unlink(missing_ok=True) return self.logger.debug(f"File successfully converted: {targetFile}") @@ -122,10 +124,6 @@ def save_files(self, download_dir, files, blacklist=None, target_filetype=None): file["FileName"], download_dir, extention=f"{target_filetype}" ) - if Path(f"{target_file_path}").is_file(): - self.logger.debug(f"File already exists: {target_file_path}") - continue - if ( target_file_path_convert and Path(f"{target_file_path_convert}").is_file() @@ -135,6 +133,12 @@ def save_files(self, download_dir, files, blacklist=None, target_filetype=None): ) continue + # with conversion on, a source left by a failed conversion is retried + downloaded = Path(f"{target_file_path}").is_file() + if downloaded and not target_file_path_convert: + self.logger.debug(f"File already exists: {target_file_path}") + continue + if blacklist: if target_file_path in blacklist: self.logger.debug(f"File is on the blacklist: {target_file_path}") @@ -145,11 +149,12 @@ def save_files(self, download_dir, files, blacklist=None, target_filetype=None): ) continue - self.logger.debug(f"Downloading {target_file_path}...") - self.cam.download_file( - file["BeginTime"], file["EndTime"], file["FileName"], target_file_path - ) - self.logger.debug(f"Finished downloading {target_file_path}...") + if not downloaded: + self.logger.debug(f"Downloading {target_file_path}...") + self.cam.download_file( + file["BeginTime"], file["EndTime"], file["FileName"], target_file_path + ) + self.logger.debug(f"Finished downloading {target_file_path}...") if target_file_path_convert: self.logger.debug(f"Converting {target_file_path_convert}...")