diff --git a/formatscaper/formatscaper.py b/formatscaper/formatscaper.py
index 9db9f8ee9d81603cd0344d7bee7cc24cd527642a..43d16fb27ae76291d55ce67f2b9e30bd8d9b44e7 100755
--- a/formatscaper/formatscaper.py
+++ b/formatscaper/formatscaper.py
@@ -2,6 +2,7 @@
 
 import argparse
 import dataclasses
+import os
 import pickle
 import re
 import subprocess
@@ -59,7 +60,7 @@ parser.add_argument(
     "-p",
     default=1,
     type=int,
-    help="number of siegfried processes to run in parallel (default: 1)",
+    help="number of siegfried processes to run in parallel; 0 and negative numbers will subtract from the number of CPU cores (default: 1)",  # noqa
 )
 parser.add_argument(
     "--sf-binary",
@@ -137,10 +138,30 @@ except OSError as e:
     sf_error_log = None
 
 
+# determine the level of threads to run in parallel
+# negative numbers mean "this much less than the number of CPUs I have",
+# as long as the result is greater than 0
+if (num_threads := args.parallel) <= 0:
+    num_cores = os.cpu_count()
+    if num_cores is None:
+        num_threads = 1
+        print(
+            "WARN: couldn't determine number of CPU cores, falling back to a single thread",  # noqa
+            file=sys.stderr,
+        )
+    else:
+        num_threads = os.cpu_count() + num_threads
+        if num_threads <= 0:
+            print(
+                f"ERROR: calculated number of threads would be less than 1: {num_threads}",  # noqa
+                file=sys.stderr,
+            )
+            sys.exit(1)
+
 # set up variables required in the collection of results
 all_results = []
 endangered_files = []
-sem = threading.Semaphore(args.parallel)
+sem = threading.Semaphore(num_threads)
 mutex = threading.Lock()
 completed_tasks = 0
 progress_bar = progressbar.ProgressBar(