From 59d81e9df450c3569dabc278114762291fd7bac6 Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 20 Apr 2026 15:06:36 +0200 Subject: [PATCH] Update torrents_dir.py --- torrents_dir.py | 181 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 2 deletions(-) diff --git a/torrents_dir.py b/torrents_dir.py index c21b48c..e7703ce 100644 --- a/torrents_dir.py +++ b/torrents_dir.py @@ -1,3 +1,180 @@ #!/usr/bin/env python3 -# (skrypt skrócony – pełna wersja w rozmowie) -print("Użyj wersji ze skryptu z rozmowy") + +import os +import csv +import re +import shutil +import hashlib +from pathlib import Path + +import bencodepy + + +BT_BACKUP_DIR = Path("BT_backup") +FIXED_TORRENTS_DIR = Path("qbit1") +OUTPUT_DIR = Path("grouped_torrents") + + +def decode_value(v): + if isinstance(v, bytes): + return v.decode("utf-8", errors="replace") + return v + + +def get_field(data, key): + return decode_value(data.get(key.encode()) or data.get(key)) + + +def safe_rel_path(path_str): + if not path_str: + return Path("_unknown") + + s = path_str.strip().replace("\\", "/") + + m = re.match(r"^([A-Za-z]):/(.*)$", s) + if m: + s = f"{m.group(1)}/{m.group(2)}" + else: + s = s.lstrip("/") + + parts = [] + for part in s.split("/"): + part = part.strip() + if not part: + continue + part = re.sub(r'[<>:"|?*\x00-\x1F]', "_", part) + parts.append(part) + + return Path(*parts) if parts else Path("_unknown") + + +def read_bencode(path): + with open(path, "rb") as f: + return bencodepy.decode(f.read()) + + +def torrent_infohash_v1(torrent_path): + """ + Liczy klasyczny SHA1 infohash z pola 'info' w pliku .torrent. + To jest hash używany przez qBittorrent dla zwykłych torrentów v1. + """ + data = read_bencode(torrent_path) + info = data.get(b"info") or data.get("info") + if info is None: + raise ValueError("Brak pola 'info' w torrent") + + encoded_info = bencodepy.encode(info) + return hashlib.sha1(encoded_info).hexdigest().upper() + + +def build_fixed_torrent_index(): + """ + Zwraca mapę: INFOHASH -> ścieżka do poprawionego .torrent + """ + index = {} + duplicates = [] + + for torrent_path in sorted(FIXED_TORRENTS_DIR.glob("*.torrent")): + try: + infohash = torrent_infohash_v1(torrent_path) + except Exception as e: + print(f"[WARN] Nie moge odczytac {torrent_path}: {e}") + continue + + if infohash in index: + duplicates.append(infohash) + else: + index[infohash] = torrent_path + + if duplicates: + print(f"[WARN] Zduplikowane infohash w qbit1: {len(duplicates)}") + + print(f"Zindeksowano poprawione torrenty: {len(index)}") + return index + + +def main(): + if not BT_BACKUP_DIR.is_dir(): + raise SystemExit(f"Brak katalogu: {BT_BACKUP_DIR}") + if not FIXED_TORRENTS_DIR.is_dir(): + raise SystemExit(f"Brak katalogu: {FIXED_TORRENTS_DIR}") + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + fixed_index = build_fixed_torrent_index() + + rows = [] + processed = 0 + copied = 0 + missing_fixed_torrent = 0 + broken_fastresume = 0 + + for fastresume_path in sorted(BT_BACKUP_DIR.glob("*.fastresume")): + torrent_hash = fastresume_path.stem.upper() + processed += 1 + + try: + data = read_bencode(fastresume_path) + except Exception as e: + broken_fastresume += 1 + rows.append({ + "hash": torrent_hash, + "torrent_name": "", + "save_path": "", + "output_dir": "", + "status": f"broken_fastresume: {e}", + }) + continue + + save_path = ( + get_field(data, "qBt-savePath") + or get_field(data, "save_path") + or "" + ) + torrent_name = ( + get_field(data, "qBt-name") + or get_field(data, "name") + or "" + ) + + rel_dir = safe_rel_path(save_path) + target_dir = OUTPUT_DIR / rel_dir + target_dir.mkdir(parents=True, exist_ok=True) + + fixed_torrent_path = fixed_index.get(torrent_hash) + + if fixed_torrent_path and fixed_torrent_path.exists(): + out_name = fixed_torrent_path.name + shutil.copy2(fixed_torrent_path, target_dir / out_name) + status = "ok" + copied += 1 + else: + status = "missing_fixed_torrent" + missing_fixed_torrent += 1 + + rows.append({ + "hash": torrent_hash, + "torrent_name": torrent_name, + "save_path": save_path, + "output_dir": str(target_dir), + "status": status, + }) + + csv_path = OUTPUT_DIR / "mapping.csv" + with open(csv_path, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter( + f, + fieldnames=["hash", "torrent_name", "save_path", "output_dir", "status"] + ) + writer.writeheader() + writer.writerows(rows) + + print(f"Przetworzono fastresume: {processed}") + print(f"Skopiowano poprawione torrent: {copied}") + print(f"Brak poprawionych torrentow: {missing_fixed_torrent}") + print(f"Uszkodzone fastresume: {broken_fastresume}") + print(f"CSV: {csv_path}") + + +if __name__ == "__main__": + main()