Ejemplo n.º 1
0
	def __init__(self, cl_args, path, album_path = None):
		path = googlecl.safe_decode(path)
		self.path = path
		self.timestamp = None
		if album_path:
			path = os.path.join(album_path, path)
		if 'stat' not in cl_args.origin:
			cl_args.origin.append('stat')
		for origin in cl_args.origin:
			if origin == 'stat':
				try:
					self.timestamp = int(os.stat(path).st_mtime)
					break
				except Exception:
					pass
			elif origin == 'exif':
				metadata = pyexiv2.ImageMetadata(path)
				try:
					metadata.read()
					if 'Exif.Image.DateTime' in metadata:
						self.timestamp = calendar.timegm(metadata['Exif.Image.DateTime'].value.timetuple())
						break
				except Exception:
					pass
			else:
				for m in re.finditer(r'\d', self.path):
					try:
						self.timestamp = calendar.timegm(dateutil.parser.parse(m.string[m.start():], fuzzy = True, dayfirst = True).timetuple())
						break
					except ValueError:
						pass
				if self.timestamp:
					break
Ejemplo n.º 2
0
	def fillFromDisk(self):
		if self.filled_from_disk:
			return

		for path in self.cl_args.paths:
			for root, dirs, files in os.walk(path):
				supported_files = sorted([f for f in files if mimetypes.guess_type(f)[0] in self.supported_types])
				if len(supported_files) == 0:
					continue
				if root == path:
					album_title = os.path.basename(os.path.normpath(root))
				elif len(self.cl_args.paths) > 1 or googlecl.safe_decode(os.path.basename(os.path.normpath(path))) in self:
					album_title = os.path.join(os.path.basename(os.path.normpath(path)), os.path.relpath(root, path))
				else:
					album_title = os.path.relpath(root, path)
				num_albums = (len(supported_files) + self.cl_args.max_photos - 1) / self.cl_args.max_photos
				full_album_title = album_title
				for i in xrange(0, num_albums):
					if num_albums > 1:
						self.LOG.debug(u'Splicing album "{} ({})" with photos from "{}" to "{}"'.format(album_title, i + 1, supported_files[i * self.cl_args.max_photos], supported_files[min(i * self.cl_args.max_photos + self.cl_args.max_photos - 1, len(supported_files) - 1)]))
						full_album_title = album_title + ' (%s)' % (i + 1)
					album = Album(self.cl_args, full_album_title, disk = AlbumDiskEntry(self.cl_args, root))
					album.fillFromDisk(supported_files[i * self.cl_args.max_photos:i * self.cl_args.max_photos + self.cl_args.max_photos])
					if album.title in self:
						self[album.title].combine(album)
					else:
						self[album.title] = album
		self.filled_from_disk = True
Ejemplo n.º 3
0
 def __init__(self, cal_entry=None, user=None, name=None):
     """Parse a CalendarEntry into "user" and human-readable names,
        or take them directly."""
     if cal_entry:
         # Non-primary calendar feeds look like this:
         # http:blah/.../feeds/JUNK%40group.calendar.google.com/private/full
         # So grab the part after /feeds/ and unquote it.
         self.user = urllib.unquote(cal_entry.content.src.split("/")[-3])
         self.name = safe_decode(cal_entry.title.text)
     else:
         self.user = user
         self.name = name
Ejemplo n.º 4
0
 def __init__(self, cal_entry=None, user=None, name=None):
     """Parse a CalendarEntry into "user" and human-readable names,
        or take them directly."""
     if cal_entry:
         # Non-primary calendar feeds look like this:
         # http:blah/.../feeds/JUNK%40group.calendar.google.com/private/full
         # So grab the part after /feeds/ and unquote it.
         self.user = urllib.unquote(cal_entry.content.src.split('/')[-3])
         self.name = safe_decode(cal_entry.title.text)
     else:
         self.user = user
         self.name = name
Ejemplo n.º 5
0
	def __init__(self, album, title = None, disk = None, picasa = None, raw = False):
		self.album = album
		self.disk = disk
		self.picasa = picasa
		self.raw = raw
		if not title:
			if disk:
				self.title = os.path.splitext(disk.path)[0]
			elif picasa:
				self.title = picasa.title.text
			else:
				raise InvalidArguments("No title for photo given and no valid entry found")
		else:
			self.title = title
		self.title = googlecl.safe_decode(self.title)
