def main(): parser = get_option_parser() (options, args) = parser.parse_args() if not options.iphoto: parser.error("Need to specify the iPhoto library with the --iphoto " "option.") album_xml_file = iphotodata.get_album_xmlfile( su.expand_home_folder(options.iphoto)) data = iphotodata.get_iphoto_data(album_xml_file) if options.pathes_file and options.ids_file: f_pathes = codecs.open(options.pathes_file,'w',"utf-16be") f_ids = codecs.open(options.ids_file,'w',"utf-16be") for image in data.images_by_id.values(): f_ids.write(image.key) f_ids.write("\0") f_pathes.write(image.getimagepath()) f_pathes.write("\0") f_pathes.close f_ids.close if options.albums_file: f_albums = codecs.open(options.albums_file,'w',"utf-16be") events = data._getrolls(); for event in events: f_albums.write("") f_albums.write("\0") f_albums.write(event.albumid) f_albums.write("\0") f_albums.write(event.name) f_albums.write("\0") f_albums.write("") # f_albums.write("\0") for image in event.images: f_albums.write(image.key) f_albums.write("\0") f_albums.write("\0") f_albums.write("\0")
def main(): """main routine for phoshare.""" parser = get_option_parser() (options, args) = parser.parse_args() if len(args) != 0: parser.error("Found some unrecognized arguments on the command line.") if options.version: print '%s %s' % (phoshare.phoshare_version.PHOSHARE_VERSION, phoshare.phoshare_version.PHOSHARE_BUILD) return 1 if options.iptc > 0 and not exiftool.check_exif_tool(): print >> sys.stderr, ( "Exiftool is needed for the --itpc or --iptcall" + " options.") return 1 if options.size and options.link: parser.error("Cannot use --size and --link together.") if not options.iphoto: parser.error("Need to specify the iPhoto library with the --iphoto " "option.") if options.export or options.picasaweb or options.checkalbumsize: if not (options.albums or options.events or options.smarts or options.facealbums): parser.error("Need to specify at least one event, album, or smart " "album for exporting, using the -e, -a, or -s " "options.") else: parser.error("No action specified. Use --export to export from your " "iPhoto library.") if options.picasaweb: if options.picasapassword: google_password = options.picasapassword else: google_password = getpass.getpass('Google password for %s: ' % options.picasaweb) logging_handler = logging.StreamHandler() logging_handler.setLevel( logging.DEBUG if options.verbose else logging.INFO) _logger.addHandler(logging_handler) album_xml_file = iphotodata.get_album_xmlfile( su.expand_home_folder(options.iphoto)) data = iphotodata.get_iphoto_data(album_xml_file) if data.aperture: if options.originals: data.load_aperture_originals() check_aperture_mode(options, parser) options.aperture = data.aperture options.foldertemplate = unicode(options.foldertemplate) options.nametemplate = unicode(options.nametemplate) options.captiontemplate = unicode(options.captiontemplate) if options.checkalbumsize: data.checkalbumsizes(int(options.checkalbumsize)) if options.export: album = ExportLibrary(su.expand_home_folder(options.export)) export_iphoto(album, data, options.exclude, options) if options.picasaweb: albums = picasaweb.PicasaAlbums(options.picasaweb, google_password) export_iphoto(albums, data, options.exclude, options)
def export_thread(self, mode): """Run an export operation in a thread, to not block the UI. Args: mode - name of operation to run, "library", "dry_run", or "export". """ try: # First, load the iPhoto library. library_path = su.expand_home_folder(self.iphoto_library.get()) album_xml_file = iphotodata.get_album_xmlfile(library_path) data = iphotodata.get_iphoto_data(album_xml_file) msg = "Version %s library with %d images" % ( data.applicationVersion, len(data.images)) self.write(msg + '\n') if mode == "library": # If we just need to check the library, we are done here. self.thread_queue.put(("done", (True, mode, msg))) return # Do the actual export. export_folder = su.expand_home_folder(self.export_folder.get()) args = ['Phoshare.py', '--export', '"' + export_folder + '"'] options = self.Options() options.iphoto = self.iphoto_library.get() args.extend(['--iphoto', '"' + options.iphoto + '"']) options.export = self.export_folder.get() options.dryrun = mode == "dry_run" options.albums = self.albums.get() if options.albums: args.extend(['--albums', '"' + options.albums + '"']) options.events = self.events.get() if options.events: args.extend(['--events', '"' + options.events + '"']) options.smarts = self.smarts.get() if options.smarts: args.extend(['--smarts', '"' + options.smarts + '"']) options.foldertemplate = unicode(self.foldertemplate.get()) if options.foldertemplate: args.extend(['--foldertemplate', '"' + options.foldertemplate + '"']) options.nametemplate = unicode(self.nametemplate.get()) if options.nametemplate: args.extend(['--nametemplate', '"' + options.nametemplate + '"']) options.captiontemplate = unicode(self.captiontemplate.get()) if options.captiontemplate: args.extend(['--captiontemplate', '"' + options.captiontemplate + '"']) options.ignore = [] # TODO options.update = self.update_var.get() == 1 if options.update: args.append('--update') options.delete = self.delete_var.get() == 1 if options.delete: args.append('--delete') options.originals = self.originals_var.get() == 1 if options.originals: args.append('--originals') options.link = self.link_var.get() == 1 if options.link: args.append('--link') options.folderhints = self.folder_hints_var.get() == 1 if options.folderhints: args.append('--folderhints') options.faces = self.faces_var.get() == 1 if options.faces: args.append('--faces') options.face_keywords = self.face_keywords_var.get() == 1 if options.face_keywords: args.append('--face_keywords') if self.iptc_all_var.get() == 1: options.iptc = 2 args.append('--iptcall') elif self.iptc_var.get() == 1: options.iptc = 1 args.append('--iptc') else: options.iptc = 0 options.gps = self.gps_var.get() if options.gps: args.append('--gps') options.facealbums = self.face_albums_var.get() == 1 if options.facealbums: args.append('--facealbums') options.facealbum_prefix = self.face_albums_text.get() if options.facealbum_prefix: args.append('--facealbum_prefix') exclude = None # TODO options.save() print " ".join(args) self.logging_handler.setLevel(logging.DEBUG if self.verbose_var.get() else logging.INFO) self.active_library = phoshare_main.ExportLibrary(export_folder) phoshare_main.export_iphoto(self.active_library, data, exclude, options) self.thread_queue.put(("done", (True, mode, ''))) except Exception, e: # IGNORE:W0703 self.thread_queue.put(("done", (False, mode, str(e) + '\n\n' + traceback.format_exc())))
def run_phoshare(cmd_args): """main routine for phoshare.""" parser = get_option_parser() (options, args) = parser.parse_args(cmd_args) if len(args) != 0: parser.error("Found some unrecognized arguments on the command line.") if options.version: print '%s %s' % (phoshare.phoshare_version.PHOSHARE_VERSION, phoshare.phoshare_version.PHOSHARE_BUILD) return 1 if options.iptc > 0 and not exiftool.check_exif_tool(): print >> sys.stderr, ("Exiftool is needed for the --itpc or --iptcall" + " options.") return 1 if options.size and options.link: parser.error("Cannot use --size and --link together.") if not options.iphoto: parser.error("Need to specify the iPhoto library with the --iphoto " "option.") if options.export or options.picasaweb or options.checkalbumsize: if not (options.albums or options.events or options.smarts or options.facealbums): parser.error("Need to specify at least one event, album, or smart " "album for exporting, using the -e, -a, or -s " "options.") else: parser.error("No action specified. Use --export to export from your " "iPhoto library.") if options.picasaweb: if options.picasapassword: google_password = options.picasapassword else: google_password = getpass.getpass('Google password for %s: ' % options.picasaweb) if options.ratings: options.ratings = [int(r) for r in options.ratings.split(",")] if options.reverse: if not options.dryrun: su.pout(u"Turning on dryrun mode because of --reverse option.") options.dryrun = True logging_handler = logging.StreamHandler() logging_handler.setLevel(logging.DEBUG if options.verbose else logging.INFO) _logger.addHandler(logging_handler) album_xml_file = iphotodata.get_album_xmlfile( su.expand_home_folder(options.iphoto)) if options.omitdatabasefile: album_sql_file="" else: album_sql_file = iphotodata.get_album_sqlfile( su.expand_home_folder(options.iphoto)) data = iphotodata.get_iphoto_data(album_xml_file, album_sql_file, ratings=options.ratings, verbose=options.verbose, aperture=options.aperture) if options.originals and options.export: data.load_aperture_originals() options.aperture = data.aperture and not data.aperture_data options.foldertemplate = unicode(options.foldertemplate) options.nametemplate = unicode(options.nametemplate) options.captiontemplate = unicode(options.captiontemplate) if options.checkalbumsize: data.checkalbumsizes(int(options.checkalbumsize)) if options.export: album = ExportLibrary(su.expand_home_folder(options.export)) export_iphoto(album, data, options.exclude, options) if options.picasaweb: try: import phoshare.picasaweb as picasaweb albums = picasaweb.PicasaAlbums(options.picasaweb, google_password) export_iphoto(albums, data, options.exclude, options) except ImportError: su.perr('Sorry, this version of Phoshare does not support uploading to PicasaWeb.')
def main(): """main routine for phoshare.""" parser = get_option_parser() (options, args) = parser.parse_args() if len(args) != 0: parser.error("Found some unrecognized arguments on the command line.") if options.version: print '%s %s' % (phoshare.phoshare_version.PHOSHARE_VERSION, phoshare.phoshare_version.PHOSHARE_BUILD) return 1 if options.iptc > 0 and not exiftool.check_exif_tool(): print >> sys.stderr, ("Exiftool is needed for the --itpc or --iptcall" + " options.") return 1 if options.size and options.link: parser.error("Cannot use --size and --link together.") if not options.iphoto: parser.error("Need to specify the iPhoto library with the --iphoto " "option.") if options.export or options.picasaweb or options.checkalbumsize: if not (options.albums or options.events or options.smarts or options.facealbums): parser.error("Need to specify at least one event, album, or smart " "album for exporting, using the -e, -a, or -s " "options.") else: parser.error("No action specified. Use --export to export from your " "iPhoto library.") if options.picasaweb: if options.picasapassword: google_password = options.picasapassword else: google_password = getpass.getpass('Google password for %s: ' % options.picasaweb) logging_handler = logging.StreamHandler() logging_handler.setLevel(logging.DEBUG if options.verbose else logging.INFO) _logger.addHandler(logging_handler) album_xml_file = iphotodata.get_album_xmlfile( su.expand_home_folder(options.iphoto)) data = iphotodata.get_iphoto_data(album_xml_file) if data.aperture: if options.originals: data.load_aperture_originals() check_aperture_mode(options, parser) options.aperture = data.aperture options.foldertemplate = unicode(options.foldertemplate) options.nametemplate = unicode(options.nametemplate) options.captiontemplate = unicode(options.captiontemplate) if options.checkalbumsize: data.checkalbumsizes(int(options.checkalbumsize)) if options.export: album = ExportLibrary(su.expand_home_folder(options.export)) export_iphoto(album, data, options.exclude, options) if options.picasaweb: albums = picasaweb.PicasaAlbums(options.picasaweb, google_password) export_iphoto(albums, data, options.exclude, options)
class ExportApp(Frame): """GUI version of the Phoshare tool.""" def __init__(self, master=None): """Initialize the app, setting up the UI.""" Frame.__init__(self, master, bd=10) top = self.winfo_toplevel() menu_bar = Menu(top) top["menu"] = menu_bar apple = Menu(menu_bar, name='apple') menu_bar.add_cascade(label='Phoshare', menu=apple) apple.add_command(label="About Phoshare", command=self.__aboutHandler) sub_menu = Menu(menu_bar, name='help') menu_bar.add_cascade(label="Help", menu=sub_menu) sub_menu.add_command(label="Phoshare Help", command=self.help_buttons) self.thread_queue = Queue.Queue(maxsize=100) self.active_library = None top.columnconfigure(0, weight=1) top.rowconfigure(0, weight=1) self.grid(sticky=N + S + E + W) self.valid_library = False self.exiftool = False self.iphoto_library = StringVar() self.iphoto_library_status = StringVar() self.browse_library_button = None self.export_folder = StringVar() self.library_status = None self.dryrun_button = None self.export_button = None self.text = None self.events = StringVar() self.albums = StringVar() self.smarts = StringVar() self.foldertemplate = StringVar() self.nametemplate = StringVar() self.captiontemplate = StringVar() self.update_var = IntVar() self.delete_var = IntVar() self.originals_var = IntVar() self.link_var = IntVar() self.folder_hints_var = IntVar() self.faces_box = None self.faces_var = IntVar() self.face_keywords_box = None self.face_keywords_var = IntVar() self.face_albums_var = IntVar() self.face_albums_text = StringVar() self.iptc_box = None self.iptc_all_box = None self.iptc_var = IntVar() self.iptc_all_var = IntVar() self.gps_box = None self.gps_var = IntVar() self.verbose_var = IntVar() self.info_icon = PhotoImage(file="info-b16.gif") self.create_widgets() # Set up logging so it gets redirected to the text area in the app. self.logging_handler = logging.StreamHandler(self) self.logging_handler.setLevel(logging.WARN) _logger.addHandler(self.logging_handler) def __aboutHandler(self): HelpDialog(self, """%s %s Copyright 2012 Google Inc. http://code.google.com/p/phoshare""" % (phoshare_version.PHOSHARE_VERSION, phoshare_version.PHOSHARE_BUILD), title="About Phoshare") def init(self): """Initializes processing by launching background thread checker and initial iPhoto library check.""" self.thread_checker() if exiftool.check_exif_tool(sys.stdout): self.exiftool = True self.faces_box.configure(state=NORMAL) self.face_keywords_box.configure(state=NORMAL) self.iptc_box.configure(state=NORMAL) self.iptc_all_box.configure(state=NORMAL) self.gps_box.configure(state=NORMAL) options = self.Options() options.load() self.init_from_options(options) self.check_iphoto_library() def init_from_options(self, options): """Populates the UI from options.""" self.iphoto_library.set(options.iphoto) self.export_folder.set(options.export) self.albums.set(su.fsdec(options.albums)) self.events.set(su.fsdec(options.events)) self.smarts.set(su.fsdec(options.smarts)) self.foldertemplate.set(su.unicode_string(options.foldertemplate)) self.nametemplate.set(su.unicode_string(options.nametemplate)) self.captiontemplate.set(su.unicode_string(options.captiontemplate)) self.update_var.set(_int_from_bool(options.update)) self.delete_var.set(_int_from_bool(options.delete)) self.originals_var.set(_int_from_bool(options.originals)) self.link_var.set(_int_from_bool(options.link)) self.folder_hints_var.set(_int_from_bool(options.folderhints)) self.faces_var.set(_int_from_bool(options.faces) and self.exiftool) self.face_keywords_var.set( _int_from_bool(options.face_keywords) and self.exiftool) self.face_albums_var.set(_int_from_bool(options.facealbums)) self.face_albums_text.set(options.facealbum_prefix) if options.iptc and self.exiftool: self.iptc_var.set(1) if options.iptc == 2: self.iptc_all_var.set(1) self.gps_var.set(_int_from_bool(options.gps) and self.exiftool) def _add_section(self, container, text, help_command): """Adds a new UI section with a bold label and an info button. Args: container: UI element that will contain this new item row: row number in grid. Uses two rows. text: label frame text. help_command: command to run when the info button is pressed. Returns: tuple of new section and content frames. """ section_frame = Frame(container) section_frame.columnconfigure(0, weight=1) label = Label(section_frame, text=text) label.config(font=_BOLD_FONT) label.grid(row=0, column=0, sticky=W, pady=5) Button(section_frame, image=self.info_icon, command=help_command).grid(row=0, column=1, sticky=E) content_frame = Frame(section_frame) content_frame.grid(row=1, column=0, columnspan=2, sticky=N + S + E + W, pady=5) return (section_frame, content_frame) def _create_button_bar(self, container, row): """Creates the button bar with the Dry Run and Export buttons. Args: row: row number in grid. Returns: next row number in grid. """ button_bar = Frame(container) button_bar.grid(row=row, column=0, sticky=E + W, padx=10) button_bar.columnconfigure(0, weight=1) verbose_box = Checkbutton(button_bar, text="Show debug output", var=self.verbose_var) verbose_box.grid(row=0, column=0, sticky=E) self.dryrun_button = Button(button_bar, text="Dry Run", command=self.do_dryrun, state=DISABLED) self.dryrun_button.grid(row=0, column=1, sticky=E, pady=5) self.export_button = Button(button_bar, text="Export", command=self.do_export, state=DISABLED) self.export_button.grid(row=0, column=2, pady=5) return row + 1 def _create_library_tab(self, library_tab): library_tab.columnconfigure(0, weight=1) row = 0 f = Frame(library_tab) f.grid(row=row, columnspan=2, stick=E + W, padx=5, pady=5) row += 1 f.columnconfigure(1, weight=1) Label(f, text="iPhoto Library:").grid(sticky=E) iphoto_library_entry = Entry(f, textvariable=self.iphoto_library) iphoto_library_entry.grid(row=0, column=1, sticky=E + W) self.browse_library_button = Button(f, text="Browse...", command=self.browse_library) self.browse_library_button.grid(row=0, column=2) self.library_status = Label(f, textvariable=self.iphoto_library_status) self.library_status.grid(row=1, column=1, sticky=W) (cf, lf) = self._add_section(library_tab, "Events, Albums and Smart Albums", self.help_events) cf.grid(row=row, columnspan=2, stick=E + W) row += 1 lf.columnconfigure(1, weight=1) Label(lf, text="Events:").grid(sticky=E) events_entry = Entry(lf, textvariable=self.events) events_entry.grid(row=0, column=1, sticky=EW) Label(lf, text="Albums:").grid(sticky=E) albums_entry = Entry(lf, textvariable=self.albums) albums_entry.grid(row=1, column=1, sticky=EW) Label(lf, text="Smart Albums:").grid(sticky=E) smarts_entry = Entry(lf, textvariable=self.smarts) smarts_entry.grid(row=2, column=1, columnspan=3, sticky=EW) # Fill up the tab so the size matches the other tabs (Notebook resizes when you switch tabs) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) def _create_files_tab(self, files_tab): files_tab.columnconfigure(0, weight=1) # Export folder and options row = 0 (cf, lf) = self._add_section(files_tab, "Export Folder and Options", self.help_export) cf.grid(row=row, columnspan=2, stick=E + W) row += 1 lf.columnconfigure(1, weight=1) label = Label(lf, text="Export Folder:") label.grid(sticky=E) export_folder_entry = Entry(lf, textvariable=self.export_folder) export_folder_entry.grid(row=0, column=1, columnspan=2, sticky=E + W) Button(lf, text="Browse...", command=self.browse_export).grid(row=0, column=3) update_box = Checkbutton(lf, text="Overwrite changed pictures", var=self.update_var) update_box.grid(row=1, column=1, sticky=W) originals_box = Checkbutton(lf, text="Export originals", var=self.originals_var) originals_box.grid(row=2, column=1, sticky=W) hint_box = Checkbutton(lf, text="Use folder hints", var=self.folder_hints_var) hint_box.grid(row=3, column=1, sticky=W) delete_box = Checkbutton(lf, text="Delete obsolete pictures", var=self.delete_var) delete_box.grid(row=4, column=1, sticky=W) link_box = Checkbutton(lf, text="Use file links", var=self.link_var) link_box.grid(row=5, column=1, sticky=W) # Templates ---------------------------------------- (cf, lf) = self._add_section(files_tab, "Name Templates", self.help_templates) cf.grid(row=row, columnspan=2, stick=E + W) row += 1 lf.columnconfigure(1, weight=1) Label(lf, text="Folder names:").grid(sticky=E) foldertemplate_entry = Entry(lf, textvariable=self.foldertemplate) foldertemplate_entry.grid(row=0, column=1, sticky=EW) Label(lf, text="File names:").grid(sticky=E) nametemplate_entry = Entry(lf, textvariable=self.nametemplate) nametemplate_entry.grid(row=1, column=1, sticky=EW) Label(lf, text="Captions:").grid(sticky=E) captiontemplate_entry = Entry(lf, textvariable=self.captiontemplate) captiontemplate_entry.grid(row=2, column=1, sticky=EW) def _create_metadata_tab(self, metadata_tab): metadata_tab.columnconfigure(0, weight=1) row = 0 # Metadata -------------------------------------------- (cf, lf) = self._add_section(metadata_tab, "Metadata", self.help_metadata) cf.grid(row=row, columnspan=2, stick=E + W) row += 1 lf.columnconfigure(0, weight=1) self.iptc_box = Checkbutton(lf, text=("Export metadata (descriptions, " "keywords, ratings, dates)"), var=self.iptc_var, state=DISABLED, command=self.change_iptc_box) self.iptc_box.grid(row=0, column=0, columnspan=2, sticky=W) self.iptc_all_box = Checkbutton( lf, text="Check previously exported images", var=self.iptc_all_var, command=self.change_metadata_box, state=DISABLED) self.iptc_all_box.grid(row=1, column=0, sticky=W) self.gps_box = Checkbutton(lf, text="Export GPS data", var=self.gps_var, command=self.change_metadata_box, state=DISABLED) self.gps_box.grid(row=2, column=0, sticky=W) # Faces --------------------------------------------------- (cf, lf) = self._add_section(metadata_tab, "Faces", self.help_faces) cf.grid(row=row, columnspan=2, stick=E + W) row += 1 lf.columnconfigure(2, weight=1) self.faces_box = Checkbutton(lf, text="Copy faces into metadata", var=self.faces_var, state=DISABLED, command=self.change_metadata_box) self.faces_box.grid(row=0, column=0, sticky=W) self.face_keywords_box = Checkbutton( lf, text="Copy face names into keywords", var=self.face_keywords_var, command=self.change_metadata_box, state=DISABLED) self.face_keywords_box.grid(row=1, column=0, sticky=W) checkbutton = Checkbutton(lf, text="Export faces into folders", var=self.face_albums_var) checkbutton.grid(row=2, column=0, sticky=W) label = Label(lf, text="Faces folder prefix:") label.grid(row=2, column=1, sticky=E) entry = Entry(lf, textvariable=self.face_albums_text) entry.grid(row=2, column=2, sticky=E + W) # Fill up the tab so the size matches the other tabs (Notebook resizes when you switch tabs) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) Label(lf, text="").grid(sticky=E) def create_widgets(self): """Builds the UI.""" self.columnconfigure(0, weight=1) n = Notebook(self) n.grid(row=0, sticky=E + W + N + S) library_tab = n.add_tab('Library') self._create_library_tab(library_tab) files_tab = n.add_tab('Files') self._create_files_tab(files_tab) metadata_tab = n.add_tab('Metadata') self._create_metadata_tab(metadata_tab) self._create_button_bar(self, 1) self.text = ScrolledText(self, borderwidth=2, relief=RIDGE, padx=4, pady=4) self.text.grid(row=2, column=0, sticky=E + W + N + S) self.rowconfigure(2, weight=1) def change_iptc_box(self): """Clears some options that depend on the metadata export option.""" mode = self.iptc_var.get() if not mode: self.faces_var.set(0) self.face_keywords_var.set(0) self.iptc_all_var.set(0) self.gps_var.set(0) def change_metadata_box(self): """Sets connected options if an option that needs meta data is changed. """ mode = (self.faces_var.get() or self.face_keywords_var.get() or self.iptc_all_var.get() or self.gps_var.get()) if mode: self.iptc_var.set(1) def help_events(self): HelpDialog( self, """Events, Albums and Smart Albums Selects which events, albums, or smart albums to export. Each field is a regular expression, and at least one must be filled in. Matches are done against the beginning of the event or album name. An entry in Events of Family will export all events that start with "Family", including "Family 2008" and "Family 2009". "|" separates alternate patterns, so Family|Travel will export all events that start with either "Family" or "Travel". "." matches any character, and therefore, . will export all events. To export all events with "2008" in the name, use .*2008 For more details on regular expressions, see http://en.wikipedia.org/wiki/Regular_expression""") def help_templates(self): HelpDialog( self, """Folder, file, and image caption templates. Templates are strings with place holders for values. The place holders have the format "{name}". Everything else in the template will be copied. Examples: {title} {yyyy}/{mm}/{dd} {title} - generates "2010/12/31 My Birthday" if the date of the pictures is Dec 31, 2010, and the title is "My Birthday". {yyyy} Event: {event} - generates "2010 Event: Birthday" for an event with any date in 2010 and the name "Birthday". Available place holders for folder names: {name} - name of the album or event. {ascii_name} - name of the album or event, using only ASCII characters. {plain_name} - name of the album or event, using only ASCII characters and on spaces. {hint} - folder hint (taken from line event or album description starting with @). {yyyy} - year of album or event date. {mm} - month of album or event date. {dd} - date of album or event date. Available place holders for file names: {album} - name of album (or in the case of an event, the name of the event). {ascii_album} - name of the album, using only ASCII characters. {plain_album} - name of the album, using only ASCII characters and no spaces. {index} - number of image in album, starting at 1. {index0} - number of image in album, padded with 0s, so that all numbers have the same length. {event} - name of the event. In the case of an album, the name of the event to which the image belongs. {ascii_event} - name of the event, using only ASCII characters. {plain_event} - name of the event, using only ASCII characters and no spaces. {event_index} - number of image in the event, starting at 1. If the case of an album, this number will be based on the event to which the image belongs. {event_index0} - same as {event_index}, but padded with leading 0s so that all values have the same length. {title} - image title. {ascii_title} - image title, using only ASCII characters. {plain_title} - image title, using only ASCII characters and no spaces. {yyyy} - year of image. {mm} - month of image (01 - 12). {dd} - day of image (01 - 31). If you are using {album}/{index}/{index0} place holders, the image will be named based on whatever album or event it is contained. That means an image in two albums will be exported with different names, even so the files are identical. If you want to use the same name for each image, regardless of which album it is in, use {event}, {event_index}, and {event_index0} instead. Available place holders for captions: {title} - image title. {description} - image description. {title_description} - concatenated image title and description, separated by a : if both are set. {yyyy} - year of image. {mm} - month of image (01 - 12). {dd} - day of image (01 - 31). """) def help_buttons(self): HelpDialog( self, """Export modes. Click on "Dry Run" to see what Phoshare would do without actually modifying any files. Click on "Export" to export your files using the current settings. All your settings will be saved when you click either Dry Run and Export, and re-loaded if you restart Phoshare. Check "Show debug output" to generate additional output message that can assist in debugging Phoshare problems. """) def help_export(self): HelpDialog( self, """Export Settings Export Folder: path to the folder for exporting images. Overwrite changed pictures: If set, pictures that already exist in the export folder will be overriden if an different version exist in iPhoto. Any edits made to previously exported images in the export folder will be lost! Use Dry Run to see which files would be overwritten. Export originals: If set, and an image has been modified in iPhoto, both the original and the edited version will be exported. The original will be stored in a sub-folder called "Originals". Use folder hints: By default, each exported event or album will become a folder in the export folder. With folder hints, a sub-folder name can be given in the event or album description by adding a line starting with a @ character. Example: Family Vacation @Vacation would export all images in that event into a sub-folder called "Vacation". Delete obsolete pictures: If set, any image, movie file or folder in the export folder that does not exist in the iPhoto library will be deleted. Use Dry Run to see which files would be deleted. Use file links: Don't copy images during export, but make a link to the files in the iPhoto library instead. This option is only available if the export folder is on the same drive as the iPhoto library. This option will save a lot of disk space because it avoids making copies of all your images and videos. Using this option causes the metadata of the images IN YOUR IPHOTO LIBRARY to be modified. While phoshare should not cause any problems to your images, it is best to use this option only if you have a backup of your iPhoto library, and you know how to restore your library from the backup. For more details on link mode, see https://sites.google.com/site/phosharedoc/Home#TOC-link-mode""" ) def help_faces(self): HelpDialog( self, """Faces options. Copy faces into metadata: faces tags and face regions will be copied into the image metadata using the Microsoft Photo Region Schema: http://msdn.microsoft.com/en-us/library/ee719905(VS.85).aspx Copy faces names into keywords: If set, face names will be merged into image keywords. Requires "Export metadata" checked. Export faces into folders: If checked, folders will be created for each face tag, each containing all the images tagged with that person. Faces folder prefix: If set, the string will be used as a prefix for the face export folders if "Exported faces into folders" is checked. This can be just a value like "Face: ", or a sub-folder name like "Faces/" if it ends with a "/" Metadata options will be disabled if exiftool is not available. """) def help_metadata(self): HelpDialog( self, """Metadata options. Export metadata: sets the description, keywords, rating and date metadata in the exported images to match the iPhoto settings. Check previously exported images: If not checked, metadata will only be set for new or updated images. If checked, metadata will be checked in all images, including ones that were previously exported. This is much slower. Export GPS data: export the GPS coordinates into the image metadata. Metadata options will be disabled if exiftool is not available.""") def check_iphoto_library(self): self.valid_library = False self.enable_buttons() self.iphoto_library_status.set( "Please wait, checking library location...") self.launch_export("library") def set_library_status(self, good, message): if good: self.valid_library = True self.enable_buttons() self.iphoto_library_status.set(message) def write_progress(self, text): self.text.insert(END, text) self.text.see(END) def enable_buttons(self): if self.valid_library: self.dryrun_button.config(state=NORMAL) self.export_button.config(state=NORMAL) else: self.dryrun_button.config(state=DISABLED) self.export_button.config(state=DISABLED) self.browse_library_button.config(state=NORMAL) def browse_library(self): path = tkFileDialog.askopenfilename(title="Locate iPhoto Library") if path: self.iphoto_library.set(path) self.check_iphoto_library() def browse_export(self): path = tkFileDialog.askdirectory(title="Locate Export Folder") self.export_folder.set(path) def do_export(self): if self.active_library: self.stop_thread() return if not self.can_export(): return self.export_button.config(text="Stop Export") self.dryrun_button.config(state=DISABLED) self.run_export(False) def do_dryrun(self): if self.active_library: self.stop_thread() return if not self.can_export(): return self.dryrun_button.config(text="Stop Dry Run") self.export_button.config(state=DISABLED) self.run_export(True) def stop_thread(self): if self.active_library: self.active_library.abort() def export_done(self): self.active_library = None self.dryrun_button.config(text="Dry Run") self.export_button.config(text="Export") self.enable_buttons() class Options(object): """Simple helper to create an object compatible with the OptionParser output in Phoshare.py.""" def __init__(self): self.iphoto = '~/Pictures/iPhoto Library' self.export = '~/Pictures/Album' self.albums = '' self.events = '.' self.smarts = '' self.ignore = [] self.delete = False self.update = False self.max_create = -1 self.max_delete = -1 self.max_update = -1 self.link = False self.dryrun = False self.folderhints = False self.folderpatterns = None self.captiontemplate = u'{description}' self.foldertemplate = u'{name}' self.nametemplate = u'{title}' self.aperture = False # TODO self.reverse = False # TODO self.size = '' # TODO self.picasa = False # TODO self.movies = True # TODO self.originals = False self.iptc = 0 self.iptc_masters = False # TODO self.gps = False self.faces = False self.facealbums = False self.facealbum_prefix = '' self.face_keywords = False self.ratings = '' # TODO self.verbose = False def load(self): """Attempts to load saved options. Returns True if saved options were available.""" if not os.path.exists(_CONFIG_PATH): return False config = ConfigParser.SafeConfigParser() config.read(_CONFIG_PATH) s = 'Export1' if config.has_option(s, 'iphoto'): self.iphoto = config.get(s, 'iphoto') if config.has_option(s, 'export'): self.export = config.get(s, 'export') if config.has_option(s, 'albums'): self.albums = config.get(s, 'albums') if config.has_option(s, 'events'): self.events = config.get(s, 'events') if config.has_option(s, 'smarts'): self.smarts = config.get(s, 'smarts') if config.has_option(s, 'foldertemplate'): self.foldertemplate = config.get(s, 'foldertemplate') if config.has_option(s, 'nametemplate'): self.nametemplate = config.get(s, 'nametemplate') if config.has_option(s, 'captiontemplate'): self.captiontemplate = config.get(s, 'captiontemplate') if config.has_option(s, 'delete'): self.delete = config.getboolean(s, 'delete') if config.has_option(s, 'update'): self.update = config.getboolean(s, 'update') if config.has_option(s, 'link'): self.link = config.getboolean(s, 'link') if config.has_option(s, 'folderhints'): self.folderhints = config.getboolean(s, 'folderhints') if config.has_option(s, 'captiontemplate'): self.nametemplate = unicode(config.get(s, 'captiontemplate')) if config.has_option(s, 'nametemplate'): self.nametemplate = unicode(config.get(s, 'nametemplate')) if config.has_option(s, 'reverse'): self.reverse = config.getboolean(s, 'reverse') if config.has_option(s, 'size'): self.size = config.get(s, 'size') if config.has_option(s, 'picasa'): self.picasa = config.getboolean(s, 'picasa') if config.has_option(s, 'movies'): self.movies = config.getboolean(s, 'movies') if config.has_option(s, 'originals'): self.originals = config.getboolean(s, 'originals') if config.has_option(s, 'iptc'): self.iptc = config.getint(s, 'iptc') if config.has_option(s, 'gps'): self.gps = config.getboolean(s, 'gps') if config.has_option(s, 'faces'): self.faces = config.getboolean(s, 'faces') if config.has_option(s, 'facealbums'): self.facealbums = config.getboolean(s, 'facealbums') if config.has_option(s, 'facealbum_prefix'): self.facealbum_prefix = config.get(s, 'facealbum_prefix') if config.has_option(s, 'face_keywords'): self.face_keywords = config.getboolean(s, 'face_keywords') return True def save(self): """Saves the current options into a file.""" config = ConfigParser.RawConfigParser() s = 'Export1' config.add_section(s) config.set(s, 'iphoto', self.iphoto) config.set(s, 'export', self.export) config.set(s, 'albums', su.fsenc(self.albums)) config.set(s, 'events', su.fsenc(self.events)) config.set(s, 'smarts', su.fsenc(self.smarts)) config.set(s, 'foldertemplate', su.fsenc(self.foldertemplate)) config.set(s, 'nametemplate', su.fsenc(self.nametemplate)) config.set(s, 'captiontemplate', su.fsenc(self.captiontemplate)) config.set(s, 'max_create', self.max_create) config.set(s, 'delete', self.delete) config.set(s, 'max_delete', self.max_delete) config.set(s, 'update', self.update) config.set(s, 'max_udpate', self.max_update) config.set(s, 'link', self.link) config.set(s, 'dryrun', self.dryrun) config.set(s, 'folderhints', self.folderhints) config.set(s, 'captiontemplate', self.captiontemplate) config.set(s, 'nametemplate', self.nametemplate) config.set(s, 'reverse', self.reverse) config.set(s, 'size', self.size) config.set(s, 'picasa', self.picasa) config.set(s, 'movies', self.movies) config.set(s, 'originals', self.originals) config.set(s, 'iptc', self.iptc) config.set(s, 'gps', self.gps) config.set(s, 'faces', self.faces) config.set(s, 'facealbums', self.facealbums) config.set(s, 'facealbum_prefix', self.facealbum_prefix) config.set(s, 'face_keywords', self.face_keywords) config_folder = os.path.split(_CONFIG_PATH)[0] if not os.path.exists(config_folder): os.makedirs(config_folder) configfile = open(_CONFIG_PATH, 'wb') config.write(configfile) configfile.close() def can_export(self): if (not self.albums.get() and not self.events.get() and not self.smarts.get()): tkMessageBox.showerror( "Export Error", ("Need to specify at least one event, album, or smart album " "for exporting.")) return False return True def run_export(self, dry_run): mode = "export" if dry_run: mode = "dry_run" self.launch_export(mode) def launch_export(self, mode): """Launch an export operation in a new thread, to not block the UI. Args: mode - name of operation to run, "library", "dry_run", or "export". """ self.text.delete('1.0', END) self.browse_library_button.config(state=DISABLED) export_thread = threading.Thread(target=self.export_thread, args=(mode, )) export_thread.start() def export_thread(self, mode): """Run an export operation in a thread, to not block the UI. Args: mode - name of operation to run, "library", "dry_run", or "export". """ try: # First, load the iPhoto library. library_path = su.expand_home_folder(self.iphoto_library.get()) album_xml_file = None try: album_xml_file = iphotodata.get_album_xmlfile(library_path) except ValueError, e: self.thread_queue.put(("done", (False, mode, str(e)))) return album_sql_file = None try: album_sql_file = iphotodata.get_album_sqlfile(library_path) except ValueError, e: self.thread_queue.put(("done", (False, mode, str(e)))) return data = iphotodata.get_iphoto_data(album_xml_file, album_sql_file) msg = "Version %s library with %d images" % ( data.applicationVersion, len(data.images)) self.write(msg + '\n') if mode == "library": # If we just need to check the library, we are done here. self.thread_queue.put(("done", (True, mode, msg))) return # Do the actual export. export_folder = su.expand_home_folder(self.export_folder.get()) args = ['Phoshare.py', '--export', '"' + export_folder + '"'] options = self.Options() options.iphoto = self.iphoto_library.get() args.extend(['--iphoto', '"' + options.iphoto + '"']) options.export = self.export_folder.get() options.dryrun = mode == "dry_run" options.albums = self.albums.get() if options.albums: args.extend(['--albums', '"' + options.albums + '"']) options.events = self.events.get() if options.events: args.extend(['--events', '"' + options.events + '"']) options.smarts = self.smarts.get() if options.smarts: args.extend(['--smarts', '"' + options.smarts + '"']) options.foldertemplate = unicode(self.foldertemplate.get()) if options.foldertemplate: args.extend( ['--foldertemplate', '"' + options.foldertemplate + '"']) options.nametemplate = unicode(self.nametemplate.get()) if options.nametemplate: args.extend( ['--nametemplate', '"' + options.nametemplate + '"']) options.captiontemplate = unicode(self.captiontemplate.get()) if options.captiontemplate: args.extend( ['--captiontemplate', '"' + options.captiontemplate + '"']) options.ignore = [] # TODO options.update = self.update_var.get() == 1 if options.update: args.append('--update') options.delete = self.delete_var.get() == 1 if options.delete: args.append('--delete') options.originals = self.originals_var.get() == 1 if options.originals: args.append('--originals') options.link = self.link_var.get() == 1 if options.link: args.append('--link') options.folderhints = self.folder_hints_var.get() == 1 if options.folderhints: args.append('--folderhints') options.faces = self.faces_var.get() == 1 if options.faces: args.append('--faces') options.face_keywords = self.face_keywords_var.get() == 1 if options.face_keywords: args.append('--face_keywords') if self.iptc_all_var.get() == 1: options.iptc = 2 args.append('--iptcall') elif self.iptc_var.get() == 1: options.iptc = 1 args.append('--iptc') else: options.iptc = 0 options.gps = self.gps_var.get() if options.gps: args.append('--gps') options.facealbums = self.face_albums_var.get() == 1 if options.facealbums: args.append('--facealbums') options.facealbum_prefix = self.face_albums_text.get() if options.facealbum_prefix: args.append('--facealbum_prefix') options.verbose = self.verbose_var.get() if options.verbose: args.append('--verbose') exclude = None # TODO options.save() print " ".join(args) self.logging_handler.setLevel( logging.DEBUG if options.verbose else logging.INFO) if options.originals and options.export: data.load_aperture_originals() self.active_library = phoshare_main.ExportLibrary(export_folder) phoshare_main.export_iphoto(self.active_library, data, exclude, options) self.thread_queue.put(("done", (True, mode, '')))
def run_phoshare(cmd_args): """main routine for phoshare.""" parser = get_option_parser() (options, args) = parser.parse_args(cmd_args) if len(args) != 0: parser.error("Found some unrecognized arguments on the command line.") if options.version: print '%s %s' % (phoshare.phoshare_version.PHOSHARE_VERSION, phoshare.phoshare_version.PHOSHARE_BUILD) return 1 if options.iptc > 0 and not exiftool.check_exif_tool(): print >> sys.stderr, ("Exiftool is needed for the --itpc or --iptcall" + " options.") return 1 if options.size and options.link: parser.error("Cannot use --size and --link together.") if not options.iphoto: parser.error("Need to specify the iPhoto library with the --iphoto " "option.") if options.export or options.picasaweb or options.checkalbumsize: if not (options.albums or options.events or options.smarts or options.facealbums): parser.error("Need to specify at least one event, album, or smart " "album for exporting, using the -e, -a, or -s " "options.") else: parser.error("No action specified. Use --export to export from your " "iPhoto library.") if options.picasaweb: if options.picasapassword: google_password = options.picasapassword else: google_password = getpass.getpass('Google password for %s: ' % options.picasaweb) if options.ratings: options.ratings = [int(r) for r in options.ratings.split(",")] if options.reverse: if not options.dryrun: su.pout(u"Turning on dryrun mode because of --reverse option.") options.dryrun = True logging_handler = logging.StreamHandler() logging_handler.setLevel(logging.DEBUG if options.verbose else logging.INFO) _logger.addHandler(logging_handler) album_xml_file = iphotodata.get_album_xmlfile( su.expand_home_folder(options.iphoto)) album_sql_file = iphotodata.get_album_sqlfile( su.expand_home_folder(options.iphoto)) data = iphotodata.get_iphoto_data(album_xml_file, album_sql_file, ratings=options.ratings, verbose=options.verbose, aperture=options.aperture) if options.originals and options.export: data.load_aperture_originals() options.aperture = data.aperture and not data.aperture_data options.foldertemplate = unicode(options.foldertemplate) options.nametemplate = unicode(options.nametemplate) options.captiontemplate = unicode(options.captiontemplate) if options.checkalbumsize: data.checkalbumsizes(int(options.checkalbumsize)) if options.export: album = ExportLibrary(su.expand_home_folder(options.export)) export_iphoto(album, data, options.exclude, options) if options.picasaweb: try: import phoshare.picasaweb as picasaweb albums = picasaweb.PicasaAlbums(options.picasaweb, google_password) export_iphoto(albums, data, options.exclude, options) except ImportError: su.perr('Sorry, this version of Phoshare does not support uploading to PicasaWeb.')