Skip to content

Commit 329a91f

Browse files
authored
Add new plugin that retrieve images metadata (#585)
1 parent 41978e3 commit 329a91f

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from PythonDepManager import ensure_import
2+
ensure_import("piexif==1.1.3")
3+
ensure_import("stashapi:stashapp-tools>=0.2.58")
4+
import os
5+
#from pickle import TRUE
6+
import sys, json
7+
import uuid
8+
import stashapi.log as log
9+
from stashapi.stashapp import StashInterface
10+
import pathlib
11+
import piexif
12+
import subprocess
13+
from PIL import Image, PngImagePlugin
14+
15+
def main():
16+
global stash
17+
global pattern
18+
19+
20+
json_input = json.loads(sys.stdin.read())
21+
hookContext = json_input['args'].get("hookContext")
22+
stash = StashInterface(json_input["server_connection"])
23+
mode_arg = json_input["args"]["mode"]
24+
if hookContext and (hookContext.get("type") == "Image.Create.Post") and (hookContext.get("date") is None):
25+
#updateImages(hookContext.get('id'))
26+
getDateFromImage(hookContext.get('id'))
27+
elif mode_arg == "find":
28+
getDateFromImages()
29+
30+
def getDateFromImage(imageID):
31+
image = stash.find_image(imageID)
32+
if image and image["date"] is None:
33+
date = None
34+
if image["visual_files"][0]["path"]:
35+
path = pathlib.Path(image["visual_files"][0]["path"])
36+
ext = path.suffix.lower()
37+
try:
38+
if ext in [".jpg", ".jpeg", ".tiff"]:
39+
# Try EXIF
40+
exif_dict = piexif.load(str(path))
41+
for tag in ["DateTimeOriginal", "DateTimeDigitized", "DateTime"]:
42+
value = exif_dict["Exif"].get(piexif.ExifIFD.__dict__.get(tag))
43+
if value:
44+
date = value.decode().split(" ")[0].replace(":", "-")
45+
break
46+
elif ext == ".png":
47+
# Try PNG tEXt/iTXt
48+
with Image.open(path) as img:
49+
info = img.info
50+
for key in ["date:create", "date:modify", "Creation Time", "creation_time"]:
51+
if key in info:
52+
date = info[key].split(" ")[0].replace(":", "-")
53+
break
54+
# Try PngImagePlugin for tEXt chunks
55+
if not date and isinstance(img, PngImagePlugin.PngImageFile):
56+
for k, v in img.text.items():
57+
if "date" in k.lower() or "time" in k.lower():
58+
date = v.split(" ")[0].replace(":", "-")
59+
break
60+
elif ext in [".webp", ".gif"]:
61+
# Try XMP metadata (if present)
62+
with open(path, "rb") as f:
63+
data = f.read()
64+
xmp_start = data.find(b"<x:xmpmeta")
65+
xmp_end = data.find(b"</x:xmpmeta>")
66+
if xmp_start != -1 and xmp_end != -1:
67+
xmp = data[xmp_start:xmp_end+12].decode(errors="ignore")
68+
import re
69+
m = re.search(r"DateTimeOriginal>([\d\-: ]+)<", xmp)
70+
if m:
71+
date = m.group(1).split(" ")[0].replace(":", "-")
72+
elif ext in [".mp4", ".webm"]:
73+
# Use ffprobe to get creation_time
74+
try:
75+
result = subprocess.run(
76+
[
77+
"ffprobe", "-v", "error", "-select_streams", "v:0",
78+
"-show_entries", "format_tags=creation_time",
79+
"-of", "default=noprint_wrappers=1:nokey=1",
80+
str(path)
81+
],
82+
stdout=subprocess.PIPE,
83+
stderr=subprocess.PIPE,
84+
text=True
85+
)
86+
if result.stdout:
87+
date = result.stdout.strip().split("T")[0]
88+
except Exception as e:
89+
log.info(f"ffprobe error: {e}")
90+
except Exception as e:
91+
log.info(f"Metadata extraction error: {e}")
92+
if date:
93+
stash.update_image({
94+
"id": imageID,
95+
"date": date
96+
})
97+
log.info("Updated image "+str(path)+" with date "+str(date))
98+
99+
def getDateFromImages():
100+
images = stash.find_images(f={
101+
"date": {
102+
"modifier": "IS_NULL",
103+
"value": ""
104+
},
105+
"galleries": {
106+
"modifier": "IS_NULL",
107+
"value": []
108+
}
109+
},fragment="id")
110+
tasks = len(images)
111+
log.info(f"Found {tasks} images with no date")
112+
prog = 0
113+
for id in images:
114+
getDateFromImage(id["id"])
115+
prog += 1
116+
log.progress(prog / tasks)
117+
118+
119+
if __name__ == "__main__":
120+
main()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Image Date From Metadata
2+
# requires: PythonDepManager
3+
description: Find date in image or clip metadata
4+
version: 1.0.0
5+
exec:
6+
- python
7+
- "{pluginDir}/image_date_from_metadata.py"
8+
interface: raw
9+
tasks:
10+
- name: Find all missing dates
11+
description: Try to find the date in the image or clip metadata and add it
12+
defaultArgs:
13+
mode: find
14+
hooks:
15+
- name: hook_image_created
16+
description: Get the image creation date from the file metadata
17+
triggeredBy:
18+
- Image.Create.Post

0 commit comments

Comments
 (0)