Fix photo filename collisions, video downloads, and EXIF dates

Photos in the same report with identical timestamps were overwriting each
other due to missing image index in the filename. Videos failed to download
because attached_video is a dict with resolution URLs, not a plain URL.

Also read EXIF DateTimeOriginal before modifying the file and write the
report date as fallback when the image has no original timestamp. Embed
creation_time metadata in downloaded videos using ffmpeg.
This commit is contained in:
Jeena 2026-04-04 01:33:16 +00:00
parent 0ada2d4858
commit 8413179b5f
4 changed files with 385 additions and 377 deletions

View file

@ -4,6 +4,8 @@ import os
import sys
import json
import datetime
import shutil
import subprocess
import requests
from pathlib import Path
from PIL import Image
@ -17,13 +19,17 @@ if not STORAGE_PATH:
print("Error: KIDSNOTE_STORAGE_PATH is not set.")
sys.exit(1)
HAS_FFMPEG = shutil.which("ffmpeg") is not None
if not HAS_FFMPEG:
print("Warning: ffmpeg not found. Videos will be saved without date metadata.")
def convert_to_degrees(value):
degrees = int(value)
minutes = int((value - degrees) * 60)
seconds = (value - degrees - minutes / 60) * 3600
return ((degrees, 1), (minutes, 1), (int(seconds * 100), 100))
def add_exif_data(image_path, title, content, location_str=None):
def add_exif_data(image_path, title, content, location_str=None, fallback_date=None):
try:
img = Image.open(image_path)
if 'exif' in img.info:
@ -31,6 +37,10 @@ def add_exif_data(image_path, title, content, location_str=None):
else:
exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "thumbnail": None}
if fallback_date and not exif_dict['Exif'].get(piexif.ExifIFD.DateTimeOriginal):
dt_str = fallback_date.strftime("%Y:%m:%d %H:%M:%S")
exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = dt_str.encode('utf-8')
if location_str:
try:
latitude, longitude = map(float, location_str.split(','))
@ -93,10 +103,35 @@ for date, reports in sorted(reports_by_date.items()):
if report.get('attached_video'):
try:
r = requests.get(report['attached_video'])
video = report['attached_video']
video_url = video.get('high') or video.get('low') if isinstance(video, dict) else video
r = requests.get(video_url)
r.raise_for_status()
with open(folder / f"V_{report_index}.MP4", "wb") as f:
f.write(r.content)
video_path = folder / f"V_{report_index}.MP4"
if HAS_FFMPEG:
temp_video = folder / f"temp_V_{report_index}.MP4"
with open(temp_video, "wb") as f:
f.write(r.content)
# Check if the video already has a creation_time
probe = subprocess.run([
"ffprobe", "-v", "quiet", "-show_entries",
"format_tags=creation_time", "-of", "csv=p=0",
str(temp_video)
], capture_output=True, text=True)
if probe.stdout.strip():
temp_video.rename(video_path)
else:
report_datetime = datetime.datetime.strptime(report['created'], "%Y-%m-%dT%H:%M:%S.%fZ")
creation_time = report_datetime.strftime("%Y-%m-%dT%H:%M:%S.000000Z")
subprocess.run([
"ffmpeg", "-y", "-i", str(temp_video),
"-metadata", f"creation_time={creation_time}",
"-c", "copy", str(video_path)
], capture_output=True)
temp_video.unlink()
else:
with open(video_path, "wb") as f:
f.write(r.content)
except requests.RequestException as e:
print(f"Video download failed: {e}")
@ -108,15 +143,17 @@ for date, reports in sorted(reports_by_date.items()):
with open(temp_path, "wb") as f:
f.write(r.content)
add_exif_data(temp_path, report['child_name'], report['content'], report.get('location'))
dt = get_creation_datetime(temp_path)
report_datetime = datetime.datetime.strptime(report['created'], "%Y-%m-%dT%H:%M:%S.%fZ")
add_exif_data(temp_path, report['child_name'], report['content'], report.get('location'), fallback_date=report_datetime)
if dt:
date_str = dt.strftime("%Y-%m-%d_%H-%M")
else:
date_str = date.strftime("%Y-%m-%d")
final_name = f"P{report_index}_{date_str}.jpg"
final_name = f"P{report_index}_{img_index}_{date_str}.jpg"
final_path = folder / final_name
temp_path.rename(final_path)