summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-06-07 01:03:37 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-06-07 01:03:37 +0300
commit2fc3d44a03d022e9ce0afc9c2d4f0255c0011dbf (patch)
tree625803118087ba8ccdb22d9db11b0b134efd4c25
parentea0ac514650972fafef13726da2e6f74fdfe11cc (diff)
tools: add some tools to automate video analyzing
-rwxr-xr-xtools/process-motion-timecodes.py77
-rwxr-xr-xtools/roi-visualize.php114
2 files changed, 191 insertions, 0 deletions
diff --git a/tools/process-motion-timecodes.py b/tools/process-motion-timecodes.py
new file mode 100755
index 0000000..8c9d5fc
--- /dev/null
+++ b/tools/process-motion-timecodes.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+import os.path
+
+from argparse import ArgumentParser
+from datetime import datetime, timedelta
+
+DATETIME_FORMAT = '%Y-%m-%d-%H.%M.%S'
+
+
+def chunks(lst, n):
+ for i in range(0, len(lst), n):
+ yield lst[i:i + n]
+
+
+def time2seconds(time: str) -> int:
+ time, frac = time.split('.')
+ frac = int(frac)
+
+ h, m, s = [int(i) for i in time.split(':')]
+
+ return round(s + m*60 + h*3600 + frac/1000)
+
+
+def filename_to_datetime(filename: str) -> datetime:
+ filename = os.path.basename(filename).replace('record_', '').replace('.mp4', '')
+ return datetime.strptime(filename, DATETIME_FORMAT)
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser()
+ parser.add_argument('--source-filename', type=str, required=True,
+ help='recording filename')
+ parser.add_argument('--timecodes', type=str, required=True,
+ help='timecodes')
+ parser.add_argument('--padding', type=int, default=2,
+ help='amount of seconds to add before and after each fragment')
+ arg = parser.parse_args()
+
+ if arg.padding < 0:
+ raise ValueError('invalid padding')
+
+ timecodes = arg.timecodes.split(',')
+ if len(timecodes) % 2 != 0:
+ raise ValueError('invalid number of timecodes')
+
+ timecodes = list(map(time2seconds, timecodes))
+ timecodes = list(chunks(timecodes, 2))
+
+ # sort out invalid fragments (dvr-scan returns them sometimes, idk why...)
+ timecodes = list(filter(lambda f: f[0] < f[1], timecodes))
+
+ file_dt = filename_to_datetime(arg.source_filename)
+
+ # https://stackoverflow.com/a/43600953
+ timecodes.sort(key=lambda interval: interval[0])
+ merged = [timecodes[0]]
+ for current in timecodes:
+ previous = merged[-1]
+ if current[0] <= previous[1]:
+ previous[1] = max(previous[1], current[1])
+ else:
+ merged.append(current)
+
+ for fragment in merged:
+ start, end = fragment
+
+ start -= arg.padding
+ end += arg.padding
+
+ if start < 0:
+ start = 0
+
+ dt1 = (file_dt + timedelta(seconds=start)).strftime(DATETIME_FORMAT)
+ dt2 = (file_dt + timedelta(seconds=end)).strftime(DATETIME_FORMAT)
+ filename = f'{dt1}__{dt2}.mp4'
+
+ print(f'{start} {end} {filename}')
diff --git a/tools/roi-visualize.php b/tools/roi-visualize.php
new file mode 100755
index 0000000..9677303
--- /dev/null
+++ b/tools/roi-visualize.php
@@ -0,0 +1,114 @@
+#!/usr/bin/env php
+<?php
+
+function fatal(string $message) {
+ fprintf(STDERR, $message);
+ exit(1);
+}
+
+function parse_roi_input(string $file): array {
+ if (!file_exists($file))
+ throw new Error("file $file does not exists");
+
+ $lines = file($file);
+ $lines = array_map('trim', $lines);
+ $lines = array_filter($lines, fn($line) => $line != '' && $line[0] != '#');
+ $lines = array_map(fn($line) => array_map('intval', explode(' ', $line)), $lines);
+ foreach ($lines as $points) {
+ if (count($points) != 4)
+ throw new Exception("invalid line: ".implode(' ', $points));
+ }
+
+ return $lines;
+}
+
+function hex2rgb(int $color): array {
+ $r = ($color >> 16) & 0xff;
+ $g = ($color >> 8) & 0xff;
+ $b = $color & 0xff;
+ return [$r, $g, $b];
+}
+
+function imageopen(string $filename) {
+ $size = getimagesize($filename);
+ $types = [
+ 2 => 'jpeg',
+ 3 => 'png'
+ ];
+ if (!$size || !isset($types[$size[2]]))
+ return false;
+
+ $f = 'imagecreatefrom'.$types[$size[2]];
+ return call_user_func($f, $filename);
+}
+
+error_reporting(E_ALL);
+ini_set('display_errors', 1);
+
+$colors = [
+ 0xff0000,
+ 0x00ff00,
+ 0x0000ff,
+ 0xffff00,
+ 0xff00ff,
+ 0x00ffff,
+];
+
+if ($argc < 2)
+ fatal("usage: {$argv[0]} --roi FILE --input PATH --output PATH\n");
+
+array_shift($argv);
+while (count($argv) > 0) {
+ switch ($argv[0]) {
+ case '--roi':
+ array_shift($argv);
+ $roi_file = array_shift($argv);
+ break;
+
+ case '--input':
+ array_shift($argv);
+ $input = array_shift($argv);
+ break;
+
+ case '--output':
+ array_shift($argv);
+ $output = array_shift($argv);
+ break;
+
+ default:
+ fatal('unsupported argument: '.$argv[0]);
+ }
+}
+
+if (!$roi_file)
+ throw new Exception("--roi is not specified");
+
+if (!$input)
+ throw new Exception('--input is not specified');
+
+if (!$output)
+ throw new Exception('--output is not specified');
+
+$regions = parse_roi_input($roi_file);
+$img = imageopen($input);
+if (!$img)
+ throw new Exception("failed to open image");
+
+$imgw = imagesx($img);
+$imgh = imagesy($img);
+
+foreach ($regions as $i => $region) {
+ list($r, $g, $b) = hex2rgb($colors[$i]);
+
+ if ($region[0]+$region[2] > $imgw || $region[1]+$region[3] > $imgh)
+ throw new Exception('error: invalid region (line '.($i+1).')');
+
+ $col = imagecolorallocatealpha($img, $r, $g, $b, 50);
+ imagerectangle($img, $region[0], $region[1], $region[0]+$region[2], $region[1]+$region[3], $col);
+
+ $col = imagecolorallocatealpha($img, $r, $g, $b, 90);
+ imagefilledrectangle($img, $region[0]+1, $region[1]+1, $region[0]+$region[2]-2, $region[1]+$region[3]-2, $col);
+}
+
+imagejpeg($img, $output, 97);
+echo "saved to $output\n";