Die Runde Stunde
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

958 lines
29 KiB

import argparse
from geopy.geocoders import Nominatim
import math
import os
import re
import shutil
# from typing_extensions import TypeVarTuple
from PIL import Image, ImageOps
from datetime import datetime
from PIL.ExifTags import TAGS
WORK_DIR = 'work'
HUGO_ROOT = '..'
HUGO_STATIC_DIR = '../static'
IMAGE_ROOT_DIR = 'images/gallery'
DROPIN_DIR = '../../dropin'
FULLS_DIR = 'fulls'
SVGS_DIR = 'svgs'
THUMBS_DIR = 'thumbs'
IMAGES_DIR = '../../images'
SPOT_DIR = 'data/spot'
HOME_LAT = 50.18573
HOME_LON = 9.14265
COLOR_CIRCLE = "#cc9966"
COLOR_DOT = "#996600"
# Radius around Home in meters
RADIUS = 15000
# pixel = meter * scale
SCALE = 0.1
# Number oy images per spot
SPOTS_SIZE = 9
images = []
bundles = []
# program arguments
args = None
def create_banner_svg():
"""
Create banner SVG files based on the build bundles.
All things will be done in the locale work folder.
SVG has following coordinate system:
(0,0)
+---------------------> +x
|
| +--------------+ (2*homx)+margx
| | |
| | (homx,homy) |
| | + |
| | |
| | |
| +--------------+
| (2*homy)+margy
|
V
+y
"""
# print(f"Creating banner SVG file")
# Circle should not hit image edges
margin = int(round(RADIUS / 20.0))
# All calculations in a home centered coordinte system (not in a SVG coordinate system),
# unscaled and without margin
# Initialize
xmax = RADIUS
ymax = RADIUS
xmin = -xmax
ymin = -ymax
# find canvas size (without margin): all image points have to be on the canvas
# TODO Not useful as long the points will not be shown
for image in images:
if image['x'] > xmax:
xmax = image['x']
if image['x'] < xmin:
xmin = image['x']
if image['y'] > ymax:
ymax = image['y']
if image['y'] < ymin:
ymin = image['y']
radius15 = RADIUS
radiusCenter = int(round(RADIUS * 0.02))
offsetx = -xmin + margin
offsety = -ymin + margin
opacbundlemax = 0.4
opacbundlemin = 0.0001
opacbundlestep = round((opacbundlemax - opacbundlemin) / len(bundles),4)
# opacimagemax = 0.8
# opacimagemin = 0.2
radius_dot =int(round(RADIUS * 0.01))
print(f"Creating banner SVG, corners=({xmin},{ymin}),({xmax},{ymax})")
name = os.path.join(WORK_DIR, IMAGE_ROOT_DIR, SVGS_DIR, "banner.svg")
with open(name, 'w') as file:
file.write(f"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
file.write(f"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
file.write(f"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n")
file.write(f"viewBox=\"0 0 {scale(offsetx + xmax + margin)} {scale(offsety + ymax + margin)}\" xml:space=\"preserve\">\n")
file.write(f"<circle cx=\"{scale(offsetx)}\" cy=\"{scale(offsety)}\" r=\"{scale(radius15)}\" fill=\"#eeeeee\"> \n ")
file.write(f"</circle>\n")
file.write(f"<circle cx=\"{scale(offsetx)}\" cy=\"{scale(offsety)}\" r=\"{scale(radiusCenter)}\" fill=\"#ffffff\"> \n ")
file.write(f"</circle>\n")
for i, bundle in enumerate(bundles):
log(f" bundle {i} with {len(bundle['images']) + 1} images")
file.write(f"<circle cx=\"{scale(offsetx + bundle['x'])}\" cy=\"{scale(offsety + bundle['y'])}\" " +
f"r=\"{scale(bundle['radius'] + radius_dot)}\" opacity=\"{opacbundlemax - (opacbundlestep * i)}\" fill=\"{COLOR_CIRCLE}\">\n")
file.write(f"</circle>")
file.write(f"</svg>")
def create_svgs():
"""
Create SVGs files based on the build bundles.
All things will be done in the locale work folder.
SVG has following coordinate system:
(0,0)
+---------------------> +x
|
| +--------------+ (2*homx)+margx
| | |
| | (homx,homy) |
| | + |
| | |
| | |
| +--------------+
| (2*homy)+margy
|
V
+y
"""
print(f"create_svgs()")
# Circle should not hit image edges
margin = int(round(RADIUS / 10.0))
# All calculations in a home centered coordinte system (not in a SVG coordinate system),
# unscaled and without margin
# Initialize
opacbundlemax = 0.4
opacbundlemin = 0.0001
opacbundlestep = round((opacbundlemax - opacbundlemin) / len(bundles),4)
opacimagemax = 0.8
opacimagemin = 0.2
opacmidmax = 0.9
opacmidmin = 0.4
radius_dot =int(round(RADIUS * 0.01))
for i, bundle in enumerate(bundles):
# log(f"create_svgs() bundle {bundle['x']} {bundle['y']} {bundle['radius']}")
# Always show center
xmax = max(bundle['x'] + bundle['radius'], 0)
ymax = max(bundle['y'] + bundle['radius'], 0)
xmin = min(bundle['x'] - bundle['radius'], 0)
ymin = min(bundle['y'] - bundle['radius'], 0)
log(f" bundle: {bundle['image']['date']}: ({xmin},{ymin},{xmax},{ymax})")
# log(f" ({xmax-xmin},{ymax-ymin}) - {round((xmax-xmin) / (ymax-ymin),1)}, ")
# All images incl. main bundle image
images = bundle['images'].copy()
images.append(bundle['image'])
# find canvas size (without margin): all image points have to be on the canvas
for image in images:
if image['x'] > xmax:
xmax = image['x']
if image['x'] < xmin:
xmin = image['x']
if image['y'] > ymax:
ymax = image['y']
if image['y'] < ymin:
ymin = image['y']
# log(f" now ({xmin},{ymin},{xmax},{ymax}) / ({xmax-xmin},{ymax-ymin}) - {round((xmax-xmin) / (ymax-ymin),1)}, ")
# Original aspect ratio: 16:10
ratio = 1.8
ytarget = ( xmax - xmin ) / ratio
# Expand y
if ( ymax - ymin ) < ytarget:
# log(f" Expanding y with ytarget={ytarget} ")
ymaxold = ymax
ymax = int(round(ymax + ((ytarget - ( ymax - ymin )) / 2)))
ymin = int(round(ymin - ((ytarget - ( ymaxold - ymin )) / 2)))
# Expand x
else:
xtarget = int(round(( ymax - ymin ) * ratio))
# log(f" Expanding x with xtarget={xtarget} ")
xmaxold = xmax
xmax = int(round(xmax + ((xtarget - ( xmax - xmin )) / 2)))
xmin = int(round(xmin - ((xtarget - ( xmaxold - xmin )) / 2)))
# log(f" -->({xmin},{ymin}),({xmax},{ymax}) ")
# log(f" ({xmax-xmin},{ymax-ymin}) - {round((xmax-xmin) / (ymax-ymin),1)}, ")
radius15 = RADIUS
home_radius_center = int(round(RADIUS * 0.02))
offsetx = -xmin + margin
offsety = -ymin + margin
name = os.path.join(WORK_DIR, IMAGE_ROOT_DIR, SVGS_DIR, f"{bundle['image']['date']}.svg")
log(f" --> Spot SVG, corners=({xmin},{ymin}),({xmax},{ymax}")
with open(name, 'w') as file:
file.write(f"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
file.write(f"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
file.write(f"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n")
# file.write(f" width=\"1440\" heigth=\"900\" \n")
file.write(f"viewBox=\"0 0 {scale(offsetx + xmax + margin)} {scale(offsety + ymax + margin)}\" xml:space=\"preserve\">\n")
file.write(f"<circle cx=\"{scale(offsetx)}\" cy=\"{scale(offsety)}\" r=\"{scale(radius15)}\" fill=\"#eeeeee\"> \n ")
file.write(f"</circle>\n")
file.write(f"<circle cx=\"{scale(offsetx)}\" cy=\"{scale(offsety)}\" r=\"{scale(home_radius_center)}\" fill=\"#ffffff\"> \n ")
file.write(f"</circle>\n")
file.write(f"<circle cx=\"{scale(offsetx + bundle['x'])}\" cy=\"{scale(offsety + bundle['y'])}\" " +
f"r=\"{scale(bundle['radius'] + radius_dot)}\" opacity=\"{opacbundlemax - (opacbundlestep * i)}\" fill=\"{COLOR_CIRCLE}\">\n")
r_from = scale((bundle['radius'] + radius_dot) * 0.995)
r_to = scale((bundle['radius'] + radius_dot) * 1.005)
file.write(f"<animate id=\"step1\" \n")
file.write(f"begin=\"0s;step2.end\" dur=\"1s\" \n")
file.write(f"attributeName=\"r\" from=\"{r_from}\" to=\"{r_to}\" \n")
file.write(f"/>\n")
file.write(f"<animate id=\"step2\" \n")
file.write(f"begin=\"step1.end\" dur=\"1s\"\n")
file.write(f"attributeName=\"r\" from=\"{r_to}\" to=\"{r_from}\" \n")
file.write(f"/>\n")
file.write(f"</circle>")
# Center dot of bundle circle
file.write(f"<circle cx=\"{scale(offsetx + bundle['x'])}\" cy=\"{scale(offsety + bundle['y'])}\" " +
f"r=\"{scale(bundle['radius'] * 0.02 )}\" opacity=\"{opacmidmax - (opacbundlestep * i)}\" fill=\"#ffffff\">\n")
file.write(f"</circle>")
opacimagestep = round((opacimagemax - opacimagemin) / len(images),2)
for i, image in enumerate(images):
file.write(f"<circle cx=\"{scale(offsetx + image['x'])}\" cy=\"{scale(offsety + image['y'])}\" " +
f"r=\"{scale(radius_dot)}\" opacity=\"{opacimagemax - (opacimagestep * i)}\" fill=\"{COLOR_DOT}\">\n")
file.write(f"</circle>")
file.write(f"</svg>")
print("..done")
def scale(value):
return int(value * SCALE)
def get_exif(fn, warns):
"""
Return dictionary with EXIF data and the optinal title.
"""
ret = {}
with Image.open(fn) as i:
info = i._getexif()
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
ret[decoded] = value
xmp = i.getxmp()
title = False
try:
title = xmp['xmpmeta']['RDF']['Description'][0]['title']['Alt']['li']['text']
# log(f"Found title for {fn}: {ret['title']}")
except:
pass
if not title:
try:
title = xmp['xmpmeta']['RDF']['Description'][0]['title']
# log(f"Found title for {fn}: {ret['title']}")
except:
pass
if title:
# log(f"{fn} Title={title}")
ret['title'] = title
else:
if args.verbose:
warns.append(f"No title found: {fn}")
return ret
def get_geo(gpsInfo):
lat = 0
lon = 0
lat = (((gpsInfo[2][2] / 60.0) + gpsInfo[2][1]) / 60.0) + gpsInfo[2][0]
if gpsInfo[1] == 'S':
lat = -lat
lon = (((gpsInfo[4][2] / 60.0) + gpsInfo[4][1]) / 60.0) + gpsInfo[4][0]
if gpsInfo[3] == 'W':
lon = -lon
# log(f"get_geo: gpsInfo={gpsInfo[1]}, {gpsInfo[2]}, {gpsInfo[3]}, {gpsInfo[4]} --> lat={lat}, lon={lon}")
return lat, lon
def get_pos(lat, lon):
"""
Calculates the distance in meters between the given geo point and the HOME.
Calculation is based on https://www.kompf.de/gps/distcalc.html
Returns a tuple (x, y) with distance in integer meters, based on ths SVG cordinate system
| -y
|
|(0,0)
-x -------+------- +x
|
|
| +y
"""
x = int(round(71.5 * 1000.0 * (lon - HOME_LON)))
y = int(round(111.3 * 1000.0 * (HOME_LAT - lat)))
# log(f"get_pos: {lon} - {HOME_LON} --> x={x} ")
# log(f"get_pos: {lat} - {HOME_LAT} --> y={y} ")
return x, y
def resolve_pos(x, y):
"""
Reverse calculation for get_pos()
"""
lon = round((x / (71.5 * 1000)) + HOME_LON, 6)
lat = round(HOME_LAT - (y / (111.3 * 1000)), 6)
log(f"resolve_pos: ({x},{y}) --> {lat}, {lon} ")
return lat, lon
def create_image_item(dir, file, warns):
"""
Create image dictionary for the given image file in the given directory. Both must exists.
Returns dectionary with image item or, in error case, false.
"""
filename = os.path.join(dir, file)
target_filename = os.path.join(IMAGES_DIR, file)
tags = get_exif(filename, warns)
# Invalid items gpsInfo=, (nan, nan, nan), , (nan, nan, nan)
if 'GPSInfo' in tags and (tags['GPSInfo'][1] == 'N' or tags['GPSInfo'][1] == 'S'):
lat, lon = get_geo(tags['GPSInfo'])
else:
lat, lon = (HOME_LAT, HOME_LON)
warns.append(f"No GPS data: {file}")
return False
x, y = get_pos(lat, lon)
# log(f"{name}")
format = "%Y:%m:%d %H:%M:%S"
dt = datetime.strptime(tags['DateTime'], format)
image = dict(dt=dt, date=dt.strftime('%Y%m%d'),name=file, filename=target_filename, tags=tags, lat=lat, lon=lon, x=x, y=y)
return image
def format_image(name):
"""
Create full and thumb for one image from the dropin folder
All things will be done in the locale work folder.
Returns True in success case, otherwise False
"""
thumb_size = (360, 360)
full_width = 1900
filename = os.path.join(DROPIN_DIR, name)
try:
with Image.open(filename) as im:
# Must be rotated by exif Orientation, otherwise thumb and full will not be
# shown the image in the correct rotation
im = ImageOps.exif_transpose(im)
# Size of the image in pixels (size of original image)
# (This is not mandatory)
width, height = im.size
# Setting the points for cropped image
if width > height:
offset = int(round((width - height) / 2))
left = offset
top = 0
right = height + offset
bottom = height
else:
offset = int(round((height - width) / 2))
left = 0
top = offset
right = width
bottom = width + offset
# Cropped image of above dimension
# (It will not change original image)
thumb = im.crop((left, top, right, bottom))
thumb.thumbnail(thumb_size)
thumb.save(os.path.join(WORK_DIR, IMAGE_ROOT_DIR, THUMBS_DIR, name), "JPEG")
concat = full_width/float(im.size[0])
size = int((float(im.size[1])*float(concat)))
# print(f"concat,size={concat}, {size}")
out = im.resize((full_width,size), Image.ANTIALIAS)
out.save(os.path.join(WORK_DIR, IMAGE_ROOT_DIR, FULLS_DIR, name), "JPEG")
except OSError:
warn(f"\ncannot convert image {filename}! ")
return False
return True
def process_images():
"""
Process all images and create fulls and thumbnails from the dropin folder.
Move the processed images into /images and rebuild hugo content
All things will be done in the locale folder (work, dropin, images)
"""
log(f"Processing images from '{DROPIN_DIR}'")
global images
images = []
p = re.compile('\d{8}.*\.(jpg|jpeg)', re.IGNORECASE)
new_files = [f for f in os.listdir(DROPIN_DIR)
if os.path.isfile(os.path.join(DROPIN_DIR, f)) and
p.match(f)]
old_files = [f for f in os.listdir(IMAGES_DIR)
if os.path.isfile(os.path.join(IMAGES_DIR, f)) and
p.match(f)]
log(f"Found {len(new_files)} images in {DROPIN_DIR} and {len(old_files)} images in {IMAGES_DIR}")
if not new_files and not old_files:
exit_with_good_bye(f"No images found.")
cnt = 0
err = 0
warns =[]
for name in new_files:
cnt += 1
print(f".", end="", flush=True)
# Can print a warning
image = create_image_item(DROPIN_DIR, name, warns)
if not image is False:
# Can print a warning
formatted = format_image(name)
if image is False or formatted is False:
char = "x"
err += 1
else:
char = '.'
shutil.move( os.path.join(DROPIN_DIR, name), image['filename'])
images.append(image)
if cnt % 10 == 0:
print(f"\b{char} ({cnt})", end="\n", flush=True)
else:
print(f"\b{char}", end="", flush=True)
for name in old_files:
cnt += 1
image = create_image_item(IMAGES_DIR, name, warns)
# This should never happens
if image is False:
char = "X"
err += 1
else:
char = 'o'
images.append(image)
if cnt % 10 == 0:
print(f"\b{char} ({cnt})", end="\n", flush=True)
else:
print(f"\b{char}", end="", flush=True)
print("\n")
if warns:
for message in warns:
warn(message)
if not images:
exit_with_good_bye("No valid images.")
print(f"Total processed {len(new_files)} new and {len(old_files)} old images: {len(images)} OK, {err} ignored.")
def create_bundles():
"""
Bundle images to spots by global images list. Output
is a list of spots with tpe dict:
{
image=main_image,
images=list_of_satellite_images,
}
All things will be done in the locale work folder.
"""
print(f"Create bundle images")
global bundles
bundles = []
if len(images)==0:
exit_on_error("No images found to bundle")
end = False
left_images = images
while not end:
bundle = create_bundle(left_images)
if bundle is None:
end = True
break
bundles.append(bundle)
log(f"removing main image={bundle['image']['name']}")
left_images.remove(bundle['image'])
for image in bundle['images']:
log(f"removing bundle image={image['name']}")
left_images.remove(image)
# log(f"len={len(left_images)}")
print(f"Processed {len(bundles)} bundles.")
def calculate_circle(image, _images):
"""
Find the smallest circle for the images by finding the two point that are furhtermost from each other.
Returns dict with x, y as distance from HOME and radius, all in meters as integer.
"""
# circle dimension. Initialization for case of single image bundle
# 100.0 is no magic number, it's just a value to show something around the single image point
max_radius = 100.0
x = image['x']
y = image['y']
images = _images.copy()
images.append(image)
log(f"calculate_circle for {len(images)} images", end="")
for i, item1 in enumerate(images):
# log(f"image {i}: {item1['x'],item1['y']}")
for item2 in images[(i+1):]:
x_dist = item2['x'] - item1['x']
y_dist = item2['y'] - item1['y']
radius = int(round(math.sqrt(x_dist**2 + y_dist**2) / 2.0))
if max_radius >= radius:
continue
max_radius = radius
x = int(round(item1['x'] + (x_dist/2.0)))
y = int(round(item1['y'] + (y_dist/2.0)))
# log(f"image {i}: {item1['x'],item1['y']}")
ret = dict(x=x,y=y,radius=max_radius)
print(f"...done: {ret}")
return ret
def create_bundle(_images):
if len(_images) == 0:
return None
# Find newest
images.sort(key = lambda item: item.get("dt"))
# Work with copy to prevent from
newest = images[-1]
log(f"Create bundle for newest={newest['name']}")
# Add distance
for image in images:
image['dist'] = int(round(math.sqrt(((newest['x'] - image['x']) ** 2) + ((newest['y'] - image['y']) ** 2))))
# find nearbys. Be careful: newest must not be at first place, if there is a second image with dist == 0
images.sort(key = lambda item: item.get("dist"))
# for image in images:
# print(f"images: image={image['name']} dist={image['dist']}")
bundle_images = []
# Exclde newest from list (must not be at first place, see above)
for image in [i for i in images if i['name'] != newest['name']][:SPOTS_SIZE-1]:
log(f"image={image['name']}")
bundle_images.append(image)
circle = calculate_circle(newest, bundle_images)
bundle_images.sort(key = lambda item: item.get("date"), reverse=True)
ret = dict(image=newest, images=bundle_images, x=circle['x'], y=circle['y'], radius=circle['radius'])
log(f"Bundle created: x={ret['x']}, y={ret['y']}, radius={ret['radius']}")
# print(f"newest ={ret['image']['date']}")
# for i in ret['images']:
# print(f"image ={i['dist']} , {i['date']}")
return ret
def get_address(latitude, longitude, language="de"):
coordinates = f"{latitude}, {longitude}"
# Switch off for develop pursoses
if args.location_mockup:
return "Entenhausen"
try:
geolocator = Nominatim(user_agent="de.kollegen.dierundestunde")
location = geolocator.reverse(coordinates)
log(f"get_address() {location}")
parts = location.address.split(", ")
if len(parts) == 8:
return f"{parts[1]}, {parts[2]}"
elif len(parts) >= 2:
return f"{parts[0]}, {parts[1]}"
else:
return location.address
except:
print("geolocator failed")
return ""
def create_spots():
"""
Create all spots content based on the build bundles.
All things will be done in the locale work folder.
"""
print(f"Creating spots", end="")
i = 0
for bundle in bundles:
i += 1
filename = os.path.join(WORK_DIR, SPOT_DIR, f"{bundle['image']['date']}.yml")
if (i % 2) == 0:
orient = "orient-left"
else:
orient = "orient-right"
# Get time range
oldest = bundle['image']['date']
for image in bundle['images']:
if image['date'] < oldest:
oldest = image['date']
start = datetime.strptime(oldest, "%Y%m%d")
end = datetime.strptime(bundle['image']['date'], "%Y%m%d")
diff = end.date() - start.date()
# Center
lon, lat = resolve_pos(bundle['x'], bundle['y'])
address = get_address(lon, lat)
print(f"address={address}")
# print(f"creating {filename}")
with open(filename, 'w') as file:
file.write(f"title: {bundle['image']['date'] }\n")
file.write(f"headerstyle: \"style1 {orient} content-align-left image-position-center onscroll-image-fade-in\"\n")
file.write(f"style: \"style2 medium lightbox onscroll-fade-in\"\n")
file.write(f"header: \"{IMAGE_ROOT_DIR}/{SVGS_DIR}/{bundle['image']['date']}.svg\"\n")
file.write(f"content: |\n")
file.write(f" &#10137; {address}<br>\n")
file.write(f" R = {bundle['radius']} m<br>\n")
file.write(f" &Delta; = {diff.days} Tage\n")
file.write(f"circle:\n")
file.write(f" - pos: \"({bundle['x']}, {bundle['y']})\"\n")
file.write(f" radius: \"{bundle['radius']}\"\n")
file.write(f"pictures:\n")
write_picture_yaml(file, bundle['image'])
for image in bundle['images']:
write_picture_yaml(file, image)
print(f".", end="")
print(f" OK {len(bundles)} files created.")
def write_picture_yaml(file, image):
if 'title' in image['tags']:
log(f"write_picture_yaml {image['date']} title={image['tags']['title']}")
file.write(f" - title: \"{image['tags']['title']}\"\n")
file.write(f" subtitle: \"{image['date']}\"\n")
else:
file.write(f" - title: \"{image['date']}\"\n")
file.write(f" content: \"distance={image['dist']} m\"\n")
file.write(f" distance: \"{image['dist']}\"\n")
file.write(f" geo: \"{image['lon']}, {image['lat']}\"\n")
file.write(f" pos: \"({str(image['x'])}, {str(image['y'])})\"\n")
file.write(f" image: \"{os.path.join(IMAGE_ROOT_DIR, FULLS_DIR, image['name'])}\"\n")
file.write(f" thumb: \"{os.path.join(IMAGE_ROOT_DIR, THUMBS_DIR, image['name'])}\"\n")
file.write(f" button: \"Ansehen\"\n")
def activate():
"""
Active spots incl images from work folder to the hugo project.
Old spots will be removed.
This should only be processed in success case.
"""
print(f"Update hugo content", end="")
spot_dir = os.path.join(HUGO_ROOT, SPOT_DIR)
image_root_dir = os.path.join(HUGO_STATIC_DIR, IMAGE_ROOT_DIR)
thumbs_dir = os.path.join(image_root_dir, THUMBS_DIR)
fulls_dir = os.path.join(image_root_dir, FULLS_DIR)
svgs_dir = os.path.join(image_root_dir, SVGS_DIR)
if os.path.isdir(spot_dir):
shutil.rmtree(spot_dir)
shutil.copytree(os.path.join(WORK_DIR, SPOT_DIR), spot_dir)
if not os.path.isdir(image_root_dir):
os.makedirs(image_root_dir)
if os.path.isdir(svgs_dir):
shutil.rmtree(svgs_dir)
shutil.copytree(os.path.join(WORK_DIR, IMAGE_ROOT_DIR, SVGS_DIR), svgs_dir)
if os.path.isdir(thumbs_dir):
shutil.rmtree(thumbs_dir)
shutil.copytree(os.path.join(WORK_DIR, IMAGE_ROOT_DIR, THUMBS_DIR), thumbs_dir)
if os.path.isdir(fulls_dir):
shutil.rmtree(fulls_dir)
shutil.copytree(os.path.join(WORK_DIR, IMAGE_ROOT_DIR, FULLS_DIR), fulls_dir)
print("...done")
def log(message, end="\n", flush=False):
if args.verbose:
print(message, end=end, flush=flush)
def init_work():
"""
Setup working directory. This should be the very first step.
Fulls and thumbs directories will only be cleared with reset argument .
"""
log(f"Initialize directories")
# The very firs time
if not os.path.exists(DROPIN_DIR):
print(f"Creating missing directory '{DROPIN_DIR}'")
os.mkdir(DROPIN_DIR)
if not os.path.exists(IMAGES_DIR):
print(f"Creating missing directory '{IMAGES_DIR}'")
os.mkdir(IMAGES_DIR)
if not os.path.exists(WORK_DIR):
print(f"Creating missing directory structure '{WORK_DIR}'")
os.mkdir(WORK_DIR)
fulls = os.path.join(WORK_DIR, IMAGE_ROOT_DIR, FULLS_DIR)
if args.rebuild_all and os.path.exists(fulls):
print(f"Removing {fulls}")
shutil.rmtree(fulls)
# The very firs time or in case of a rebuild all
if not os.path.exists(fulls):
os.makedirs(fulls)
thumbs = os.path.join(WORK_DIR, IMAGE_ROOT_DIR, THUMBS_DIR)
if args.rebuild_all and os.path.exists(thumbs):
print(f"Removing {thumbs}")
shutil.rmtree(thumbs)
# The very first time or in case of a rebuild all
if not os.path.exists(thumbs):
log(f"Creating {thumbs}")
os.makedirs(thumbs)
# Reset always
svgs = os.path.join(WORK_DIR, IMAGE_ROOT_DIR, SVGS_DIR)
if os.path.isdir(svgs):
log(f"Clearing {svgs}")
shutil.rmtree(svgs)
os.makedirs(svgs)
# Reset always
spot = os.path.join(WORK_DIR, SPOT_DIR)
if os.path.isdir(spot):
log(f"Clearing {spot}")
shutil.rmtree(spot)
os.makedirs(spot)
# Rebuild all: move existing original images to dropin
if args.rebuild_all:
p = re.compile('\d{8}.*\.(jpg|jpeg)', re.IGNORECASE)
old_files = [f for f in os.listdir(IMAGES_DIR)
if os.path.isfile(os.path.join(IMAGES_DIR, f)) and
p.match(f)]
if len(old_files) == 0:
print(f"No files im {IMAGES_DIR}")
else:
print(f"Moving {len(old_files)} files from {IMAGES_DIR} to {DROPIN_DIR}", end="")
for file in old_files:
shutil.move(os.path.join(IMAGES_DIR, file), os.path.join(DROPIN_DIR, file))
log("..done")
log("Initialization done")
def exit_on_error(message):
print(f"ERROR {message}")
exit(1)
def exit_with_good_bye(message):
print(f"{message} --- Good bye ---")
exit(0)
def warn(message):
print(f"WARN {message}")
def do_it():
init_work()
process_images()
create_bundles()
create_svgs()
create_banner_svg()
create_spots()
activate()
print("Congratulations! Generation done.")
def parse_args():
global args
parser = argparse.ArgumentParser(description="Update drs by adding new images and create spot files. " +
"This script must be called from its directory 'redaktion'. Usage example: Add new images into ../../dropin directory and call without parameters. This images will be moved to ../../images. ")
parser.add_argument("-v", "--verbose", action="store_true",
help="Increase output verbosity.")
parser.add_argument("-a", "--rebuild_all", action="store_true",
help="Process images in /images in additional to the /dropin images")
parser.add_argument("-l", "--location_mockup", action="store_true",
help="Supress cost intensive location service. Mock all locations. Just for developer purposes.")
args = parser.parse_args()
if __name__ == '__main__':
parse_args()
do_it()