Skip to content

Instantly share code, notes, and snippets.

@bobrik
Last active November 12, 2023 04:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bobrik/d7b5ecdc7946070512cb5de19881638e to your computer and use it in GitHub Desktop.
Save bobrik/d7b5ecdc7946070512cb5de19881638e to your computer and use it in GitHub Desktop.
Amcrest cleanup script

Amcrest cleanup script

Handy if you want to keep the most recent Amcrest recordings around.

Example output:

$ ./cleanup.py -s $((400 * 1024)) /Volumes/cameras
Checked /Volumes/cameras/amcrest02/2023-11-11/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-07/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-05/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-06/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-10/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-09/001/dav
Checked /Volumes/cameras/amcrest02/2023-11-08/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-11/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-07/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-05/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-06/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-10/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-09/001/dav
Checked /Volumes/cameras/amcrest01/2023-11-08/001/dav
Total size: 488.29GiB, max allowed: 400.00GiB
Removing 2023-11-05T01 amcrest01 [3.89GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/01)
Removing 2023-11-05T01 amcrest02 [1.35GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/01)
Removing 2023-11-05T02 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/02)
Removing 2023-11-05T02 amcrest02 [0.67GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/02)
Removing 2023-11-05T03 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/03)
Removing 2023-11-05T03 amcrest02 [0.67GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/03)
Removing 2023-11-05T04 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/04)
Removing 2023-11-05T04 amcrest02 [0.68GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/04)
Removing 2023-11-05T05 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/05)
Removing 2023-11-05T05 amcrest02 [0.67GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/05)
Removing 2023-11-05T06 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/06)
Removing 2023-11-05T06 amcrest02 [0.71GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/06)
Removing 2023-11-05T07 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/07)
Removing 2023-11-05T07 amcrest02 [1.29GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/07)
Removing 2023-11-05T08 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/08)
Removing 2023-11-05T08 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/08)
Removing 2023-11-05T09 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/09)
Removing 2023-11-05T09 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/09)
Removing 2023-11-05T10 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/10)
Removing 2023-11-05T10 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/10)
Removing 2023-11-05T11 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/11)
Removing 2023-11-05T11 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/11)
Removing 2023-11-05T12 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/12)
Removing 2023-11-05T12 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/12)
Removing 2023-11-05T13 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/13)
Removing 2023-11-05T13 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/13)
Removing 2023-11-05T14 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/14)
Removing 2023-11-05T14 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/14)
Removing 2023-11-05T15 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/15)
Removing 2023-11-05T15 amcrest02 [1.95GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/15)
Removing 2023-11-05T16 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/16)
Removing 2023-11-05T16 amcrest02 [1.05GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/16)
Removing 2023-11-05T17 amcrest01 [1.88GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/17)
Removing 2023-11-05T17 amcrest02 [0.73GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/17)
Removing 2023-11-05T18 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/18)
Removing 2023-11-05T18 amcrest02 [0.90GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/18)
Removing 2023-11-05T19 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/19)
Removing 2023-11-05T19 amcrest02 [0.66GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/19)
Removing 2023-11-05T20 amcrest01 [1.94GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/20)
Removing 2023-11-05T20 amcrest02 [0.73GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/20)
Removing 2023-11-05T21 amcrest01 [2.90GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/21)
Removing 2023-11-05T21 amcrest02 [0.64GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/21)
Removing 2023-11-05T22 amcrest01 [0.97GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/22)
Removing 2023-11-05T22 amcrest02 [0.64GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/22)
Removing 2023-11-05T23 amcrest01 [1.94GiB] (/Volumes/cameras/amcrest01/2023-11-05/001/dav/23)
Removing 2023-11-05T23 amcrest02 [0.64GiB] (/Volumes/cameras/amcrest02/2023-11-05/001/dav/23)
Removing 2023-11-06T00 amcrest01 [1.93GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/00)
Removing 2023-11-06T00 amcrest02 [0.64GiB] (/Volumes/cameras/amcrest02/2023-11-06/001/dav/00)
Removing 2023-11-06T01 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/01)
Removing 2023-11-06T01 amcrest02 [0.65GiB] (/Volumes/cameras/amcrest02/2023-11-06/001/dav/01)
Removing 2023-11-06T02 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/02)
Removing 2023-11-06T02 amcrest02 [0.64GiB] (/Volumes/cameras/amcrest02/2023-11-06/001/dav/02)
Removing 2023-11-06T03 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/03)
Removing 2023-11-06T03 amcrest02 [0.65GiB] (/Volumes/cameras/amcrest02/2023-11-06/001/dav/03)
Removing 2023-11-06T04 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/04)
Removing 2023-11-06T04 amcrest02 [0.65GiB] (/Volumes/cameras/amcrest02/2023-11-06/001/dav/04)
Removing 2023-11-06T05 amcrest01 [1.95GiB] (/Volumes/cameras/amcrest01/2023-11-06/001/dav/05)
Removing empty directory /Volumes/cameras/amcrest02/2023-11-05/001/dav
Removing empty directory /Volumes/cameras/amcrest01/2023-11-05/001/dav
Removing empty directory /Volumes/cameras/amcrest02/2023-11-05/001
Removing empty directory /Volumes/cameras/amcrest01/2023-11-05/001
Removing empty directory /Volumes/cameras/amcrest01/2023-11-05
Removing empty directory /Volumes/cameras/amcrest02/2023-11-05
#!/usr/bin/env python3
import os
import sys
import shutil
import argparse
SKIP_NAMES = set([".DS_Store"])
class Chunk:
def __init__(self, path):
self.path = path
parts = self.path.split("/")
self.key = f"{parts[-4]}T{parts[-1]} {parts[-5]}"
self.size = dir_size(path)
def __repr__(self):
return f"{self.key} [{gib(self.size):.2f}GiB] ({self.path})"
def gib(size):
return size / 1024 / 1024 / 1024
def dir_size(path):
size = 0
for dirpath, dirnames, filenames in os.walk(path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
size += os.path.getsize(fp)
return size
def find_empty(path):
empty = set([])
for dirpath, dirnames, filenames in os.walk(path, topdown=False):
filenames = list(filter(lambda name: name not in SKIP_NAMES, filenames))
if not dirnames and not filenames:
empty.add(dirpath)
return empty
def cleanup_empty_dirs(path):
empty = find_empty(path)
check = set([])
for empty in find_empty(path):
print(f"Removing empty directory {empty}")
shutil.rmtree(empty)
check.add(os.path.dirname(empty))
while check:
next_check = set([])
for candidate in check:
if not os.path.exists(candidate):
continue
items = list(
filter(lambda name: name not in SKIP_NAMES, os.listdir(candidate))
)
if not items:
print(f"Removing empty directory {candidate}")
shutil.rmtree(candidate)
next_check.add(os.path.dirname(candidate))
check = next_check
def collect_chunks(path):
chunks = []
for dirpath, dirnames, filenames in os.walk(path, topdown=False):
if not dirpath.endswith("dav"):
continue
for dirname in dirnames:
chunks.append(Chunk(os.path.join(dirpath, dirname)))
print(f"Checked {dirpath}", file=sys.stderr)
return chunks
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="Amcrest camera cleanup",
description="Cleanup old videos from Amcrest cameras",
)
parser.add_argument("path", help="Path to amcrest video files root")
parser.add_argument("-s", "--size", required=True, help="Max size to keep in MiB")
args = parser.parse_args()
permitted_size = int(args.size) * 1024 * 1024
chunks = sorted(collect_chunks(args.path), key=lambda chunk: chunk.key)
total_size = 0
for chunk in chunks:
total_size += chunk.size
print(
f"Total size: {gib(total_size):.2f}GiB, max allowed: {gib(permitted_size):.2f}GiB",
file=sys.stderr,
)
while total_size > permitted_size:
chunk = chunks.pop(0)
print(f"Removing {chunk}", file=sys.stderr)
total_size -= chunk.size
shutil.rmtree(chunk.path)
cleanup_empty_dirs(args.path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment