def set_file_modified_time_from_metadata(path: str): """ Traverse a path, seeking photos & videos, and when located, set the file's modification time on the file system to match the metadata value in the file (e.g. exif, or video metadata (if valid)). Preserves access time. :param path: the folder which to walk """ with exiftool.ExifTool() as exiftool_process: for dir_name, subdirs, file_list in walk(path): for file_name in file_list: file_type = fileformats.file_type_from_splitext( file_name=file_name) if file_type is not None: file = os.path.join(dir_name, file_name) modification_time = os.path.getmtime(file) try: if file_type == FileType.photo: metadata = metadataphoto.MetaData( full_file_name=file, et_process=exiftool_process) else: metadata = metadatavideo.MetaData( full_file_name=file, et_process=exiftool_process) except: print("Could not load metadata for %s" % file) break dt = metadata.date_time(missing=None) if dt is not None: ts = time.mktime(dt.timetuple()) if ts != modification_time: statinfo = os.stat(file) access_time = statinfo.st_atime print("Setting modification time for %s to %s" % (file_name, dt.strftime('%c'))) try: os.utime(file, times=(access_time, ts)) print("Set modification time for %s to %s" % (file_name, dt.strftime('%c'))) except: print( "Setting file modificaiton time failed for %s" % file_name)
def do_work(self): if False: # exiv2 pumps out a LOT to stderr - use cautiously! context = show_errors() self.error_stream = sys.stderr else: # Redirect stderr, hiding error output from exiv2 context = stdchannel_redirected(sys.stderr, os.devnull) self.error_stream = sys.stdout with context: # In some situations, using a context manager for exiftool can # result in exiftool processes not being terminated. So let's # handle starting and terminating it manually. self.exiftool_process = exiftool.ExifTool() self.exiftool_process.start() self.process_files() self.exit()
return '1920' def height(self, stream=0, missing=''): return '1080' def frames_per_second(self, stream=0, missing=''): return '24' def fourcc(self, stream=0, missing=''): return 'AVC1' if __name__ == '__main__': import sys with exiftool.ExifTool() as et_process: if (len(sys.argv) != 2): print('Usage: ' + sys.argv[0] + ' path/to/video/containing/metadata') else: file = sys.argv[1] print("ExifTool", EXIFTOOL_VERSION) m = MetaData(file, et_process) dt = m.date_time() print(dt) print("%sx%s" % (m.width(), m.height())) print("Length:", m.length()) print("FPS: ", m.frames_per_second()) print("Codec:", m.codec())
def orientation(self, missing=''): return 1 def file_number(self, missing=''): return '428' if __name__ == '__main__': import sys if (len(sys.argv) != 2): print('Usage: ' + sys.argv[0] + ' path/to/photo/containing/metadata') m = DummyMetaData() et_process = None else: et_process = exiftool.ExifTool() et_process.start() m = MetaData(full_file_name=sys.argv[1], et_process=et_process) print("f" + m.aperture('missing ')) print("ISO " + m.iso('missing ')) print(m.exposure_time(missing='missing ') + " sec") print(m.exposure_time(alternativeFormat=True, missing='missing ')) print(m.focal_length('missing ') + "mm") print(m.camera_make()) print(m.camera_model()) print(m.short_camera_model()) print(m.short_camera_model(includeCharacters="\-")) print(m.date_time()) print(m.orientation()) print('Serial number:', m.camera_serial(missing='missing'))
def run(self) -> None: """ Generate subfolder and filename, and attempt to move the file from its temporary directory. Move video THM and/or audio file if there is one. If successful, increment sequence values. Report any success or failure. """ i = 0 # Dict of filename keys and int values used to track ints to add as # suffixes to duplicate files self.duplicate_files = {} self.initialise_downloads_today_stored_number() self.sequences = gn.Sequences(self.downloads_today_tracker, self.prefs.stored_sequence_no) with stdchannel_redirected(sys.stderr, os.devnull): with exiftool.ExifTool() as self.exiftool_process: while True: if i: logging.debug("Finished %s. Getting next task.", i) # rename file and move to generated subfolder directive, content = self.receiver.recv_multipart() self.check_for_command(directive, content) data = pickle.loads(content) # type: RenameAndMoveFileData if data.message == RenameAndMoveStatus.download_started: # reinitialize downloads today and stored sequence number # in case the user has updated them via the user interface self.initialise_downloads_today_stored_number() self.sequences.downloads_today_tracker = self.downloads_today_tracker self.sequences.stored_sequence_no = self.prefs.stored_sequence_no dl_today = self.downloads_today_tracker.get_or_reset_downloads_today( ) logging.debug("Completed downloads today: %s", dl_today) self.initialise_sequence_number_usage() self.must_synchronize_raw_jpg = self.prefs.must_synchronize_raw_jpg( ) self.problems = RenamingProblems() elif data.message == RenameAndMoveStatus.download_completed: if len(self.problems): self.content = pickle.dumps( RenameAndMoveFileResults( problems=self.problems), pickle.HIGHEST_PROTOCOL) self.send_message_to_sink() # Ask main application process to update prefs with stored # sequence number and downloads today values. Cannot do it # here because to save QSettings, QApplication should be # used. self.content = pickle.dumps( RenameAndMoveFileResults( stored_sequence_no=self.sequences. stored_sequence_no, downloads_today=self.downloads_today_tracker. downloads_today), pickle.HIGHEST_PROTOCOL) dl_today = self.downloads_today_tracker.get_or_reset_downloads_today( ) logging.debug("Downloads today: %s", dl_today) self.send_message_to_sink() else: rpd_file = data.rpd_file download_count = data.download_count if data.download_succeeded: move_succeeded = self.process_file( rpd_file, download_count) if not move_succeeded: self.process_rename_failure(rpd_file) else: # Record file as downloaded in SQLite database try: self.downloaded.add_downloaded_file( name=rpd_file.name, size=rpd_file.size, modification_time=rpd_file. modification_time, download_full_file_name=rpd_file. download_full_file_name) except sqlite3.OperationalError as e: # This should never happen because this is the only process # writing to the database..... but just in case logging.error( "Database error adding download file %s: %s. Will not " "retry.", rpd_file.download_full_file_name, e) else: move_succeeded = False rpd_file.metadata = None self.content = pickle.dumps( RenameAndMoveFileResults( move_succeeded=move_succeeded, rpd_file=rpd_file, download_count=download_count), pickle.HIGHEST_PROTOCOL) self.send_message_to_sink() i += 1