From 792d3059e549d7fa8bc28328cde4f5ab0725ed8a Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Wed, 12 Feb 2025 01:33:34 -0600 Subject: [PATCH 1/9] feat: add EXIF implementation --- fussel/generator/generate.py | 8 +++- fussel/generator/util.py | 13 +++++- fussel/web/src/component/Collection.css | 26 +++++++++++ fussel/web/src/component/Collection.js | 59 +++++++++++++++++++++++-- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/fussel/generator/generate.py b/fussel/generator/generate.py index 2382fe5..2699aab 100755 --- a/fussel/generator/generate.py +++ b/fussel/generator/generate.py @@ -162,7 +162,7 @@ def has_thumbnail(self): class Photo: - def __init__(self, name, width, height, src, thumb, slug, srcSet): + def __init__(self, name, width, height, src, thumb, slug, srcSet, exif): self.width = width self.height = height @@ -172,6 +172,7 @@ def __init__(self, name, width, height, src, thumb, slug, srcSet): self.srcSet = srcSet self.faces: list = [] self.slug = slug + self.exif = exif @classmethod def process_photo(cls, external_path, photo, filename, slug, output_path, people_q: Queue): @@ -198,6 +199,7 @@ def process_photo(cls, external_path, photo, filename, slug, output_path, people with Image.open(new_original_photo) as im: original_size = im.size width, height = im.size + exif = exif_to_str(im.getexif()) except UnidentifiedImageError as e: shutil.rmtree(new_original_photo, ignore_errors=True) raise PhotoProcessingFailure(message=str(e)) @@ -247,7 +249,8 @@ def process_photo(cls, external_path, photo, filename, slug, output_path, people "%s/%s" % (quote(external_path), quote(os.path.basename(smallest_src))), slug, - srcSet + srcSet, + exif ) # Faces @@ -434,6 +437,7 @@ def generate(self): shutil.rmtree(output_photos_path, ignore_errors=True) shutil.rmtree(generated_site_path, ignore_errors=True) + print("create", output_photos_path) os.makedirs(output_photos_path, exist_ok=True) shutil.rmtree(output_data_path, ignore_errors=True) os.makedirs(output_data_path, exist_ok=True) diff --git a/fussel/generator/util.py b/fussel/generator/util.py index 7f2240b..5ed9c67 100644 --- a/fussel/generator/util.py +++ b/fussel/generator/util.py @@ -1,11 +1,23 @@ from PIL import Image +from PIL.ExifTags import TAGS from slugify import slugify from .config import * import os +def exif_to_str(exif: dict) -> str: + result = "" + if exif is not None: + for tag, value in exif.items(): + if tag in TAGS: + result += f"{TAGS[tag]}: {value}\n" + else: + result += f"{tag}: {value}\n" + return result + + def is_supported_album(path): folder_name = os.path.basename(path) return not folder_name.startswith(".") and os.path.isdir(path) @@ -36,7 +48,6 @@ def find_unique_slug(slugs, lock, name): slugs.add(slug) lock.release() - return slug diff --git a/fussel/web/src/component/Collection.css b/fussel/web/src/component/Collection.css index c5c2d51..a785ab9 100644 --- a/fussel/web/src/component/Collection.css +++ b/fussel/web/src/component/Collection.css @@ -5,6 +5,32 @@ Keep an eye on the ticket and maybe one day I can remove this */ align-self: flex-start; } +.modal-info-button { + position: absolute; + z-index: 100; + right: 60px; + top: 15px; +} + +.info-modal-overlay { + position: absolute !important; + width: auto !important; + height: auto !important; + right: 60px; + background-color: rgba(0, 0, 0, 0.3); +} + +.info-modal { + position: relative; + display: inline-block; + padding: 8px; +} + +.info-popup { + color: #fff; + font-size: 12px; +} + .modal-close-button { position: absolute; z-index: 100; diff --git a/fussel/web/src/component/Collection.js b/fussel/web/src/component/Collection.js index 0d5d69c..41a1118 100644 --- a/fussel/web/src/component/Collection.js +++ b/fussel/web/src/component/Collection.js @@ -22,7 +22,8 @@ class Collection extends Component { constructor(props) { super(props); this.state = { - viewerIsOpen: true ? this.props.params.image != undefined : false + viewerIsOpen: true ? this.props.params.image != undefined : false, + infoModalIsOpen: false }; } @@ -70,7 +71,8 @@ class Collection extends Component { this.props.navigate("/collections/" + this.props.params.collectionType + "/" + this.props.params.collection + "/" + event.target.attributes.slug.value); this.setState({ - viewerIsOpen: true + viewerIsOpen: true, + infoModalIsOpen: false }) // Add listener to detect if the back button was pressed and the modal should be closed window.addEventListener('hashchange', this.modalStateTracker, false); @@ -82,12 +84,25 @@ class Collection extends Component { this.props.navigate("/collections/" + this.props.params.collectionType + "/" + this.props.params.collection); this.setState({ - viewerIsOpen: false + viewerIsOpen: false, + infoModalIsOpen: false }) // var page = document.getElementsByTagName('body')[0]; // page.classList.remove('noscroll'); }; + openInfoModal = () => { + this.setState({ + infoModalIsOpen: true + }) + }; + + closeInfoModal = () => { + this.setState({ + infoModalIsOpen: false + }) + }; + title = (collectionType) => { var titleStr = "Unknown" if (collectionType == "albums") { @@ -155,7 +170,7 @@ class Collection extends Component { isOpen={this.state.viewerIsOpen} onRequestClose={this.closeModal} preventScroll={true} - + style={{ overlay: { backgroundColor: 'rgba(0, 0, 0, 0.3)' @@ -167,11 +182,47 @@ class Collection extends Component { } }} > + + +
{collection_data["photos"].find(x => x.slug == this.props.params.image)?.exif}
+
+ Date: Wed, 12 Feb 2025 01:34:55 -0600 Subject: [PATCH 2/9] chore: cleanup --- fussel/generator/generate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fussel/generator/generate.py b/fussel/generator/generate.py index 2699aab..8bd8a2b 100755 --- a/fussel/generator/generate.py +++ b/fussel/generator/generate.py @@ -437,7 +437,6 @@ def generate(self): shutil.rmtree(output_photos_path, ignore_errors=True) shutil.rmtree(generated_site_path, ignore_errors=True) - print("create", output_photos_path) os.makedirs(output_photos_path, exist_ok=True) shutil.rmtree(output_data_path, ignore_errors=True) os.makedirs(output_data_path, exist_ok=True) From ffe5c9edca77772664728f3458ca798b61669539 Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Wed, 12 Feb 2025 01:47:38 -0600 Subject: [PATCH 3/9] feat: fix limited EXIF --- fussel/generator/generate.py | 2 +- fussel/generator/util.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fussel/generator/generate.py b/fussel/generator/generate.py index 8bd8a2b..849d865 100755 --- a/fussel/generator/generate.py +++ b/fussel/generator/generate.py @@ -199,7 +199,7 @@ def process_photo(cls, external_path, photo, filename, slug, output_path, people with Image.open(new_original_photo) as im: original_size = im.size width, height = im.size - exif = exif_to_str(im.getexif()) + exif = extract_exif(im) except UnidentifiedImageError as e: shutil.rmtree(new_original_photo, ignore_errors=True) raise PhotoProcessingFailure(message=str(e)) diff --git a/fussel/generator/util.py b/fussel/generator/util.py index 5ed9c67..f2bb7dc 100644 --- a/fussel/generator/util.py +++ b/fussel/generator/util.py @@ -1,20 +1,32 @@ - - -from PIL import Image -from PIL.ExifTags import TAGS +from PIL import Image, ImageFile +from PIL.ExifTags import TAGS, GPSTAGS, IFD from slugify import slugify from .config import * import os -def exif_to_str(exif: dict) -> str: +def extract_exif(im: ImageFile.ImageFile) -> str: result = "" + # https://stackoverflow.com/a/75357594 + exif = im.getexif() if exif is not None: for tag, value in exif.items(): if tag in TAGS: result += f"{TAGS[tag]}: {value}\n" else: result += f"{tag}: {value}\n" + for ifd_id in IFD: + try: + ifd = exif.get_ifd(ifd_id) + if ifd_id == IFD.GPSInfo: + resolve = GPSTAGS + else: + resolve = TAGS + for k, v in ifd.items(): + tag = resolve.get(k, k) + result += f"{tag}: {v}\n" + except KeyError: + pass return result From 67293903bbffef66e175f5c8aab93e57c6e1a8ca Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Wed, 12 Feb 2025 21:18:46 -0600 Subject: [PATCH 4/9] chore: modification --- fussel/generator/util.py | 12 +++++------- fussel/web/src/component/Collection.css | 10 +++------- fussel/web/src/component/Collection.js | 13 ++----------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/fussel/generator/util.py b/fussel/generator/util.py index f2bb7dc..d999eca 100644 --- a/fussel/generator/util.py +++ b/fussel/generator/util.py @@ -6,15 +6,13 @@ def extract_exif(im: ImageFile.ImageFile) -> str: - result = "" + result = {} # https://stackoverflow.com/a/75357594 exif = im.getexif() - if exif is not None: + if exif: for tag, value in exif.items(): if tag in TAGS: - result += f"{TAGS[tag]}: {value}\n" - else: - result += f"{tag}: {value}\n" + result[TAGS[tag]] = value for ifd_id in IFD: try: ifd = exif.get_ifd(ifd_id) @@ -24,10 +22,10 @@ def extract_exif(im: ImageFile.ImageFile) -> str: resolve = TAGS for k, v in ifd.items(): tag = resolve.get(k, k) - result += f"{tag}: {v}\n" + result[tag] = v except KeyError: pass - return result + return "\n".join([f"{k}: {v}" for k, v in result.items()]) def is_supported_album(path): diff --git a/fussel/web/src/component/Collection.css b/fussel/web/src/component/Collection.css index a785ab9..36b48be 100644 --- a/fussel/web/src/component/Collection.css +++ b/fussel/web/src/component/Collection.css @@ -13,17 +13,13 @@ Keep an eye on the ticket and maybe one day I can remove this */ } .info-modal-overlay { - position: absolute !important; - width: auto !important; - height: auto !important; - right: 60px; background-color: rgba(0, 0, 0, 0.3); + padding: 0; } .info-modal { - position: relative; - display: inline-block; - padding: 8px; + margin: 0; + padding: 0; } .info-popup { diff --git a/fussel/web/src/component/Collection.js b/fussel/web/src/component/Collection.js index 41a1118..ccc6a34 100644 --- a/fussel/web/src/component/Collection.js +++ b/fussel/web/src/component/Collection.js @@ -201,22 +201,13 @@ class Collection extends Component { style={{ overlay: { display: "inline-block", - backgroundColor: 'rgba(0, 0, 0, 0.3)', position: "absolute", right: 60, - top: 20, - padding: 0 + top: 20 }, content: { - display: "inline-block", position: "relative", - inset: 0, - padding: 0, - border: "none", - borderRadius: 0, - margin: 0, - whiteSpace: "pre-wrap", - background: "transparent" + whiteSpace: "pre-wrap" } }} > From 35cde880bb2eaff3d749ec66a24d110ff3dfa8c8 Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Thu, 13 Feb 2025 22:14:44 -0600 Subject: [PATCH 5/9] feat: new info page --- fussel/generator/util.py | 6 +- fussel/web/src/component/App.js | 2 + fussel/web/src/component/Collection.css | 22 ----- fussel/web/src/component/Collection.js | 33 +------- fussel/web/src/component/Info.css | 52 ++++++++++++ fussel/web/src/component/Info.js | 105 ++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 fussel/web/src/component/Info.css create mode 100644 fussel/web/src/component/Info.js diff --git a/fussel/generator/util.py b/fussel/generator/util.py index d999eca..086b4bf 100644 --- a/fussel/generator/util.py +++ b/fussel/generator/util.py @@ -5,7 +5,7 @@ import os -def extract_exif(im: ImageFile.ImageFile) -> str: +def extract_exif(im: ImageFile.ImageFile) -> dict: result = {} # https://stackoverflow.com/a/75357594 exif = im.getexif() @@ -22,10 +22,10 @@ def extract_exif(im: ImageFile.ImageFile) -> str: resolve = TAGS for k, v in ifd.items(): tag = resolve.get(k, k) - result[tag] = v + result[tag] = str(v) except KeyError: pass - return "\n".join([f"{k}: {v}" for k, v in result.items()]) + return result def is_supported_album(path): diff --git a/fussel/web/src/component/App.js b/fussel/web/src/component/App.js index 5131a00..53a0b53 100644 --- a/fussel/web/src/component/App.js +++ b/fussel/web/src/component/App.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import Navbar from "./Navbar"; import Collections from "./Collections"; import Collection from "./Collection"; +import Info from "./Info"; import NotFound from "./NotFound"; import { site_data } from "../_gallery/site_data.js" import { Routes, Route } from "react-router-dom"; @@ -25,6 +26,7 @@ export default class App extends Component { } /> } /> } /> + } /> {/* Using path="*"" means "match anything", so this route acts like a catch-all for URLs that we don't have explicit diff --git a/fussel/web/src/component/Collection.css b/fussel/web/src/component/Collection.css index 36b48be..c5c2d51 100644 --- a/fussel/web/src/component/Collection.css +++ b/fussel/web/src/component/Collection.css @@ -5,28 +5,6 @@ Keep an eye on the ticket and maybe one day I can remove this */ align-self: flex-start; } -.modal-info-button { - position: absolute; - z-index: 100; - right: 60px; - top: 15px; -} - -.info-modal-overlay { - background-color: rgba(0, 0, 0, 0.3); - padding: 0; -} - -.info-modal { - margin: 0; - padding: 0; -} - -.info-popup { - color: #fff; - font-size: 12px; -} - .modal-close-button { position: absolute; z-index: 100; diff --git a/fussel/web/src/component/Collection.js b/fussel/web/src/component/Collection.js index ccc6a34..812685a 100644 --- a/fussel/web/src/component/Collection.js +++ b/fussel/web/src/component/Collection.js @@ -92,15 +92,7 @@ class Collection extends Component { }; openInfoModal = () => { - this.setState({ - infoModalIsOpen: true - }) - }; - - closeInfoModal = () => { - this.setState({ - infoModalIsOpen: false - }) + this.props.navigate("/collections/" + this.props.params.collectionType + "/" + this.props.params.collection + "/" + this.props.params.image + "/info"); }; title = (collectionType) => { @@ -182,7 +174,7 @@ class Collection extends Component { } }} > - - -
{collection_data["photos"].find(x => x.slug == this.props.params.image)?.exif}
-
{ + var titleStr = "Unknown" + if (collectionType == "albums") { + titleStr = "Albums" + } + else if (collectionType == "people") { + titleStr = "People" + } + return titleStr + } + + collection = (collectionType, collection) => { + let data = {} + if (collectionType == "albums") { + data = albums_data + } + else if (collectionType == "people") { + data = people_data + } + if (collection in data) { + return data[collection] + } + return {} + } + + photo = (collection, slug) => { + return collection.photos.find(photo => photo.slug === slug) + } + + render() { + let collection_data = this.collection(this.props.params.collectionType, this.props.params.collection) + let photo = this.photo(collection_data, this.props.params.image) + return ( +
+
+
+ +
+
+ {photo.name} +
{ + Object.entries(photo.exif).map((item, i) => ( +
+ {item[0]}: {item[1]} +
+ )) + }
+
+ { + "Make" in photo.exif && <>
{photo.exif.Make}
| + } +
+ { + "Model" in photo.exif &&
{photo.exif.Model}
+ } + { + "LensModel" in photo.exif &&
{photo.exif.LensModel}
+ } +
+
+
+ ); + } +} + +export default withRouter(Info) \ No newline at end of file From 39587f321cc937c858b1ee7a0973f7ffeec9b211 Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Thu, 13 Feb 2025 22:20:07 -0600 Subject: [PATCH 6/9] chore: cleanup --- fussel/web/src/component/Collection.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/fussel/web/src/component/Collection.js b/fussel/web/src/component/Collection.js index 812685a..7bde63d 100644 --- a/fussel/web/src/component/Collection.js +++ b/fussel/web/src/component/Collection.js @@ -22,8 +22,7 @@ class Collection extends Component { constructor(props) { super(props); this.state = { - viewerIsOpen: true ? this.props.params.image != undefined : false, - infoModalIsOpen: false + viewerIsOpen: true ? this.props.params.image != undefined : false }; } @@ -71,8 +70,7 @@ class Collection extends Component { this.props.navigate("/collections/" + this.props.params.collectionType + "/" + this.props.params.collection + "/" + event.target.attributes.slug.value); this.setState({ - viewerIsOpen: true, - infoModalIsOpen: false + viewerIsOpen: true }) // Add listener to detect if the back button was pressed and the modal should be closed window.addEventListener('hashchange', this.modalStateTracker, false); @@ -84,8 +82,7 @@ class Collection extends Component { this.props.navigate("/collections/" + this.props.params.collectionType + "/" + this.props.params.collection); this.setState({ - viewerIsOpen: false, - infoModalIsOpen: false + viewerIsOpen: false }) // var page = document.getElementsByTagName('body')[0]; // page.classList.remove('noscroll'); @@ -174,7 +171,12 @@ class Collection extends Component { } }} > -