Ejemplo n.º 6
0
	def __init__(self, cl_args, title = None, disk = None, picasa = None):
		self.client = None
		self.cl_args = cl_args
		self.disk = disk
		self.picasa = picasa
		if not title:
			if disk:
				self.title = disk.path
			elif picasa:
				self.title = picasa.title.text
			else:
				raise InvalidArguments("No title for photo given and no valid entry found")
		else:
			self.title = title
		self.title = googlecl.safe_decode(self.title)
		self.filled_from_disk = False
		self.filled_from_picasa = False
Ejemplo n.º 7
0
 def add_item_to_dict(self, title, album):
     if not album.picasa:
         # Skip if there is no online entry.
         return
     album_dict = dict()
     album_dict['title'] = title
     album_dict['published'] = album.picasa.published.text
     #album_dict['thumbnail'] = album.picasa.media.thumbnail.url
     album_dict['photos'] = []
     for photo_name in sorted(album.keys(), key=lambda x: album[x].order_id):
         photo = album[photo_name]
         photo_dict = {'name': photo_name}
         if photo.picasa and photo.picasa.summary.text:
             photo_dict['summary'] = googlecl.safe_decode(photo.picasa.summary.text)
         album_dict['photos'].append(photo_dict)
     self.dict_for_dump[title] = album_dict
     self.LOG.error("%s: %s", title, album)
Ejemplo n.º 8
0
	def __init__(self, cl_args, path):
		self.path = googlecl.safe_decode(path)
		self.timestamp = None
		if 'stat' not in cl_args.origin:
			cl_args.origin.append('stat')
		for origin in cl_args.origin:
			if origin == 'stat':
				try:
					self.timestamp = int(os.stat(path).st_mtime)
					break
				except Exception:
					pass
			elif origin == 'filename':
				for m in re.finditer(r'\d', self.path):
					try:
						self.timestamp = calendar.timegm(dateutil.parser.parse(m.string[m.start():], fuzzy = True, dayfirst = True).timetuple())
						break
					except ValueError:
						pass
				if self.timestamp:
					break
Ejemplo n.º 9
0
    def delete_recurring_events(self, events, start_date, end_date, cal_user, prompt):
        """Delete recurring events from a calendar.

        Keyword arguments:
          events: List of non-expanded calendar events to delete.
          start_date: Date specifying the start of events (inclusive).
          end_date: Date specifying the end of events (inclusive). None for no end
              date.
          cal_user: "******" of the calendar to delete events from.
          prompt: True if we should prompt before deleting events, False otherwise.

        """
        # option_list is a list of tuples, (prompt_string, deletion_instruction)
        # prompt_string gets displayed to the user,
        # deletion_instruction is a special value that will let the program know
        #   what to do.
        #     'ALL' -- delete all events in the series.
        #     'NONE' -- don't delete anything.
        #     'TWIXT' -- delete events between start_date and end_date.
        #     'ON' -- delete events on the single date given.
        #     'ONAFTER' -- delete events on and after the date given.
        deletion_choice = "ALL"
        option_list = [("All events in this series", deletion_choice)]
        if start_date and end_date:
            deletion_choice = "TWIXT"
            option_list.append(("Instances between %s and %s" % (start_date, end_date), deletion_choice))
        elif start_date or end_date:
            delete_date = start_date or end_date
            option_list.append(("Instances on %s" % delete_date, "ON"))
            option_list.append(("All events on and after %s" % delete_date, "ONAFTER"))
            deletion_choice = "ON"
        option_list.append(("Do not delete", "NONE"))
        prompt_str = ""
        for i, option in enumerate(option_list):
            prompt_str += str(i) + ") " + option[0] + "\n"
        # Condense events so that the user isn't prompted for the same event
        # multiple times. This is assuming that recurring events have been
        # expanded.
        events = googlecl.calendar.condense_recurring_events(events)
        for event in events:
            if prompt:
                delete_selection = -1
                while delete_selection < 0 or delete_selection > len(option_list) - 1:
                    msg = 'Delete "%s"?\n%s' % (safe_decode(event.title.text), prompt_str)
                    try:
                        delete_selection = int(raw_input(safe_encode(msg)))
                    except ValueError:
                        continue
                deletion_choice = option_list[delete_selection][1]

            # deletion_choice has either been picked by the prompt, or is the default
            # value. The default value is determined by the date info passed in,
            # and should be the "least destructive" option.
            if deletion_choice == "ALL":
                self._delete_original_event(event, cal_user)
            elif deletion_choice == "TWIXT":
                self._batch_delete_recur(event, cal_user, start_date=start_date, end_date=end_date)
            elif deletion_choice == "ON":
                self._batch_delete_recur(event, cal_user, start_date=delete_date, end_date=delete_date)
            elif deletion_choice == "ONAFTER":
                self._batch_delete_recur(event, cal_user, start_date=delete_date)
            elif deletion_choice != "NONE":
                raise CalendarError("Got unexpected batch deletion command!")
