├── BatchRenamer.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── entry_points.txt └── top_level.txt ├── batchrenamer ├── __init__.py ├── __init__.pyc ├── common.py ├── common.pyc ├── core.py ├── core.pyc ├── data │ ├── batchrenamer.js │ ├── config.glade │ └── rename.glade ├── gtkui.py ├── gtkui.pyc ├── test.py ├── webui.py └── webui.pyc ├── create_dev_link.sh └── setup.py /BatchRenamer.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: BatchRenamer 3 | Version: 0.2 4 | Summary: UNKNOWN 5 | Home-page: UNKNOWN 6 | Author: Nathan Hoad 7 | Author-email: nathan@getoffmalawn.com 8 | License: GPLv3 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /BatchRenamer.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | BatchRenamer.egg-info/PKG-INFO 3 | BatchRenamer.egg-info/SOURCES.txt 4 | BatchRenamer.egg-info/dependency_links.txt 5 | BatchRenamer.egg-info/entry_points.txt 6 | BatchRenamer.egg-info/top_level.txt 7 | batchrenamer/__init__.py 8 | batchrenamer/common.py 9 | batchrenamer/core.py 10 | batchrenamer/gtkui.py 11 | batchrenamer/test.py 12 | batchrenamer/webui.py -------------------------------------------------------------------------------- /BatchRenamer.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /BatchRenamer.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | 2 | [deluge.plugin.core] 3 | BatchRenamer = batchrenamer:CorePlugin 4 | [deluge.plugin.gtkui] 5 | BatchRenamer = batchrenamer:GtkUIPlugin 6 | [deluge.plugin.web] 7 | BatchRenamer = batchrenamer:WebUIPlugin 8 | -------------------------------------------------------------------------------- /BatchRenamer.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | batchrenamer 2 | -------------------------------------------------------------------------------- /batchrenamer/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | from deluge.plugins.init import PluginInitBase 42 | 43 | class CorePlugin(PluginInitBase): 44 | def __init__(self, plugin_name): 45 | from core import Core as _plugin_cls 46 | self._plugin_cls = _plugin_cls 47 | super(CorePlugin, self).__init__(plugin_name) 48 | 49 | class GtkUIPlugin(PluginInitBase): 50 | def __init__(self, plugin_name): 51 | from gtkui import GtkUI as _plugin_cls 52 | self._plugin_cls = _plugin_cls 53 | super(GtkUIPlugin, self).__init__(plugin_name) 54 | 55 | class WebUIPlugin(PluginInitBase): 56 | def __init__(self, plugin_name): 57 | from webui import WebUI as _plugin_cls 58 | self._plugin_cls = _plugin_cls 59 | super(WebUIPlugin, self).__init__(plugin_name) 60 | -------------------------------------------------------------------------------- /batchrenamer/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoad/batchrenamer/1f92d3833b3b5b662f63ab8a07abffbaba4a61e9/batchrenamer/__init__.pyc -------------------------------------------------------------------------------- /batchrenamer/common.py: -------------------------------------------------------------------------------- 1 | # 2 | # common.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | 42 | def get_resource(filename): 43 | import pkg_resources, os 44 | return pkg_resources.resource_filename("batchrenamer", os.path.join("data", filename)) 45 | -------------------------------------------------------------------------------- /batchrenamer/common.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoad/batchrenamer/1f92d3833b3b5b662f63ab8a07abffbaba4a61e9/batchrenamer/common.pyc -------------------------------------------------------------------------------- /batchrenamer/core.py: -------------------------------------------------------------------------------- 1 | # 2 | # core.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | from deluge.plugins.pluginbase import CorePluginBase 42 | import deluge.component as component 43 | import deluge.configmanager 44 | from deluge.core.rpcserver import export 45 | 46 | from deluge.log import LOG as log 47 | 48 | DEFAULT_PREFS = { 49 | "test":"NiNiNi" 50 | } 51 | 52 | class Core(CorePluginBase): 53 | def enable(self): 54 | self.config = deluge.configmanager.ConfigManager("batchrenamer.conf", DEFAULT_PREFS) 55 | self.core = component.get("Core") 56 | self.tor_manager = component.get("TorrentManager") 57 | 58 | def disable(self): 59 | pass 60 | 61 | def update(self): 62 | pass 63 | 64 | @export 65 | def get_torrent_files(self, tor_id): 66 | """Return the files associated with tor_id""" 67 | f = self.tor_manager[tor_id].get_files() 68 | return (tor_id, f) 69 | 70 | @export 71 | def rename_torrent_files(self, tor_id, files): 72 | self.tor_manager[tor_id].rename_files(files) 73 | -------------------------------------------------------------------------------- /batchrenamer/core.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoad/batchrenamer/1f92d3833b3b5b662f63ab8a07abffbaba4a61e9/batchrenamer/core.pyc -------------------------------------------------------------------------------- /batchrenamer/data/batchrenamer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Script: batchrenamer.js 3 | The client-side javascript code for the BatchRenamer plugin. 4 | 5 | Copyright: 6 | (C) Nathan Hoad 2011 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 3, or (at your option) 10 | any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, write to: 19 | The Free Software Foundation, Inc., 20 | 51 Franklin Street, Fifth Floor 21 | Boston, MA 02110-1301, USA. 22 | 23 | In addition, as a special exception, the copyright holders give 24 | permission to link the code of portions of this program with the OpenSSL 25 | library. 26 | You must obey the GNU General Public License in all respects for all of 27 | the code used other than OpenSSL. If you modify file(s) with this 28 | exception, you may extend this exception to your version of the file(s), 29 | but you are not obligated to do so. If you do not wish to do so, delete 30 | this exception statement from your version. If you delete this exception 31 | statement from all source files in the program, then also delete it here. 32 | */ 33 | 34 | BatchRenamerPlugin = Ext.extend(Deluge.Plugin, { 35 | constructor: function(config) { 36 | config = Ext.apply({ 37 | name: "BatchRenamer" 38 | }, config); 39 | BatchRenamerPlugin.superclass.constructor.call(this, config); 40 | }, 41 | 42 | 43 | 44 | onDisable: function() { 45 | 46 | }, 47 | 48 | onEnable: function() { 49 | 50 | /* 51 | this.torrent_menu = new Ext.menu.Menu({ 52 | items: [{ 53 | text: _('Rename Torrent'), 54 | label: '', 55 | handler: this.onTorrentMenuClick, 56 | scope: this 57 | }] 58 | }); 59 | 60 | this.tm = deluge.menus.torrent.add({ 61 | text: _('Batch Renamer'), 62 | menu: this.torrent_menu 63 | });*/ 64 | }, 65 | 66 | onTorrentMenuClick: function(item, e) { 67 | 68 | } 69 | }); 70 | 71 | new BatchRenamerPlugin(); 72 | -------------------------------------------------------------------------------- /batchrenamer/data/config.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | True 9 | vertical 10 | 11 | 12 | True 13 | <b>The Batch Renamer plugin has no preferences.</b> 14 | True 15 | 16 | 17 | 0 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /batchrenamer/data/rename.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 5 7 | True 8 | 900 9 | 260 10 | True 11 | dialog 12 | True 13 | 14 | 15 | 16 | True 17 | vertical 18 | 3 19 | 20 | 21 | 35 22 | True 23 | 24 | 25 | True 26 | gtk-preferences 27 | 28 | 29 | False 30 | False 31 | 0 32 | 33 | 34 | 35 | 36 | True 37 | <b>Rename Files</b> 38 | True 39 | 40 | 41 | False 42 | False 43 | 1 44 | 45 | 46 | 47 | 48 | True 49 | 50 | 51 | 2 52 | 53 | 54 | 55 | 56 | True 57 | <b>Filename Template: </b> 58 | True 59 | 60 | 61 | False 62 | 3 63 | 64 | 65 | 66 | 67 | True 68 | True 69 | Use this to override the generated name. Placeholders ^s and ^e denote where to put the season and episode numbers. 70 | 71 | 72 | 73 | False 74 | 4 75 | 76 | 77 | 78 | 79 | True 80 | <b> Default Season: </b> 81 | True 82 | 83 | 84 | False 85 | False 86 | 5 87 | 88 | 89 | 90 | 91 | 0 92 | True 93 | True 94 | The default season number to use if a season can't be detected. 95 | 96 | 1 1 100 1 10 10 97 | 98 | 99 | False 100 | 6 101 | 102 | 103 | 104 | 105 | False 106 | False 107 | 0 108 | 109 | 110 | 111 | 112 | True 113 | 114 | 115 | False 116 | False 117 | 2 118 | 119 | 120 | 121 | 122 | True 123 | True 124 | automatic 125 | automatic 126 | 127 | 128 | True 129 | True 130 | 131 | 132 | 133 | 134 | 3 135 | 136 | 137 | 138 | 139 | True 140 | end 141 | 142 | 143 | gtk-ok 144 | True 145 | True 146 | True 147 | True 148 | 149 | 150 | 151 | False 152 | False 153 | 1 154 | 155 | 156 | 157 | 158 | gtk-cancel 159 | True 160 | True 161 | True 162 | True 163 | 164 | 165 | 166 | False 167 | False 168 | end 169 | 0 170 | 171 | 172 | 173 | 174 | False 175 | False 176 | end 177 | 1 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /batchrenamer/gtkui.py: -------------------------------------------------------------------------------- 1 | # 2 | # gtkui.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | import gtk 42 | import re 43 | import os 44 | 45 | from deluge.log import LOG as log 46 | from deluge.ui.client import client 47 | from deluge.plugins.pluginbase import GtkPluginBase 48 | import deluge.component as component 49 | import deluge.common 50 | from deluge.core.torrent import Torrent 51 | 52 | from common import get_resource 53 | 54 | 55 | class RenameFiles(): 56 | """Class to wrap up the GUI and all filename processing functions""" 57 | def __init__(self, tor_id, files): 58 | self.tor_id = tor_id 59 | self.files = files 60 | 61 | def run(self): 62 | """Build the GUI and display it.""" 63 | self.glade = gtk.glade.XML(get_resource("rename.glade")) 64 | self.window = self.glade.get_widget("RenameDialog") 65 | self.window.set_transient_for(component.get("MainWindow").window) 66 | self.template_field = self.glade.get_widget("filename_field") 67 | self.default_season_field = self.glade.get_widget("default_season") 68 | 69 | dic = {"on_ok_clicked": self.ok, 70 | "on_cancel_clicked": self.cancel} 71 | 72 | self.glade.signal_autoconnect(dic) 73 | 74 | self.build_tree_store() 75 | self.load_tree() 76 | treeview = self.glade.get_widget("treeview") 77 | treeview.expand_all() 78 | self.window.show() 79 | 80 | def enable_row(self, cell, path, model): 81 | """Enable a row and display the new name""" 82 | model[path][0] = not model[path][0] 83 | 84 | i = 0 85 | 86 | for child in model[path].iterchildren(): 87 | i += 1 88 | child[0] = model[path][0] 89 | self.rename(child) 90 | 91 | if i > 0: 92 | if model[path][0]: 93 | model[path][3] = "Can't rename folders. Click to edit me manually!" 94 | else: 95 | model[path][3] = "" 96 | else: 97 | self.rename(model[path]) 98 | 99 | def rename(self, row): 100 | """Clean the name, and try to add the season and episode info.""" 101 | # pre-emptively try and find the season and episode numbers 102 | if row[0] and row[1]: 103 | default_season = self.default_season_field.get_value_as_int() 104 | season = None 105 | episode = None 106 | new_name = None 107 | 108 | old_name = row[2] 109 | 110 | # clean up the name 111 | new_name = self.clean(old_name) 112 | 113 | # try and extract season + episode numbering 114 | season, episode, new_name = self.parse_season_episode(new_name) 115 | 116 | # last resort, guess the episode number and use the default season 117 | if season is None: 118 | episode, name = self.guess(new_name) 119 | 120 | if episode: 121 | new_name = name 122 | season = default_season 123 | 124 | if len(self.template_field.get_text()) > 0: 125 | file_extension = os.path.splitext(old_name)[1] 126 | new_name = self.template_field.get_text() + file_extension 127 | 128 | new_name = new_name.replace('^s', 'S' + str(season).zfill(2)) 129 | new_name = new_name.replace('^e', 'E' + str(episode).zfill(2)) 130 | # annoying v2 tags still stick around, so get rid of them 131 | new_name = re.sub('\s?v\d\s?', '', new_name) 132 | 133 | row[3] = new_name 134 | elif row[0] and not row[1]: 135 | row[3] = "Can't rename folders. Click to edit me manually!" 136 | else: 137 | row[3] = "" 138 | 139 | for child in row.iterchildren(): 140 | child[0] = row[0] 141 | self.rename(child) 142 | 143 | def fix_episode(self, matchobj): 144 | """Used by the clean function to fix season capitalisation""" 145 | return matchobj.group(0).upper() 146 | 147 | def clean(self, s): 148 | """Replace underscores with spaces, capitalise words and remove 149 | brackets and anything inbetween them. 150 | """ 151 | opening_brackets = ['(', '[', '<', '{'] 152 | closing_brackets = [')', ']', '>', '}'] 153 | for i in range(len(opening_brackets)): 154 | b = opening_brackets[i] 155 | c = closing_brackets[i] 156 | 157 | while b in s: 158 | start = s.find(b) 159 | end = s.find(c) + 1 160 | 161 | s = re.sub(re.escape(s[start:end]), '', s) 162 | 163 | results = os.path.splitext(s) 164 | extension = results[1] 165 | s = results[0] 166 | 167 | s = s.replace('_', ' ') 168 | s = s.replace('.', ' ') 169 | s = s.strip() 170 | words = s.split(' ') 171 | s = ' '.join([w.capitalize() for w in words[:-1]]) 172 | 173 | #results = os.path.splitext(words[-1]) 174 | #words[-1] = results[0].upper() 175 | 176 | s += ' %s%s' % (words[-1], extension) 177 | 178 | # this fixes the problem with the E before episode numbers being lowercase. 179 | s = re.sub('S\d+(e)\d+', self.fix_episode, s) 180 | 181 | return s 182 | 183 | def parse_season_episode(self, s): 184 | """Try and parse the season and episode numbers from the filename. 185 | 186 | Searches for the SxxExx, SEE and SxEE season/episode structures.""" 187 | season = None 188 | episode = None 189 | 190 | results = re.search(r'S(\d+)E(\d+)', s) 191 | 192 | if results: 193 | s = re.sub('S(\d+)E(\d+)', '^s^e', s) 194 | season = int(results.groups()[0]) 195 | episode = int(results.groups()[1]) 196 | return (season, episode, s) 197 | 198 | results = re.search(' (\d)(\d\d) ', s) 199 | if results: 200 | s = re.sub(' \d\d\d ', '^s^e', s) 201 | season = int(results.groups()[0]) 202 | episode = int(results.groups()[1]) 203 | return (season, episode, s) 204 | 205 | results = re.search('(\d)[Xx](\d\d)', s) 206 | if results: 207 | s = re.sub('\d[Xx]\d\d', '^s^e', s) 208 | season = int(results.groups()[0]) 209 | episode = int(results.groups()[1]) 210 | 211 | return (season, episode, s) 212 | 213 | return (season, episode, s) 214 | 215 | def guess(self, s): 216 | """This is called if nothing else has been found. Takes one last swoop. 217 | 218 | It assumes that the first number it finds is the episode number, and 219 | uses the default season number set by the user to set the season.""" 220 | # plain old HOPE that's an episode number, not a TV show number. 221 | results = re.findall(r'\d+', s) 222 | words = s.split(' ') 223 | 224 | extension = os.path.splitext(s)[1] 225 | 226 | pattern = re.compile(r'(\d+)') 227 | for w in words: 228 | r = pattern.search(w) 229 | 230 | if r: 231 | episode = r.groups()[0] 232 | # filenames with years in them! 233 | if len(episode) > 2: 234 | continue 235 | i = words.index(w) 236 | s = ' '.join(words[:i] + ['^s^e'] + words[i + 1:]) 237 | 238 | if i == len(words) - 1: 239 | s += extension 240 | 241 | return episode, s 242 | 243 | return None, None 244 | 245 | def edit_row(self, cell, path, new_text): 246 | """Set the new name of folders to what was typed.""" 247 | model = self.tree_store 248 | # this way you can only edit folders, not files. 249 | if not model[path][1]: 250 | self.tree_store[path][3] = new_text 251 | model[path][0] = True 252 | 253 | def build_tree_store(self): 254 | """Build the tree store to store data.""" 255 | tree_store = gtk.TreeStore(bool, str, str, str) 256 | 257 | treeview = self.glade.get_widget("treeview") 258 | treeview.set_model(tree_store) 259 | 260 | enable_column = gtk.TreeViewColumn('Rename?') 261 | index = gtk.TreeViewColumn('Index') 262 | old_name = gtk.TreeViewColumn('Old Name') 263 | new_name = gtk.TreeViewColumn('New Name') 264 | 265 | cell = gtk.CellRendererText() 266 | cell.set_property('editable', True) 267 | cell.connect('edited', self.edit_row) 268 | 269 | bool_cell = gtk.CellRendererToggle() 270 | bool_cell.set_property('activatable', True) 271 | bool_cell.connect('toggled', self.enable_row, tree_store) 272 | 273 | enable_column.pack_start(bool_cell, False) 274 | index.pack_start(cell, False) 275 | old_name.pack_start(cell, False) 276 | new_name.pack_start(cell, False) 277 | 278 | enable_column.add_attribute(bool_cell, 'active', 0) 279 | index.add_attribute(cell, 'text', 1) 280 | old_name.add_attribute(cell, 'text', 2) 281 | new_name.add_attribute(cell, 'text', 3) 282 | 283 | treeview.append_column(enable_column) 284 | treeview.append_column(index) 285 | treeview.append_column(old_name) 286 | treeview.append_column(new_name) 287 | 288 | self.tree_store = tree_store 289 | 290 | def load_tree(self): 291 | """Load the tree store up with the file data.""" 292 | structure = {0: []} 293 | parents = {} 294 | 295 | files = [(p['path'], p['index']) for p in self.files] 296 | 297 | real_parent = None 298 | for p, index in files: 299 | if os.path.basename(p) == p: 300 | structure[0].append(p) 301 | self.tree_store.append(None, [False, index, p, '']) 302 | 303 | else: 304 | parts = p.split('/') 305 | for i in range(len(parts)): 306 | # make sure the depth exists 307 | try: 308 | structure[i] 309 | except KeyError: 310 | structure[i] = [] 311 | 312 | # prevents doubles of folders 313 | if parts[i] not in structure[i]: 314 | structure[i].append(parts[i]) 315 | 316 | try: 317 | parent = parents[parts[i - 1]] 318 | except KeyError: 319 | parent = real_parent 320 | 321 | # if this, we're adding the actual files, no folders 322 | if os.path.basename(p) == parts[i]: 323 | self.tree_store.append(parent, [False, index, parts[i], '']) 324 | # still adding folders -_- 325 | else: 326 | result = self.tree_store.append(parent, [False, "", parts[i], '']) 327 | parents[parts[i]] = result 328 | 329 | def ok(self, arg): 330 | """Process renaming, as the dialog was closed with OK""" 331 | self.window.hide() 332 | self.window.destroy() 333 | 334 | i = 0 335 | model = self.tree_store 336 | 337 | new_files = [] 338 | try: 339 | base_name = "" 340 | while True: 341 | # only rename selected files 342 | result = self.get_new_name(model[i], base_name) 343 | 344 | if result: 345 | new_files.extend(result) 346 | 347 | i += 1 348 | except IndexError: 349 | pass 350 | 351 | log.debug("New file names and indexes:") 352 | for index, f in new_files: 353 | log.debug("%d : %s" % (index, f)) 354 | 355 | client.batchrenamer.rename_torrent_files(self.tor_id, new_files) 356 | 357 | def get_new_name(self, item, base_name): 358 | """Get the new name from model, and all it's children. 359 | 360 | Keyword arguments: 361 | item -- The item to retrieve new name info from. 362 | base_name -- the basename to prepend to new filenames. 363 | 364 | """ 365 | new_files = [] 366 | bad_name = "Can't rename folders. Click to edit me manually!" 367 | 368 | if item[0] and item[1]: 369 | name = os.path.join(base_name, item[3]) 370 | index = item[1] 371 | t = [int(index), name] 372 | new_files.append(t) 373 | elif not item[1]: 374 | tmp_base_name = os.path.join(base_name, item[2]) 375 | # if the folder has been renamed to a good name 376 | if item[3] != bad_name and item[3] != "": 377 | name = item[3] 378 | tmp_base_name = os.path.join(base_name, name) 379 | 380 | new_files.extend(self.get_child_names(tmp_base_name, item, bad_name)) 381 | 382 | return new_files 383 | 384 | def cancel(self, arg=None): 385 | """Do nothing, the user doesn't want to rename :(""" 386 | self.window.hide() 387 | self.window.destroy() 388 | 389 | def get_child_names(self, base_name, parent, bad_name): 390 | """Get all the new filenames of child elements of the treestore. 391 | 392 | Keyword arguments: 393 | base_name -- the basename (folder path) to prepend to each filename. 394 | parent -- the parent to get the children from. 395 | bad_name -- if this string is found, this item won't be renamed (for folders). 396 | 397 | """ 398 | new_files = [] 399 | for child in parent.iterchildren(): 400 | result = self.get_new_name(child, base_name) 401 | 402 | if result: 403 | new_files.extend(result) 404 | 405 | return new_files 406 | 407 | 408 | class GtkUI(GtkPluginBase): 409 | def enable(self): 410 | self.glade = gtk.glade.XML(get_resource("config.glade")) 411 | 412 | component.get("Preferences").add_page("BatchRenamer", self.glade.get_widget("batch_prefs")) 413 | 414 | # add the MenuItem to the context menu. 415 | torrentmenu = component.get("MenuBar").torrentmenu 416 | self.menu_item = gtk.ImageMenuItem("Rename Torrent") 417 | 418 | img = gtk.image_new_from_stock(gtk.STOCK_CONVERT, gtk.ICON_SIZE_MENU) 419 | self.menu_item.set_image(img) 420 | self.menu_item.connect("activate", self.rename_selected_torrent) 421 | torrentmenu.append(self.menu_item) 422 | torrentmenu.show_all() 423 | 424 | def disable(self): 425 | component.get("Preferences").remove_page("BatchRenamer") 426 | component.get("MenuBar").torrentmenu.remove(self.menu_item) 427 | 428 | def rename_selected_torrent(self, arg): 429 | torrent_id = component.get("TorrentView").get_selected_torrent() 430 | client.batchrenamer.get_torrent_files(torrent_id).addCallback(self.build_dialog) 431 | 432 | def build_dialog(self, result): 433 | """Display the dialog using the torrent ID and files.""" 434 | tor_id = result[0] 435 | files = result[1] 436 | r = RenameFiles(tor_id, files) 437 | r.run() 438 | -------------------------------------------------------------------------------- /batchrenamer/gtkui.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoad/batchrenamer/1f92d3833b3b5b662f63ab8a07abffbaba4a61e9/batchrenamer/gtkui.pyc -------------------------------------------------------------------------------- /batchrenamer/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dirs = [] 4 | for i in range(26): 5 | s = 'Torrent/[moo-shi]_Mushishi_[DVD]/[moo-shi]_Mushishi_-_%d_[DVD]_[3BE3DA10].mkv' % i 6 | s = os.path.dirname(s) 7 | 8 | parts = s.split('/') 9 | 10 | dirs = [parts[0]] 11 | -------------------------------------------------------------------------------- /batchrenamer/webui.py: -------------------------------------------------------------------------------- 1 | # 2 | # webui.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | from deluge.log import LOG as log 42 | from deluge.ui.client import client 43 | from deluge import component 44 | from deluge.plugins.pluginbase import WebPluginBase 45 | 46 | from common import get_resource 47 | 48 | class WebUI(WebPluginBase): 49 | 50 | scripts = [get_resource("batchrenamer.js")] 51 | 52 | def enable(self): 53 | for i in range(5): 54 | log.info("Hello from WebUI.enable!") 55 | 56 | def disable(self): 57 | pass 58 | -------------------------------------------------------------------------------- /batchrenamer/webui.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoad/batchrenamer/1f92d3833b3b5b662f63ab8a07abffbaba4a61e9/batchrenamer/webui.pyc -------------------------------------------------------------------------------- /create_dev_link.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /home/nathan/projects/deluge-plugin/new/batchrenamer 3 | mkdir temp 4 | export PYTHONPATH=./temp 5 | python2 setup.py build develop --install-dir ./temp 6 | cp ./temp/BatchRenamer.egg-link /home/nathan/.config/deluge/plugins 7 | rm -fr ./temp 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # setup.py 3 | # 4 | # Copyright (C) 2011 Nathan Hoad 5 | # 6 | # Basic plugin template created by: 7 | # Copyright (C) 2008 Martijn Voncken 8 | # Copyright (C) 2007-2009 Andrew Resch 9 | # Copyright (C) 2009 Damien Churchill 10 | # Copyright (C) 2010 Pedro Algarvio 11 | # 12 | # Deluge is free software. 13 | # 14 | # You may redistribute it and/or modify it under the terms of the 15 | # GNU General Public License, as published by the Free Software 16 | # Foundation; either version 3 of the License, or (at your option) 17 | # any later version. 18 | # 19 | # deluge is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | # See the GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with deluge. If not, write to: 26 | # The Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor 28 | # Boston, MA 02110-1301, USA. 29 | # 30 | # In addition, as a special exception, the copyright holders give 31 | # permission to link the code of portions of this program with the OpenSSL 32 | # library. 33 | # You must obey the GNU General Public License in all respects for all of 34 | # the code used other than OpenSSL. If you modify file(s) with this 35 | # exception, you may extend this exception to your version of the file(s), 36 | # but you are not obligated to do so. If you do not wish to do so, delete 37 | # this exception statement from your version. If you delete this exception 38 | # statement from all source files in the program, then also delete it here. 39 | # 40 | 41 | from setuptools import setup 42 | 43 | __plugin_name__ = "BatchRenamer" 44 | __author__ = "Nathan Hoad" 45 | __author_email__ = "nathan@getoffmalawn.com" 46 | __version__ = "0.2" 47 | __url__ = "" 48 | __license__ = "GPLv3" 49 | __description__ = "" 50 | __long_description__ = """""" 51 | __pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]} 52 | 53 | setup( 54 | name=__plugin_name__, 55 | version=__version__, 56 | description=__description__, 57 | author=__author__, 58 | author_email=__author_email__, 59 | url=__url__, 60 | license=__license__, 61 | long_description=__long_description__ if __long_description__ else __description__, 62 | 63 | packages=[__plugin_name__.lower()], 64 | package_data = __pkg_data__, 65 | 66 | entry_points=""" 67 | [deluge.plugin.core] 68 | %s = %s:CorePlugin 69 | [deluge.plugin.gtkui] 70 | %s = %s:GtkUIPlugin 71 | [deluge.plugin.web] 72 | %s = %s:WebUIPlugin 73 | """ % ((__plugin_name__, __plugin_name__.lower())*3) 74 | ) 75 | --------------------------------------------------------------------------------