Add MP3 fingerprint lookup script
This commit is contained in:
143
extract_7z.py
Normal file
143
extract_7z.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract 7z archives into a directory with the same filename.
|
||||
|
||||
Behavior:
|
||||
- scans a directory for *.7z files
|
||||
- extracts album.7z -> album/
|
||||
- optionally recurses into subdirectories
|
||||
|
||||
Examples:
|
||||
python extract_7z.py ~/Music/inbox
|
||||
python extract_7z.py ~/Music/inbox --dry-run
|
||||
python extract_7z.py ~/Music/inbox --no-recursive
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import py7zr # type: ignore
|
||||
except Exception:
|
||||
py7zr = None
|
||||
|
||||
|
||||
class ToolError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def warn(msg: str) -> None:
|
||||
print(f"[warn] {msg}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def err(msg: str) -> None:
|
||||
print(f"[error] {msg}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract 7z archives into sibling directories named after the archive stem."
|
||||
)
|
||||
parser.add_argument("directory", help="Root directory to scan")
|
||||
parser.add_argument(
|
||||
"--no-recursive",
|
||||
action="store_true",
|
||||
help="Only scan the top-level directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Only print planned actions",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def find_7z_files(root: Path, recursive: bool) -> list[Path]:
|
||||
if recursive:
|
||||
return sorted(p for p in root.rglob("*.7z") if p.is_file())
|
||||
return sorted(p for p in root.glob("*.7z") if p.is_file())
|
||||
|
||||
|
||||
def find_7z_bin() -> str | None:
|
||||
for name in ("7z", "7za", "7zr"):
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def run_extract_cli(archive: Path, dest_dir: Path, seven_z: str, dry_run: bool) -> None:
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
cmd = [seven_z, "x", f"-o{str(dest_dir)}", "-y", str(archive)]
|
||||
printable = " ".join(shlex_quote(a) for a in cmd)
|
||||
if dry_run:
|
||||
log(f"[dry-run] {printable}")
|
||||
return
|
||||
proc = subprocess.run(cmd)
|
||||
if proc.returncode != 0:
|
||||
raise ToolError(f"extraction failed ({proc.returncode}): {archive}")
|
||||
|
||||
|
||||
def run_extract_py7zr(archive: Path, dest_dir: Path, dry_run: bool) -> None:
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
if dry_run:
|
||||
log(f"[dry-run] py7zr extract {archive} -> {dest_dir}")
|
||||
return
|
||||
if py7zr is None:
|
||||
raise ToolError("py7zr is not installed")
|
||||
with py7zr.SevenZipFile(archive, mode="r") as zf:
|
||||
zf.extractall(path=dest_dir)
|
||||
|
||||
|
||||
def shlex_quote(text: str) -> str:
|
||||
import shlex
|
||||
|
||||
return shlex.quote(text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
root = Path(args.directory).expanduser().resolve()
|
||||
if not root.exists() or not root.is_dir():
|
||||
err(f"directory not found: {root}")
|
||||
return 2
|
||||
|
||||
seven_z = find_7z_bin()
|
||||
archives = find_7z_files(root, recursive=not args.no_recursive)
|
||||
if not archives:
|
||||
log("no 7z archives found")
|
||||
return 0
|
||||
|
||||
if seven_z is None and py7zr is None:
|
||||
raise ToolError("missing required tool: 7z/7za/7zr and python module py7zr")
|
||||
|
||||
ok = 0
|
||||
failed = 0
|
||||
for archive in archives:
|
||||
dest_dir = archive.with_suffix("")
|
||||
log(f"[archive] {archive}")
|
||||
log(f" output: {dest_dir}")
|
||||
try:
|
||||
if seven_z is not None:
|
||||
run_extract_cli(archive, dest_dir, seven_z, dry_run=args.dry_run)
|
||||
else:
|
||||
run_extract_py7zr(archive, dest_dir, dry_run=args.dry_run)
|
||||
ok += 1
|
||||
except Exception as exc:
|
||||
failed += 1
|
||||
err(f"{archive}: {exc}")
|
||||
|
||||
log(f"done: {ok} ok, {failed} failed")
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user