Ejemplo n.º 10
0
    def delete_recurring_events(self, events, start_date, end_date, cal_user,
                                prompt):
        """Delete recurring events from a calendar.

        Keyword arguments:
          events: List of non-expanded calendar events to delete.
          start_date: Date specifying the start of events (inclusive).
          end_date: Date specifying the end of events (inclusive). None for no end
              date.
          cal_user: "******" of the calendar to delete events from.
          prompt: True if we should prompt before deleting events, False otherwise.

        """
        # option_list is a list of tuples, (prompt_string, deletion_instruction)
        # prompt_string gets displayed to the user,
        # deletion_instruction is a special value that will let the program know
        #   what to do.
        #     'ALL' -- delete all events in the series.
        #     'NONE' -- don't delete anything.
        #     'TWIXT' -- delete events between start_date and end_date.
        #     'ON' -- delete events on the single date given.
        #     'ONAFTER' -- delete events on and after the date given.
        deletion_choice = 'ALL'
        option_list = [('All events in this series', deletion_choice)]
        if start_date and end_date:
            deletion_choice = 'TWIXT'
            option_list.append(('Instances between %s and %s' %
                                (start_date, end_date), deletion_choice))
        elif start_date or end_date:
            delete_date = (start_date or end_date)
            option_list.append(('Instances on %s' % delete_date, 'ON'))
            option_list.append(('All events on and after %s' % delete_date,
                                'ONAFTER'))
            deletion_choice = 'ON'
        option_list.append(('Do not delete', 'NONE'))
        prompt_str = ''
        for i, option in enumerate(option_list):
            prompt_str += str(i) + ') ' + option[0] + '\n'
        # Condense events so that the user isn't prompted for the same event
        # multiple times. This is assuming that recurring events have been
        # expanded.
        events = googlecl.calendar.condense_recurring_events(events)
        for event in events:
            if prompt:
                delete_selection = -1
                while delete_selection < 0 or delete_selection > len(option_list) - 1:
                    msg = 'Delete "%s"?\n%s' %\
                          (safe_decode(event.title.text), prompt_str)
                    try:
                        delete_selection = int(raw_input(safe_encode(msg)))
                    except ValueError:
                        continue
                deletion_choice = option_list[delete_selection][1]

            # deletion_choice has either been picked by the prompt, or is the default
            # value. The default value is determined by the date info passed in,
            # and should be the "least destructive" option.
            if deletion_choice == 'ALL':
                self._delete_original_event(event, cal_user)
            elif deletion_choice == 'TWIXT':
                self._batch_delete_recur(event, cal_user,
                                         start_date=start_date,
                                         end_date=end_date)
            elif deletion_choice == 'ON':
                self._batch_delete_recur(event, cal_user,
                                         start_date=delete_date,
                                         end_date=delete_date)
            elif deletion_choice == 'ONAFTER':
                self._batch_delete_recur(event, cal_user,
                                         start_date=delete_date)
            elif deletion_choice != 'NONE':
                raise CalendarError('Got unexpected batch deletion command!')
