summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon duSaint2023-09-14 09:07:47 -0700
committerJon duSaint2023-09-14 09:07:47 -0700
commitaf3fe6aa15126e10b7e3e8fe67e0d5b28c63f0c2 (patch)
tree053390200091588de48b3247913c24bc65b8840c
parentcd0dbda115ef37e56adfc1095079d8370940b444 (diff)

reolnk: Support multiple cameras

Multiple cameras can now have snapshots taken and videos generated. The cameras are hardcoded for now. Images and videos go into a per-camera spool.

-rwxr-xr-xreolink/reolink210
1 files changed, 136 insertions, 74 deletions
diff --git a/reolink/reolink b/reolink/reolink
index 453f203..b2ac31f 100755
--- a/reolink/reolink
+++ b/reolink/reolink
@@ -66,7 +66,7 @@ my %commands = (
timelapse => {
args => 1,
server => \&Server::timelapse,
- validate => sub { $_[0] =~ m/^\d{8}$/ },
+ validate => sub { $_[-1] =~ m/^\d{8}$/ },
help => 'Generate a timelapse for YYYYMMDD'
},
exit => {
@@ -120,7 +120,7 @@ use Getopt::Long;
use IO::Select;
use OpenBSD::Pledge;
use OpenBSD::Unveil;
-use POSIX qw(mktime setsid :sys_wait_h);
+use POSIX qw(mktime setsid :signal_h :sys_wait_h);
use Socket;
use Sys::Syslog qw/:standard :macros/;
@@ -140,7 +140,16 @@ my %server_params = (interval => $interval,
ntp_ip => '192.168.127.1',
spooltime => $spool_retention_time,
video => $default_video_range);
-my $camera_host = '192.168.127.10';
+# See dhcpd.conf for IP assignments
+my %cameras = ('camera1' => {display => 'Camera 1',
+ spool => 'camera_1',
+ ip => '192.168.127.10',
+ args => {}},
+ 'camera2' => {display => 'Camera 2',
+ spool => 'camera_2',
+ ip => '192.168.127.11',
+ args => {image_quality_workaround => 1}});
+
my ($debug, $local) = (0, 0);
sub message {
@@ -208,16 +217,25 @@ sub open_socket {
$server_socket;
}
+sub spool {
+ File::Spec->catdir ($server_params{spool_dir}, $cameras{$_[0]}->{spool});
+}
+
+sub host {
+ $cameras{$_[0]}->{ip};
+}
+
sub setup_reolink {
+ my $camera = $_[0];
my ($resp, $code);
- my $r = Reolink->new (host => $camera_host);
- $r->Login || die "Failed to login\n";
+ my $r = Reolink->new (host => host ($camera), %{ $cameras{$camera}->{args} });
+ $r->Login || die "Failed to login to $camera\n";
$r->Errors (1);
$code = $r->SetNtp (1, $server_params{ntp_ip});
$code = $r->SetTime; # just enable DST
- $code = $r->SetOsd (0, 0, "Ay, Spy", "Upper Left", 0, "Upper Right");
+ $code = $r->SetOsd (0, 0, "AySpy", "Upper Left", 0, "Upper Right");
$r->Logout;
}
@@ -248,25 +266,29 @@ sub video {
}
sub snapshot {
- my $r = Reolink->new (host => $camera_host);
- $r->Login || die "Failed to login\n";
- $r->Errors (1);
+ my @cameras = @_ ? @_ : keys (%cameras);
- my @t = localtime;
- my $fn = sprintf ("$server_params{spool_dir}/%04d%02d%02d-%02d%02d%02d.jpg",
- $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
+ foreach my $camera (@cameras) {
+ my $r = Reolink->new (host => host ($camera));
+ $r->Login || die "Failed to login to $camera\n";
+ $r->Errors (1);
- # Could have changed since last time through
- make_path ($server_params{spool_dir}, { mode => 0755 }) unless -d $server_params{spool_dir};
+ my @t = localtime;
+ my $fn = File::Spec->catfile (spool ($camera), sprintf ("%04d%02d%02d-%02d%02d%02d.jpg",
+ $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]));
- debug ("snapshot => $fn");
+ # Could have changed since last time through
+ make_path (spool ($camera), { mode => 0755 }) unless -d spool ($camera);
- # take snapshot
- if ($r->Snap ($fn)) {
- warn "Error creating snapshot\n";
- }
+ debug ("snapshot => $fn");
- $r->Logout;
+ # take snapshot
+ if ($r->Snap ($fn)) {
+ warn "Error creating snapshot\n";
+ }
+
+ $r->Logout;
+ }
"snap";
}
@@ -314,77 +336,102 @@ sub process_command {
}
-my $process_child;
+my %process_children;
my @sigchld_handlers;
sub process_complete {
local ($!, $?);
while ((my $pid = waitpid (-1, WNOHANG)) > 0) {
- if ($pid == $process_child) {
- debug ("child process $process_child complete");
+ if (defined $process_children{$pid}) {
+ debug ("child process $pid ($process_children{$pid}) complete");
+ delete $process_children{$pid};
if (@sigchld_handlers) {
$SIG{CHLD} = pop @sigchld_handlers;
}
- undef $process_child;
}
}
}
sub maybe_generate_video {
+ # [camera list] [time]
+ my @cameras;
+ foreach my $a (0..$#_) {
+ if (defined $cameras{$_[$a]}) {
+ push @cameras, $_[$a];
+ delete $_[$a];
+ }
+ }
+ @cameras = keys (%cameras) unless @cameras;
+
+ my $date = $_[0] if $_[0] && $_[0] =~ m/^\d{8}$/;
+
# if current time has passed $end_time and no video exists for this date and there is no pending video process
- if (defined ($process_child)) {
- debug ("child process $process_child running...");
- return;
- }
+ CAMERA:
+ foreach my $camera (@cameras) {
+ foreach my $child (values %process_children) {
+ if ($child eq $camera) {
+ debug ("child process $child running...");
+ next CAMERA;
+ }
+ }
- my ($start_time, $end_time) = $commands{video}->{validate} ($server_params{video});
+ my ($start_time, $end_time) = $commands{video}->{validate} ($server_params{video});
- my @t = localtime;
- my $time = sprintf ('%d%02d', $t[2], $t[1]);
- if ($time < $end_time && @_ == 0) { # an arg means we have a particular date in mind so skip this check
- debug ("too early to generate video ($time < $end_time)");
- return;
- }
+ my @t = localtime;
+ my $time = sprintf ('%d%02d', $t[2], $t[1]);
+ if ($time < $end_time && @_ == 0) { # an arg means we have a particular date in mind so skip this check
+ debug ("too early to generate video ($time < $end_time)");
+ return;
+ }
- my $video_prefix = @_ >= 1 ? $_[0] : sprintf ('%04d%02d%02d', $t[5]+1900, $t[4]+1, $t[3]);
+ my $video_prefix = $date // sprintf ('%04d%02d%02d', $t[5]+1900, $t[4]+1, $t[3]);
- my @videos;
- foreach my $ext (@video_extensions) {
- push @videos, <$server_params{spool_dir}/$video_prefix*.$ext>;
- }
+ my @videos;
+ foreach my $ext (@video_extensions) {
+ push @videos, <spool($camera)/$video_prefix*.$ext>;
+ }
- if (@videos) {
- debug ("already generated for $video_prefix");
- return;
- }
+ if (@videos) {
+ debug ("already generated for $camera $video_prefix");
+ next;
+ }
- message ("generating video for $video_prefix");
+ message ("generating video for $camera $video_prefix");
- # extract program name and any "--debug" from @saved_argv
- my @process_args;
- foreach (@saved_argv) {
- next if m/server/;
- push @process_args, $_;
- }
- push @process_args, ('--process' => $video_prefix, '--range' => $server_params{video});
+ # extract program name and any "--debug" from @saved_argv
+ my @process_args;
+ foreach (@saved_argv) {
+ next if m/server/;
+ push @process_args, $_;
+ }
+ push @process_args, ('--process' => $video_prefix, '--range' => $server_params{video}, '--spool', spool ($camera));
- push @sigchld_handlers, $SIG{CHLD};
- $SIG{CHLD} = \&process_complete;
+ push @sigchld_handlers, $SIG{CHLD};
+ $SIG{CHLD} = \&process_complete;
- $process_child = fork ();
- unless (defined ($process_child)) {
- error ("failed to fork video process");
- pop @sigchld_handlers;
- return;
- }
+ my $sigset = POSIX::SigSet->new (SIGCHLD);
+ my $sigset_prev = POSIX::SigSet->new ();
- if ($process_child == 0) {
- exec @process_args or die "failed to launch child: @process_args: $!";
- }
+ sigprocmask (SIG_BLOCK, $sigset, $sigset_prev);
+
+ my $process_child = fork ();
+ unless (defined ($process_child)) {
+ error ("failed to fork video process for $camera");
+ pop @sigchld_handlers;
+ next;
+ }
+
+ if ($process_child == 0) {
+ exec @process_args or die "failed to launch child for $camera: @process_args: $!";
+ }
- debug ("launched video process as pid $process_child");
+ $process_children{$process_child} = $camera;
+ sigprocmask (SIG_UNBLOCK, $sigset_prev);
+
+ debug ("launched video process for $camera as pid $process_child");
+ }
}
sub timelapse {
@@ -393,6 +440,8 @@ sub timelapse {
}
sub respool_and_generate_slideshow {
+ my $camera = 'camera1'; ### XXX:
+
# Retain only past 24 hours of stills
my $t = time - $server_params{spooltime} * 60 * 60;
@@ -404,6 +453,7 @@ sub respool_and_generate_slideshow {
my $generated = localtime;
+ ### XXX: Figure out how to get iDevice page size correct
if ($fh) {
print $fh <<"SLIDESHOW";
<!DOCTYPE html>
@@ -498,7 +548,7 @@ sub respool_and_generate_slideshow {
SLIDESHOW
}
- foreach my $image (<"$server_params{spool_dir}/*.jpg">) {
+ foreach my $image (<spool($camera)/*.jpg>) {
if ($image =~ m/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})\.jpg/) {
my $file_time = mktime ($6, $5, $4, $3, $2 - 1, $1 - 1900);
if ($file_time < $t) {
@@ -614,7 +664,7 @@ SLIDESHOW
my @video_list;
my @videos;
foreach my $ext (@video_extensions) {
- push @video_list, <$server_params{spool_dir}/*.$ext>;
+ push @video_list, <spool($camera)/*.$ext>;
}
foreach my $video (sort @video_list) {
@@ -626,15 +676,23 @@ SLIDESHOW
print $fh $_ foreach map { qq( <option value="$_->[0]$_->[1]$_->[2]">$_->[0]-$_->[1]-$_->[2]</option>\n) } reverse @videos;
- my ($video, $keyframe) = ("$videos[-1]->[0]$videos[-1]->[1]$videos[-1]->[2].$videos[-1]->[3]",
- "$videos[-1]->[0]$videos[-1]->[1]$videos[-1]->[2]_kf.jpg");
-
print $fh <<"SLIDESHOW";
</select>
</div>
+SLIDESHOW
+
+ if (@videos) {
+ my ($video, $keyframe) = ("$videos[-1]->[0]$videos[-1]->[1]$videos[-1]->[2].$videos[-1]->[3]",
+ "$videos[-1]->[0]$videos[-1]->[1]$videos[-1]->[2]_kf.jpg");
+
+ print $fh <<"SLIDESHOW";
<video id="videobox" src="$video" poster="$keyframe" controls>
This could be a video instead.
</video>
+SLIDESHOW
+ }
+
+ print $fh <<"SLIDESHOW";
</div>
<div class="slideshow" id="slideshow-tab">
@@ -710,7 +768,7 @@ sub run {
$SIG{INT} = \&server_terminate;
$SIG{TERM} = \&server_terminate;
- setup_reolink ();
+ setup_reolink ($_) foreach keys %cameras;
my $s = open_socket ();
my $i = IO::Select->new ();
@@ -978,10 +1036,12 @@ sub process {
sub run {
my ($start_time, $end_time);
my $range = $default_video_range;
+ my $spool;
- GetOptions (process => sub {},
- 'range=s' => \$range,
- debug => sub {
+ GetOptions (process => sub {},
+ 'range=s' => \$range,
+ 'spool=s' => \$spool,
+ debug => sub {
$global_config = \%debug_config;
});
@@ -990,6 +1050,8 @@ sub run {
main::load_params (\%process_params);
+ $process_params{spool_dir} = $spool if $spool;
+
# Summer: 5:42AM / 8:08PM (DST)
# Winter: 6:55AM / 4:48PM
#
@@ -1000,7 +1062,7 @@ sub run {
my @filelist = map { /-(?|0?(\d{3})|(\d{4}))\d{2}\./ && $1 >= $start_time && $1 <= $end_time ? $_ : () } glob "$process_params{spool_dir}/$ARGV[0]-*";
- die "no files found with prefix $ARGV[0]\n" unless @filelist;
+ die "no files found with prefix $ARGV[0] in $process_params{spool_dir}\n" unless @filelist;
print join("\n", @filelist)."\n";
my @outfiles;
my @times;