def order(): """ order by date using exif info """ log_function_call(order.__name__) inpath = os.getcwd() Tagdict = read_exiftags(file_types=settings.image_types, ignore_model=True) timeJumpDetector = TimeJumpDetector() time_old = giveDatetime() dircounter = 1 filenames = [] leng = len(list(Tagdict.values())[0]) dirNameDict_firsttime = OrderedDict() dirNameDict_lasttime = OrderedDict() time = giveDatetime(create_model(Tagdict, 0).get_date()) daystring = dateformating(time, "YYMMDD_") dirName = daystring + "%02d" % dircounter dirNameDict_firsttime[time] = dirName log().info('Number of JPG: %d', leng) for i in range(leng): model = create_model(Tagdict, i) time = giveDatetime(model.get_date()) if timeJumpDetector.isJump(time, len(filenames)): dirNameDict_lasttime[time_old] = dirName moveFiles(filenames, os.path.join(inpath, dirName)) filenames = [] if newdate(time, time_old): daystring = dateformating(time, "YYMMDD_") dircounter = 1 else: dircounter += 1 dirName = daystring + "%02d" % dircounter dirNameDict_firsttime[time] = dirName filenames.append((model.dir, model.filename)) time_old = time dirNameDict_lasttime[time_old] = dirName moveFiles(filenames, os.path.join(inpath, dirName)) print_firstlast_of_dirname(dirNameDict_firsttime, dirNameDict_lasttime) Tagdict_mp4 = read_exiftags(file_types=settings.video_types) if len(Tagdict_mp4) == 0: return leng = len(list(Tagdict_mp4.values())[0]) log().info('Number of mp4: %d', leng) for i in range(leng): model = create_model(Tagdict_mp4, i) time = giveDatetime(model.get_date()) dirName = find_dir_with_closest_time(dirNameDict_firsttime, dirNameDict_lasttime, time) if dirName: move(model.filename, model.dir, os.path.join(inpath, dirName, "mp4")) else: log().warning("Did not move %s to %s", model.filename, dirName)
def print_info(tagGroupNames=(), allGroups=False): """ write tag info of tagGroupNames to a file in saves dir :param tagGroupNames: selectable groups (look into constants) :param allGroups: take all tagGroupNames """ tagdict = read_exiftags() model = create_model(tagdict, 0) tagnames = model.TagNames if allGroups: tagGroupNames = tagnames.keys() for tagGroupName in tagnames: if not tagGroupNames == [] and not tagGroupName in tagGroupNames: continue outstring = "%-80s\t" % "File Name" for entry in tagnames[tagGroupName]: if not entry in tagdict: continue outstring += "%-30s\t" % entry outstring += "\n" for i in range(len(tagdict["File Name"])): outstring += "%-80s\t" % tagdict["File Name"][i] for entry in tagnames[tagGroupName]: if not entry in tagdict: continue val = tagdict[entry] outstring += "%-30s\t" % val[i] outstring += "\n" dirname = get_saves_dir() writeToFile(os.path.join(dirname, "tags_" + tagGroupName + ".txt"), outstring)
def shift_time(hours: int = 0, minutes: int = 0, seconds: int = 0, is_video: bool = False): """ shift DateTimeOriginal to correct for wrong camera time setting :example: to adjust time zone by one, set hours=-1 :param hours: hours to shift :param minutes: minutes to shift :param seconds: seconds to shift :param is_video: whether to modify videos, if false modifies pictures """ log_function_call(shift_time.__name__, hours, minutes, seconds, is_video) inpath = os.getcwd() delta_t = dt.timedelta(hours=hours, minutes=minutes, seconds=seconds) Tagdict = read_exiftags(inpath, settings.video_types if is_video else settings.image_types) if has_not_keys(Tagdict, keys=["Directory", "File Name", "Date/Time Original"]): return leng = len(list(Tagdict.values())[0]) time_tags = ["DateTimeOriginal", "CreateDate", "ModifyDate"] time_tags_mp4 = ["TrackCreateDate", "TrackModifyDate", "MediaCreateDate", "MediaModifyDate"] dir_change_printer = DirChangePrinter(Tagdict["Directory"][0]) for i in range(leng): model = create_model(Tagdict, i) time = giveDatetime(model.get_date()) newtime = time + delta_t timestring = dateformating(newtime, "YYYY:MM:DD HH:mm:ss") outTagDict = {} for time_tag in time_tags: outTagDict[time_tag] = timestring if is_video: for time_tag in time_tags_mp4: outTagDict[time_tag] = timestring write_exiftag(outTagDict, model.dir, model.filename) dir_change_printer.update(model.dir) dir_change_printer.finish()
def _detect_3D(): """ not yet fully implemented """ inpath = os.getcwd() Tagdict = read_exiftags(inpath) if has_not_keys(Tagdict, keys=[ "Directory", "File Name", "Date/Time Original", "Burst Mode", "Sequence Number" ]): return time_old = giveDatetime() filenames = [] dir3D = "3D" for i in range(len(list(Tagdict.values())[0])): newDir = os.path.join(Tagdict["Directory"][i], dir3D) os.makedirs(newDir, exist_ok=True) model = create_model(Tagdict, i) SequenceNumber = model.get_sequence_number() if model.is_series() or SequenceNumber > 1: continue time = giveDatetime(model.get_date()) timedelta = time - time_old timedelta_sec = timedelta.days * 3600 * 24 + timedelta.seconds time_old = time if timedelta_sec < 10 or (SequenceNumber == 1 and timedelta_sec < 15) or filenames == []: filenames.append(getPath(Tagdict, i)) elif len(filenames) > 1: for filename in filenames: if os.path.isfile( filename.replace(Tagdict["Directory"][i], newDir)): continue shutil.copy2(filename, newDir) filenames = []
def rename_from_exif(): """ use exif information written by :func:`write_exif_using_csv` to restore filename """ Tagdict = read_exiftags() if has_not_keys(Tagdict, keys=["Label"]): return temppostfix = renameTemp(Tagdict["Directory"], Tagdict["File Name"]) leng = len(list(Tagdict.values())[0]) for i in range(leng): filename = Tagdict["File Name"][i] ext = filename[filename.rfind('.'):] renameInPlace(Tagdict["Directory"][i], filename + temppostfix, Tagdict["Label"][i] + ext)
def rotate(subname: str = "", folder: str = r"", sign=1, override=True, ask=True): """ rotate back according to tag information (Rotate 90 CW or Rotate 270 CW) Some programs like franzis hdr projects rotate the resolution of the picture -> picture gets upward resolution and shown as rotated two times. This function reverses the resolution rotation according to exif info. Pictures that either have no rotation according to exif or have a normal resolution ratio are not modified. So calling it a second time wont change anything. :param subname: only files that contain this name are rotated, empty string: no restriction :param sign: direction of rotation :param folder: only files in directories that match this regex are rotated, empty string: no restriction :param override: override file with rotated one :param ask: if should ask for user confirmation """ log_function_call(rotate.__name__, subname, folder, sign, override, ask) from PIL import Image NFiles = 0 clock = Clock() inpath = os.getcwd() for (dirpath, dirnames, filenames) in os.walk(inpath): if is_invalid_path(dirpath, regex=folder): continue if len(filenames) == 0: continue Tagdict = read_exiftags(dirpath, settings.image_types, ask=ask) if has_not_keys(Tagdict, keys=["Orientation"]): continue leng = len(list(Tagdict.values())[0]) for i in range(leng): # Load the original image: model = create_model(Tagdict, i) if not subname in model.filename: continue if model.is_rotated_by(0) or not model.is_upward(): continue name = model.get_path() log().info("rotate %s", model.filename) img = Image.open(name) if model.is_rotated_by(90): img_rot = img.rotate(90 * sign, expand=True) elif model.is_rotated_by(-90): img_rot = img.rotate(-90 * sign, expand=True) else: continue NFiles += 1 if not override: name = name[:name.rfind(".")] + "_ROTATED" + name[name. rfind("."):] img_rot.save(name, 'JPEG', quality=99, exif=img.info['exif']) clock.finish()
def searchby_exiftag_equality(tag_name: str, value: str): """ searches for files where the value of the exiftag equals the input value :param tag_name: exiftag key :param value: exiftag value """ Tagdict = read_exiftags() if has_not_keys( Tagdict, keys=["Directory", "File Name", "Date/Time Original", tag_name]): return leng = len(list(Tagdict.values())[0]) files = [] for i in range(leng): if not Tagdict[tag_name][i] == value: continue files.append(getPath(Tagdict, i)) copyFilesTo(files, os.path.join(os.getcwd(), "matches"))
def searchby_exiftag_interval(tag_name: str, min_value: float, max_value: float): """ searches for files where the value of the exiftag is in the specified interval :param tag_name: exiftag key :param min_value: interval start :param max_value: interval end """ inpath = os.getcwd() Tagdict = read_exiftags(inpath) if has_not_keys(Tagdict, keys=[tag_name]): return leng = len(list(Tagdict.values())[0]) files = [] for i in range(leng): value = tofloat(Tagdict[tag_name][i]) if not (value and min_value < value < max_value): continue files.append(getPath(Tagdict, i)) copyFilesTo(files, os.path.join(inpath, "matches"))
def _detect_sunset(): """ not yet fully implemented """ inpath = os.getcwd() Tagdict = read_exiftags(inpath) if has_not_keys(Tagdict, keys=["Directory", "File Name", "Scene Mode"]): return for i in range(len(list(Tagdict.values())[0])): newDir = os.path.join(Tagdict["Directory"][i], "Sunset") os.makedirs(newDir, exist_ok=True) model = create_model(Tagdict, i) time = giveDatetime(model.get_date()) if 23 < time.hour or time.hour < 17: continue if not model.is_sun(): continue filename = getPath(Tagdict, i) if os.path.isfile(filename.replace(Tagdict["Directory"][i], newDir)): continue shutil.copy2(filename, newDir)
def location_to_keywords(): """ import location exif information to put into Keywords deprecated: try to use write_exif_using_csv() instead """ inpath = os.getcwd() log().info("process %d Files in %s, subdir: %r", count_files_in(inpath, settings.image_types + settings.video_types, ""), inpath, settings.includeSubdirs) askToContinue() Tagdict = read_exiftags(inpath, ask=False) leng = len(list(Tagdict.values())[0]) for i in range(leng): dirpath = Tagdict["Directory"][i] filename = Tagdict["File Name"][i] image_tags = Tagdict["Keywords"][i].split(', ') outTagDict = {'Keywords': image_tags, 'Subject': list(image_tags)} location = Location(Tagdict, i) add_dict(outTagDict, location.to_tag_dict()) write_exiftag(outTagDict, dirpath, filename)
def first_date_per_folder() -> OrderedDict: """ find files with missing exif data """ log_function_call(first_date_per_folder.__name__) clock = Clock() inpath = os.getcwd() folder_dict = OrderedDict() for (dirpath, dirnames, filenames) in os.walk(inpath): if is_invalid_path(dirpath): continue if fileop.count_files(filenames, settings.image_types) == 0: continue Tagdict = read_exiftags(dirpath, settings.image_types, ask=False) if len(list(Tagdict.values())) == 0: continue folder_dict[os.path.relpath(dirpath, inpath)] = [ date for date in Tagdict["Date/Time Original"] if date ][0] clock.finish() return folder_dict
def order_with_timetable(timefile: str = None): """ use timetable to create folder structure :param timefile: timetable file :return: """ if not timefile: timefile = get_info_dir("timetable.txt") log_function_call(order_with_timetable.__name__, timefile) dirNameDict_firsttime, dirNameDict_lasttime = _read_timetable(timefile) Tagdict = read_exiftags() leng = len(list(Tagdict.values())[0]) log().info('Number of jpg: %d', leng) for i in range(leng): model = create_model(Tagdict, i) time = giveDatetime(model.get_date()) dirName = find_dir_with_closest_time(dirNameDict_firsttime, dirNameDict_lasttime, time) if dirName: move(model.filename, model.dir, os.path.join(os.getcwd(), dirName))
def rename(Prefix="", dateformat='YYMM-DD', startindex=1, onlyprint=False, keeptags=True, is_video=False, name=""): """ Rename into Format: [Prefix][dateformat](_[name])_[Filenumber][SeriesType][SeriesSubNumber]_[PhotoMode] :param Prefix: prefix has to fulfil regex [-a-zA-Z]* :param dateformat: Y:Year,M:Month,D:Day,N:DayCounter; Number of occurrences determine number of digits :param startindex: minimal counter :param onlyprint: do not rename; only output file of proposed renaming into saves directory :param keeptags: any tags - name or postfixes will be preserved :param is_video: is video file extension :param name: optional name between date and filenumber, seldom used """ log_function_call(rename.__name__, Prefix, dateformat, startindex, onlyprint, keeptags, is_video, name) Tagdict = read_exiftags( file_types=settings.video_types if is_video else settings.image_types) if not Tagdict: return # rename temporary if not onlyprint: temppostfix = renameTemp(Tagdict["Directory"], Tagdict["File Name"]) else: temppostfix = "" # initialize outstring = "" Tagdict["File Name new"] = [] time_old = giveDatetime() counter = startindex - 1 digits = _count_files_for_each_date(Tagdict, startindex, dateformat) number_of_files = len(list(Tagdict.values())[0]) daystring = "" for i in range(number_of_files): model = create_model(Tagdict, i) time = giveDatetime(model.get_date()) filename = model.filename if newdate(time, time_old, 'D' in dateformat or 'N' in dateformat): daystring = dateformating(time, dateformat) if not i == 0: counter = 0 filenameBuilder = FilenameBuilder(filename) filenameBuilder.add_main(Prefix + daystring) filenameBuilder.add_main(model.get_model_abbr()) filenameBuilder.add_main(name) if is_video: counter += 1 counterString = "M%02d" % counter filenameBuilder.add_post(model.get_recMode()) else: # SequenceNumber sequence_number = model.get_sequence_number() if sequence_number < 2 and not time == time_old or model.ignore_same_date( ): counter += 1 counterString = ("%0" + digits + "d") % counter if not "HDR" in filename: counterString += model.get_sequence_string() filenameBuilder.add_main(counterString) filenameBuilder.add_post(model.get_mode()) if keeptags: filenameBuilder.use_old_tags() newname = filenameBuilder.build() # handle already exiting filename - its ok when they are in different directories # its not ok to have to files with one uppercase and another lowercase -> equals with ignore case if len(Tagdict["File Name new"]) > 0: if newname.lower() == Tagdict["File Name new"][-1].lower() and \ Tagdict["Directory"][i].lower() == Tagdict["Directory"][i - 1].lower(): log().warning("%s already exists - postfix it with V%d", os.path.join(model.dir, newname), 1) newname = filenameBuilder.set_version("V%d" % 1).build() for version in range(2, 100): indexes_samename = [ i for i, name in enumerate(Tagdict["File Name new"]) if name.lower() == newname.lower() ] if indexes_samename: if Tagdict["Directory"][i] == Tagdict["Directory"][ indexes_samename[0]]: log().warning( "%s already exists - postfix it with V%d", os.path.join(model.dir, newname), version) newname = filenameBuilder.set_version("V%d" % version).build() else: break else: break time_old = time Tagdict["File Name new"].append(newname) outstring += _write(model.dir, filename, temppostfix, newname, onlyprint) dirname = get_saves_dir() timestring = dateformating(dt.datetime.now(), "_MMDDHHmmss") video_str = "_video" if is_video else "" np.savez_compressed(os.path.join(dirname, "Tags" + video_str + timestring), Tagdict=Tagdict) writeToFile( os.path.join(dirname, "newnames" + video_str + timestring + ".txt"), outstring)
def find_bad_exif(do_move=True, check_date_additional=False, folder: str = r""): """ find files with missing exif data """ log_function_call(find_bad_exif.__name__, do_move) clock = Clock() inpath = os.getcwd() lines_no_tags = OrderedSet() lines_bad_date_additional = OrderedSet() lines_date_missing = OrderedSet() out_filename_no_tags = get_info_dir("no_tags.csv") file_no_tags, writer_no_tags = fileop.create_csv_writer( out_filename_no_tags, ["directory", "name_part"]) out_filename_bad_date_additional = get_info_dir("bad_date_additional.csv") file_bad_date_additional, writer_bad_date_additional = fileop.create_csv_writer( out_filename_bad_date_additional, ["directory", "name_part"]) out_filename_date_missing = get_info_dir("date_missing.csv") file_date_missing, writer_date_missing = fileop.create_csv_writer( out_filename_date_missing, ["directory", "name_part"]) for (dirpath, dirnames, filenames) in os.walk(inpath): if is_invalid_path(dirpath, regex=folder): continue if fileop.count_files(filenames, settings.image_types) == 0: continue Tagdict = read_exiftags(dirpath, settings.image_types, ask=False) if len(list(Tagdict.values())) == 0: continue leng = len(list(Tagdict.values())[0]) for i in range(leng): if (not "Keywords" in Tagdict or not Tagdict["Keywords"][i]) or \ (not "Subject" in Tagdict or not Tagdict["Subject"][i]) or \ (not "Description" in Tagdict or not Tagdict["Description"][i]) or \ (not "User Comment" in Tagdict or not Tagdict["User Comment"][i]): lines_no_tags.add((os.path.basename(dirpath), _remove_counter(Tagdict["File Name"][i]))) if do_move and not "bad_exif" in dirpath: move( Tagdict["File Name"][i], dirpath, dirpath.replace( inpath, os.path.join(inpath, "bad_exif_keywords"))) if not "Date/Time Original" in Tagdict or not Tagdict[ "Date/Time Original"][i]: lines_date_missing.add( (os.path.basename(dirpath), _remove_counter(Tagdict["File Name"][i]))) if do_move and not "bad_exif" in dirpath: move( Tagdict["File Name"][i], dirpath, dirpath.replace( inpath, os.path.join(inpath, "bad_exif_date_missing"))) if check_date_additional and \ (("Date Created" in Tagdict and Tagdict["Date Created"][i]) or ("Time Created" in Tagdict and Tagdict["Time Created"][i]) or ("Create Date" in Tagdict and Tagdict["Create Date"][i]) or ("Modify Date" in Tagdict and Tagdict["Modify Date"][i]) or ("Digital Creation Date" in Tagdict and Tagdict["Digital Creation Date"][i])): lines_bad_date_additional.add( (os.path.basename(dirpath), _remove_counter(Tagdict["File Name"][i]))) if do_move and not "bad_exif" in dirpath: move( Tagdict["File Name"][i], dirpath, dirpath.replace( inpath, os.path.join(inpath, "bad_exif_date_additional"))) writer_no_tags.writerows(lines_no_tags) writer_bad_date_additional.writerows(lines_bad_date_additional) writer_date_missing.writerows(lines_date_missing) file_no_tags.close() file_bad_date_additional.close() file_date_missing.close() clock.finish()