skilleter-thingy 0.2.1__py3-none-any.whl → 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/METADATA +1 -46
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/RECORD +6 -15
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/entry_points.txt +0 -9
- skilleter_thingy/borger.py +0 -273
- skilleter_thingy/diskspacecheck.py +0 -67
- skilleter_thingy/localphotosync.py +0 -201
- skilleter_thingy/moviemover.py +0 -133
- skilleter_thingy/photodupe.py +0 -135
- skilleter_thingy/phototidier.py +0 -248
- skilleter_thingy/splitpics.py +0 -99
- skilleter_thingy/sysmon.py +0 -435
- skilleter_thingy/window_rename.py +0 -92
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {skilleter_thingy-0.2.1.dist-info → skilleter_thingy-0.2.4.dist-info}/top_level.txt +0 -0
skilleter_thingy/moviemover.py
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
""" Search for files matching a wildcard in a directory tree and move them to an
|
|
4
|
-
equivalent location in a different tree """
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import glob
|
|
10
|
-
import pathlib
|
|
11
|
-
import shutil
|
|
12
|
-
import filecmp
|
|
13
|
-
|
|
14
|
-
################################################################################
|
|
15
|
-
|
|
16
|
-
def error(msg, status=1):
|
|
17
|
-
""" Exit with an error message """
|
|
18
|
-
|
|
19
|
-
print(msg)
|
|
20
|
-
|
|
21
|
-
sys.exit(status)
|
|
22
|
-
|
|
23
|
-
################################################################################
|
|
24
|
-
|
|
25
|
-
def parse_command_line():
|
|
26
|
-
""" Handle command line arguments """
|
|
27
|
-
|
|
28
|
-
parser = argparse.ArgumentParser(description='File relocation - move files by wildcard from one directory tree to another')
|
|
29
|
-
|
|
30
|
-
parser.add_argument('--source', '-s', type=str, required=True, help='Source directory')
|
|
31
|
-
parser.add_argument('--destination', '-d', type=str, required=True, help='Destination directory')
|
|
32
|
-
parser.add_argument('--dry-run', '-D', action='store_true', help='Report what files would be moved, without actually moving them')
|
|
33
|
-
parser.add_argument('files', nargs='*', help='List of wildcard matches')
|
|
34
|
-
|
|
35
|
-
args = parser.parse_args()
|
|
36
|
-
|
|
37
|
-
if not args.files:
|
|
38
|
-
print('You must specify at least one wildcard/regex parameter')
|
|
39
|
-
|
|
40
|
-
if not os.path.isdir(args.source):
|
|
41
|
-
error(f'{args.source} is not a directory')
|
|
42
|
-
|
|
43
|
-
if not os.path.isdir(args.destination):
|
|
44
|
-
error(f'{args.destination} is not a directory')
|
|
45
|
-
|
|
46
|
-
args.source_path = pathlib.Path(os.path.realpath(args.source))
|
|
47
|
-
args.destination_path = pathlib.Path(os.path.realpath(args.destination))
|
|
48
|
-
|
|
49
|
-
if args.source_path == args.destination_path:
|
|
50
|
-
error('Source and destination paths cannot be the same')
|
|
51
|
-
|
|
52
|
-
if args.source_path in args.destination_path.parents:
|
|
53
|
-
error('The destination directory cannot be within the source path')
|
|
54
|
-
|
|
55
|
-
if args.destination_path in args.source_path.parents:
|
|
56
|
-
error('The source directory cannot be within the destination path')
|
|
57
|
-
|
|
58
|
-
return args
|
|
59
|
-
|
|
60
|
-
################################################################################
|
|
61
|
-
|
|
62
|
-
def main():
|
|
63
|
-
""" Entry point """
|
|
64
|
-
|
|
65
|
-
args = parse_command_line()
|
|
66
|
-
|
|
67
|
-
# Process each wildcard
|
|
68
|
-
|
|
69
|
-
for wild in args.files:
|
|
70
|
-
# Find matching files in the source tree
|
|
71
|
-
|
|
72
|
-
for source_file in args.source_path.glob(f'**/{wild}'):
|
|
73
|
-
# Ignore anything that isn't a file
|
|
74
|
-
|
|
75
|
-
if source_file.is_file():
|
|
76
|
-
# Determine where to put it
|
|
77
|
-
|
|
78
|
-
dest_file = args.destination_path / source_file.relative_to(args.source_path)
|
|
79
|
-
|
|
80
|
-
if dest_file.exists():
|
|
81
|
-
|
|
82
|
-
if filecmp.cmp(source_file, dest_file, shallow=False):
|
|
83
|
-
print(f'Destination file {dest_file} already exists and is identical, so deleting source')
|
|
84
|
-
if not args.dry_run:
|
|
85
|
-
os.unlink(source_file)
|
|
86
|
-
else:
|
|
87
|
-
print(f'Destination file {dest_file} already exists and is DIFFERENT')
|
|
88
|
-
else:
|
|
89
|
-
# If the destination directory doesn't exist, then create it
|
|
90
|
-
|
|
91
|
-
if not dest_file.parent.is_dir():
|
|
92
|
-
print(f'Creating directory {dest_file.parent}')
|
|
93
|
-
|
|
94
|
-
if not args.dry_run:
|
|
95
|
-
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
-
|
|
97
|
-
# Move the file
|
|
98
|
-
|
|
99
|
-
print(f'Moving {source_file.name} to {dest_file.parent}')
|
|
100
|
-
|
|
101
|
-
if not args.dry_run:
|
|
102
|
-
try:
|
|
103
|
-
shutil.move(source_file, dest_file)
|
|
104
|
-
except PermissionError:
|
|
105
|
-
print(f'WARNING: Permissions error moving {source_file}')
|
|
106
|
-
|
|
107
|
-
# Delete the source directory if it is not empty
|
|
108
|
-
|
|
109
|
-
source_dir = os.path.dirname(source_file)
|
|
110
|
-
|
|
111
|
-
if not glob.glob(source_dir, recursive=True):
|
|
112
|
-
print('Deleting directory "{source_dir}"')
|
|
113
|
-
if not args.dry_run:
|
|
114
|
-
os.path.unlink(source_dir)
|
|
115
|
-
|
|
116
|
-
################################################################################
|
|
117
|
-
|
|
118
|
-
def moviemover():
|
|
119
|
-
"""Entry point"""
|
|
120
|
-
|
|
121
|
-
try:
|
|
122
|
-
main()
|
|
123
|
-
|
|
124
|
-
except KeyboardInterrupt:
|
|
125
|
-
sys.exit(1)
|
|
126
|
-
|
|
127
|
-
except BrokenPipeError:
|
|
128
|
-
sys.exit(2)
|
|
129
|
-
|
|
130
|
-
################################################################################
|
|
131
|
-
|
|
132
|
-
if __name__ == '__main__':
|
|
133
|
-
moviemover()
|
skilleter_thingy/photodupe.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Hash photos to find closely-similar images and report them"""
|
|
3
|
-
|
|
4
|
-
import sys
|
|
5
|
-
import os
|
|
6
|
-
import pickle
|
|
7
|
-
import argparse
|
|
8
|
-
|
|
9
|
-
import PIL
|
|
10
|
-
|
|
11
|
-
from collections import defaultdict
|
|
12
|
-
|
|
13
|
-
from PIL import Image
|
|
14
|
-
import imagehash
|
|
15
|
-
|
|
16
|
-
################################################################################
|
|
17
|
-
|
|
18
|
-
def read_image_hashes(directories):
|
|
19
|
-
"""Read all the specfied directories and hash every picture therein"""
|
|
20
|
-
|
|
21
|
-
hashes = defaultdict(list)
|
|
22
|
-
|
|
23
|
-
# Walk each directory tree
|
|
24
|
-
|
|
25
|
-
for directory in directories:
|
|
26
|
-
print(f'Scanning directory tree {directory}')
|
|
27
|
-
|
|
28
|
-
for root, _, files in os.walk(directory):
|
|
29
|
-
print(f'Scanning directory {root}')
|
|
30
|
-
|
|
31
|
-
for file in files:
|
|
32
|
-
filepath = os.path.join(root, file)
|
|
33
|
-
|
|
34
|
-
fileext = os.path.splitext(file)[1]
|
|
35
|
-
|
|
36
|
-
if fileext.lower() not in ('.jbf', '.ini', '.xml', '.ffs_db'):
|
|
37
|
-
# Calculate the hash and store path, dimensions and file size under the hash entry in the hashes table
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
with Image.open(filepath) as image:
|
|
41
|
-
hash_value = imagehash.average_hash(image, hash_size=12)
|
|
42
|
-
|
|
43
|
-
size = os.stat(filepath).st_size
|
|
44
|
-
hashes[hash_value].append({'path': filepath, 'width': image.width, 'height': image.height, 'size': size})
|
|
45
|
-
|
|
46
|
-
except PIL.UnidentifiedImageError:
|
|
47
|
-
sys.stderr.write(f'ERROR: Unrecognized format {filepath} (size={size})\n')
|
|
48
|
-
|
|
49
|
-
except OSError:
|
|
50
|
-
sys.stderr.write(f'ERROR: Unable to read {filepath} (size={size})\n')
|
|
51
|
-
|
|
52
|
-
# Return the hash table
|
|
53
|
-
|
|
54
|
-
return hashes
|
|
55
|
-
|
|
56
|
-
################################################################################
|
|
57
|
-
|
|
58
|
-
def main():
|
|
59
|
-
"""Read the hashes and report duplicates in a vaguely civilised way"""
|
|
60
|
-
|
|
61
|
-
parser = argparse.ArgumentParser(description='Search for similar images')
|
|
62
|
-
parser.add_argument('directories', nargs='*', action='store', help='Directories to search')
|
|
63
|
-
|
|
64
|
-
args = parser.parse_args()
|
|
65
|
-
|
|
66
|
-
if not args.directories:
|
|
67
|
-
print('You must be specify at least one directory')
|
|
68
|
-
sys.exit(1)
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
print('Loading cached data')
|
|
72
|
-
|
|
73
|
-
with open('photodupe.pickle', 'rb') as pickles:
|
|
74
|
-
hashes = pickle.load(pickles)
|
|
75
|
-
except (FileNotFoundError, EOFError):
|
|
76
|
-
print('Scanning directories')
|
|
77
|
-
|
|
78
|
-
hashes = read_image_hashes(args.directories)
|
|
79
|
-
|
|
80
|
-
# Sort the list of hashes so that we can easily find close matches
|
|
81
|
-
|
|
82
|
-
print('Sorting hashes')
|
|
83
|
-
|
|
84
|
-
hash_values = sorted([str(hashval) for hashval in hashes])
|
|
85
|
-
|
|
86
|
-
for hash_value in hash_values:
|
|
87
|
-
if len(hashes[hash_value]) > 1:
|
|
88
|
-
print(hash_value)
|
|
89
|
-
max_len = 0
|
|
90
|
-
min_size = None
|
|
91
|
-
|
|
92
|
-
for entry in hashes[hash_value]:
|
|
93
|
-
max_len = max(max_len, len(entry['path']))
|
|
94
|
-
|
|
95
|
-
if min_size is None:
|
|
96
|
-
min_size = entry['size']
|
|
97
|
-
else:
|
|
98
|
-
min_size = min(min_size, entry['size'])
|
|
99
|
-
|
|
100
|
-
if min_size >= 1024 * 1024:
|
|
101
|
-
size_suffix = 'MiB'
|
|
102
|
-
size_div = 1024*1024
|
|
103
|
-
|
|
104
|
-
elif min_size > 1024:
|
|
105
|
-
size_suffix = 'KiB'
|
|
106
|
-
size_div = 1024
|
|
107
|
-
else:
|
|
108
|
-
size_div = 1
|
|
109
|
-
size_suffix = ''
|
|
110
|
-
|
|
111
|
-
for entry in hashes[hash_value]:
|
|
112
|
-
size = entry['size'] // size_div
|
|
113
|
-
print(f' {entry["path"]:{max_len}} {size:>4} {size_suffix} ({entry["width"]}x{entry["height"]})')
|
|
114
|
-
|
|
115
|
-
with open('photodupe.pickle', 'wb') as pickles:
|
|
116
|
-
pickle.dump(hashes, pickles)
|
|
117
|
-
|
|
118
|
-
################################################################################
|
|
119
|
-
|
|
120
|
-
def photodupe():
|
|
121
|
-
"""Entry point"""
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
main()
|
|
125
|
-
|
|
126
|
-
except KeyboardInterrupt:
|
|
127
|
-
sys.exit(1)
|
|
128
|
-
|
|
129
|
-
except BrokenPipeError:
|
|
130
|
-
sys.exit(2)
|
|
131
|
-
|
|
132
|
-
################################################################################
|
|
133
|
-
|
|
134
|
-
if __name__ == '__main__':
|
|
135
|
-
photodupe()
|
skilleter_thingy/phototidier.py
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
""" Perform various tidying operations on a directory full of photos:
|
|
4
|
-
1. Remove leading '$' and '_' from filenames
|
|
5
|
-
2. Move files in hidden directories up 1 level
|
|
6
|
-
3. If the EXIF data in a photo indicates that it was taken on date that
|
|
7
|
-
doesn't match the name of the directory it is stored in (in YYYY-MM-DD format)
|
|
8
|
-
then it is moved to the correct directory, creating it if necessary.
|
|
9
|
-
|
|
10
|
-
All move/rename operations are carried out safely with the file being moved having
|
|
11
|
-
a numeric suffix added to the name if it conflicts with an existing file.
|
|
12
|
-
|
|
13
|
-
TODO: Ignore .stversions files
|
|
14
|
-
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
################################################################################
|
|
18
|
-
|
|
19
|
-
import argparse
|
|
20
|
-
import os
|
|
21
|
-
import sys
|
|
22
|
-
import pathlib
|
|
23
|
-
import re
|
|
24
|
-
|
|
25
|
-
from PIL import UnidentifiedImageError
|
|
26
|
-
from PIL import Image
|
|
27
|
-
from PIL.ExifTags import TAGS
|
|
28
|
-
|
|
29
|
-
import thingy.colour as colour
|
|
30
|
-
|
|
31
|
-
################################################################################
|
|
32
|
-
|
|
33
|
-
FILE_TYPES = ('.jpg', '.jpeg')
|
|
34
|
-
|
|
35
|
-
DATE_RE = re.compile(r'[0-9]{4}-[0-9]{2}-[0-9]{2}')
|
|
36
|
-
|
|
37
|
-
NUMBER_RE = re.compile(r'(.*) +\([0-9]+\).*')
|
|
38
|
-
|
|
39
|
-
################################################################################
|
|
40
|
-
|
|
41
|
-
def error(msg, status=1):
|
|
42
|
-
""" Exit with an error message """
|
|
43
|
-
|
|
44
|
-
print(msg)
|
|
45
|
-
|
|
46
|
-
sys.exit(status)
|
|
47
|
-
|
|
48
|
-
################################################################################
|
|
49
|
-
|
|
50
|
-
def parse_command_line():
|
|
51
|
-
""" Handle command line arguments """
|
|
52
|
-
|
|
53
|
-
parser = argparse.ArgumentParser(description='Re-organise photos into (hopefully) the correct folders.')
|
|
54
|
-
|
|
55
|
-
parser.add_argument('--dry-run', '-D', action='store_true', help='Report what files would be moved, without actually moving them')
|
|
56
|
-
parser.add_argument('path', nargs=1, help='Path to the picture storage directory')
|
|
57
|
-
|
|
58
|
-
args = parser.parse_args()
|
|
59
|
-
|
|
60
|
-
if not os.path.isdir(args.path[0]):
|
|
61
|
-
error(f'{args.path} is not a directory')
|
|
62
|
-
|
|
63
|
-
args.path = pathlib.Path(os.path.realpath(args.path[0]))
|
|
64
|
-
|
|
65
|
-
return args
|
|
66
|
-
|
|
67
|
-
################################################################################
|
|
68
|
-
|
|
69
|
-
def safe_rename(args, source_file, new_name):
|
|
70
|
-
""" Rename a file, adding a numeric suffix to avoid overwriting anything """
|
|
71
|
-
|
|
72
|
-
# If the destination file exists, add a numeric suffix to the new name
|
|
73
|
-
# until we find one that doesn't
|
|
74
|
-
|
|
75
|
-
index = 1
|
|
76
|
-
new_name_stem = new_name.stem
|
|
77
|
-
|
|
78
|
-
while new_name.exists():
|
|
79
|
-
new_name = new_name.with_name(f'{new_name_stem}-{index}{new_name.suffix}')
|
|
80
|
-
index += 1
|
|
81
|
-
|
|
82
|
-
colour.write(f'Rename [BLUE:{source_file}] to [BLUE:{new_name}]')
|
|
83
|
-
|
|
84
|
-
# Panic if the destination parent directory exists, but isn't actually a directory
|
|
85
|
-
|
|
86
|
-
if new_name.parent.exists and not new_name.parent.is_dir:
|
|
87
|
-
colour.write('[RED:WARNING]: Destination [BLUE:{new_name.parent}] exists, but is not a directory - [BLUE:{source_file}] will not be renamed')
|
|
88
|
-
return source_file
|
|
89
|
-
|
|
90
|
-
# Rename and return the new namem, creating the directory for it to go in, if necessary
|
|
91
|
-
|
|
92
|
-
if not args.dry_run:
|
|
93
|
-
new_name.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
-
|
|
95
|
-
source_file.rename(new_name)
|
|
96
|
-
|
|
97
|
-
return new_name
|
|
98
|
-
|
|
99
|
-
################################################################################
|
|
100
|
-
|
|
101
|
-
def get_exif_date(source_file):
|
|
102
|
-
""" Try an extract the daste when the photo was taken from the EXIF data
|
|
103
|
-
and return it in YYYY/YYYY-MM-DD format as the subdirectory where
|
|
104
|
-
the photo should be located """
|
|
105
|
-
|
|
106
|
-
# Get the EXIF data
|
|
107
|
-
|
|
108
|
-
try:
|
|
109
|
-
photo = Image.open(source_file)
|
|
110
|
-
except (OSError, UnidentifiedImageError):
|
|
111
|
-
colour.write(f'[RED:ERROR]: [BLUE:{source_file}] does not appear to be a valid image - ignoring EXIF data')
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
exif = photo.getexif()
|
|
115
|
-
|
|
116
|
-
# Search for the original date/time tag
|
|
117
|
-
|
|
118
|
-
for tag_id in exif:
|
|
119
|
-
tag = TAGS.get(tag_id, tag_id)
|
|
120
|
-
|
|
121
|
-
if tag == 'DateTimeOriginal':
|
|
122
|
-
data = exif.get(tag_id)
|
|
123
|
-
if isinstance(data, bytes):
|
|
124
|
-
data = data.decode()
|
|
125
|
-
|
|
126
|
-
# Ignore dummy value
|
|
127
|
-
|
|
128
|
-
if data.startswith('0000:00:00'):
|
|
129
|
-
return None
|
|
130
|
-
|
|
131
|
-
# Convert to YYYY-MM-DD format, removing the time
|
|
132
|
-
|
|
133
|
-
date = f'{int(data[0:4]):04}-{int(data[5:7]):02}-{int(data[8:10]):02}'
|
|
134
|
-
|
|
135
|
-
return date
|
|
136
|
-
|
|
137
|
-
# No date tag found
|
|
138
|
-
|
|
139
|
-
return None
|
|
140
|
-
|
|
141
|
-
################################################################################
|
|
142
|
-
|
|
143
|
-
def fix_file(args, source_file):
|
|
144
|
-
""" Fix a file by moving or renaming it to fix naming or directory issues """
|
|
145
|
-
|
|
146
|
-
# Get the image date from the EXIF data
|
|
147
|
-
|
|
148
|
-
image_date = get_exif_date(source_file)
|
|
149
|
-
|
|
150
|
-
# If the file starts with $, ~, _ or ., rename it to remove it
|
|
151
|
-
|
|
152
|
-
while source_file.name[0] in ('$', '~', '_', '.'):
|
|
153
|
-
new_name = source_file.with_name(source_file.name[1:])
|
|
154
|
-
|
|
155
|
-
source_file = safe_rename(args, source_file, new_name)
|
|
156
|
-
|
|
157
|
-
# If filename contains '~' then truncate it
|
|
158
|
-
|
|
159
|
-
if '~' in source_file.name:
|
|
160
|
-
new_name = source_file.with_name(source_file.name.split('~')[0] + source_file.suffix)
|
|
161
|
-
|
|
162
|
-
source_file = safe_rename(args, source_file, new_name)
|
|
163
|
-
|
|
164
|
-
# If the directory name starts with . or $ move the file up 1 level
|
|
165
|
-
|
|
166
|
-
while source_file.parts[-2][0] in ('$', '.'):
|
|
167
|
-
new_name = source_file.parent.parent / source_file.name
|
|
168
|
-
|
|
169
|
-
source_file = safe_rename(args, source_file, new_name)
|
|
170
|
-
|
|
171
|
-
# If the filename has a number in parentheses, then remove it
|
|
172
|
-
|
|
173
|
-
num_match = NUMBER_RE.fullmatch(source_file.stem)
|
|
174
|
-
if num_match:
|
|
175
|
-
new_name = source_file.parent / (num_match.group(1) + source_file.suffix)
|
|
176
|
-
|
|
177
|
-
source_file = safe_rename(args, source_file, new_name)
|
|
178
|
-
|
|
179
|
-
# See if the date in the EXIF data matches the directory name prefix
|
|
180
|
-
# and move it to the correct location if it doesn't
|
|
181
|
-
|
|
182
|
-
if image_date:
|
|
183
|
-
image_year = image_date.split('-')[0]
|
|
184
|
-
|
|
185
|
-
image_path = args.path / image_year / image_date
|
|
186
|
-
|
|
187
|
-
# If the file isn't already in a directory with the correct year and date
|
|
188
|
-
# move it to one that it
|
|
189
|
-
|
|
190
|
-
if not str(source_file.parent).startswith(str(image_path)):
|
|
191
|
-
# If the source directory has a description after the date, append that
|
|
192
|
-
# to the destination directory
|
|
193
|
-
# Otherwise, if the source directory doesn't have a date, append the whole
|
|
194
|
-
# directory name.
|
|
195
|
-
|
|
196
|
-
source_parent_dir = source_file.parts[-2]
|
|
197
|
-
|
|
198
|
-
if DATE_RE.match(source_parent_dir):
|
|
199
|
-
if len(source_parent_dir) > 10:
|
|
200
|
-
image_path = args.path / image_year / f'{image_date}{source_parent_dir[10:]}'
|
|
201
|
-
else:
|
|
202
|
-
image_path = args.path / image_year / f'{image_date} - {source_parent_dir}'
|
|
203
|
-
|
|
204
|
-
source_file = safe_rename(args, source_file, image_path / source_file.name)
|
|
205
|
-
|
|
206
|
-
################################################################################
|
|
207
|
-
|
|
208
|
-
def main():
|
|
209
|
-
""" Entry point """
|
|
210
|
-
|
|
211
|
-
args = parse_command_line()
|
|
212
|
-
|
|
213
|
-
# Disable the maximum image size in PIL
|
|
214
|
-
|
|
215
|
-
Image.MAX_IMAGE_PIXELS = None
|
|
216
|
-
|
|
217
|
-
# Find matching files in the source tree
|
|
218
|
-
|
|
219
|
-
print(f'Searching {args.path} with extension matching {", ".join(FILE_TYPES)}')
|
|
220
|
-
|
|
221
|
-
all_matches = args.path.glob('**/*')
|
|
222
|
-
|
|
223
|
-
matches = [file for file in all_matches if file.suffix.lower() in FILE_TYPES and file.is_file()]
|
|
224
|
-
|
|
225
|
-
print(f'Found {len(matches)} matching files')
|
|
226
|
-
|
|
227
|
-
for source_file in matches:
|
|
228
|
-
if '.stversions' not in source_file.parts:
|
|
229
|
-
fix_file(args, source_file)
|
|
230
|
-
|
|
231
|
-
################################################################################
|
|
232
|
-
|
|
233
|
-
def phototidier():
|
|
234
|
-
"""Entry point"""
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
main()
|
|
238
|
-
|
|
239
|
-
except KeyboardInterrupt:
|
|
240
|
-
sys.exit(1)
|
|
241
|
-
|
|
242
|
-
except BrokenPipeError:
|
|
243
|
-
sys.exit(2)
|
|
244
|
-
|
|
245
|
-
################################################################################
|
|
246
|
-
|
|
247
|
-
if __name__ == '__main__':
|
|
248
|
-
phototidier()
|
skilleter_thingy/splitpics.py
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
################################################################################
|
|
4
|
-
""" Copy a directory full of pictures to a destination, creating subdiretories
|
|
5
|
-
with N pictures in each in the destination directory
|
|
6
|
-
"""
|
|
7
|
-
################################################################################
|
|
8
|
-
|
|
9
|
-
import os
|
|
10
|
-
import glob
|
|
11
|
-
import argparse
|
|
12
|
-
|
|
13
|
-
from PIL import Image
|
|
14
|
-
|
|
15
|
-
################################################################################
|
|
16
|
-
# Constants
|
|
17
|
-
|
|
18
|
-
DEFAULT_SOURCE_DIR = '/storage/Starred Photos/'
|
|
19
|
-
DEFAULT_DEST_DIR = '/media/jms/48A7-BE16'
|
|
20
|
-
DEFAULT_MAX_SIZE = 3840
|
|
21
|
-
|
|
22
|
-
################################################################################
|
|
23
|
-
|
|
24
|
-
def parse_command_line():
|
|
25
|
-
""" Parse the command line """
|
|
26
|
-
|
|
27
|
-
parser = argparse.ArgumentParser(description='Copy a collection of pictures to a set of numbered directories')
|
|
28
|
-
|
|
29
|
-
parser.add_argument('--pics', type=int, help='Number of pictures per directory (default is not to use numbered subdirectories)', default=None)
|
|
30
|
-
parser.add_argument('--max-size', type=int, help='Maximum size for each image in pixels (default=%d, images will be resized if larger)' %
|
|
31
|
-
DEFAULT_MAX_SIZE, default=DEFAULT_MAX_SIZE)
|
|
32
|
-
parser.add_argument('source', nargs=1, help='Source directory', default=DEFAULT_SOURCE_DIR)
|
|
33
|
-
parser.add_argument('destination', nargs=1, help='Destination directory', default=DEFAULT_DEST_DIR)
|
|
34
|
-
|
|
35
|
-
args = parser.parse_args()
|
|
36
|
-
|
|
37
|
-
return args
|
|
38
|
-
|
|
39
|
-
################################################################################
|
|
40
|
-
|
|
41
|
-
def copy_images(args):
|
|
42
|
-
""" Copy the images """
|
|
43
|
-
|
|
44
|
-
dir_num = -1
|
|
45
|
-
|
|
46
|
-
pictures = glob.glob(os.path.join(args.source[0], '*'))
|
|
47
|
-
dest_dir = args.destination[0]
|
|
48
|
-
|
|
49
|
-
if not os.path.isdir(dest_dir):
|
|
50
|
-
os.makedirs(dest_dir)
|
|
51
|
-
|
|
52
|
-
for index, picture in enumerate(pictures):
|
|
53
|
-
picture_name = os.path.basename(picture)
|
|
54
|
-
|
|
55
|
-
# Create the new directory in the destination every N pcitures
|
|
56
|
-
|
|
57
|
-
if args.pics and index % args.pics == 0:
|
|
58
|
-
dir_num += 1
|
|
59
|
-
dest_dir = os.path.join(args.destination[0], '%05d' % dir_num)
|
|
60
|
-
if not os.path.isdir(dest_dir):
|
|
61
|
-
os.makedirs(dest_dir)
|
|
62
|
-
|
|
63
|
-
print('%d/%d: Copying %s to %s' % (index + 1, len(pictures), picture, dest_dir))
|
|
64
|
-
|
|
65
|
-
# Resize the image if neccessary
|
|
66
|
-
|
|
67
|
-
image = Image.open(picture)
|
|
68
|
-
|
|
69
|
-
if args.max_size and (image.width > args.max_size or image.height > args.max_size):
|
|
70
|
-
if image.width > image.height:
|
|
71
|
-
scale = image.width / args.max_size
|
|
72
|
-
else:
|
|
73
|
-
scale = image.height / args.max_size
|
|
74
|
-
|
|
75
|
-
new_size = (round(image.width / scale), round(image.height / scale))
|
|
76
|
-
|
|
77
|
-
print(' Resizing from %d x %d to %d x %d' % (image.width, image.height, new_size[0], new_size[1]))
|
|
78
|
-
|
|
79
|
-
image.resize(new_size)
|
|
80
|
-
|
|
81
|
-
# Write the image
|
|
82
|
-
|
|
83
|
-
destination = os.path.join(dest_dir, picture_name)
|
|
84
|
-
|
|
85
|
-
image.save(destination)
|
|
86
|
-
|
|
87
|
-
################################################################################
|
|
88
|
-
|
|
89
|
-
def splitpics():
|
|
90
|
-
"""Entry point"""
|
|
91
|
-
|
|
92
|
-
args = parse_command_line()
|
|
93
|
-
|
|
94
|
-
copy_images(args)
|
|
95
|
-
|
|
96
|
-
################################################################################
|
|
97
|
-
|
|
98
|
-
if __name__ == '__main__':
|
|
99
|
-
splitpics()
|