Exclude folders from processing, add Page number to list view
The include folders argument changed from -x to -i -x is used for excluding folder or files form processing In the list view the page number and max pages is also printed out (cosmetic)
This commit is contained in:
16
README.md
16
README.md
@@ -21,8 +21,9 @@ See more information for [Python XMP Tool kit](http://python-xmp-toolkit.readthe
|
|||||||
|
|
||||||
## Command line arguments
|
## Command line arguments
|
||||||
|
|
||||||
reverse_geolocate.py [-h] -x
|
reverse_geolocate.py [-h] -i
|
||||||
[XMP SOURCE FOLDER [XMP SOURCE FOLDER ...]]
|
[XMP SOURCE FOLDER [XMP SOURCE FOLDER ...]]
|
||||||
|
[-x [EXCLUDE XMP SOURCE FOLDER [EXCLUDE XMP SOURCE FOLDER ...]]]
|
||||||
[-l LIGHTROOM FOLDER] [-s]
|
[-l LIGHTROOM FOLDER] [-s]
|
||||||
[-f <overwrite, location, city, state, country, countrycode>]
|
[-f <overwrite, location, city, state, country, countrycode>]
|
||||||
[-g GOOGLE API KEY] [-o] [-e EMIL ADDRESS] [-w]
|
[-g GOOGLE API KEY] [-o] [-e EMIL ADDRESS] [-w]
|
||||||
@@ -32,7 +33,8 @@ reverse_geolocate.py [-h] -x
|
|||||||
|
|
||||||
Argument | Argument Value | Description
|
Argument | Argument Value | Description
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
-x, --xmp | XMP sidecar source folder or XMP sidecar file itself | Must given argument. It sets the path where the script will search for XMP sidecar files. It will traverse into subdirectories. A single XMP sidecar file can also be given. If the same file folder combination is found only one is processed.
|
-i, --include-source | XMP sidecar source folder or XMP sidecar file itself | Must given argument. It sets the path where the script will search for XMP sidecar files. It will traverse into subdirectories. A single XMP sidecar file can also be given. If the same file folder combination is found only one is processed.
|
||||||
|
-x, --exclude-source | Folder or File | If given those files and folders will be excluded from work
|
||||||
-l, --lightroom | Lightroom DB base folder | The folder where the .lrcat file is located. Optional, if this is set, LR values are read before any Google maps connection is done. Fills the Latitude and Longitude and the location names. Lightroom data never overwrites data already set in the XMP sidecar file. It is recommended to have Lightroom write the XMP sidecar file before this script is run
|
-l, --lightroom | Lightroom DB base folder | The folder where the .lrcat file is located. Optional, if this is set, LR values are read before any Google maps connection is done. Fills the Latitude and Longitude and the location names. Lightroom data never overwrites data already set in the XMP sidecar file. It is recommended to have Lightroom write the XMP sidecar file before this script is run
|
||||||
-s, --strict | | Do strict check for Lightroom files and include the path into the check
|
-s, --strict | | Do strict check for Lightroom files and include the path into the check
|
||||||
-f, --field | Keyword: overwrite, location, city, state, country, countrycode | In the default no data is overwritten if it is already set. With the 'overwrite' flag all data is set new from the Google Maps location data. Other arguments are each of the location fields and if set only this field will be set. This can be combined with the 'overwrite' flag to overwrite already set data
|
-f, --field | Keyword: overwrite, location, city, state, country, countrycode | In the default no data is overwritten if it is already set. With the 'overwrite' flag all data is set new from the Google Maps location data. Other arguments are each of the location fields and if set only this field will be set. This can be combined with the 'overwrite' flag to overwrite already set data
|
||||||
@@ -53,13 +55,19 @@ If the Lightroom lookup is used without the --strict argument and several files
|
|||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
```
|
```
|
||||||
reverse_geolocate.py -x Photos/2017/01 -x Photos/2017/02 -l LightRoom/MyCatalogue -f overwrite -g <API KEY>
|
reverse_geolocate.py -i Photos/2017/01 -i Photos/2017/02 -l LightRoom/MyCatalogue -f overwrite -g <API KEY>
|
||||||
```
|
```
|
||||||
|
|
||||||
Will find all XMP sidecar files in both folders *Photos/2017/01* and *Photos/2017/02* and all folder below it. Uses the Lightroom database at *LightRoom/MyCatalogue*. The script will overwrite all data, even if it is already set
|
Will find all XMP sidecar files in both folders *Photos/2017/01* and *Photos/2017/02* and all folder below it. Uses the Lightroom database at *LightRoom/MyCatalogue*. The script will overwrite all data, even if it is already set
|
||||||
|
|
||||||
```
|
```
|
||||||
reverse_geolocate.py -x Photos/2017/01/Event-01/some_photo.xmp -f location
|
reverse_geolocate.py -i Photos/2017/01 -i Photos/2017/02 -x Photos/2017/02/Folder\ A -x Photos/2017/01/Folder\ B/some_file.xmp -l LightRoom/MyCatalogue
|
||||||
|
```
|
||||||
|
|
||||||
|
Will exlucde "Photos/2017/02/Folder A" from processing. For -x also a file (including the .xmp extension) can be given.
|
||||||
|
|
||||||
|
```
|
||||||
|
reverse_geolocate.py -i Photos/2017/01/Event-01/some_photo.xmp -f location
|
||||||
```
|
```
|
||||||
|
|
||||||
Only works on *some_photo.xmp* file and will only set the *location* field if it is not yet set.
|
Only works on *some_photo.xmp* file and will only set the *location* field if it is not yet set.
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import argparse, sqlite3, requests, configparser, textwrap
|
|||||||
import glob, os, sys, re
|
import glob, os, sys, re
|
||||||
# Note XMPFiles does not work with sidecar files, need to read via XMPMeta
|
# Note XMPFiles does not work with sidecar files, need to read via XMPMeta
|
||||||
from libxmp import XMPMeta, XMPError, consts
|
from libxmp import XMPMeta, XMPError, consts
|
||||||
from shutil import copyfile
|
from shutil import copyfile, get_terminal_size
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
### FUNCTIONS
|
### FUNCTIONS
|
||||||
@@ -33,8 +34,8 @@ class writable_dir_folder(argparse.Action):
|
|||||||
# init new output array
|
# init new output array
|
||||||
out = []
|
out = []
|
||||||
# if we have a previous list in the namespace extend current list
|
# if we have a previous list in the namespace extend current list
|
||||||
if type(namespace.xmp_sources) is list:
|
if type(getattr(namespace, self.dest)) is list:
|
||||||
out.extend(namespace.xmp_sources)
|
out.extend(getattr(namespace, self.dest))
|
||||||
# add the new dir to it
|
# add the new dir to it
|
||||||
out.append(prospective_dir)
|
out.append(prospective_dir)
|
||||||
# and write that list back to the self.dest in the namespace
|
# and write that list back to the self.dest in the namespace
|
||||||
@@ -350,7 +351,11 @@ def shortenPath(path, length = 30, file_only = False, path_only = False):
|
|||||||
# RETURN: line counter +1
|
# RETURN: line counter +1
|
||||||
# DESC : prints header line and header seperator line
|
# DESC : prints header line and header seperator line
|
||||||
def printHeader(header, lines = 0, header_line = 0):
|
def printHeader(header, lines = 0, header_line = 0):
|
||||||
|
global page_no
|
||||||
if lines == header_line:
|
if lines == header_line:
|
||||||
|
# add one to the pages shown and reset the lines to start new page
|
||||||
|
page_no += 1
|
||||||
|
lines = 0
|
||||||
# print header
|
# print header
|
||||||
print("{}".format(header))
|
print("{}".format(header))
|
||||||
lines += 1
|
lines += 1
|
||||||
@@ -376,7 +381,7 @@ parser = argparse.ArgumentParser(
|
|||||||
|
|
||||||
# xmp folder (or folders), or file (or files)
|
# xmp folder (or folders), or file (or files)
|
||||||
# note that the target directory or file needs to be writeable
|
# note that the target directory or file needs to be writeable
|
||||||
parser.add_argument('-x', '--xmp',
|
parser.add_argument('-i', '--include-source',
|
||||||
required = True,
|
required = True,
|
||||||
nargs = '*',
|
nargs = '*',
|
||||||
action = writable_dir_folder,
|
action = writable_dir_folder,
|
||||||
@@ -384,6 +389,14 @@ parser.add_argument('-x', '--xmp',
|
|||||||
metavar = 'XMP SOURCE FOLDER',
|
metavar = 'XMP SOURCE FOLDER',
|
||||||
help = 'The source folder or folders with the XMP files that need reverse geo encoding to be set. Single XMP files can be given here'
|
help = 'The source folder or folders with the XMP files that need reverse geo encoding to be set. Single XMP files can be given here'
|
||||||
)
|
)
|
||||||
|
# exclude folders
|
||||||
|
parser.add_argument('-x', '--exclude-source',
|
||||||
|
nargs = '*',
|
||||||
|
action = writable_dir_folder,
|
||||||
|
dest = 'exclude_sources',
|
||||||
|
metavar = 'EXCLUDE XMP SOURCE FOLDER',
|
||||||
|
help = 'Folders and files that will be excluded.'
|
||||||
|
)
|
||||||
|
|
||||||
# LR database (base folder)
|
# LR database (base folder)
|
||||||
# get .lrcat file in this folder
|
# get .lrcat file in this folder
|
||||||
@@ -480,8 +493,9 @@ if not args.verbose:
|
|||||||
args.verbose = 0
|
args.verbose = 0
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print("### ARGUMENT VARS: X: {xmp}, L: {lr}, F: {fc}, M: {osm}, G: {gp}, E: {em}, N: {nbk}, W: {wrc}, V: {v}, D: {d}, T: {t}".format(
|
print("### ARGUMENT VARS: I: {incl}, X: {excl}, L: {lr}, F: {fc}, M: {osm}, G: {gp}, E: {em}, N: {nbk}, W: {wrc}, V: {v}, D: {d}, T: {t}".format(
|
||||||
xmp = args.xmp_sources,
|
incl = args.xmp_sources,
|
||||||
|
excl = args.exclude_sources,
|
||||||
lr = args.lightroom_folder,
|
lr = args.lightroom_folder,
|
||||||
fc = args.field_controls,
|
fc = args.field_controls,
|
||||||
osm = args.use_openstreetmap,
|
osm = args.use_openstreetmap,
|
||||||
@@ -662,24 +676,30 @@ xmp = XMPMeta()
|
|||||||
for xmp_file_source in args.xmp_sources:
|
for xmp_file_source in args.xmp_sources:
|
||||||
# if folder, open and loop
|
# if folder, open and loop
|
||||||
# NOTE: we do check for folders in there, if there are we recourse traverse them
|
# NOTE: we do check for folders in there, if there are we recourse traverse them
|
||||||
if os.path.isdir(xmp_file_source):
|
# also check that folder is not in exclude list
|
||||||
|
if os.path.isdir(xmp_file_source) and xmp_file_source.rstrip('/') not in [x.rstrip('/') for x in args.exclude_sources]:
|
||||||
# open folder and look for any .xmp files and push them into holding array
|
# open folder and look for any .xmp files and push them into holding array
|
||||||
# if there are folders, dive into them
|
# if there are folders, dive into them
|
||||||
# or glob glob all .xmp files + directory
|
# or glob glob all .xmp files + directory
|
||||||
for root, dirs, files in os.walk(xmp_file_source):
|
for root, dirs, files in os.walk(xmp_file_source):
|
||||||
for file in sorted(files):
|
for file in sorted(files):
|
||||||
# but has no .BK. inside
|
# 1) but has no .BK. inside
|
||||||
if file.endswith(".xmp") and ".BK." not in file:
|
# 2) file is not in exclude list
|
||||||
|
# 3) full folder is not in exclude list
|
||||||
|
if file.endswith(".xmp") and ".BK." not in file \
|
||||||
|
and "{}/{}".format(root, file) not in args.exclude_sources \
|
||||||
|
and root.rstrip('/') not in [x.rstrip('/') for x in args.exclude_sources]:
|
||||||
if "{}/{}".format(root, file) not in work_files:
|
if "{}/{}".format(root, file) not in work_files:
|
||||||
work_files.append("{}/{}".format(root, file))
|
work_files.append("{}/{}".format(root, file))
|
||||||
count['all'] += 1
|
count['all'] += 1
|
||||||
else:
|
else:
|
||||||
if xmp_file_source not in work_files:
|
# not already added to list and not in the exclude list either
|
||||||
|
if xmp_file_source not in work_files and xmp_file_source not in args.exclude_sources:
|
||||||
work_files.append(xmp_file_source)
|
work_files.append(xmp_file_source)
|
||||||
count['all'] += 1
|
count['all'] += 1
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print("### Work Files {}".format(work_files))
|
print("### Work Files {}".format(work_files))
|
||||||
|
|
||||||
# if we have read only we print list format style
|
# if we have read only we print list format style
|
||||||
if args.read_only:
|
if args.read_only:
|
||||||
# various string lengths
|
# various string lengths
|
||||||
@@ -692,10 +712,14 @@ if args.read_only:
|
|||||||
'state': 18,
|
'state': 18,
|
||||||
'city': 20,
|
'city': 20,
|
||||||
'location': 25,
|
'location': 25,
|
||||||
'path': 30,
|
'path': 40,
|
||||||
}
|
}
|
||||||
# after how many lines do we reprint the header
|
# after how many lines do we reprint the header
|
||||||
header_repeat = 40;
|
header_repeat = 50;
|
||||||
|
# how many pages will we have
|
||||||
|
page_all = ceil(len(work_files) / header_repeat)
|
||||||
|
# current page number
|
||||||
|
page_no = 1
|
||||||
# the formatted line for the output
|
# the formatted line for the output
|
||||||
format_line = " {{filename:<{}}} | {{latitude:>{}}} | {{longitude:>{}}} | {{code:<{}}} | {{country:<{}}} | {{state:<{}}} | {{city:<{}}} | {{location:<{}}} | {{path:<{}}}".format(
|
format_line = " {{filename:<{}}} | {{latitude:>{}}} | {{longitude:>{}}} | {{code:<{}}} | {{country:<{}}} | {{state:<{}}} | {{city:<{}}} | {{location:<{}}} | {{path:<{}}}".format(
|
||||||
format_length['filename'],
|
format_length['filename'],
|
||||||
@@ -715,7 +739,7 @@ if args.read_only:
|
|||||||
header_line = '''{}
|
header_line = '''{}
|
||||||
{}
|
{}
|
||||||
{}'''.format(
|
{}'''.format(
|
||||||
'', # can later be set to something else, eg page numbers
|
'> Page {page_no:,}/{page_all:,}', # can later be set to something else, eg page numbers
|
||||||
format_line.format( # the header title line
|
format_line.format( # the header title line
|
||||||
filename = 'File',
|
filename = 'File',
|
||||||
latitude = 'Latitude',
|
latitude = 'Latitude',
|
||||||
@@ -740,7 +764,10 @@ if args.read_only:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# print header
|
# print header
|
||||||
printHeader(header_line)
|
printHeader(header_line.format(page_no = page_no, page_all = page_all))
|
||||||
|
# print no files found if we have no files
|
||||||
|
if not work_files:
|
||||||
|
print("{:<60}".format('[!!!] No files found'))
|
||||||
# now we just loop through each file and work on them
|
# now we just loop through each file and work on them
|
||||||
for xmp_file in work_files:
|
for xmp_file in work_files:
|
||||||
if not args.read_only:
|
if not args.read_only:
|
||||||
@@ -789,7 +816,7 @@ for xmp_file in work_files:
|
|||||||
if args.read_only:
|
if args.read_only:
|
||||||
# for read only we print out the data formatted
|
# for read only we print out the data formatted
|
||||||
# headline check, do we need to print that
|
# headline check, do we need to print that
|
||||||
count['read'] = printHeader(header_line, count['read'], header_repeat)
|
count['read'] = printHeader(header_line.format(page_no = page_no, page_all = page_all), count['read'], header_repeat)
|
||||||
# the data content
|
# the data content
|
||||||
print(format_line.format(
|
print(format_line.format(
|
||||||
filename = shortenPath(xmp_file, format_length['filename'], file_only = True), # shorten from the left
|
filename = shortenPath(xmp_file, format_length['filename'], file_only = True), # shorten from the left
|
||||||
|
|||||||
Reference in New Issue
Block a user