Skip to content

Instantly share code, notes, and snippets.

@brianonn
Created November 9, 2025 05:04
Show Gist options
  • Select an option

  • Save brianonn/8546f6d1aa286fe2229c2eff72507d57 to your computer and use it in GitHub Desktop.

Select an option

Save brianonn/8546f6d1aa286fe2229c2eff72507d57 to your computer and use it in GitHub Desktop.
SFV verifier
#!/usr/bin/env python3
"""
SFV (Simple File Verification) file validator.
Reads an SFV file and verifies the CRC32 checksum of each listed file.
"""
import sys
import os
import zlib
def calculate_crc32(filepath):
"""
Calculate the CRC32 checksum for a file.
Returns the checksum as an 8-character uppercase hex string.
"""
crc = 0
try:
with open(filepath, 'rb') as f:
# Read file in chunks to handle large files efficiently
while chunk := f.read(65536): # 64KB chunks
crc = zlib.crc32(chunk, crc)
# Convert to unsigned 32-bit value and format as 8-digit hex
return format(crc & 0xFFFFFFFF, '08X')
except IOError as e:
raise IOError(f"Error reading file '{filepath}': {e}")
def parse_sfv_line(line):
"""
Parse a line from an SFV file.
Returns (filename, expected_crc) tuple or None if it's a comment/empty line.
"""
# Strip whitespace
line = line.strip()
# Skip empty lines and comments (lines starting with semicolon)
if not line or line.startswith(';'):
return None
# Split on whitespace - last token should be the CRC32
parts = line.rsplit(None, 1)
if len(parts) != 2:
return None
filename, crc = parts
# CRC should be 8 hex characters
if len(crc) == 8 and all(c in '0123456789ABCDEFabcdef' for c in crc):
return filename, crc.upper()
return None
def verify_sfv_file(sfv_filepath):
"""
Read and verify all files listed in the SFV file.
Returns tuple of (total_files, passed_files, failed_files).
"""
if not os.path.exists(sfv_filepath):
print(f"ERROR: SFV file '{sfv_filepath}' not found")
return 0, 0, 0
# Get the directory containing the SFV file to resolve relative paths
sfv_dir = os.path.dirname(os.path.abspath(sfv_filepath))
total = 0
passed = 0
failed = 0
try:
with open(sfv_filepath, 'r', encoding='utf-8', errors='replace') as sfv:
for line_num, line in enumerate(sfv, 1):
result = parse_sfv_line(line)
if result is None:
continue
filename, expected_crc = result
total += 1
# Resolve file path relative to SFV file location
filepath = os.path.join(sfv_dir, filename)
# Check if file exists
if not os.path.exists(filepath):
print(f"FAIL: File not found: {filename}")
failed += 1
continue
# Check if it's a directory
if os.path.isdir(filepath):
print(f"FAIL: Path is a directory, not a file: {filename}")
failed += 1
continue
# Calculate CRC32
try:
calculated_crc = calculate_crc32(filepath)
if calculated_crc == expected_crc:
print(f"OK: {filename}")
passed += 1
else:
print(f"FAIL: {filename}")
print(f" Expected: {expected_crc}, Got: {calculated_crc}")
failed += 1
except IOError as e:
print(f"FAIL: {filename}")
print(f" {e}")
failed += 1
except IOError as e:
print(f"ERROR: Cannot read SFV file '{sfv_filepath}': {e}")
return 0, 0, 0
return total, passed, failed
def main():
if len(sys.argv) != 2:
print("Usage: python sfv_validator.py <sfv_file>")
print("Example: python sfv_validator.py files.sfv")
sys.exit(1)
sfv_file = sys.argv[1]
print(f"Verifying files listed in: {sfv_file}")
print("-" * 60)
total, passed, failed = verify_sfv_file(sfv_file)
print("-" * 60)
if total == 0:
print("No files found to verify in SFV file")
else:
print(f"Total: {total} files")
print(f"Passed: {passed} files")
print(f"Failed: {failed} files")
if failed == 0:
print("\nAll files verified successfully!")
sys.exit(0)
else:
print(f"\nVerification failed for {failed} file(s)")
sys.exit(1)
if __name__ == "__main__":
main()
@brianonn
Copy link
Author

brianonn commented Nov 9, 2025

Description

Verify the CRCs listed in SFV files against their file contents

SFV files have the format:

filename <space> crc32

i.e.

ac-foobar.r01 e8f627bc
ac-foobar.r02 2f8f5d98
ac-foobar.r03 2d9d6b77
ac-foobar.r04 17055847
ac-foobar.rar 3a6a2f6f

Usage

sfv_verify <filename.sfv>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment