#!/usr/bin/env python # Nautilus CD burning script # # Copyright (c) 2002 Michele Campeotto # http://www.micampe.it # # Licence: GNU GPL # Copyright (C) 2002 Michele Campeotto # # Dependency: GTK+ >= 2.0 # PyGTK >= 1.99 # Python >= 2.2 # mkisofs, cdrecord and access to the burner device # # 20020330 - Ver 0.1 # First release # 20021007 - Ver 0.2 # Drag and drop support # Check for image size # 20021217 - Ver 0.3 # Added the pygtk.require magic import pygtk; pygtk.require("2.0") import sys, os, os.path, popen2 from urlparse import * import gobject, gtk # To configure the script, you should only need to edit these values device = '0,0,0' driver = 'mmc_cdr' speed = '8' options = 'driveropts=burnproof -dummy' MAX_DATA_SIZE = 700000 burn_command_template = 'mkisofs -gui -r -J -iso-level 3 -o image.img -graft-points %(graft_points)s' # To actually burn CDs, uncomment the following line #burn_command_template = 'mkisofs -gui -r -J -iso-level 3 -graft-points %(graft_points)s | cdrecord dev="%(device)s" %(options)s driver=%(driver)s speed=%(speed)s -eject -tsize=%(size)ss -' size_command_template = 'mkisofs -r -J -q -print-size -graft-points %(graft_points)s' class BurnView(gtk.TreeView): def __init__(self, paths): self.store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) gtk.TreeView.__init__(self, self.store) self.set_headers_visible(gtk.FALSE) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Name', renderer, text=0) self.append_column(column) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Name', renderer, text=1) column.set_visible(gtk.FALSE) self.append_column(column) target = [('text/uri-list', 0, 0)] self.drag_dest_set(gtk.DEST_DEFAULT_ALL, target, gtk.gdk.ACTION_COPY) self.connect('drag_data_received', self.drag_data_received) self.paths = paths def drag_data_received(self, w, context, x, y, data, info, time): if data and data.format == 8: map(self.mount, data.data.split('\r\n')[:-1]) context.finish(gtk.TRUE, gtk.FALSE, time) def mount(self, uri, parent=None): uri = urlparse(uri)[2] for item in self.store: if self.store.get_value(self.store.get_iter(item.path), 1) == uri: return iter = self.store.append() self.store.set(iter, 0, os.path.basename(uri)) self.store.set(iter, 1, uri) self.paths.append(uri) def build_mappings(paths): mappings = [] for i in paths: if os.path.isdir(i): mappings.append('"%s/=%s"' % (i.split('/')[-1], i)) else: mappings.append('"%s=%s"' % (i.split('/')[-1], i)) return mappings def burn_command(paths, size): graft_points = ' '.join(build_mappings(paths)) params = locals() params['device'] = device params['driver'] = driver params['options'] = options params['speed'] = speed return burn_command_template % params def size_command(paths): graft_points = ' '.join(build_mappings(paths)) return size_command_template % locals() def get_image_size(paths): return int(os.popen4(size_command(paths))[1].readlines()[-1]) def burn(paths, size, progress): def cb(fd, cond): data = os.read(fd, 80) if data.find('extents') >= 0: progress.set_fraction(1.0) progress.set_text('100%') return gtk.TRUE elif data.find('read/written') >= 0: progress.set_text('Finishing...') try: perc = int(data.split('.')[0]) progress.set_fraction(perc/100.0) progress.set_text('%d%%' % (perc,)) except (ValueError): pass command = burn_command(paths, size) pipe = popen2.Popen4(command) gtk.input_add(pipe.fromchild, gtk.gdk.INPUT_READ, cb) while pipe.poll() < 0: gtk.mainiteration() return pipe.poll() def response(dialog, response): sys.exit() def burn_window(paths, size): win = gtk.Dialog("Quick Burner", None, gtk.DIALOG_MODAL, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) win.connect('response', response) win.set_default_size(400, -1) hbox = gtk.HBox() hbox.set_border_width(8) hbox.set_spacing(8) win.vbox.pack_start(hbox) stock = gtk.image_new_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG) hbox.pack_start(stock, gtk.FALSE, gtk.FALSE) vbox = gtk.VBox() vbox.set_spacing(8) hbox.pack_start(vbox, gtk.TRUE, gtk.TRUE) label = gtk.Label("Burning...") label.set_use_markup(gtk.TRUE) label.set_alignment(0, 0.5) vbox.pack_start(label) label = gtk.Label('\n'.join(paths)) label.set_alignment(0, 0.5) vbox.pack_start(label) progress = gtk.ProgressBar() progress.set_fraction(0.0) progress.set_text("Initializing...") vbox.pack_start(progress) win.show_all() if burn(paths, size, progress) == 0: success(win) else: failure(win) def error(paths, message=None): win = gtk.Dialog("Burning finished", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_OK)) hbox = gtk.HBox(gtk.FALSE, 8) hbox.set_border_width(8) win.vbox.pack_start(hbox) stock = gtk.image_new_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) hbox.pack_start(stock) vbox = gtk.VBox() vbox.set_spacing(8) hbox.pack_start(vbox) if message: label = gtk.Label("%s" % message) else: label = gtk.Label("Some error occurred while building the image.") label.set_use_markup(gtk.TRUE) label.set_alignment(0.0, 0.5) vbox.pack_start(label) if not message: label = gtk.Label("Run:\n%s\nfrom the commman line to see what went wrong." % size_command(paths)) label.set_use_markup(gtk.TRUE) label.set_selectable(gtk.TRUE) label.set_line_wrap(gtk.TRUE) vbox.pack_start(label) win.show_all() response = win.run() win.destroy() return response def success(parent): win = gtk.Dialog("Burning finished", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_OK)) win.set_transient_for(parent) hbox = gtk.HBox(gtk.FALSE, 8) hbox.set_border_width(8) win.vbox.pack_start(hbox) stock = gtk.image_new_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG) hbox.pack_start(stock) label = gtk.Label("Burning process successfully completed.") hbox.pack_start(label) win.show_all() response = win.run() win.destroy() return response def failure(parent): win = gtk.Dialog("Burning failed", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_OK)) win.set_transient_for(parent) hbox = gtk.HBox(gtk.FALSE, 8) hbox.set_border_width(8) win.vbox.pack_start(hbox) stock = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG) hbox.pack_start(stock) label = gtk.Label("Burning process failed.") hbox.pack_start(label) win.show_all() response = win.run() win.destroy() return response def main_window(paths): win = gtk.Dialog("Quick Burner", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, "_Burn 'em!", gtk.RESPONSE_OK)) win.set_default_size(300, 240) vbox = gtk.VBox() win.vbox.pack_start(vbox) vbox.set_border_width(4) vbox.set_spacing(8) label = gtk.Label("Drag here the files to be burned") label.set_use_markup(gtk.TRUE) label.set_alignment(0, 0.5) vbox.pack_start(label, expand=gtk.FALSE) scrollwin = gtk.ScrolledWindow() scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) tree = BurnView(paths) params = paths[:] while len(paths): paths.pop() map(tree.mount, params) scrollwin.add(tree) vbox.pack_start(scrollwin, expand=gtk.TRUE) win.show_all() response = win.run() win.destroy() if response == gtk.RESPONSE_OK: try: size = get_image_size(paths) if size > MAX_DATA_SIZE / 2: error(paths, "You're trying to burn too much data!") return except ValueError: error(paths) else: burn_window(paths, size) def main(): paths = [os.path.abspath(i) for i in sys.argv[1:]] main_window(paths) if __name__ == '__main__': main()