Select and convert ANNKE camera footage program
Post ReplySelect and convert ANNKE camera footage programPosted: Wednesday, May 12, 2021 [15:45:05] - 1
Back-end programPosted: Wednesday, May 12, 2021 [20:10:58] - 2
Here is the program we run from cron. It takes a text file as an input and creates a hash for processing. Program written in Perl: # This software is free under AL/GPL, copyright (c) 2021 CodeMacs.com # You can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. # Use it at your own risk! use strict; use warnings; use POSIX qw(mktime); ## If running from cron - make sure PATH is set properly $ENV{'PATH'} = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local:/usr/local/bin:/opt/local/bin'; my $nowd = time; my $finaltime = localtime($nowd); my($wd,$mo,$da,$ti,$ye) = split(/\s/,$finaltime); # Record program process open STDOUT, '>>/program/location/process.last.txt' or die "Can't write to /program/location/process.last.txt: $!"; print "$finaltime\n"; unless(-f '/help/folder/camera.footage.request.txt') {print "No source file found - exit\n"; if(-f "/help/folder/ipLogD/cam.process.running") {print "Program busy\n"; my $exip=''; ## Get your external IP if sending link accessible on the Internet if(-f "/help/folder/ipLogD/currentIPeXt.txt") {$exip = `cat /help/folder/ipLogD/currentIPeXt.txt`;} else {$exip = `curl ipecho.net/plain ; echo`; open(IPT,">/help/folder/ipLogD/currentIPeXt.txt"); } ## END NO FILE PRESENT my %dataread=(); ## Read data submitted by GUI program open(TXT,"</help/folder/camera.footage.request.txt"); while(<TXT>) {$_ =~ s/\n|\r//g; my($ut,$k,$v) = split(/\t/,$_); } ## WHILE END close(TXT); `mv /help/folder/camera.footage.request.txt /help/folder/camera.footage.request.old.txt`; $cleanup{"/help/folder/camera.footage.request.old.txt"}=1; $cleanup{"/help/folder/ipLogD/cam.process.running"}=1; our %filesinfo = (); if(-f '/program/location/files.data.txt') {eval {require '/program/location/files.data.txt'};} my($user,$pass,$nvrip) = ('NVR_User_Name','NVR_Password','NVR_IP_Address'); our %caldata=(); my %convmvis = ( 'Jan','January','Feb','February','Mar','March','Apr','April','May','May','Jun','June', 'Jul','July','Aug','August','Sep','September','Oct','October','Nov','November','Dec','December' ); my %convmdig = ( 'Jan','01','Feb','02','Mar','03','Apr','04','May','05','Jun','06', 'Jul','07','Aug','08','Sep','09','Oct','10','Nov','11','Dec','12' ); my %convmdigmk = ( 'Jan','0','Feb','1','Mar','2','Apr','3','May','4','Jun','5','Jul','6','Aug','7','Sep','8','Oct','9','Nov','10','Dec','11' ); my %weekd = ('Mon','Monday','Tue','Tuesday','Wed','Wednesday','Thu','Thursday','Fri','Friday','Sat','Saturday','Sun','Sunday'); my @allm = ( 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec' ); my %cameras = ( ## Add Your cameras here, Key is cameras' NVR number 1,'Front Yard', 2,'Porch Top', 3,'Side Back', 4,'Back Yard' #5,'Cam 5', #6,'Cam 6' ); my %working=(); foreach my $t (keys(%dataread)) { my $cam = $dataread{$t}{cam}; if(length($dataread{$t}{day}) < 2) {$dataread{$t}{day}='0'.$dataread{$t}{day};} my $sttmdt="<startTime>$dataread{$t}{year}\-$convmdig{$dataread{$t}{month}}\-$dataread{$t}{day}T00:00:00Z</startTime>"; my $entmdt="<endTime>$dataread{$t}{year}\-$convmdig{$dataread{$t}{month}}\-$dataread{$t}{day}T23:59:59Z</endTime>"; my $gtkey=" $sttmdt\n $entmdt"; $datesget{$cam}{$gtkey}="$cam.$dataread{$t}{month}.$dataread{$t}{day}.$dataread{$t}{year}"; my $sttm="<startTime>$dataread{$t}{year}\-$convmdig{$dataread{$t}{month}}\-$dataread{$t}{day}T$dataread{$t}{hourst}:$dataread{$t}{minst}:00Z</startTime>"; my $entm="<endTime>$dataread{$t}{year}\-$convmdig{$dataread{$t}{month}}\-$dataread{$t}{day}T$dataread{$t}{houren}:$dataread{$t}{minen}:59Z</endTime>"; my $tkey=" $sttm\n $entm"; $working{$cam}{$tkey}="$dataread{$t}{month}.$dataread{$t}{day}.$dataread{$t}{year}-$dataread{$t}{hourst}.$dataread{$t}{minst}.00-$dataread{$t}{houren}.$dataread{$t}{minen}.59"; my $wday='0'; my $yer = $dataread{$t}{year}; $yer -= 1900; my $epstart = mktime($sec,$dataread{$t}{minst},$dataread{$t}{hourst},$dataread{$t}{day},$convmdigmk{$dataread{$t}{month}},$yer,$wday,0,-1); $cutmovie{$cam}{$tkey}{start}=$epstart; my $epend = mktime($sec,$dataread{$t}{minen},$dataread{$t}{houren},$dataread{$t}{day},$convmdigmk{$dataread{$t}{month}},$yer,$wday,0,-1); $cutmovie{$cam}{$tkey}{end}=$epend; } ## FOREACH UNIX TIME END %dataread=(); my $cook='/program/location/cookies/cookies.txt'; my $brows='Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'; getallfiles(); ## Get all files and their information for given camera for one day my $cnts=0; foreach my $cam (keys(%working)) { foreach my $time (keys %{$working{$cam}}) {$cnts++; my $vfilename=$working{$cam}{$time}; # Create XML request for event's files on NVR my $rqest = '<?xml version="1.0" encoding="utf-8"?> <CMSearchDescription> <searchID>{K13J2KE1-7226-0008-B4ZZ-3A8199B021B0}</searchID> <trackList> <trackID>'.$cam.'01</trackID> </trackList> <timeSpanList> <timeSpan> '.$time.' </timeSpan> </timeSpanList> <contentTypeList> <contentType>video</contentType> </contentTypeList> <maxResults>40</maxResults> <searchResultPostion>0</searchResultPostion> <metadataList> <metadataDescriptor>//recordType.meta.std-cgi.com/CMR</metadataDescriptor> </metadataList> </CMSearchDescription>'; open(TXD,">/help/folder/iplogD/req.$cam.$cnts.txt"); my $run = "curl -X POST -b $cook -c $cook -A \"$brows\" -d \@/help/folder/iplogD/req.$cam.$cnts.txt http://$user:$pass\@$nvrip/ISAPI/ContentMgmt/search"; print "$run\n\n"; my $result = `$run`; print "$result\n"; # Get rtsp requests for the files while($result =~ /(.*?)(\<playbackURI\>)(.*?)(\<\/playbackURI\>)/gs) {push @getvid,$3; my $subcnt=0; ## FOREACH VIDEO FILE START foreach my $vfile (@getvid) {$subcnt++; my $trun = "curl -d \"<downloadRequest><playbackURI>$vfile</playbackURI><userName>$user</userName><password>$pass</password></downloadRequest>\" http://$user:$pass\@$nvrip/ISAPI/ContentMgmt/download-o /program/location/temp/$vfilename.$cnts.$subcnt.mp4"; unless(-f "/program/location/temp/$vfilename.$cnts.$subcnt.mp4") {`$trun`;} $vfile =~ s#name=(.*?)\#; print "$trun\n\n"; $process{$cnts}{$subcnt}{full}=$filename; $process{$cnts}{$subcnt}{loc}="/program/location/temp/$vfilename.$cnts.$subcnt.mp4"; $process{$cnts}{cutstart}=$cutmovie{$cam}{$time}{start}; $process{$cnts}{cutend}=$cutmovie{$cam}{$time}{end}; $process{$cnts}{camera}=$cam; } ## FOREACH VIDEO FILE END } ## FOREACH TIMES END } ## FOREACH CAM END %cutmovie=(); # Write locally data for files on NVR if($fupdate) { ##################### my $wrttm=time; open(TXT,">/program/location/files.data.txt"); foreach my $n (sort keys (%filesinfo)) { if($filesinfo{$n}{delete}{when} < $wrttm) {next;} ## SKIP OLD RECORDS print TXT "$cma'$n' => {\n"; foreach my $d (sort keys %{$filesinfo{$n}}) { print TXT "$cma1'$d' => {\n"; foreach my $k (sort keys %{$filesinfo{$n}{$d}}) { print TXT "$cma2'$k','$filesinfo{$n}{$d}{$k}'"; } ## FOREACH K2 END print TXT "\n\t}"; } ## FOREACH K1 END print TXT "\n}"; } ## FOREACH FILE NAME print TXT "\n);\n"; } ## END IF FILE UPDATE REQUIRED ### ## Now process video files, convert, cut and combine them as needed my $errors=''; foreach my $n (keys (%process)) { ######### print "Processing $n\n"; my $movstart = $process{$n}{cutstart}; my $movend = $process{$n}{cutend}; my $camera = $process{$n}{camera}; my $cameraname=$cameras{$camera}; my $finalsave="$cameraname.$part1\-$part2"; if(-f "/help/folder/web/cameras/dnl/$finalsave.mp4") { ## SKIP IF ALREADY PRESENT $emailmess .= "File /cameras/dnl/$finalsave.mp4 already exist - skipping\n\n"; my $cmbnmb=0; foreach my $sf (sort num keys %{$process{$n}}) { my $fkey = $process{$n}{$sf}{full}; my $ifile = $process{$n}{$sf}{loc}; unless($movstart && $movend && $fkey && $ifile) { $errors .= "Camera: $cameras{$camera}\nTime frame: $part1 $part2 Missing data found:\nMovie start: $movstart\nMovie end: $movend\nOrig file: $fkey\nLocal file: $ifile\n----------------\n"; next;} $cmbnmb++; $filesaved=getmovie($movstart,$movend,$fkey,$ifile,$cmbnmb); print "\t$movstart\t$movend\t$fkey\t$ifile\t$cmbnmb\n"; $tofile .= "file '$filesaved'\n"; $cleanup{$filesaved}=1; } ## FOREACH FILE END if($cmbnmb > 1) {chomp $tofile; print "Combining all files\n"; open(FLS,">/program/location/temp/$finalsave.txt"); $cleanup{"/program/location/temp/$finalsave.txt"}=1; # Turn off ffmpeg safety check for full path files: "-safe 0" or programs will error-out `ffmpeg -safe 0 -f concat -i /program/location/temp/$finalsave.txt -codec copy /program/location/temp/$finalsave.mp4`; } ## END MULTIPLE FILES else {`mv $filesaved /program/location/temp/$finalsave.mp4`; $filesaved=''; `mv /program/location/temp/$finalsave.mp4 /help/folder/web/cameras/dnl/$finalsave.mp4`; # PORT is a port forwarding port on your router $emailmess .= "http://LAN_IP_Address/cameras/dnl/$finalsave.mp4\nhttp://$exip:PORT/cameras/dnl/$finalsave.mp4\n\n"; } ## FOREACH NMB END ###################### %process=(); # Now, email results my $sendmail = '/usr/sbin/sendmail -t -oi'; if($emailmess) { print "Sending email\n"; open(MAIL,"|$sendmail"); print MAIL <<EOM; From: your_user\@your_domain\nTo: to_user\@from_domain\nSubject: Video processed\n The following files were processed and ready for download: $emailmess EOM close(MAIL); } ## END EMAIL MESSAGES PRESENT if($errors) { print "Sending errors\n"; open(MAIL,"|$sendmail"); print MAIL <<EOM; From: your_user\@your_domain\nTo: to_user\@from_domain\nSubject: While processing Videos error(s) found\n The following errors were found while processing video files: $errors EOM close(MAIL); } ## END IF ERRORS PRESENT print "Cleaning-up\n"; foreach my $f (keys (%cleanup)) { if(-f $f) {unlink $f; } exit(0); # Program finished ## Subs ## sub num {$a <=> $b;} sub getmovie { ## GET MOVIE my($from,$to,$name,$read,$savet) = @_; my $recfrom = $filesinfo{$name}{start}{epoch}; my $recto = $filesinfo{$name}{end}{epoch}; print "Get from/to: $from $to\nMovie from/to: $recfrom $recto\n"; if($recfrom < $from) {my $diff = $from - $recfrom; $offset = " -ss $hr\:$min\:$sec"; } ## END OFFSET AT THE START if($recto > $to) {my $dstrt=''; if($recfrom < $from) {$dstrt=$from;} else {$dstrt=$recfrom;} my $diff = $to - $dstrt; $duration = " -t $hr\:$min\:$sec"; } ## END IF MOVIE ENDS AFTER CLIP END my $frun="ffmpeg$offset$duration -i $read -vf scale=3414:1920 -c:v libx264 -crf 25 -preset slow /program/location/temp/making.$savet.mp4"; print "$frun\n"; unless(-f "/program/location/temp/making.$savet.mp4") {`$frun`;} return("/program/location/temp/making.$savet.mp4"); } ## END SUB GET MOVIE #### sub getoffset { my $elapsed=shift; my $hours = int($elapsed / (60 * 60)); if(length($hours) < 2) {$hours = '0'.$hours;} if(length($mins) < 2) {$mins = '0'.$mins;} if(length($secs) < 2) {$secs = '0'.$secs;} return($hours,$mins,$secs); } ## END SUB GET OFFSET sub getfileinfo { ## Getting file data for easy time processing # Example of rtsp request #<playbackURI>rtsp://NVR_IP/Streaming/tracks/201/?starttime=20210511T000000Z&endtime=20210511T015812Z&name=00000000283000000&size=1066098420</playbackURI> # Based on start time and end time we can calculate the offset for our portion # by using the epoch time my $d=shift; $d =~ s#starttime=(.*?)\#; $d =~ s#endtime=(.*?)\#; $d =~ s#name=(.*?)\#; $d =~ s#size=(\d+)##; my $szmb = $size / (1024 * 1024); unless($filesinfo{$name}{recorded}{time}) {$fupdate=1;} else {return;} my($ye,$mo,$da,$hr,$min,$sec)=getdates($start); $filesinfo{$name}{file}{size}=$size; $filesinfo{$name}{file}{sizeMb}=$szmb; $filesinfo{$name}{recorded}{time}=$rectz; $filesinfo{$name}{delete}{when}=$deletewhen; $filesinfo{$name}{start}{hrs}=$hr; $filesinfo{$name}{start}{min}=$min; $filesinfo{$name}{start}{sec}=$sec; $filesinfo{$name}{start}{mo}=$mo; $filesinfo{$name}{start}{da}=$da; $filesinfo{$name}{start}{ye}=$ye; my $yer = $ye; $yer -= 1900; my $timestamp = mktime($sec,$min,$hr,$da,$mo1,$yer,$wday,0,-1); # Epoch time $filesinfo{$name}{start}{epoch}=$timestamp; ## ---------- ## ($ye,$mo,$da,$hr,$min,$sec)=getdates($end); $filesinfo{$name}{end}{hrs}=$hr; $filesinfo{$name}{end}{min}=$min; $filesinfo{$name}{end}{sec}=$sec; $filesinfo{$name}{end}{mo}=$mo; $filesinfo{$name}{end}{da}=$da; $filesinfo{$name}{end}{ye}=$ye; $yer = $ye; $yer -= 1900; $timestamp = mktime($sec,$min,$hr,$da,$mo1,$yer,$wday,0,-1); # Epoch time $filesinfo{$name}{end}{epoch}=$timestamp; } ## END SUB GET FILE INFO sub getdates { my $r=shift; return($1,$2,$3,$4,$5,$6); } ## END GET DATES sub getallfiles { ## GET INFO ON FILES THIS DAY print "\nGetting all Files\n"; foreach my $cam (keys (%datesget)) { foreach my $r (keys %{$datesget{$cam}}) { my $rqest = '<?xml version="1.0" encoding="utf-8"?> <CMSearchDescription> <searchID>{K13J2KE1-7226-0008-B4ZZ-3A8199B021B0}</searchID> <trackList> <trackID>'.$cam.'01</trackID> </trackList> <timeSpanList> <timeSpan> '.$r.' </timeSpan> </timeSpanList> <contentTypeList> <contentType>video</contentType> </contentTypeList> <maxResults>40</maxResults> <searchResultPostion>0</searchResultPostion> <metadataList> <metadataDescriptor>//recordType.meta.std-cgi.com/CMR</metadataDescriptor> </metadataList> </CMSearchDescription>'; open(RQ,">/help/folder/iplogD/reqest.$datesget{$cam}{$r}.txt"); my $run = "curl -X POST -b $cook -c $cook -A \"$brows\" -d \@/help/folder/iplogD/reqest.$datesget{$cam}{$r}.txt http://$user:$pass\@$nvrip/ISAPI/ContentMgmt/search"; print "$run\n\n"; my $result = `$run`; print "$result\n"; while($result =~ /(.*?)(\<playbackURI\>)(.*?)(\<\/playbackURI\>)/gs) {push @getvid,$3; foreach my $vfile (@getvid) {getfileinfo($vfile);} } ## FOREACH GET RECS END } ## FOREACH CAM END %datesget=(); } ## END SUB GET ALL DATES sub getfnconverted { my $t=shift; my($wd,$mo,$da,$ti,$ye) = split(/\s/,$finaltime); return("$mo.$da.$ye\-$hrs.$min"); } ## END GET DATE CONVERTED Just as simple as that! Prerequisites: ffmpeg, curl |