diff options
| author | Jon duSaint | 2023-09-14 09:07:47 -0700 |
|---|---|---|
| committer | Jon duSaint | 2023-09-14 09:07:47 -0700 |
| commit | af3fe6aa15126e10b7e3e8fe67e0d5b28c63f0c2 (patch) | |
| tree | 053390200091588de48b3247913c24bc65b8840c | |
| parent | cd0dbda115ef37e56adfc1095079d8370940b444 (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-x | reolink/reolink | 210 |
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; |
