mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-06-29 03:26:16 +00:00
Formatted metadata
Add API for metadata Load more images if page is resized Adjusted animations
This commit is contained in:
parent
651fd8aa49
commit
da1579555b
9 changed files with 742 additions and 86 deletions
|
@ -1,8 +1,9 @@
|
||||||
from flask import Blueprint, render_template, current_app, send_from_directory, send_file, request, g, abort, flash
|
from flask import Blueprint, render_template, current_app, send_from_directory, send_file, request, g, abort, flash, jsonify
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from gallery.auth import login_required
|
from gallery.auth import login_required
|
||||||
from gallery.db import get_db
|
from gallery.db import get_db
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
from . import metadata as mt
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
@ -10,6 +11,14 @@ from uuid import uuid4
|
||||||
blueprint = Blueprint('viewsbp', __name__, url_prefix='/api')
|
blueprint = Blueprint('viewsbp', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
|
||||||
|
def human_size(num, suffix="B"):
|
||||||
|
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
||||||
|
if abs(num) < 1024.0:
|
||||||
|
return f"{num:3.1f}{unit}{suffix}"
|
||||||
|
num /= 1024.0
|
||||||
|
return f"{num:.1f}Yi{suffix}"
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/uploads/<file>/<int:quality>', methods=['GET'])
|
@blueprint.route('/uploads/<file>/<int:quality>', methods=['GET'])
|
||||||
def uploads(file, quality):
|
def uploads(file, quality):
|
||||||
# If quality is 0, return original file
|
# If quality is 0, return original file
|
||||||
|
@ -106,4 +115,18 @@ def remove(id):
|
||||||
abort(500)
|
abort(500)
|
||||||
|
|
||||||
flash(['Image was all in Le Head!', 1])
|
flash(['Image was all in Le Head!', 1])
|
||||||
return 'Gwa Gwa'
|
return 'Gwa Gwa'
|
||||||
|
|
||||||
|
@blueprint.route('/metadata/<int:id>', methods=['GET'])
|
||||||
|
def metadata(id):
|
||||||
|
img = get_db().execute(
|
||||||
|
'SELECT file_name, description, alt FROM posts WHERE id = ?',
|
||||||
|
(id, )).fetchone()
|
||||||
|
|
||||||
|
if img is None:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
exif = mt.metadata.yoink(os.path.join(current_app.config['UPLOAD_FOLDER'], img['file_name']))
|
||||||
|
filesize = os.path.getsize(os.path.join(current_app.config['UPLOAD_FOLDER'], img['file_name']))
|
||||||
|
|
||||||
|
return jsonify({'metadata': exif, 'filesize': {'bytes': filesize, 'human': human_size(filesize)}})
|
|
@ -5,17 +5,24 @@ from werkzeug.utils import secure_filename
|
||||||
from gallery.auth import login_required
|
from gallery.auth import login_required
|
||||||
from gallery.db import get_db
|
from gallery.db import get_db
|
||||||
|
|
||||||
|
from . import metadata as mt
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL.ExifTags import TAGS
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
from datetime import datetime
|
||||||
|
dt = datetime.now()
|
||||||
dt = datetime.datetime.now()
|
|
||||||
|
|
||||||
blueprint = Blueprint('gallery', __name__)
|
blueprint = Blueprint('gallery', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def human_size(num, suffix="B"):
|
||||||
|
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
||||||
|
if abs(num) < 1024.0:
|
||||||
|
return f"{num:3.1f}{unit}{suffix}"
|
||||||
|
num /= 1024.0
|
||||||
|
return f"{num:.1f}Yi{suffix}"
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/')
|
@blueprint.route('/')
|
||||||
def index():
|
def index():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
@ -34,40 +41,19 @@ def image(id):
|
||||||
if image is None:
|
if image is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
# Get exif data from image
|
exif = mt.metadata.yoink(os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name']))
|
||||||
file = Image.open(
|
file_size = human_size(os.path.getsize(os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name'])))
|
||||||
os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name']))
|
|
||||||
raw_exif = file.getexif()
|
|
||||||
human_exif = {}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for tag in raw_exif:
|
width = exif['File']['Width']['value']
|
||||||
name = TAGS.get(tag, tag)
|
height = exif['File']['Height']['value']
|
||||||
value = raw_exif.get(tag)
|
except:
|
||||||
|
try:
|
||||||
|
width, height = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name'])).size
|
||||||
|
except:
|
||||||
|
width, height = 0, 0
|
||||||
|
|
||||||
if isinstance(value, bytes):
|
return render_template('image.html', image=image, exif=exif, file_size=file_size, width=width, height=height)
|
||||||
value = value.decode()
|
|
||||||
|
|
||||||
human_exif[name] = value
|
|
||||||
except Exception as e:
|
|
||||||
human_exif = False
|
|
||||||
|
|
||||||
def human_size(num, suffix="B"):
|
|
||||||
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
|
||||||
if abs(num) < 1024.0:
|
|
||||||
return f"{num:3.1f}{unit}{suffix}"
|
|
||||||
num /= 1024.0
|
|
||||||
return f"{num:.1f}Yi{suffix}"
|
|
||||||
|
|
||||||
size = os.path.getsize(
|
|
||||||
os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name']))
|
|
||||||
|
|
||||||
# All in le head
|
|
||||||
return render_template('image.html',
|
|
||||||
image=image,
|
|
||||||
exif=human_exif,
|
|
||||||
file=file,
|
|
||||||
size=human_size(size))
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/group')
|
@blueprint.route('/group')
|
||||||
|
|
521
gallery/metadata.py
Normal file
521
gallery/metadata.py
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
import PIL
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.ExifTags import TAGS, GPSTAGS
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class metadata:
|
||||||
|
def yoink(filename):
|
||||||
|
exif = metadata.getFile(filename)
|
||||||
|
|
||||||
|
if exif:
|
||||||
|
formatted = metadata.format(exif)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return metadata.deleteEmpty(formatted)
|
||||||
|
|
||||||
|
def deleteEmpty(dict):
|
||||||
|
new_dict = {}
|
||||||
|
|
||||||
|
for section in dict:
|
||||||
|
tmp = {}
|
||||||
|
for value in dict[section]:
|
||||||
|
if dict[section][value]['raw'] != None:
|
||||||
|
if isinstance(dict[section][value]['raw'], PIL.TiffImagePlugin.IFDRational):
|
||||||
|
dict[section][value]['raw'] = dict[section][value]['raw'].__float__()
|
||||||
|
elif isinstance(dict[section][value]['raw'], bytes):
|
||||||
|
dict[section][value]['raw'] = dict[section][value]['raw'].decode('utf-8')
|
||||||
|
|
||||||
|
tmp[value] = dict[section][value]
|
||||||
|
|
||||||
|
|
||||||
|
if len(tmp) > 0:
|
||||||
|
new_dict[section] = tmp
|
||||||
|
|
||||||
|
return new_dict
|
||||||
|
|
||||||
|
def getFile(filename):
|
||||||
|
try:
|
||||||
|
file = Image.open(filename)
|
||||||
|
raw = file._getexif()
|
||||||
|
exif = {}
|
||||||
|
|
||||||
|
for tag, value in TAGS.items():
|
||||||
|
|
||||||
|
if tag in raw:
|
||||||
|
data = raw[tag]
|
||||||
|
else:
|
||||||
|
data = None
|
||||||
|
|
||||||
|
exif[value] = {"tag": tag, "raw": data}
|
||||||
|
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
return exif
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format(raw):
|
||||||
|
exif = {}
|
||||||
|
|
||||||
|
exif['Photographer'] = {
|
||||||
|
'Artist': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw["Artist"]["raw"]
|
||||||
|
},
|
||||||
|
'Comment': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw["UserComment"]["raw"]
|
||||||
|
},
|
||||||
|
'Description': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw["ImageDescription"]["raw"]
|
||||||
|
},
|
||||||
|
'Date Digitized': {
|
||||||
|
'type': 'date',
|
||||||
|
'raw': raw["DateTimeDigitized"]["raw"],
|
||||||
|
'formatted': metadata.date(raw["DateTimeDigitized"]["raw"])
|
||||||
|
},
|
||||||
|
'Copyright': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw["Copyright"]["raw"]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exif['Camera'] = {
|
||||||
|
'Model': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['Model']['raw']
|
||||||
|
},
|
||||||
|
'Make': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['Make']['raw']
|
||||||
|
},
|
||||||
|
'Lense Model': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['LensModel']['raw'],
|
||||||
|
},
|
||||||
|
'Lense Spec': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['LensSpecification']['raw'],
|
||||||
|
'formatted': metadata.lensSpecification(raw['LensSpecification']['raw'])
|
||||||
|
},
|
||||||
|
'Component Config': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['ComponentsConfiguration']['raw'],
|
||||||
|
'formatted': metadata.componentsConfiguration(raw['ComponentsConfiguration']['raw'])
|
||||||
|
},
|
||||||
|
'Date Processed': {
|
||||||
|
'type': 'date',
|
||||||
|
'raw': raw['DateTime']['raw'],
|
||||||
|
'formatted': metadata.date(raw['DateTime']['raw'])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exif['Software'] = {
|
||||||
|
'Software': {
|
||||||
|
'type': 'text',
|
||||||
|
'raw': raw['Software']['raw']
|
||||||
|
},
|
||||||
|
'Colour Space': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw['ColorSpace']['raw'],
|
||||||
|
'formatted': metadata.colorSpace(raw['ColorSpace']['raw'])
|
||||||
|
},
|
||||||
|
'Compression': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw['Compression']['raw'],
|
||||||
|
'formatted': metadata.compression(raw['Compression']['raw'])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exif['Photo'] = {
|
||||||
|
'FNumber': {
|
||||||
|
'type': 'fnumber',
|
||||||
|
'raw': raw["FNumber"]["raw"],
|
||||||
|
'formatted': metadata.fnumber(raw["FNumber"]["raw"])
|
||||||
|
},
|
||||||
|
'Focal Length': {
|
||||||
|
'type': 'focal',
|
||||||
|
'raw': raw["FocalLength"]["raw"]
|
||||||
|
},
|
||||||
|
'Focal Length - Film': {
|
||||||
|
'type': 'focal',
|
||||||
|
'raw': raw["FocalLengthIn35mmFilm"]["raw"]
|
||||||
|
},
|
||||||
|
'Max Aperture': {
|
||||||
|
'type': 'fnumber',
|
||||||
|
'raw': raw["MaxApertureValue"]["raw"],
|
||||||
|
'formatted': metadata.fnumber(raw["MaxApertureValue"]["raw"])
|
||||||
|
},
|
||||||
|
'Aperture': {
|
||||||
|
'type': 'fnumber',
|
||||||
|
'raw': raw["ApertureValue"]["raw"],
|
||||||
|
'formatted': metadata.fnumber(raw["ApertureValue"]["raw"])
|
||||||
|
},
|
||||||
|
'Shutter Speed': {
|
||||||
|
'type': 'shutter',
|
||||||
|
'raw': raw["ShutterSpeedValue"]["raw"],
|
||||||
|
'formatted': metadata.shutter(raw["ShutterSpeedValue"]["raw"])
|
||||||
|
},
|
||||||
|
'ISO Speed Ratings': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ISOSpeedRatings"]["raw"],
|
||||||
|
'formatted': metadata.iso(raw["ISOSpeedRatings"]["raw"])
|
||||||
|
},
|
||||||
|
'ISO Speed': {
|
||||||
|
'type': 'iso',
|
||||||
|
'raw': raw["ISOSpeed"]["raw"],
|
||||||
|
'formatted': metadata.iso(raw["ISOSpeed"]["raw"])
|
||||||
|
},
|
||||||
|
'Sensitivity Type': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["SensitivityType"]["raw"],
|
||||||
|
'formatted': metadata.sensitivityType(raw["SensitivityType"]["raw"])
|
||||||
|
},
|
||||||
|
'Exposure Bias': {
|
||||||
|
'type': 'ev',
|
||||||
|
'raw': raw["ExposureBiasValue"]["raw"],
|
||||||
|
'formatted': metadata.ev(raw["ExposureBiasValue"]["raw"])
|
||||||
|
},
|
||||||
|
'Exposure Time': {
|
||||||
|
'type': 'shutter',
|
||||||
|
'raw': raw["ExposureTime"]["raw"],
|
||||||
|
'formatted': metadata.shutter(raw["ExposureTime"]["raw"])
|
||||||
|
},
|
||||||
|
'Exposure Mode': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ExposureMode"]["raw"],
|
||||||
|
'formatted': metadata.exposureMode(raw["ExposureMode"]["raw"])
|
||||||
|
},
|
||||||
|
'Exposure Program': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ExposureProgram"]["raw"],
|
||||||
|
'formatted': metadata.exposureProgram(raw["ExposureProgram"]["raw"])
|
||||||
|
},
|
||||||
|
'White Balance': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["WhiteBalance"]["raw"],
|
||||||
|
'formatted': metadata.whiteBalance(raw["WhiteBalance"]["raw"])
|
||||||
|
},
|
||||||
|
'Flash': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["Flash"]["raw"],
|
||||||
|
'formatted': metadata.flash(raw["Flash"]["raw"])
|
||||||
|
},
|
||||||
|
'Metering Mode': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["MeteringMode"]["raw"],
|
||||||
|
'formatted': metadata.meteringMode(raw["MeteringMode"]["raw"])
|
||||||
|
},
|
||||||
|
'Light Source': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["LightSource"]["raw"],
|
||||||
|
'formatted': metadata.lightSource(raw["LightSource"]["raw"])
|
||||||
|
},
|
||||||
|
'Scene Capture Type': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["SceneCaptureType"]["raw"],
|
||||||
|
'formatted': metadata.sceneCaptureType(raw["SceneCaptureType"]["raw"])
|
||||||
|
},
|
||||||
|
'Scene Type': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["SceneType"]["raw"],
|
||||||
|
'formatted': metadata.sceneType(raw["SceneType"]["raw"])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exif['File'] = {
|
||||||
|
'Width': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ImageWidth"]["raw"]
|
||||||
|
},
|
||||||
|
'Height': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ImageLength"]["raw"]
|
||||||
|
},
|
||||||
|
'Orientation': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["Orientation"]["raw"],
|
||||||
|
'formatted': metadata.orientation(raw["Orientation"]["raw"])
|
||||||
|
},
|
||||||
|
'Xresolution': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["XResolution"]["raw"]
|
||||||
|
},
|
||||||
|
'Yresolution': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["YResolution"]["raw"]
|
||||||
|
},
|
||||||
|
'Resolution Units': {
|
||||||
|
'type': 'number',
|
||||||
|
'raw': raw["ResolutionUnit"]["raw"],
|
||||||
|
'formatted': metadata.resolutionUnit(raw["ResolutionUnit"]["raw"])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return exif
|
||||||
|
|
||||||
|
def date(date):
|
||||||
|
date_format = '%Y:%m:%d %H:%M:%S'
|
||||||
|
|
||||||
|
if date:
|
||||||
|
return str(datetime.strptime(date, date_format))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fnumber(value):
|
||||||
|
if value != None:
|
||||||
|
return 'f/' + str(value)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def iso(value):
|
||||||
|
if value != None:
|
||||||
|
return 'ISO ' + str(value)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutter(value):
|
||||||
|
if value != None:
|
||||||
|
return str(value) + 's'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def focal(value):
|
||||||
|
if value != None:
|
||||||
|
return str(value[0] / value[1]) + 'mm'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ev(value):
|
||||||
|
if value != None:
|
||||||
|
return str(value) + 'EV'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def colorSpace(value):
|
||||||
|
types = {
|
||||||
|
1: 'sRGB',
|
||||||
|
65535: 'Uncalibrated',
|
||||||
|
0: 'Reserved'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def flash(value):
|
||||||
|
types = {
|
||||||
|
0: 'Flash did not fire',
|
||||||
|
1: 'Flash fired',
|
||||||
|
5: 'Strobe return light not detected',
|
||||||
|
7: 'Strobe return light detected',
|
||||||
|
9: 'Flash fired, compulsory flash mode',
|
||||||
|
13: 'Flash fired, compulsory flash mode, return light not detected',
|
||||||
|
15: 'Flash fired, compulsory flash mode, return light detected',
|
||||||
|
16: 'Flash did not fire, compulsory flash mode',
|
||||||
|
24: 'Flash did not fire, auto mode',
|
||||||
|
25: 'Flash fired, auto mode',
|
||||||
|
29: 'Flash fired, auto mode, return light not detected',
|
||||||
|
31: 'Flash fired, auto mode, return light detected',
|
||||||
|
32: 'No flash function',
|
||||||
|
65: 'Flash fired, red-eye reduction mode',
|
||||||
|
69: 'Flash fired, red-eye reduction mode, return light not detected',
|
||||||
|
71: 'Flash fired, red-eye reduction mode, return light detected',
|
||||||
|
73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
|
||||||
|
77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
|
||||||
|
79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
|
||||||
|
89: 'Flash fired, auto mode, red-eye reduction mode',
|
||||||
|
93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
|
||||||
|
95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def exposureProgram(value):
|
||||||
|
types = {
|
||||||
|
0: 'Not defined',
|
||||||
|
1: 'Manual',
|
||||||
|
2: 'Normal program',
|
||||||
|
3: 'Aperture priority',
|
||||||
|
4: 'Shutter priority',
|
||||||
|
5: 'Creative program',
|
||||||
|
6: 'Action program',
|
||||||
|
7: 'Portrait mode',
|
||||||
|
8: 'Landscape mode'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def meteringMode(value):
|
||||||
|
types = {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Average',
|
||||||
|
2: 'Center-Weighted Average',
|
||||||
|
3: 'Spot',
|
||||||
|
4: 'Multi-Spot',
|
||||||
|
5: 'Pattern',
|
||||||
|
6: 'Partial',
|
||||||
|
255: 'Other'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolutionUnit(value):
|
||||||
|
types = {
|
||||||
|
1: 'No absolute unit of measurement',
|
||||||
|
2: 'Inch',
|
||||||
|
3: 'Centimeter'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def lightSource(value):
|
||||||
|
types = {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Daylight',
|
||||||
|
2: 'Fluorescent',
|
||||||
|
3: 'Tungsten (incandescent light)',
|
||||||
|
4: 'Flash',
|
||||||
|
9: 'Fine weather',
|
||||||
|
10: 'Cloudy weather',
|
||||||
|
11: 'Shade',
|
||||||
|
12: 'Daylight fluorescent (D 5700 - 7100K)',
|
||||||
|
13: 'Day white fluorescent (N 4600 - 5400K)',
|
||||||
|
14: 'Cool white fluorescent (W 3900 - 4500K)',
|
||||||
|
15: 'White fluorescent (WW 3200 - 3700K)',
|
||||||
|
17: 'Standard light A',
|
||||||
|
18: 'Standard light B',
|
||||||
|
19: 'Standard light C',
|
||||||
|
20: 'D55',
|
||||||
|
21: 'D65',
|
||||||
|
22: 'D75',
|
||||||
|
23: 'D50',
|
||||||
|
24: 'ISO studio tungsten',
|
||||||
|
255: 'Other light source',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sceneCaptureType(value):
|
||||||
|
types = {
|
||||||
|
0: 'Standard',
|
||||||
|
1: 'Landscape',
|
||||||
|
2: 'Portrait',
|
||||||
|
3: 'Night scene',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sceneType(value):
|
||||||
|
if value:
|
||||||
|
return 'Directly photographed image'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def whiteBalance(value):
|
||||||
|
types = {
|
||||||
|
0: 'Auto white balance',
|
||||||
|
1: 'Manual white balance',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def exposureMode(value):
|
||||||
|
types = {
|
||||||
|
0: 'Auto exposure',
|
||||||
|
1: 'Manual exposure',
|
||||||
|
2: 'Auto bracket',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sensitivityType(value):
|
||||||
|
types = {
|
||||||
|
0: 'Unknown',
|
||||||
|
1: 'Standard Output Sensitivity',
|
||||||
|
2: 'Recommended Exposure Index',
|
||||||
|
3: 'ISO Speed',
|
||||||
|
4: 'Standard Output Sensitivity and Recommended Exposure Index',
|
||||||
|
5: 'Standard Output Sensitivity and ISO Speed',
|
||||||
|
6: 'Recommended Exposure Index and ISO Speed',
|
||||||
|
7: 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def lensSpecification(value):
|
||||||
|
if value:
|
||||||
|
return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compression(value):
|
||||||
|
types = {
|
||||||
|
1: 'Uncompressed',
|
||||||
|
6: 'JPEG compression',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def orientation(value):
|
||||||
|
types = {
|
||||||
|
1: 'Horizontal (normal)',
|
||||||
|
2: 'Mirror horizontal',
|
||||||
|
3: 'Rotate 180',
|
||||||
|
4: 'Mirror vertical',
|
||||||
|
5: 'Mirror horizontal and rotate 270 CW',
|
||||||
|
6: 'Rotate 90 CW',
|
||||||
|
7: 'Mirror horizontal and rotate 90 CW',
|
||||||
|
8: 'Rotate 270 CW',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return types[int(value)]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def componentsConfiguration(value):
|
||||||
|
types = {
|
||||||
|
0: '',
|
||||||
|
1: 'Y',
|
||||||
|
2: 'Cb',
|
||||||
|
3: 'Cr',
|
||||||
|
4: 'R',
|
||||||
|
5: 'G',
|
||||||
|
6: 'B',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ''.join([types[int(x)] for x in value])
|
||||||
|
except:
|
||||||
|
return None
|
|
@ -20,14 +20,13 @@ document.querySelector('.jumpUp').onclick = function() {
|
||||||
|
|
||||||
function imgFade(obj) {
|
function imgFade(obj) {
|
||||||
$(obj).animate({opacity: 1}, 500);
|
$(obj).animate({opacity: 1}, 500);
|
||||||
//$(obj).parent().style.backgroundColor = 'transparent';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var times = document.getElementsByClassName('time');
|
var times = document.getElementsByClassName('time');
|
||||||
for (var i = 0; i < times.length; i++) {
|
for (var i = 0; i < times.length; i++) {
|
||||||
var time = times[i].innerHTML;
|
var time = times[i].innerHTML;
|
||||||
var date = new Date(time);
|
var date = new Date(time);
|
||||||
times[i].innerHTML = date.toLocaleString( 'en-GB', { timeZone: 'UTC' } );
|
times[i].innerHTML = date.toLocaleString('en-GB');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNotification(text='Sample notification', type=4) {
|
function addNotification(text='Sample notification', type=4) {
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
src="/api/uploads/{{ image['file_name'] }}/1000"
|
src="/api/uploads/{{ image['file_name'] }}/1000"
|
||||||
onload="imgFade(this)" style="opacity:0;"
|
onload="imgFade(this)" style="opacity:0;"
|
||||||
onerror="this.src='/static/images/error.png'"
|
onerror="this.src='/static/images/error.png'"
|
||||||
width="{{ file['width'] }}"
|
width="{{ width }}"
|
||||||
height="{{ file['height'] }}"
|
height="{{ height }}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -108,28 +108,98 @@
|
||||||
<h2>Info</h2>
|
<h2>Info</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-info__content">
|
<div class="image-info__content">
|
||||||
<p>Filename: {{ image['file_name'] }}</p>
|
<table>
|
||||||
<p>Image ID: {{ image['id'] }}</p>
|
<tr>
|
||||||
<p>Author: {{ image['author_id'] }}</p>
|
<td>Image ID</td>
|
||||||
<p>Upload date: <span class="time">{{ image['created_at'] }}</span></p>
|
<td>{{ image['id'] }}</td>
|
||||||
<p>Dimensions: {{ file['width'] }}x{{ file['height'] }}</p>
|
</tr>
|
||||||
<p>File size: {{ size }}</p>
|
<tr>
|
||||||
|
<td>Author</td>
|
||||||
|
<td>{{ image['author_id'] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Upload date</td>
|
||||||
|
<td><span class="time">{{ image['created_at'] }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Filename</td>
|
||||||
|
<td>{{ image['file_name'] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>File size</td>
|
||||||
|
<td>{{ file_size }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if exif %}
|
{% if exif %}
|
||||||
<div class="image-info">
|
{% for tag in exif %}
|
||||||
<div class="image-info__header">
|
<div class="image-info">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
|
{% if tag == 'Photographer' %}
|
||||||
<path d="M14.95 7.879l-.707-.707a1 1 0 0 1 1.414-1.415l.707.707 1.414-1.414-2.828-2.828L2.222 14.95l2.828 2.828 1.414-1.414L5.05 14.95a1 1 0 0 1 1.414-1.414L7.88 14.95l1.414-1.414-.707-.708A1 1 0 0 1 10 11.414l.707.707 1.414-1.414-1.414-1.414a1 1 0 0 1 1.414-1.414l1.415 1.414 1.414-1.414zM.808 13.536L13.536.808a2 2 0 0 1 2.828 0l2.828 2.828a2 2 0 0 1 0 2.828L6.464 19.192a2 2 0 0 1-2.828 0L.808 16.364a2 2 0 0 1 0-2.828z"></path>
|
<div class="image-info__header">
|
||||||
</svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -2 24 24" fill="currentColor">
|
||||||
<h2>Metadata</h2>
|
<path d="M3.534 10.07a1 1 0 1 1 .733 1.86A3.579 3.579 0 0 0 2 15.26V17a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1.647a3.658 3.658 0 0 0-2.356-3.419 1 1 0 1 1 .712-1.868A5.658 5.658 0 0 1 14 15.353V17a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3v-1.74a5.579 5.579 0 0 1 3.534-5.19zM7 0a4 4 0 0 1 4 4v2a4 4 0 1 1-8 0V4a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0V4a2 2 0 0 0-2-2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>Photographer</h2>
|
||||||
|
</div>
|
||||||
|
{% elif tag == 'Camera' %}
|
||||||
|
<div class="image-info__header">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -4 24 24" fill="currentColor">
|
||||||
|
<path d="M5.676 5H4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-1.676l-.387-1.501A2.002 2.002 0 0 0 12 2H8a2 2 0 0 0-1.937 1.499L5.676 5zm-1.55-2C4.57 1.275 6.136 0 8 0h4a4.002 4.002 0 0 1 3.874 3H16a4 4 0 0 1 4 4v5a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h.126zM10 13a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm6-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>Camera</h2>
|
||||||
|
</div>
|
||||||
|
{% elif tag == 'Software' %}
|
||||||
|
<div class="image-info__header">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -4 24 24" fill="currentColor">
|
||||||
|
<path d="M2 13v1h3V2H2v9h1v2H2zM1 0h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm9 3h8a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm0 2v6h8V5h-8zm2 9h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>Software</h2>
|
||||||
|
</div>
|
||||||
|
{% elif tag == 'Photo' %}
|
||||||
|
<div class="image-info__header">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
|
||||||
|
<path d="M14 8.322V2H2v12h3.576l3.97-5.292A3 3 0 0 1 14 8.322zm0 3.753l-1.188-2.066a1 1 0 0 0-1.667-.101L8.076 14H14v-1.925zM14 16H2v2h12v-2zM2 0h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm4 9a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>Photo</h2>
|
||||||
|
</div>
|
||||||
|
{% elif tag == 'File' %}
|
||||||
|
<div class="image-info__header">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
|
||||||
|
<path d="M10.298 2H3a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V4.961L10.298 2zM3 0h8l5 4v13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>File</h2>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="image-info__header">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
|
||||||
|
<path d="M14.95 7.879l-.707-.707a1 1 0 0 1 1.414-1.415l.707.707 1.414-1.414-2.828-2.828L2.222 14.95l2.828 2.828 1.414-1.414L5.05 14.95a1 1 0 0 1 1.414-1.414L7.88 14.95l1.414-1.414-.707-.708A1 1 0 0 1 10 11.414l.707.707 1.414-1.414-1.414-1.414a1 1 0 0 1 1.414-1.414l1.415 1.414 1.414-1.414zM.808 13.536L13.536.808a2 2 0 0 1 2.828 0l2.828 2.828a2 2 0 0 1 0 2.828L6.464 19.192a2 2 0 0 1-2.828 0L.808 16.364a2 2 0 0 1 0-2.828z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2>{{tag}}</h2>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="image-info__content">
|
||||||
|
<table>
|
||||||
|
{% for subtag in exif[tag] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{subtag}}</td>
|
||||||
|
{% if exif[tag][subtag]['formatted'] %}
|
||||||
|
{% if exif[tag][subtag]['type'] == 'date' %}
|
||||||
|
<td><span class="time">{{exif[tag][subtag]['formatted']}}</span></td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{exif[tag][subtag]['formatted']}}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% elif exif[tag][subtag]['raw'] %}
|
||||||
|
<td>{{exif[tag][subtag]['raw']}}</td>
|
||||||
|
{% else %}
|
||||||
|
<td class="empty-table">sad noises</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-info__content">
|
{% endfor %}
|
||||||
{% for tag in exif %}
|
|
||||||
<p>{{ tag }}: {{ exif[tag] }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -24,33 +24,29 @@
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script>
|
<script>
|
||||||
var images = document.querySelectorAll('.gallery__item-image');
|
function loadOnView() {
|
||||||
|
var images = document.querySelectorAll('.gallery__item-image');
|
||||||
|
for (var i = 0; i < images.length; i++) {
|
||||||
|
var image = images[i];
|
||||||
|
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
|
||||||
|
if (!image.src) {
|
||||||
|
image.src = `/api/uploads/${image.getAttribute('data-src')}/400`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
for (var i = 0; i < images.length; i++) {
|
loadOnView();
|
||||||
var image = images[i];
|
|
||||||
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
|
|
||||||
if (!image.src) {
|
|
||||||
image.src = `/api/uploads/${image.getAttribute('data-src')}/400`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onscroll = function() {
|
window.onscroll = function() {
|
||||||
for (var i = 0; i < images.length; i++) {
|
loadOnView();
|
||||||
var image = images[i];
|
};
|
||||||
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
|
window.onresize = function() {
|
||||||
if (!image.src) {
|
loadOnView();
|
||||||
image.src = `/api/uploads/${image.getAttribute('data-src')}/400`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const src = urlParams.get('src');
|
const src = urlParams.get('src');
|
||||||
|
|
||||||
if (src) {
|
if (src) {
|
||||||
var imgOffset = document.getElementById('image-' + src).offsetTop;
|
var imgOffset = document.getElementById('image-' + src).offsetTop;
|
||||||
var imgHeight = document.getElementById('image-' + src).offsetHeight;
|
var imgHeight = document.getElementById('image-' + src).offsetHeight;
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
opacity: 0
|
opacity: 0
|
||||||
transform: translateX(100%)
|
transform: translateX(100%)
|
||||||
|
|
||||||
transition: all 0.25s ease-in-out
|
transition: all 0.4s ease-in-out, max-height 0.2s ease-in-out
|
||||||
|
|
||||||
@media (max-width: $breakpoint)
|
@media (max-width: $breakpoint)
|
||||||
.notifications
|
.notifications
|
||||||
|
|
|
@ -229,7 +229,6 @@
|
||||||
|
|
||||||
border-radius: $rad
|
border-radius: $rad
|
||||||
transform: translateY(5rem)
|
transform: translateY(5rem)
|
||||||
//box-shadow: 0px 8px 0px $black2;
|
|
||||||
|
|
||||||
.pop-up-content
|
.pop-up-content
|
||||||
max-height: 100%
|
max-height: 100%
|
||||||
|
|
|
@ -107,6 +107,7 @@
|
||||||
|
|
||||||
background-color: $black
|
background-color: $black
|
||||||
border-radius: $rad
|
border-radius: $rad
|
||||||
|
|
||||||
.image-info__header
|
.image-info__header
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0.5rem
|
padding: 0.5rem
|
||||||
|
@ -119,7 +120,11 @@
|
||||||
align-items: center
|
align-items: center
|
||||||
gap: 0.5rem
|
gap: 0.5rem
|
||||||
|
|
||||||
background-color: $black2
|
position: sticky
|
||||||
|
top: 0
|
||||||
|
z-index: +1
|
||||||
|
|
||||||
|
background-image: linear-gradient(to right, rgba($black2, 1), rgba($black, 1))
|
||||||
|
|
||||||
svg
|
svg
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -150,6 +155,10 @@
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
gap: 0.5rem
|
gap: 0.5rem
|
||||||
|
|
||||||
|
color: $white
|
||||||
|
|
||||||
|
overflow-x: hidden
|
||||||
|
|
||||||
p
|
p
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
|
@ -157,11 +166,51 @@
|
||||||
font-size: 1rem
|
font-size: 1rem
|
||||||
font-weight: 500
|
font-weight: 500
|
||||||
|
|
||||||
color: $white
|
|
||||||
|
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
|
||||||
|
table
|
||||||
|
margin: 0 0 0 1rem
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
max-width: 100%
|
||||||
|
|
||||||
|
overflow-x: hidden
|
||||||
|
border-collapse: collapse
|
||||||
|
|
||||||
|
tr
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
td:first-child
|
||||||
|
padding: 0.25rem 1rem 0.25rem 0
|
||||||
|
|
||||||
|
width: 50%
|
||||||
|
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
font-size: 1rem
|
||||||
|
font-weight: 500
|
||||||
|
td:last-child
|
||||||
|
padding: 0.25rem 0
|
||||||
|
|
||||||
|
width: 50%
|
||||||
|
max-width: 0
|
||||||
|
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
font-size: 1rem
|
||||||
|
font-weight: 500
|
||||||
|
|
||||||
|
td.empty-table
|
||||||
|
opacity: 0.3
|
||||||
|
|
||||||
#image-tools
|
#image-tools
|
||||||
margin-bottom: 0.5rem
|
margin-bottom: 0.5rem
|
||||||
|
|
||||||
|
@ -214,3 +263,16 @@
|
||||||
|
|
||||||
.image-wrapper
|
.image-wrapper
|
||||||
padding-bottom: 4rem
|
padding-bottom: 4rem
|
||||||
|
|
||||||
|
.image-info__content
|
||||||
|
table
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
tr
|
||||||
|
td:first-child
|
||||||
|
width: auto
|
||||||
|
td:last-child
|
||||||
|
width: 75%
|
||||||
|
.image-info__header
|
||||||
|
background-image: none
|
||||||
|
background-color: $black2
|
Loading…
Add table
Add a link
Reference in a new issue