Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 61 additions & 2 deletions backend/app/database/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class ImageRecord(TypedDict, total=False):
latitude: Optional[float]
longitude: Optional[float]
captured_at: Optional[datetime]
# New fields for video support
is_video: Optional[bool]
duration: Optional[float]
codec: Optional[str]


class UntaggedImageRecord(TypedDict):
Expand Down Expand Up @@ -70,6 +74,9 @@ def db_create_images_table() -> None:
metadata TEXT,
isTagged BOOLEAN DEFAULT 0,
isFavourite BOOLEAN DEFAULT 0,
is_video BOOLEAN DEFAULT 0,
duration REAL,
codec TEXT,
latitude REAL,
longitude REAL,
captured_at DATETIME,
Expand Down Expand Up @@ -103,6 +110,17 @@ def db_create_images_table() -> None:
"""
)

# Ensure new video-related columns exist for pre-existing databases
cursor.execute("PRAGMA table_info(images)")
existing_columns = {row[1] for row in cursor.fetchall()}

if "is_video" not in existing_columns:
cursor.execute("ALTER TABLE images ADD COLUMN is_video BOOLEAN DEFAULT 0")
if "duration" not in existing_columns:
cursor.execute("ALTER TABLE images ADD COLUMN duration REAL")
if "codec" not in existing_columns:
cursor.execute("ALTER TABLE images ADD COLUMN codec TEXT")

conn.commit()
conn.close()

Expand All @@ -118,8 +136,36 @@ def db_bulk_insert_images(image_records: List[ImageRecord]) -> bool:
try:
cursor.executemany(
"""
INSERT INTO images (id, path, folder_id, thumbnailPath, metadata, isTagged, latitude, longitude, captured_at)
VALUES (:id, :path, :folder_id, :thumbnailPath, :metadata, :isTagged, :latitude, :longitude, :captured_at)
INSERT INTO images (
id,
path,
folder_id,
thumbnailPath,
metadata,
isTagged,
isFavourite,
is_video,
duration,
codec,
latitude,
longitude,
captured_at
)
VALUES (
:id,
:path,
:folder_id,
:thumbnailPath,
:metadata,
:isTagged,
COALESCE(:isFavourite, 0),
COALESCE(:is_video, 0),
:duration,
:codec,
:latitude,
:longitude,
:captured_at
)
ON CONFLICT(path) DO UPDATE SET
folder_id=excluded.folder_id,
thumbnailPath=excluded.thumbnailPath,
Expand All @@ -128,6 +174,10 @@ def db_bulk_insert_images(image_records: List[ImageRecord]) -> bool:
WHEN excluded.isTagged THEN 1
ELSE images.isTagged
END,
isFavourite=COALESCE(excluded.isFavourite, images.isFavourite),
is_video=COALESCE(excluded.is_video, images.is_video),
duration=COALESCE(excluded.duration, images.duration),
codec=COALESCE(excluded.codec, images.codec),
latitude=COALESCE(excluded.latitude, images.latitude),
longitude=COALESCE(excluded.longitude, images.longitude),
captured_at=COALESCE(excluded.captured_at, images.captured_at)
Expand Down Expand Up @@ -169,6 +219,9 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
i.metadata,
i.isTagged,
i.isFavourite,
i.is_video,
i.duration,
i.codec,
i.latitude,
i.longitude,
i.captured_at,
Expand Down Expand Up @@ -199,6 +252,9 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
metadata,
is_tagged,
is_favourite,
is_video,
duration,
codec,
latitude,
longitude,
captured_at,
Expand All @@ -218,6 +274,9 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
"metadata": metadata_dict,
"isTagged": bool(is_tagged),
"isFavourite": bool(is_favourite),
"is_video": bool(is_video),
"duration": duration,
"codec": codec,
"latitude": latitude,
"longitude": longitude,
"captured_at": (
Expand Down
6 changes: 6 additions & 0 deletions backend/app/routes/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class ImageData(BaseModel):
metadata: MetadataModel
isTagged: bool
isFavourite: bool
isVideo: bool = False
duration: Optional[float] = None
codec: Optional[str] = None
tags: Optional[List[str]] = None


Expand Down Expand Up @@ -66,6 +69,9 @@ def get_all_images(
metadata=image_util_parse_metadata(image["metadata"]),
isTagged=image["isTagged"],
isFavourite=image.get("isFavourite", False),
isVideo=bool(image.get("is_video", False)),
duration=image.get("duration"),
codec=image.get("codec"),
tags=image["tags"],
)
for image in images
Expand Down
5 changes: 5 additions & 0 deletions backend/app/utils/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ def image_util_prepare_image_records(
"thumbnailPath": thumbnail_path,
"metadata": metadata_json,
"isTagged": False,
# Default video-related fields for image ingestion
"isFavourite": False,
"is_video": False,
"duration": None,
"codec": None,
"latitude": latitude, # Can be None
"longitude": longitude, # Can be None
"captured_at": (
Expand Down
56 changes: 56 additions & 0 deletions backend/tests/test_video_metadata_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sqlite3
from pathlib import Path

from app.database.images import db_create_images_table


def test_db_create_images_table_adds_video_columns(tmp_path, monkeypatch):
"""
Ensure db_create_images_table adds is_video, duration, and codec columns
to an existing images table that was created without them.
"""

db_path = tmp_path / "test_video_schema.sqlite3"

# Create an "old schema" images table without video columns
conn = sqlite3.connect(db_path)
try:
conn.execute(
"""
CREATE TABLE images (
id TEXT PRIMARY KEY,
path VARCHAR UNIQUE,
folder_id INTEGER,
thumbnailPath TEXT UNIQUE,
metadata TEXT,
isTagged BOOLEAN DEFAULT 0,
isFavourite BOOLEAN DEFAULT 0,
latitude REAL,
longitude REAL,
captured_at DATETIME
)
"""
)
conn.commit()
finally:
conn.close()

# Point the images module to this temporary database
monkeypatch.setattr("app.database.images.DATABASE_PATH", str(db_path))

# This should run CREATE TABLE IF NOT EXISTS (no-op on existing)
# and then ensure the new video-related columns exist via ALTER TABLE.
db_create_images_table()

# Verify that the new columns are present
conn = sqlite3.connect(db_path)
try:
cursor = conn.execute("PRAGMA table_info(images)")
columns = {row[1] for row in cursor.fetchall()}
finally:
conn.close()

assert "is_video" in columns
assert "duration" in columns
assert "codec" in columns

Loading