diff --git a/build_catalog.sh b/build_catalog.sh index bd5702e..e5bd71d 100644 --- a/build_catalog.sh +++ b/build_catalog.sh @@ -14,7 +14,7 @@ set -euo pipefail -MOVIES_DIR="/data/coolify/applications/e9ghwyp5n5kyfshk55krbhki/movies" +MOVIES_DIR="${MOVIES_DIR:-/data/coolify/applications/e9ghwyp5n5kyfshk55krbhki/movies}" OUT="$MOVIES_DIR/catalog.json" [[ -d "$MOVIES_DIR" ]] || { echo "Нет папки $MOVIES_DIR"; exit 1; } @@ -25,6 +25,50 @@ import sys, os, json, datetime movies_dir, out = sys.argv[1], sys.argv[2] movies = [] +def decode_bencode(b, pos): + """Minimal bencode decoder. Returns (value, new_pos).""" + c = b[pos:pos+1] + if c == b'd': + pos += 1; dd = {} + while b[pos:pos+1] != b'e': + kk, pos = decode_bencode(b, pos) + vv, pos = decode_bencode(b, pos) + dd[kk] = vv + return dd, pos+1 + if c == b'l': + pos += 1; ll = [] + while b[pos:pos+1] != b'e': + vv, pos = decode_bencode(b, pos) + ll.append(vv) + return ll, pos+1 + if c == b'i': + e = b.index(b'e', pos) + return int(b[pos+1:e]), e+1 + # string: : -- read the length digits first, then ':' + co = pos + while b[co:co+1] != b':': + co += 1 + n = int(b[pos:co]) + s = b[co+1:co+1+n] + return s, co+1+n + +def encode_bencode(obj): + """Re-encode a decoded bencode value back to bytes (canonical).""" + if isinstance(obj, int): + return b'i' + str(obj).encode('ascii') + b'e' + if isinstance(obj, bytes): + return str(len(obj)).encode('ascii') + b':' + obj + if isinstance(obj, str): + b = obj.encode('utf-8') + return str(len(b)).encode('ascii') + b':' + b + if isinstance(obj, list): + return b'l' + b''.join(encode_bencode(x) for x in obj) + b'e' + if isinstance(obj, dict): + keys = sorted(obj.keys()) + return b'd' + b''.join(encode_bencode(k) + encode_bencode(obj[k]) for k in keys) + b'e' + raise TypeError(obj) + + for entry in sorted(os.listdir(movies_dir)): d = os.path.join(movies_dir, entry) if not os.path.isdir(d) or entry.startswith('.'): @@ -33,7 +77,7 @@ for entry in sorted(os.listdir(movies_dir)): # читаем meta.txt metafile = os.path.join(d, 'meta.txt') if os.path.exists(metafile): - for line in open(metafile, encoding='utf-8'): + for line in open(metafile, encoding='utf-8', errors='replace'): line = line.strip() if not line or ':' not in line: continue @@ -47,13 +91,23 @@ for entry in sorted(os.listdir(movies_dir)): meta['rating'] = float(v) elif k in ('title', 'original_title', 'description'): meta[k] = v - # обложка - poster = None - for f in os.listdir(d): - if f.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')): - poster = f; break + # обложка: poster.* для карточки, movie.* для модалки торрента + imgs = [f for f in os.listdir(d) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.webp'))] + def first(prefix): + for f in imgs: + if f.lower().startswith(prefix): + return f + return None + poster = first('poster.') or (imgs[0] if imgs else None) + movie = first('movie.') or poster if poster: meta['poster'] = f"movies/{entry}/{poster}" + if movie: + meta['img'] = f"movies/{entry}/{movie}" + # cover: та же картинка, что и фон (img/movie.jpg), для режима 'обложка' в Lampa + cover = movie or poster + if cover: + meta['cover'] = f"movies/{entry}/{cover}" # торрент torrent = None for f in os.listdir(d): @@ -61,6 +115,19 @@ for entry in sorted(os.listdir(movies_dir)): torrent = f; break if torrent: meta['torrent'] = f"movies/{entry}/{torrent}" + # extract info-hash -> magnet (native Lampa torrent playback needs torrent_hash) + try: + import hashlib + tdata = open(os.path.join(d, torrent), 'rb').read() + parsed, _ = decode_bencode(tdata, 0) # parse whole file from the start + info = parsed[b'info'] + # re-encode the info dict to get the exact bencoded bytes for hashing + encoded = encode_bencode(info) + h = hashlib.sha1(encoded).hexdigest() + meta['hash'] = h + meta['magnet'] = f"magnet:?xt=urn:btih:{h}" + except Exception as e: + print(' ! hash error for', entry, e) # fallback title if 'title' not in meta: meta['title'] = entry