Ejemplo n.º 11
0
	def upload(self):
		if self.isInPicasa():
			if self.album.cl_args.force_update and self.album.cl_args.force_update == 'metadata':
				self.picasa.timestamp = gdata.photos.Timestamp(text = str(long(self.disk.timestamp) * 1000))
				try:
					self.picasa = self.album.client.UpdatePhotoMetadata(self.picasa)
				except GooglePhotosException as e:
					self.LOG.error(u'Error updating metadata for photo "{}": '.format(self.title) + str(e))
				finally:
					return
			else:
				metadata = self.picasa
				metadata.timestamp = gdata.photos.Timestamp(text = str(long(self.disk.timestamp) * 1000))
				metadata.title = atom.Title(text = self.title)
		else:
			metadata = gdata.photos.PhotoEntry()
			metadata.title = atom.Title(text = self.title)
			metadata.timestamp = gdata.photos.Timestamp(text = str(long(self.disk.timestamp) * 1000))

		mimetype = mimetypes.guess_type(self.path)[0]
		transforms = self.album.cl_args.transform[:] if self.album.cl_args.transform else None
		if transforms:
			original = pyexiv2.ImageMetadata(googlecl.safe_decode(self.path))
			try:
				original.read()
			except Exception as e:
				self.LOG.error(u'Error reading file "{}": '.format(self.disk.path) + str(e))
				return

			if 'raw' in transforms and not self.isRaw():
				transforms.remove('raw')
			if 'resize' in transforms and not (original.dimensions[0] > self.album.cl_args.max_size[0] or original.dimensions[1] > self.album.cl_args.max_size[1]):
				transforms.remove('resize')
			if 'rotate' in transforms and ('Exif.Image.Orientation' not in original or original['Exif.Image.Orientation'].value == 1):
				transforms.remove('rotate')

			if 'raw' in transforms:
				if len(original.previews) == 0:
					self.LOG.error(u'Error getting valid preview from raw file "{}"'.format(self.disk.path))
					return
				try:
					preview = next(x for x in original.previews if (x.dimensions[0] >= self.album.cl_args.max_size[0] or x.dimensions[1] >= self.album.cl_args.max_size[1]) and x.mime_type in AlbumList.standard_types)
				except StopIteration:
					preview = metadata.previews[-1]
				mimetype = preview.mime_type
				if mimetype not in AlbumList.standard_types:
					self.LOG.error(u'Error getting valid preview from raw file "{}"'.format(self.disk.path))
					return
				photo = cStringIO.StringIO(preview.data)
			else:
				photo = cStringIO.StringIO(original.buffer)
#			if 'resize' in transforms or 'rotate' in transforms and mimetype != 'image/jpeg':
			if 'resize' in transforms or 'rotate' in transforms:
				image = Image.open(photo)
				if 'resize' in transforms:
					image.thumbnail(self.album.cl_args.max_size, Image.ANTIALIAS)
				if 'rotate' in transforms:
					for t in self.transforms.get(original['Exif.Image.Orientation'].value, ()):
						image = image.transpose(t)
					original['Exif.Image.Orientation'] = 1
				photo = cStringIO.StringIO()
				# TODO: save in the same format and size approx
				image.save(photo, 'JPEG', quality = 95)
				mimetype = 'image/jpeg'
				photo.seek(0)
#			if 'rotate' in transforms and 'resize' not in transforms and mimetype == 'image/jpeg':
#				# TODO: lossless jpeg rotate
#				pass
			if not self.album.cl_args.strip_exif:
				modified = pyexiv2.ImageMetadata.from_buffer(photo.getvalue())
				modified.read()
				original.copy(modified)
				modified.write()
				photo = cStringIO.StringIO(modified.buffer)
		else:
			if self.album.cl_args.strip_exif:
				original = pyexiv2.ImageMetadata.from_buffer(file(self.path).read())
				original.read()
				for k in original.exif_keys + original.iptc_keys + original.xmp_keys:
					del original[k]
				del original.comment
				original.write()
				photo = cStringIO.StringIO(original.buffer)
			else:
				photo = self.path
		try:
			if self.isInPicasa():
				metadata = self.album.client.UpdatePhotoMetadata(metadata)
				self.picasa = self.album.client.UpdatePhotoBlob(metadata, photo, mimetype)
			else:
				self.picasa = self.album.client.InsertPhoto(self.album.picasa, metadata, photo, mimetype)
		except GooglePhotosException as e:
			self.LOG.error(u'Error uploading file "{}": '.format(self.disk.path) + str(e))