Initial commit to photo-dater with pytest test
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
# photo-dater
|
||||
# PhotoOrganizer_FileName
|
||||
|
||||
Organizes photos based on file name.
|
||||
- File name must be of format yyyyMMdd_xx.jpg
|
||||
- Where xx is an index
|
||||
- Sets exif data
|
||||
- Puts into a pre specified directory structure
|
||||
|
||||
Date photos based on name of file
|
||||
BIN
pytest/example_pics/u_001.jpg
Executable file
BIN
pytest/example_pics/u_001.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 411 KiB |
BIN
pytest/example_pics/u_002.jpg
Executable file
BIN
pytest/example_pics/u_002.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 366 KiB |
43
pytest/fixtures.py
Normal file
43
pytest/fixtures.py
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from helpers import *
|
||||
|
||||
log_file_name = 'example.log'
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cleanup_logs():
|
||||
if os.path.exists(log_file_name):
|
||||
os.remove(log_file_name)
|
||||
yield
|
||||
if os.path.exists(log_file_name):
|
||||
os.remove(log_file_name)
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_dir():
|
||||
tmp_dir_name = gen_rand_name(12)
|
||||
tmp_dir_path = os.path.join("/tmp/photosorter/", tmp_dir_name)
|
||||
os.makedirs(tmp_dir_path, exist_ok=True)
|
||||
return tmp_dir_path
|
||||
|
||||
@pytest.fixture
|
||||
def log_file():
|
||||
return log_file_name
|
||||
|
||||
@pytest.fixture
|
||||
def in_out_dirs(tmp_dir):
|
||||
in_dir_name = gen_rand_name(12)
|
||||
out_dir_name = gen_rand_name(12)
|
||||
|
||||
while out_dir_name == in_dir_name:
|
||||
out_dir_name = gen_rand_name(12)
|
||||
|
||||
in_dir_path = os.path.join(tmp_dir, in_dir_name)
|
||||
out_dir_path = os.path.join(tmp_dir, out_dir_name)
|
||||
|
||||
os.mkdir(in_dir_path)
|
||||
os.mkdir(out_dir_path)
|
||||
|
||||
return (in_dir_path, out_dir_path)
|
||||
|
||||
25
pytest/helpers.py
Normal file
25
pytest/helpers.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
def run_successfull_cmd(command):
|
||||
command = command.split()
|
||||
output = subprocess.run(command, capture_output=True, encoding='UTF-8')
|
||||
assert output.returncode == 0
|
||||
return output.stdout
|
||||
|
||||
def gen_rand_name(max_name_len):
|
||||
name_len = random.randint(3, max_name_len)
|
||||
return ''.join(random.choice(string.ascii_letters) for x in range(name_len))
|
||||
|
||||
def check_log_string(log_file, search_str):
|
||||
lines = ''
|
||||
with open(log_file, 'r') as my_log:
|
||||
lines = my_log.readlines()
|
||||
for line in lines:
|
||||
if search_str in line:
|
||||
return True
|
||||
|
||||
print(lines)
|
||||
return False
|
||||
|
||||
68
pytest/test_bad_files.py
Normal file
68
pytest/test_bad_files.py
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from helpers import *
|
||||
from fixtures import *
|
||||
|
||||
cmd = 'python3 updatescannedpics.py'
|
||||
|
||||
def test_bad_filename(in_out_dirs, log_file):
|
||||
in_dir, out_dir = in_out_dirs
|
||||
|
||||
file_name = gen_rand_name(12) + ".jpg"
|
||||
Path(os.path.join(in_dir, file_name)).touch()
|
||||
|
||||
command = f'{cmd} -i {in_dir} -o {out_dir}'
|
||||
output = run_successfull_cmd(command)
|
||||
|
||||
expected = f"File name not compatable: {file_name}"
|
||||
assert check_log_string(log_file, expected)
|
||||
|
||||
def test_bad_jpg_format(in_out_dirs, log_file):
|
||||
in_dir, out_dir = in_out_dirs
|
||||
|
||||
year = random.randint(1900, 2000)
|
||||
month = random.randint(1, 12)
|
||||
day = random.randint(1, 28)
|
||||
index = random.randint(0, 99)
|
||||
|
||||
file_name = f"{year}{month:02d}{day:02d}_{index:02d}.jpg"
|
||||
Path(os.path.join(in_dir, file_name)).touch()
|
||||
|
||||
command = f'{cmd} -i {in_dir} -o {out_dir}'
|
||||
output = run_successfull_cmd(command)
|
||||
|
||||
expected = f"Bad file format for {file_name}"
|
||||
assert check_log_string(log_file, expected)
|
||||
|
||||
|
||||
def test_bad_jpg_year(in_out_dirs, log_file):
|
||||
in_dir, out_dir = in_out_dirs
|
||||
|
||||
year_low = random.randint(1000, 1799)
|
||||
year_high = random.randint(2010, 2100)
|
||||
month = random.randint(1, 12)
|
||||
day = random.randint(1, 28)
|
||||
index = random.randint(0, 99)
|
||||
|
||||
file_name_high = f"{year_high}{month:02d}{day:02d}_{index:02d}.jpg"
|
||||
file_name_low = f"{year_low}{month:02d}{day:02d}_{index:02d}.jpg"
|
||||
|
||||
shutil.copyfile("pytest/example_pics/u_001.jpg", f"{in_dir}/{file_name_high}")
|
||||
shutil.copyfile("pytest/example_pics/u_002.jpg", f"{in_dir}/{file_name_low}")
|
||||
|
||||
command = f'{cmd} -m -i {in_dir} -o {out_dir}'
|
||||
output = run_successfull_cmd(command)
|
||||
|
||||
expected = [
|
||||
f"Year too great: {year_high}",
|
||||
f"Year too early: {year_low}"
|
||||
]
|
||||
|
||||
for exp in expected:
|
||||
assert check_log_string(log_file, exp)
|
||||
43
pytest/test_cmd_arguments.py
Normal file
43
pytest/test_cmd_arguments.py
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import pytest
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from helpers import *
|
||||
from fixtures import *
|
||||
|
||||
cmd = 'python3 updatescannedpics.py'
|
||||
|
||||
@pytest.mark.parametrize("cmd_line", ["-h", "--help"])
|
||||
def test_help(cmd_line):
|
||||
command = f'{cmd} {cmd_line}'.split()
|
||||
output = subprocess.run(command, capture_output=True, encoding='UTF-8')
|
||||
|
||||
assert output.returncode == 1
|
||||
assert output.stdout == "-i Input Directory\n-o Output Directory\n-m My Pre-Configured Directory Layout\nDefaults directory to PWD\n"
|
||||
|
||||
def test_bad_param():
|
||||
bad_options = [char for char in string.ascii_letters]
|
||||
bad_options.remove('h')
|
||||
bad_options.remove('i')
|
||||
bad_options.remove('o')
|
||||
bad_options.remove('m')
|
||||
|
||||
rand_bad_opt = random.choice(bad_options)
|
||||
command = f'{cmd} -{rand_bad_opt}'.split()
|
||||
output = subprocess.run(command, capture_output=True, encoding='UTF-8')
|
||||
|
||||
assert output.returncode == 2
|
||||
assert output.stdout == f"option -{rand_bad_opt} not recognized\n-i Input Directory\n-o Output Directory\n-m My Pre-Configured Directory Layout\nDefaults directory to PWD\n"
|
||||
|
||||
@pytest.mark.parametrize("cmd_line, expected", [("", "Not Using"), ("-m", "Using")])
|
||||
def test_my_format(cmd_line, expected, log_file):
|
||||
command = f'{cmd} {cmd_line}'
|
||||
run_successfull_cmd(command)
|
||||
|
||||
with open(log_file, 'r') as my_log:
|
||||
lines = my_log.readlines()
|
||||
assert f"{expected} pre formated directories" in lines[2]
|
||||
|
||||
|
||||
36
pytest/test_file_paths.py
Normal file
36
pytest/test_file_paths.py
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from helpers import *
|
||||
from fixtures import *
|
||||
|
||||
cmd = 'python3 updatescannedpics.py'
|
||||
|
||||
def test_file_path(log_file):
|
||||
command = f'{cmd}'
|
||||
run_successfull_cmd(command)
|
||||
|
||||
with open(log_file, 'r') as my_log:
|
||||
lines = my_log.readlines()
|
||||
assert "Input Dir = ." in lines[0]
|
||||
assert "Output Dir = ." in lines[1]
|
||||
assert "Not Using pre formated directories" in lines[2]
|
||||
|
||||
@pytest.mark.parametrize("option, log_start", [("-i", "Input Dir = "), ("--indir", "Input Dir = "), ("-o", "Output Dir = "), ("--outdir", "Output Dir = ")])
|
||||
@pytest.mark.parametrize("in_dir_start", ["", "./", "/", "../"])
|
||||
def test_set_indir(option, log_start, in_dir_start, log_file):
|
||||
indir_name = gen_rand_name(12)
|
||||
|
||||
command = f'{cmd} {option} {in_dir_start}{indir_name}'
|
||||
run_successfull_cmd(command)
|
||||
|
||||
expected_start = in_dir_start
|
||||
if len(in_dir_start) == 0:
|
||||
expected_start = './'
|
||||
|
||||
expected_file_path = f"{log_start}{expected_start}{indir_name}"
|
||||
|
||||
assert check_log_string(log_file, expected_file_path)
|
||||
|
||||
69
pytest/test_proper_copy.py
Normal file
69
pytest/test_proper_copy.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from datetime import datetime
|
||||
|
||||
import os
|
||||
import time
|
||||
import piexif
|
||||
import pytest
|
||||
import random
|
||||
import shutil
|
||||
|
||||
from helpers import *
|
||||
from fixtures import *
|
||||
|
||||
cmd = 'python3 updatescannedpics.py'
|
||||
|
||||
def test_diff_in_out(in_out_dirs):
|
||||
in_dir, out_dir = in_out_dirs
|
||||
|
||||
year = random.randint(1900, 2000)
|
||||
month = random.randint(1, 12)
|
||||
day = random.randint(1, 28)
|
||||
index = random.randint(0, 99)
|
||||
|
||||
file_name = f"{year}{month:02d}{day:02d}_{index:02d}.jpg"
|
||||
in_file = os.path.join(in_dir, file_name)
|
||||
out_file = os.path.join(out_dir, file_name)
|
||||
|
||||
shutil.copyfile("pytest/example_pics/u_001.jpg", in_file)
|
||||
|
||||
command = f"{cmd} -i {in_dir} -o {out_dir}"
|
||||
print(command)
|
||||
output = run_successfull_cmd(command)
|
||||
|
||||
exif_dict = piexif.load(out_file)
|
||||
|
||||
test_date = datetime(year, month, day, 0, 0, 0)
|
||||
test_date_str = test_date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
|
||||
assert test_date_str == exif_dict["0th"][piexif.ImageIFD.DateTime].decode('utf-8')
|
||||
assert test_date_str == exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
|
||||
assert test_date_str == exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
|
||||
|
||||
def test_same_in_out(in_out_dirs):
|
||||
in_dir, out_dir = in_out_dirs
|
||||
out_dir = in_dir
|
||||
|
||||
year = random.randint(1900, 2000)
|
||||
month = random.randint(1, 12)
|
||||
day = random.randint(1, 28)
|
||||
index = random.randint(0, 99)
|
||||
|
||||
file_name = f"{year}{month:02d}{day:02d}_{index:02d}.jpg"
|
||||
in_file = out_file = os.path.join(in_dir, file_name)
|
||||
|
||||
shutil.copyfile("pytest/example_pics/u_001.jpg", in_file)
|
||||
|
||||
command = f"{cmd} -i {in_dir} -o {out_dir}"
|
||||
print(command)
|
||||
output = run_successfull_cmd(command)
|
||||
|
||||
exif_dict = piexif.load(out_file)
|
||||
|
||||
test_date = datetime(year, month, day, 0, 0, 0)
|
||||
test_date_str = test_date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
|
||||
assert test_date_str == exif_dict["0th"][piexif.ImageIFD.DateTime].decode('utf-8')
|
||||
assert test_date_str == exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
|
||||
assert test_date_str == exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
|
||||
|
||||
|
||||
136
updatescannedpics.py
Normal file
136
updatescannedpics.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# Adding comment
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import time
|
||||
import getopt
|
||||
import piexif
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
help_str = "-i Input Directory\n-o Output Directory\n-m My Pre-Configured Directory Layout\nDefaults directory to PWD"
|
||||
|
||||
logging.basicConfig(filename='example.log', level=logging.INFO)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hi:o:m", ["help", "indir=", "outdir="])
|
||||
except getopt.GetoptError as err:
|
||||
print(str(err) + f"\n{help_str}")
|
||||
sys.exit(2)
|
||||
|
||||
indir = "."
|
||||
outdir = "."
|
||||
myformat = False
|
||||
for o, a in opts:
|
||||
if o in ("-h", "--help"):
|
||||
print(help_str)
|
||||
exit(1)
|
||||
elif o in ("-i", "--indir"):
|
||||
if a.find('/') == 0 or a.find("./") == 0 or a.find("../") == 0:
|
||||
indir = a
|
||||
else:
|
||||
indir = "./" + a
|
||||
elif o in("-o", "--outdir"):
|
||||
if a.find('/') == 0 or a.find("./") == 0 or a.find("../") == 0:
|
||||
outdir = a
|
||||
else:
|
||||
outdir = "./" + a
|
||||
elif o == "-m":
|
||||
myformat = True
|
||||
|
||||
logging.info("Input Dir = " + indir)
|
||||
logging.info("Output Dir = " + outdir)
|
||||
logging.info(("Using" if myformat else "Not Using") + " pre formated directories")
|
||||
|
||||
folderDate0 = datetime(1800, 1, 1, 0, 0, 0)
|
||||
folderDate1 = datetime(1900, 1, 1, 0, 0, 0)
|
||||
folderDate2 = datetime(1950, 1, 1, 0, 0, 0)
|
||||
folderDate3 = datetime(1980, 1, 1, 0, 0, 0)
|
||||
folderDate4 = datetime(1990, 1, 1, 0, 0, 0)
|
||||
folderDate5 = datetime(1995, 1, 1, 0, 0, 0)
|
||||
folderDate6 = datetime(2000, 1, 1, 0, 0, 0)
|
||||
folderDate7 = datetime(2010, 1, 1, 0, 0, 0)
|
||||
|
||||
for fullpath in glob.glob(indir + "/*.jpg"):
|
||||
logging.info("==========")
|
||||
logging.info("fullpath: " + fullpath)
|
||||
|
||||
filename = os.path.basename(fullpath)
|
||||
|
||||
try:
|
||||
namedate = filename.split("_")[0]
|
||||
date = datetime.strptime(namedate, "%Y%m%d")
|
||||
strdate = date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
except Exception as err:
|
||||
logging.error(f"File name not compatable: {filename} :: {err}")
|
||||
continue
|
||||
|
||||
try:
|
||||
exif_dict = piexif.load(fullpath)
|
||||
logging.debug(exif_dict)
|
||||
|
||||
exif_dict["0th"][piexif.ImageIFD.DateTime]=strdate
|
||||
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal]=strdate
|
||||
exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized]=strdate
|
||||
|
||||
exif_bytes=piexif.dump(exif_dict)
|
||||
|
||||
piexif.insert(exif_bytes, fullpath)
|
||||
|
||||
modTime = time.mktime(date.timetuple())
|
||||
os.utime(fullpath, (modTime, modTime))
|
||||
|
||||
exif_dict = piexif.load(fullpath)
|
||||
logging.debug(exif_dict)
|
||||
|
||||
copy = False
|
||||
endyear = 2006
|
||||
foldername = outdir
|
||||
if myformat:
|
||||
if date > folderDate7:
|
||||
logging.error("Year too great: " + str(date.year))
|
||||
continue
|
||||
elif date < folderDate0:
|
||||
logging.error("Year too early: " + str(date.year))
|
||||
continue
|
||||
|
||||
if date < folderDate1:
|
||||
foldername = "1800-1899"
|
||||
elif date < folderDate2:
|
||||
foldername = "1900-1949"
|
||||
elif date < folderDate3:
|
||||
foldername = "1950-1979"
|
||||
elif date < folderDate4:
|
||||
foldername = "1980-1990"
|
||||
elif date < folderDate5:
|
||||
foldername = "1990-1994"
|
||||
elif date < folderDate6:
|
||||
foldername = "1995-1999"
|
||||
else:
|
||||
foldername = date.year
|
||||
|
||||
foldername = foldername + str(foldername)
|
||||
|
||||
logging.info(f"Outfolder: {foldername}")
|
||||
|
||||
if not os.path.isdir(foldername):
|
||||
os.mkdir(foldername)
|
||||
|
||||
dest = foldername + "/" + filename
|
||||
|
||||
while os.path.exists(dest):
|
||||
parts = re.split("[_\.]", filename)
|
||||
parts[1] = int(parts[1]) + 1
|
||||
filename = "%s_%02d.%s" % (parts[0], parts[1], parts[2])
|
||||
dest = foldername + "/" + filename
|
||||
|
||||
shutil.copyfile(fullpath, dest)
|
||||
os.utime(dest, (modTime, modTime))
|
||||
except Exception as err:
|
||||
logging.error(f"Bad file format for {filename} :: {err}")
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user