Install Python and follow the README at https://github.com/jslay88/qbt_migrate
TL;DR
Once Python is installed, open a terminal/command prompt and run the following
Code: Select all
pip install qbt_migrate
qbt_migrate
Code: Select all
pip install qbt_migrate
qbt_migrate
Code: Select all
rear_string[sp_pos_stop:]
Code: Select all
rear_string = data[sp_pos_stop:]
Code: Select all
qbt_matches = self._pattern_qBt_save_path.search(self._data)
print(self.file_path)
if save_path_matches is None:
raise ValueError('Unable to determine \'save_path\'')
Code: Select all
import os
import re
import sys
import logging
import zipfile
from datetime import datetime
logger = logging.getLogger(__name__)
class QBTBatchMove(object):
def __init__(self, bt_backup_path=None):
if bt_backup_path is None:
logger.debug('Discovering BT_backup path...')
if sys.platform.startswith('win32'):
logger.debug('Windows System')
bt_backup_path = os.path.join(
os.getenv('localappdata'), 'qBittorrent\\BT_backup')
elif sys.platform.startswith('linux'):
logger.debug('Linux System')
bt_backup_path = os.path.join(
os.getenv('HOME'), '.local/share/data/qBittorrent/BT_backup')
if not os.path.exists(bt_backup_path) or not os.path.isdir(bt_backup_path):
raise NotADirectoryError(bt_backup_path)
logger.debug('BT_backup Path: %s' % bt_backup_path)
self.bt_backup_path = bt_backup_path
self.discovered_files = None
def run(self, existing_path, new_path, create_backup=True):
"""
Perform Batch Processing of path changes.
:param existing_path: Existing path to look for
:type existing_path: str
:param new_path: New Path to replace with
:type new_path: str
:param create_backup: Create a backup of the file before modifying?
:type create_backup: bool
"""
if create_backup:
backup_filename = 'fastresume_backup' + \
datetime.now().strftime('%Y%m%d%H%M%S') + '.zip'
self.backup_folder(self.bt_backup_path,
os.path.join(os.path.dirname(self.bt_backup_path), backup_filename))
if sys.platform.startswith('win32') and not existing_path.endswith('\\'):
existing_path += '\\'
elif sys.platform.startswith('linux') and not existing_path.endswith('/'):
existing_path += '/'
if sys.platform.startswith('win32') and not new_path.endswith('\\'):
new_path += '\\'
elif sys.platform.startswith('linux') and not new_path.endswith('/'):
new_path += '/'
logger.info('Searching for .fastresume files with path %s ...' %
existing_path)
self.discovered_files = self.discover_relevant_fast_resume(self.bt_backup_path,
existing_path)
if not self.discovered_files:
raise ValueError(
'Found no .fastresume files with existing path %s' % existing_path)
logger.info('Found %s Files.' % len(self.discovered_files))
logger.debug('Discovered FastResumes: %s' % self.discovered_files)
for file in self.discovered_files:
logger.info('Updating File %s...' % file.file_path)
new_save_path = file.save_path.replace(bytes(existing_path, 'utf-8'),
bytes(new_path, 'utf-8'))
file.set_save_path(new_save_path.decode('utf-8'),
save_file=False,
create_backup=False)
new_qbt_save_path = file.qbt_save_path.replace(bytes(existing_path, 'utf-8'),
bytes(new_path, 'utf-8'))
file.set_qbt_save_path(new_qbt_save_path.decode('utf-8'),
save_file=False,
create_backup=False)
file.save()
logger.info('File (%s) Updated!' % file.file_path)
@staticmethod
def discover_relevant_fast_resume(bt_backup_path, existing_path):
"""
Find .fastresume files that contain the existing path.
:param bt_backup_path: Path to BT_backup folder
:type bt_backup_path: str
:param existing_path: The existing path to look for
:type existing_path: str
:return: List of FastResume Objects
:rtype: list[FastResume]
"""
existing_path = bytes(existing_path, 'utf-8')
fast_resume_files = [FastResume(os.path.join(bt_backup_path, file))
for file in os.listdir(bt_backup_path)
if file.endswith('.fastresume')]
fast_resume_files = [file for file in fast_resume_files if existing_path in file.save_path
or existing_path in file.qbt_save_path]
return fast_resume_files
@staticmethod
def backup_folder(folder_path, archive_path):
logger.info('Creating Archive %s ...' % archive_path)
with zipfile.ZipFile(archive_path, 'w') as archive:
for file in os.listdir(folder_path):
archive.write(os.path.join(folder_path, file))
logger.info('Done!')
class FastResume(object):
save_path_pattern = br'save_path(\d+)'
qBt_save_path_pattern = br'qBt-savePath(\d+)'
def __init__(self, file_path):
self.file_path = os.path.realpath(file_path)
if not os.path.exists(self.file_path) or not os.path.isfile(self.file_path):
raise FileNotFoundError(self.file_path)
self._save_path = None
self._qbt_save_path = None
self._pattern_save_path = re.compile(self.save_path_pattern)
self._pattern_qBt_save_path = re.compile(self.qBt_save_path_pattern)
self._data = None
self._load_data()
def _load_data(self):
with open(self.file_path, 'rb') as f:
self._data = f.read()
save_path_matches = self._pattern_save_path.search(self._data)
qbt_matches = self._pattern_qBt_save_path.search(self._data)
if save_path_matches is None:
raise ValueError('Unable to determine \'save_path\'')
if qbt_matches is None:
raise ValueError('Unable to determine \'qBt-savePath\'')
self._save_path = self._data[save_path_matches.end() + 1:
save_path_matches.end() + 1 + int(save_path_matches.group(1))]
self._qbt_save_path = self._data[qbt_matches.end() + 1:
qbt_matches.end() + 1 + int(qbt_matches.group(1))]
@property
def save_path(self):
return self._save_path
@property
def qbt_save_path(self):
return self._qbt_save_path
def set_save_path(self, path, save_file=True, create_backup=True):
if sys.platform.startswith('win32') and not path.endswith('\\'):
path += '\\'
elif sys.platform.startswith('linux') and not path.endswith('/'):
path += '/'
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self._data = self._data.replace(
b'save_path' + bytes(str(len(self._save_path)),
'utf-8') + b':' + self._save_path,
b'save_path' + bytes(str(len(bytes(path, 'utf-8'))), 'utf-8') +
b':' + bytes(path, 'utf-8')
)
self._save_path = bytes(path, 'utf-8')
if save_file:
self.save()
def set_qbt_save_path(self, path, save_file=True, create_backup=True):
if sys.platform.startswith('win32') and not path.endswith('\\'):
path += '\\'
elif sys.platform.startswith('linux') and not path.endswith('/'):
path += '/'
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self._data = self._data.replace(
b'qBt-savePath' +
bytes(str(len(self._qbt_save_path)), 'utf-8') +
b':' + self._qbt_save_path,
b'qBt-savePath' + bytes(str(len(bytes(path, 'utf-8'))), 'utf-8') +
b':' + bytes(path, 'utf-8')
)
self._qbt_save_path = bytes(path, 'utf-8')
if save_file:
self.save()
def set_save_paths(self, path, save_file=True, create_backup=True):
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self.set_save_path(path, save_file=False, create_backup=False)
self.set_qbt_save_path(path, save_file=False, create_backup=False)
if save_file:
self.save()
def save(self, file_name=None):
if file_name is None:
file_name = self.file_path
logger.info('Saving File %s...' % file_name)
with open(file_name, 'wb') as f:
f.write(self._data)
if __name__ == '__main__':
logger.addHandler(logging.StreamHandler(stream=sys.stdout))
logger.setLevel('INFO')
qbm = QBTBatchMove()
bt_backup = input('BT_Backup Path (%s): ' % qbm.bt_backup_path)
if bt_backup != '':
qbm.bt_backup_path = bt_backup
if len(sys.argv) > 1:
ep = sys.argv[1]
print('Existing Path: (%s)' % ep)
else:
ep = input('Existing Path: ')
if len(sys.argv) > 2:
np = sys.argv[2]
print('New Path: (%s)' % np)
else:
np = input('New Path: ')
qbm.run(ep, np)
I fixed the problem with non-ascii characters.Wepeel wrote: ↑Sun Nov 03, 2019 9:01 am Is there any way to make this work with non-ASCII characters in the path?
I have paths that kind of looks like this D:\ÅÅÅ\ÄÄÄ\ÖÖÖ\ and if I want to do a drive letter change the torrent breaks and doesn't show up in qBittorrent anymore. It works otherwise, but if one sub-folder has one of these characters it doesn't seem to work.
Code: Select all
import os
import re
import sys
import logging
import zipfile
from datetime import datetime
logger = logging.getLogger(__name__)
class QBTBatchMove(object):
def __init__(self, bt_backup_path=None):
if bt_backup_path is None:
logger.debug('Discovering BT_backup path...')
if sys.platform.startswith('win32'):
logger.debug('Windows System')
bt_backup_path = os.path.join(
os.getenv('localappdata'), 'qBittorrent\\BT_backup')
elif sys.platform.startswith('linux'):
logger.debug('Linux System')
bt_backup_path = os.path.join(
os.getenv('HOME'), '.local/share/data/qBittorrent/BT_backup')
if not os.path.exists(bt_backup_path) or not os.path.isdir(bt_backup_path):
raise NotADirectoryError(bt_backup_path)
logger.debug('BT_backup Path: %s' % bt_backup_path)
self.bt_backup_path = bt_backup_path
self.discovered_files = None
def run(self, existing_path, new_path, create_backup=True):
"""
Perform Batch Processing of path changes.
:param existing_path: Existing path to look for
:type existing_path: str
:param new_path: New Path to replace with
:type new_path: str
:param create_backup: Create a backup of the file before modifying?
:type create_backup: bool
"""
if create_backup:
backup_filename = 'fastresume_backup' + \
datetime.now().strftime('%Y%m%d%H%M%S') + '.zip'
self.backup_folder(self.bt_backup_path,
os.path.join(os.path.dirname(self.bt_backup_path), backup_filename))
if sys.platform.startswith('win32') and not existing_path.endswith('\\'):
existing_path += '\\'
elif sys.platform.startswith('linux') and not existing_path.endswith('/'):
existing_path += '/'
if sys.platform.startswith('win32') and not new_path.endswith('\\'):
new_path += '\\'
elif sys.platform.startswith('linux') and not new_path.endswith('/'):
new_path += '/'
logger.info('Searching for .fastresume files with path %s ...' %
existing_path)
self.discovered_files = self.discover_relevant_fast_resume(self.bt_backup_path,
existing_path)
if not self.discovered_files:
raise ValueError(
'Found no .fastresume files with existing path %s' % existing_path)
logger.info('Found %s Files.' % len(self.discovered_files))
logger.debug('Discovered FastResumes: %s' % self.discovered_files)
for file in self.discovered_files:
logger.info('Updating File %s...' % file.file_path)
new_save_path = file.save_path.replace(bytes(existing_path, 'utf-8'),
bytes(new_path, 'utf-8'))
file.set_save_path(new_save_path.decode('utf-8'),
save_file=False,
create_backup=False)
new_qbt_save_path = file.qbt_save_path.replace(bytes(existing_path, 'utf-8'),
bytes(new_path, 'utf-8'))
file.set_qbt_save_path(new_qbt_save_path.decode('utf-8'),
save_file=False,
create_backup=False)
file.save()
logger.info('File (%s) Updated!' % file.file_path)
@staticmethod
def discover_relevant_fast_resume(bt_backup_path, existing_path):
"""
Find .fastresume files that contain the existing path.
:param bt_backup_path: Path to BT_backup folder
:type bt_backup_path: str
:param existing_path: The existing path to look for
:type existing_path: str
:return: List of FastResume Objects
:rtype: list[FastResume]
"""
existing_path = bytes(existing_path, 'utf-8')
fast_resume_files = [FastResume(os.path.join(bt_backup_path, file))
for file in os.listdir(bt_backup_path)
if file.endswith('.fastresume')]
fast_resume_files = [file for file in fast_resume_files if existing_path in file.save_path
or existing_path in file.qbt_save_path]
return fast_resume_files
@staticmethod
def backup_folder(folder_path, archive_path):
logger.info('Creating Archive %s ...' % archive_path)
with zipfile.ZipFile(archive_path, 'w') as archive:
for file in os.listdir(folder_path):
archive.write(os.path.join(folder_path, file))
logger.info('Done!')
class FastResume(object):
save_path_pattern = br'save_path(\d+)'
qBt_save_path_pattern = br'qBt-savePath(\d+)'
def __init__(self, file_path):
self.file_path = os.path.realpath(file_path)
if not os.path.exists(self.file_path) or not os.path.isfile(self.file_path):
raise FileNotFoundError(self.file_path)
self._save_path = None
self._qbt_save_path = None
self._pattern_save_path = re.compile(self.save_path_pattern)
self._pattern_qBt_save_path = re.compile(self.qBt_save_path_pattern)
self._data = None
self._load_data()
def _load_data(self):
with open(self.file_path, 'rb') as f:
self._data = f.read()
save_path_matches = self._pattern_save_path.search(self._data)
qbt_matches = self._pattern_qBt_save_path.search(self._data)
if save_path_matches is None:
raise ValueError('Unable to determine \'save_path\'')
if qbt_matches is None:
raise ValueError('Unable to determine \'qBt-savePath\'')
self._save_path = self._data[save_path_matches.end() + 1:
save_path_matches.end() + 1 + int(save_path_matches.group(1))]
self._qbt_save_path = self._data[qbt_matches.end() + 1:
qbt_matches.end() + 1 + int(qbt_matches.group(1))]
@property
def save_path(self):
return self._save_path
@property
def qbt_save_path(self):
return self._qbt_save_path
def set_save_path(self, path, save_file=True, create_backup=True):
if sys.platform.startswith('win32') and not path.endswith('\\'):
path += '\\'
elif sys.platform.startswith('linux') and not path.endswith('/'):
path += '/'
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self._data = self._data.replace(
b'save_path' + bytes(str(len(self._save_path)),
'utf-8') + b':' + self._save_path,
b'save_path' + bytes(str(len(bytes(path, 'utf-8'))), 'utf-8') +
b':' + bytes(path, 'utf-8')
)
self._save_path = bytes(path, 'utf-8')
if save_file:
self.save()
def set_qbt_save_path(self, path, save_file=True, create_backup=True):
if sys.platform.startswith('win32') and not path.endswith('\\'):
path += '\\'
elif sys.platform.startswith('linux') and not path.endswith('/'):
path += '/'
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self._data = self._data.replace(
b'qBt-savePath' +
bytes(str(len(self._qbt_save_path)), 'utf-8') +
b':' + self._qbt_save_path,
b'qBt-savePath' + bytes(str(len(bytes(path, 'utf-8'))), 'utf-8') +
b':' + bytes(path, 'utf-8')
)
self._qbt_save_path = bytes(path, 'utf-8')
if save_file:
self.save()
def set_save_paths(self, path, save_file=True, create_backup=True):
if create_backup:
today = datetime.now()
file_name = '%s.%s.%s' % (self.file_path,
today.strftime('%Y%m%d%H%M%S'),
'bkup')
self.save(file_name)
self.set_save_path(path, save_file=False, create_backup=False)
self.set_qbt_save_path(path, save_file=False, create_backup=False)
if save_file:
self.save()
def save(self, file_name=None):
if file_name is None:
file_name = self.file_path
logger.info('Saving File %s...' % file_name)
with open(file_name, 'wb') as f:
f.write(self._data)
if __name__ == '__main__':
logger.addHandler(logging.StreamHandler(stream=sys.stdout))
logger.setLevel('INFO')
qbm = QBTBatchMove()
bt_backup = input('BT_Backup Path (%s): ' % qbm.bt_backup_path)
if bt_backup != '':
qbm.bt_backup_path = bt_backup
if len(sys.argv) > 1:
ep = sys.argv[1]
print('Existing Path: (%s)' % ep)
else:
ep = input('Existing Path: ')
if len(sys.argv) > 2:
np = sys.argv[2]
print('New Path: (%s)' % np)
else:
np = input('New Path: ')
qbm.run(ep, np)
Code: Select all
mapped_files_modified = re.sub("\\\\", "/",str(x))
Code: Select all
import os
import bencode
import re
#---Data directory of QBittorrent fastresume files
tor_dir = "/home/wim/.local/share/data/qBittorrent/BT_backup/"
#---Work on files with extension .fastresume
for file in os.listdir(tor_dir):
if file.endswith(".fastresume"):
#---File name per file
file_path_name = os.path.join(tor_dir, file)
#---Load contents of file
torrent = bencode.bread(file_path_name)
#---Define x as the 'mapped_files' field
x = torrent.get('mapped_files')
#---Uncomment for testing
# print(x)
#---replace Windows blackslash with Linux fw slash
mapped_files_modified = re.sub("\\\\", "/",str(x))
#---Uncomment for testing
# print(mapped_files_modified)
#---Replace paths in loaded decoded data
torrent['mapped_files']=mapped_files_modified
#---Uncomment for testing
# print(torrent['mapped_files'])
#---Write modified file
bencode.bwrite(torrent, file_path_name)