Update torrents_dir.py
This commit is contained in:
181
torrents_dir.py
181
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()
|
||||
|
||||
Reference in New Issue
Block a user