49 lines
2.0 KiB
Python
49 lines
2.0 KiB
Python
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
|
|
root = Path.home() / 'Library/Application Support/KJamsClone/DJ'
|
|
media = root / 'Media'
|
|
def digest(path):
|
|
h = hashlib.sha256()
|
|
with path.open('rb') as stream:
|
|
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
with sqlite3.connect(f'file:{root / "Library.sqlite"}?mode=ro', uri=True) as db:
|
|
originals = [Path(row[0]) for row in db.execute('select original_path from songs') if row[0]]
|
|
# Include the failed, unregistered source only within the same explicitly imported folder.
|
|
folders = {p.parent for p in originals}
|
|
sources = set(originals)
|
|
for folder in folders:
|
|
sources.update(p for p in folder.iterdir() if p.is_file() and p.suffix.lower() == '.mp3')
|
|
repairs = []
|
|
for source in sorted(sources):
|
|
if not source.is_file() or source.stat().st_size == 0:
|
|
continue
|
|
expected = digest(source)
|
|
target = media / (expected + source.suffix.lower())
|
|
if target.is_file() and target.stat().st_size == 0:
|
|
repairs.append((source, target, expected))
|
|
print(f'Verified {len(repairs)} empty DJ copies with matching original audio.')
|
|
if '--apply' in sys.argv:
|
|
for source, target, expected in repairs:
|
|
descriptor, temporary = tempfile.mkstemp(prefix='.kjmix-repair-', dir=media)
|
|
try:
|
|
with os.fdopen(descriptor, 'wb') as output, source.open('rb') as input_stream:
|
|
for chunk in iter(lambda: input_stream.read(1024 * 1024), b''):
|
|
output.write(chunk)
|
|
output.flush()
|
|
os.fsync(output.fileno())
|
|
assert digest(Path(temporary)) == expected
|
|
assert target.stat().st_size == 0, 'Destination changed; refusing to replace it'
|
|
os.chmod(temporary, 0o644)
|
|
os.replace(temporary, target)
|
|
print(f'Repaired verified audio: {target.name} ({target.stat().st_size} bytes)')
|
|
finally:
|
|
if os.path.exists(temporary):
|
|
os.unlink(temporary)
|