pax_global_header00006660000000000000000000000064126656102530014520gustar00rootroot0000000000000052 comment=ef8977cfbe12bb9627c48892236201f24289886d autorenamer-0.4/000077500000000000000000000000001266561025300137055ustar00rootroot00000000000000autorenamer-0.4/README000066400000000000000000000021071266561025300145650ustar00rootroot00000000000000AutoRenamer - a simple program to rename files so that they sort the way you want. Requires Python, GTK and GNOME libraries. The program is based on an example "iconview.py" program by Jan Bodnar, from the PyGTK tutorial at http://zetcode.com/tutorials/pygtktutorial/ A copy of an email from Jan, clarifying the license, follows: Hi Marcin, Source code is BSD licensed. Which is the most convenient license I know. Which is in my words do what you want with the code; if you mention/link to the code you used it is fine, but even this is not required. What is licensed is the content, the tutorials. They cannot be republished on other sites without permission. Regards, Jan Bodnar On Fri, Nov 25, 2011 at 11:01 PM, Marcin Owsiany wrote: > Hello, > > Can you please clarify what is the license of the source code in your > PyGTK tutorial? I've made a tiny utility based on your iconview.py and > I'd like to publish it under a free license. That would be much easier > if your code was also published under a free license :-) autorenamer-0.4/autorenamer.1000066400000000000000000000021561266561025300163150ustar00rootroot00000000000000.TH AUTORENAMER 1 "November 27, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME autorenamer \- program to rename files so they sort according to given ordering .SH SYNOPSIS .B autorenamer .SH DESCRIPTION .B autorenamer is an interactive graphical user interface program. It displays contents of a directory using icons that you can reorder using drag-and-drop. Once you are done, the program renames the files such that their alphabetical ordering is the same as the order you chose. .P If any entries are selected when "Save" is clicked, only the selected entries are renamed. Otherwise all the entries in the directory are renamed. .SH SEE ALSO .BR mv (1). .br .SH AUTHOR autorenamer was written by Marcin Owsiany . autorenamer-0.4/autorenamer.desktop000066400000000000000000000003551266561025300176250ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=AutoRenamer GenericName=Reorder files by renaming Comment=Automatically rename files have them sort in given order Exec=autorenamer Terminal=false Categories=GNOME;GTK;Utility;FileTools;FileManager; autorenamer-0.4/autorenamer.py000066400000000000000000000275041266561025300166110ustar00rootroot00000000000000#!/usr/bin/python # AutoRenamer - renames files so they sort in a given order # Copyright 2011-2016 Marcin Owsiany # Derived from an example program from the ZetCode.com PyGTK tutorial # Copyright 2007-2009 Jan Bodnar # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the Authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import logging import math import random from gi.repository import Gtk from gi.repository import GdkPixbuf import os from autorenamer import thumbnails COL_PATH = 0 COL_PIXBUF = 1 COL_IS_DIRECTORY = 2 APP_NAME = "AutoRenamer" class AutoRenamer(Gtk.Window): def close(self, unused_event, unused_data): if self.modified_store: return not self.pop_dialog("Discard changes?", "Do you want to exit and lose your changes?", ok_only=False, accept_save=False) else: return False def __init__(self): super(AutoRenamer, self).__init__() self.thumbnailer = thumbnails.Thumbnailer() self.set_size_request(650, 400) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", self.close) self.connect("destroy", Gtk.main_quit) self.set_title(APP_NAME) self.home_directory = os.path.realpath(os.path.expanduser('~')) self.current_directory = os.path.realpath('.') self.store_modified_handle = None self.upButton = Gtk.ToolButton(Gtk.STOCK_GO_UP) self.upButton.set_is_important(True) self.upButton.connect("clicked", self.on_up_clicked) self.homeButton = Gtk.ToolButton(Gtk.STOCK_HOME) self.homeButton.set_is_important(True) self.homeButton.connect("clicked", self.on_home_clicked) self.saveButton = Gtk.ToolButton(Gtk.STOCK_SAVE) self.saveButton.set_is_important(True) self.saveButton.connect("clicked", self.on_save_clicked) self.discardButton = Gtk.ToolButton(Gtk.STOCK_CANCEL) self.discardButton.set_is_important(True) self.discardButton.connect("clicked", self.on_discard_clicked) shuffle_image = Gtk.Image.new_from_icon_name("media-playlist-shuffle", Gtk.IconSize.BUTTON) self.randomizeButton = Gtk.ToolButton() self.randomizeButton.set_icon_widget(shuffle_image) self.randomizeButton.set_is_important(True) self.randomizeButton.set_label("Shuffle") self.randomizeButton.connect("clicked", self.on_randomize_clicked) self.dirsButton = Gtk.ToolButton(Gtk.STOCK_DIRECTORY) self.dirsButton.set_is_important(True) self.dirsButton.set_label("Toggle directories") self.dirsButton.connect("clicked", self.on_dirs_clicked) toolbar = Gtk.Toolbar() toolbar.insert(self.upButton, -1) toolbar.insert(self.homeButton, -1) toolbar.insert(self.saveButton, -1) toolbar.insert(self.discardButton, -1) toolbar.insert(self.randomizeButton, -1) toolbar.insert(self.dirsButton, -1) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) vbox = Gtk.VBox(False, 0) vbox.pack_start(toolbar, False, False, 0) vbox.pack_start(sw, True, True, 0) self.store = Gtk.ListStore(str, GdkPixbuf.Pixbuf, bool) self.fill_store() self.iconView = Gtk.IconView(self.store) self.iconView.set_reorderable(True) self.iconView.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.iconView.set_text_column(COL_PATH) self.iconView.set_pixbuf_column(COL_PIXBUF) self.iconView.connect("item-activated", self.on_item_activated) sw.add(self.iconView) self.iconView.grab_focus() self.add(vbox) self.show_all() def fill_store(self): if self.store_modified_handle: self.store.disconnect(self.store_modified_handle) self.store.clear() self.modified_store = False if self.current_directory == None: return if self.current_directory == "/": self.upButton.set_sensitive(False) else: self.upButton.set_sensitive(True) if self.current_directory == self.home_directory: self.homeButton.set_sensitive(False) else: self.homeButton.set_sensitive(True) self.saveButton.set_sensitive(False) self.discardButton.set_sensitive(False) self.initial_order = [f for f in sorted(os.listdir(self.current_directory)) if f[0] != "."] self.randomizeButton.set_sensitive(bool(self.initial_order)) self.set_title(APP_NAME + ": " + self.current_directory) directories_present = False for fl in self.initial_order: full_path = os.path.join(self.current_directory, fl) is_dir = os.path.isdir(full_path) if is_dir: directories_present = True self.store.append([fl, self.thumbnailer.pixbuf_for(full_path, is_dir), is_dir]) self.store_modified_handle = self.store.connect("row-deleted", self.on_row_deleted) self.dirsButton.set_sensitive(directories_present) def on_row_deleted(self, treemodel, path): self.on_order_changed() def on_order_changed(self): if self.initial_order == [e[0] for e in self.store]: self.modified_store = False self.upButton.set_sensitive(True) self.homeButton.set_sensitive(True) self.saveButton.set_sensitive(False) self.discardButton.set_sensitive(False) else: self.modified_store = True self.upButton.set_sensitive(False) self.homeButton.set_sensitive(False) self.saveButton.set_sensitive(True) self.discardButton.set_sensitive(True) def on_home_clicked(self, widget): self.current_directory = self.home_directory self.fill_store() def on_discard_clicked(self, widget): self.fill_store() def on_dirs_clicked(self, widget): all_store_indices = xrange(len(self.store)) directory_indices = [index for index, item in zip(all_store_indices, self.store) if item[COL_IS_DIRECTORY]] for index in directory_indices: path = Gtk.TreePath(index) if self.iconView.path_is_selected(path): self.iconView.unselect_path(path) else: self.iconView.select_path(path) def selected_elements_in_order(self): selected_indices = [path[0] for path in self.iconView.get_selected_items()] selected_indices.sort() return [self.store[index] for index in selected_indices] def on_save_clicked(self, widget): new_order_elements = self.selected_elements_in_order() or self.store rename_selected_only = new_order_elements is not self.store ordered_names_to_rename = [e[COL_PATH] for e in new_order_elements] num_items = len(ordered_names_to_rename) width = math.ceil(math.log10(num_items)) fmt = "%%0%dd-%%s" % width prefixed = [(fmt % (i, f)) for i, f in zip(xrange(num_items), ordered_names_to_rename)] all_names = [e[COL_PATH] for e in self.store] conflicts = set.intersection(set(all_names), set(prefixed)) if conflicts: self.pop_dialog("Cannot rename", "The following filenames conflict.", column_names=("Filename",), column_values=[(c,) for c in conflicts]) return renames = zip(ordered_names_to_rename, prefixed) if self.pop_dialog("Renames", "The following renames will be performed." + (rename_selected_only and "\nNote: only the selected entries are renamed." or ""), ok_only=False, column_names=("From", "To"), column_values=renames): for source, dest in renames: source = os.path.join(self.current_directory, source) dest = os.path.join(self.current_directory, dest) os.rename(source, dest) self.fill_store() def pop_dialog(self, title, label_text, ok_only=True, accept_save=True, column_names=None, column_values=None): label = Gtk.Label(label=label_text) if ok_only: buttons = (Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) elif accept_save: buttons = (Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT, Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) else: buttons = (Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT, Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) dialog = Gtk.Dialog(title, self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, buttons) dialog.vbox.props.homogeneous = False dialog.vbox.pack_start(label, False, False, 0) if column_names is not None and column_values is not None: types = [str for c in column_names] store = Gtk.ListStore(*types) for value in column_values: store.append(value) list_view = Gtk.TreeView(store) list_view.set_reorderable(False) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) for name, offset in zip(column_names, range(len(column_names))): list_view.append_column(Gtk.TreeViewColumn(name, Gtk.CellRendererText(), text=offset)) sw.add(list_view) dialog.vbox.pack_start(sw, True, True, 0) dialog.show_all() try: return dialog.run() == Gtk.ResponseType.ACCEPT finally: dialog.destroy() def on_item_activated(self, widget, item): model = widget.get_model() path = model[item][COL_PATH] isDir = model[item][COL_IS_DIRECTORY] if not isDir: return if self.modified_store: self.pop_dialog("Save or discard", "Save or discard first!") return self.current_directory = self.current_directory + os.path.sep + path self.fill_store() def on_up_clicked(self, widget): self.current_directory = os.path.dirname(self.current_directory) self.fill_store() def on_randomize_clicked(self, widget): order = range(len(self.initial_order)) random.shuffle(order) self.store.reorder(order) self.on_order_changed() if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) AutoRenamer() Gtk.main() autorenamer-0.4/autorenamer/000077500000000000000000000000001266561025300162275ustar00rootroot00000000000000autorenamer-0.4/autorenamer/__init__.py000066400000000000000000000000001266561025300203260ustar00rootroot00000000000000autorenamer-0.4/autorenamer/thumbnails.py000066400000000000000000000112261266561025300207510ustar00rootroot00000000000000# AutoRenamer - renames files so they sort in a given order # Copyright 2015 Marcin Owsiany # Derived from an example program from the ZetCode.com PyGTK tutorial # Copyright 2007-2009 Jan Bodnar # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the Authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import logging from gi.repository import Gio from gi.repository import GLib try: from gi.repository import GnomeDesktop except: logging.exception('Importing GNOME desktop library failed. Thumbnail support will be limited.') GnomeDesktop = None from gi.repository import Gtk _ICON_SIZE = 48 class Thumbnailer(object): """Handles thumbnails for autorenamer.""" def __init__(self): if GnomeDesktop: self._thumbnail_factory = GnomeDesktop.DesktopThumbnailFactory() else: self._thumbnail_factory = None self._theme = Gtk.IconTheme.get_default() self.file_icon = self._theme.load_icon(Gtk.STOCK_FILE, _ICON_SIZE, 0) self.dir_icon = self._theme.load_icon(Gtk.STOCK_DIRECTORY, _ICON_SIZE, 0) def pixbuf_for(self, full_path, is_dir): if is_dir: logging.debug('%s is a dir', full_path) return self.dir_icon uri = GLib.filename_to_uri(full_path) gio_file = Gio.File.new_for_path(full_path) file_info = gio_file.query_info('*', Gio.FileQueryInfoFlags.NONE, None) mtime = file_info.get_attribute_uint64(Gio.FILE_ATTRIBUTE_TIME_MODIFIED) # Use existing thumbnail if any. if self._thumbnail_factory: existing_thumbnail = self._thumbnail_factory.lookup(uri, mtime) else: existing_thumbnail = file_info.get_attribute_byte_string(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH) if existing_thumbnail: logging.debug('%s has existing thumbnail', full_path) image = Gtk.Image.new_from_file(existing_thumbnail) if image: return image.get_pixbuf() else: logging.debug('%s failed to load existing thumbnail', full_path) # Attempt to generate a new thumbnail, saving the result as appropriate. if self._thumbnail_factory: mime_type = file_info.get_attribute_as_string(Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE) if self._thumbnail_factory.can_thumbnail(uri, mime_type, mtime): logging.debug('%s can be thumbnailed by GNOME thumbnailer', full_path) pixbuf = self._thumbnail_factory.generate_thumbnail(uri, mime_type) if pixbuf: self._thumbnail_factory.save_thumbnail(pixbuf, uri, mtime) return pixbuf else: logging.debug('%s could NOT be thumbnailed by GNOME thumbnailer after all', full_path) self._thumbnail_factory.create_failed_thumbnail(uri, mtime) # Use an appropriate icon. icon = file_info.get_icon() if icon: icon_info = self._theme.lookup_by_gicon(icon, _ICON_SIZE, Gtk.IconLookupFlags(0)) if icon_info: loaded_icon = icon_info.load_icon() if loaded_icon: logging.debug('%s has a suggested icon', full_path) return loaded_icon else: logging.debug('%s icon suggestion could not be loaded', full_path) else: logging.debug('%s icon suggestion could not be looked up in current theme', full_path) else: logging.debug('%s has NO particular icon suggestion', full_path) # As last resort, use a generic file icon. return self.file_icon autorenamer-0.4/setup.py000066400000000000000000000005561266561025300154250ustar00rootroot00000000000000#!/usr/bin/env python from distutils.core import setup setup(name='Autorenamer', version='0.4', description='Rrename files to make them sort in given order.', author='Marcin Owsiany', author_email='marcin@owsiany.pl', url='http://marcin.owsiany.pl/autorenamer-page', packages=['autorenamer'], scripts=['autorenamer.py'])