Created
December 23, 2023 09:59
-
-
Save tarruda/1973d317f6195189828492af33679cc1 to your computer and use it in GitHub Desktop.
Deluge torrent bulk renamer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# This script does batch renaming of torrents served by deluge. Useful to | |
# quickly rename all episodes of a TV show to match the expected patterns of | |
# local streaming services such as Plex or Jellyfin while still seeding the | |
# torrent. | |
# For example, consider you have a TV show that has a strange name not | |
# recognized by jellyfin, just use a regex replace to capture the episode | |
# number and rename to a pattern understood by Jellyfin: | |
# python3 bulk-rename.py -d DELUGE_HOST -U USER -P PASSWORD TORRENT_HASH '(?P<dir>[^/]+)/.+ep\.(?P<episode>\d+)(?P<suffix>.+)$' '\g<dir>/TV.Show.Name.s01.e\g<episode>\g<suffix>' | |
import re | |
import argparse | |
import sys | |
from deluge.ui.client import client | |
from twisted.internet import reactor | |
def parse_args(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--daemon-host', '-d', default='127.0.0.1', | |
help='Deluge daemon host') | |
parser.add_argument('--daemon-port', '-p', default='58846', type=int, | |
help='Deluge daemon port') | |
parser.add_argument('--daemon-username', '-U', | |
help='Deluge daemon username') | |
parser.add_argument('--daemon-password', '-P', | |
help='Deluge daemon password') | |
parser.add_argument('torrent_id', | |
help='Torrent hash') | |
parser.add_argument('regex', | |
help='Regexp to match filenames') | |
parser.add_argument('repl', | |
help='Replacement, which can reference captures') | |
return parser.parse_args() | |
def rename(host, | |
port, | |
username, | |
password, | |
torrent_id, | |
pattern, | |
repl): | |
connect = client.connect(host, | |
port=port, | |
username=username, | |
password=password) | |
def on_connect(result): | |
def on_get_torrent(torrent): | |
renames_preview = [] | |
for file in torrent['files']: | |
m = pattern.match(file['path']) | |
if m: | |
renames_preview.append({ | |
'index': file['index'], | |
'src': file['path'], | |
'dst':re.sub(pattern, repl, file['path']) | |
}) | |
if not renames_preview: | |
return error('No files matched') | |
target_set = set() | |
print('Will rename:') | |
for p in renames_preview: | |
print(p['src'], '->', p['dst']) | |
target_set.add(p['dst']) | |
src_len = len(renames_preview) | |
tgt_len = len(target_set) | |
if src_len != tgt_len: | |
return error( | |
f'Target count({tgt_len}) != Source count({src_len})') | |
def on_rename(result): | |
print('Rename was successful') | |
client.disconnect() | |
reactor.stop() | |
def on_rename_failure(result): | |
return error(f'Failed to rename: {str(result)}') | |
answer = input("Confirm? (y/N)").lower() | |
if answer in ['y', 'Y']: | |
renames = list((p['index'], p['dst']) for p in renames_preview) | |
rename_files = client.core.rename_files(torrent_id, renames) | |
rename_files.addCallback(on_rename) | |
rename_files.addErrback(on_rename_failure) | |
def on_get_torrent_error(err): | |
print('failed to get torrent status:', err) | |
client.disconnect() | |
reactor.stop() | |
get_torrent_status = client.core.get_torrent_status(torrent_id, ["files"]) | |
get_torrent_status.addCallback(on_get_torrent) | |
get_torrent_status.addErrback(on_get_torrent_error) | |
connect.addCallback(on_connect) | |
def on_connect_fail(result): | |
print("Connection failed!") | |
print("result:", result) | |
reactor.stop() | |
connect.addErrback(on_connect_fail) | |
reactor.run() | |
def error(message): | |
print(message, file=sys.stderr) | |
client.disconnect() | |
reactor.stop() | |
def main(): | |
args = parse_args() | |
pattern = re.compile(args.regex) | |
rename( | |
args.daemon_host, | |
args.daemon_port, | |
args.daemon_username, | |
args.daemon_password, | |
args.torrent_id, | |
pattern, | |
args.repl) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment