Son aktivite 1 month ago

将 torrent 种子文件转为 magnet 磁力链接

Revizyon 07dccc6ffd504c16f630dc0b458f61974eb2c0b9

torrent_to_magnet_batch.py Ham
1import subprocess
2import sys
3import os
4import re
5import urllib.parse
6
7"""
8torrent_to_magnet_batch.py
9
10功能:
11 批量扫描指定目录及其所有子目录下的 .torrent 文件,
12 使用 aria2c 将每个 .torrent 文件转换成磁力链接(magnet URI),
13 并尝试从磁力链接中提取显示名称 (dn) 作为新的文件名重命名原 .torrent 文件。
14
15 最终将所有成功转换的文件名及对应的磁力链接按文件名排序,写入指定的日志文件。
16 每个条目格式为:
17 文件名
18 磁力链接
19
20 条目之间以空行分隔。
21
22 转换失败的文件会在日志中标注为“Failed to convert”。
23
24用法:
25 python torrent_to_magnet_batch.py <目录路径>
26
27 例如:
28 python torrent_to_magnet_batch.py /path/to/torrents
29
30依赖:
31 - Python 3 标准库
32 - 系统需安装 aria2c 命令行工具,并确保可通过环境变量调用
33
34限制:
35 - 磁力链接中默认不包含文件大小,脚本不尝试获取或记录文件大小信息。
36 - 需要网络环境正常,aria2c 能够成功下载种子元信息(metadata)才能正确提取磁力链接。
37 - 如果 aria2c 不可用或转换失败,该文件会被记录为转换失败。
38
39日志输出:
40 输出日志文件路径为固定名称 output.log,位于当前运行目录。
41
42示例输出:
43 Ubuntu 20.04 ISO
44 magnet:?xt=urn:btih:...
45
46 Another File
47 magnet:?xt=urn:btih:...
48
49作者:
50 ChatGPT(OpenAI)
51
52日期:
53 2025年7月27日
54"""
55
56
57def extract_dn_from_magnet(magnet_link):
58 """Extract the 'dn' (display name) from magnet URI"""
59 parsed = urllib.parse.urlparse(magnet_link)
60 params = urllib.parse.parse_qs(parsed.query)
61 dn = params.get('dn', [None])[0]
62 return dn
63
64def torrent_to_magnet(torrent_file):
65 try:
66 if not os.path.exists(torrent_file):
67 return None, None, None
68
69 subprocess.run(['aria2c', '--version'], capture_output=True, check=True)
70
71 cmd = ['aria2c', '--bt-metadata-only=true', '--bt-save-metadata=false',
72 '--show-files=true', torrent_file]
73
74 result = subprocess.run(cmd, capture_output=True, text=True)
75
76 if result.returncode != 0:
77 return None, None, None
78
79 output = result.stdout
80
81 # Extract magnet link
82 magnet_link = None
83 for line in output.splitlines():
84 if line.startswith('Magnet URI:'):
85 magnet_link = line.replace('Magnet URI:', '').strip()
86 break
87
88 # Extract actual name from .torrent (fallback: filename)
89 name = os.path.splitext(os.path.basename(torrent_file))[0]
90
91 # Extract size
92 total_size = 0
93 file_line_pattern = re.compile(r'^\s+\d+\|\s+(.+?)\s+\|\s+([\d.]+)\s+([A-Z]+)$')
94
95 for line in output.splitlines():
96 match = file_line_pattern.match(line)
97 if match:
98 size_value = float(match.group(2))
99 unit = match.group(3)
100 total_size += size_to_bytes(size_value, unit)
101
102 size_str = human_readable_size(total_size) if total_size > 0 else "Unknown"
103
104 return name, magnet_link, size_str
105
106 except Exception:
107 return None, None, None
108
109def size_to_bytes(value, unit):
110 units = ['B', 'KB', 'MB', 'GB', 'TB']
111 try:
112 index = units.index(unit.upper())
113 return int(value * (1024 ** index))
114 except ValueError:
115 return 0
116
117def human_readable_size(size_bytes):
118 for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
119 if size_bytes < 1024.0:
120 return f"{size_bytes:.2f} {unit}"
121 size_bytes /= 1024.0
122 return f"{size_bytes:.2f} PB"
123
124def process_directory(directory, log_file_path):
125 results = []
126
127 for root, _, files in os.walk(directory):
128 for file in files:
129 if file.lower().endswith('.torrent'):
130 torrent_path = os.path.join(root, file)
131 name, magnet, _ = torrent_to_magnet(torrent_path)
132
133 if name and magnet:
134 real_name = extract_dn_from_magnet(magnet)
135 if real_name:
136 new_filename = f"{real_name}.torrent"
137 new_path = os.path.join(root, new_filename)
138
139 if not os.path.exists(new_path):
140 try:
141 os.rename(torrent_path, new_path)
142 torrent_path = new_path
143 name = real_name
144 except Exception as e:
145 print(f"Failed to rename: {file} -> {new_filename}: {e}")
146 else:
147 print(f"Skip rename: {new_filename} already exists")
148
149 results.append((name, magnet))
150 else:
151 results.append((file, "Failed to convert"))
152
153 # 按文件名排序(忽略大小写)
154 results.sort(key=lambda x: x[0].lower())
155
156 # 写入日志
157 with open(log_file_path, 'w', encoding='utf-8') as log_file:
158 for name, magnet in results:
159 log_file.write(f"{name}\n{magnet}\n\n")
160
161def main():
162 if len(sys.argv) < 2:
163 print("Usage: python torrent_to_magnet_batch.py <directory_path> [output.log]")
164 sys.exit(1)
165
166 directory = sys.argv[1]
167 log_file_path = sys.argv[2] if len(sys.argv) >= 3 else "output.log"
168
169 if not os.path.isdir(directory):
170 print(f"Error: '{directory}' is not a valid directory")
171 sys.exit(1)
172
173 process_directory(directory, log_file_path)
174 print(f"Conversion completed. Output saved to: {log_file_path}")
175
176if __name__ == "__main__":
177 main()
178