initial commit

This commit is contained in:
2026-07-17 10:16:43 +02:00
commit 60e290c37e
4 changed files with 1223 additions and 0 deletions

191
scripts/import_site.py Executable file
View File

@ -0,0 +1,191 @@
#!/usr/bin/env python3
import json
import sqlite3
from pathlib import Path
from typing import Any
JSON_FILE = Path("/storage/disk1/WCX/import/wcx_site_index.json")
DATABASE_FILE = Path("/storage/disk1/WCX/database/wcx.db")
def parse_duration(value: str | None) -> int | None:
"""Convert mm:ss or hh:mm:ss to seconds."""
if not value:
return None
parts = value.split(":")
try:
numbers = [int(part) for part in parts]
except ValueError as error:
raise ValueError(f"Ogiltig speltid: {value!r}") from error
if len(numbers) == 2:
minutes, seconds = numbers
hours = 0
elif len(numbers) == 3:
hours, minutes, seconds = numbers
else:
raise ValueError(f"Ogiltigt duration-format: {value!r}")
if hours < 0 or minutes < 0 or seconds < 0:
raise ValueError(f"Speltiden får inte vara negativ: {value!r}")
if seconds >= 60:
raise ValueError(f"Ogiltigt sekundvärde i speltid: {value!r}")
if len(numbers) == 3 and minutes >= 60:
raise ValueError(f"Ogiltigt minutvärde i speltid: {value!r}")
return hours * 3600 + minutes * 60 + seconds
def get_site_updated(movie: dict[str, Any]) -> str | None:
"""
Read the site's update date.
Accept both 'update' and 'updated', although the site's normal field
is expected to be named 'update'.
"""
return movie.get("update") or movie.get("updated")
def validate_movie(movie: Any, index: int) -> dict[str, Any]:
if not isinstance(movie, dict):
raise ValueError(f"Post {index} är inte ett JSON-objekt.")
for required_field in ("id", "titel"):
value = movie.get(required_field)
if not isinstance(value, str) or not value.strip():
raise ValueError(
f"Post {index} saknar ett giltigt fält: {required_field}"
)
return movie
def insert_movie(
connection: sqlite3.Connection,
movie: dict[str, Any],
site_updated: str | None,
) -> None:
connection.execute(
"""
INSERT INTO movie (
id,
name,
duration_seconds,
web_url,
thumbnail,
published,
updated
)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
movie["id"],
movie["titel"],
parse_duration(movie.get("duration")),
movie.get("details"),
movie.get("thumb"),
movie.get("published"),
site_updated,
),
)
def update_movie(
connection: sqlite3.Connection,
movie: dict[str, Any],
site_updated: str,
) -> None:
connection.execute(
"""
UPDATE movie
SET
name = ?,
duration_seconds = ?,
web_url = ?,
thumbnail = ?,
published = ?,
updated = ?,
modified_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(
movie["titel"],
parse_duration(movie.get("duration")),
movie.get("details"),
movie.get("thumb"),
movie.get("published"),
site_updated,
movie["id"],
),
)
def main() -> None:
if not JSON_FILE.is_file():
raise FileNotFoundError(f"JSON-filen finns inte: {JSON_FILE}")
if not DATABASE_FILE.is_file():
raise FileNotFoundError(f"Databasen finns inte: {DATABASE_FILE}")
with JSON_FILE.open("r", encoding="utf-8") as file:
movies = json.load(file)
if not isinstance(movies, list):
raise ValueError("JSON-filen måste innehålla en array.")
inserted = 0
updated = 0
unchanged = 0
with sqlite3.connect(DATABASE_FILE) as connection:
for index, raw_movie in enumerate(movies, start=1):
movie = validate_movie(raw_movie, index)
movie_id = movie["id"]
site_updated = get_site_updated(movie)
existing = connection.execute(
"""
SELECT updated
FROM movie
WHERE id = ?
""",
(movie_id,),
).fetchone()
if existing is None:
insert_movie(connection, movie, site_updated)
inserted += 1
continue
database_updated = existing[0]
should_update = (
site_updated is not None
and (
database_updated is None
or site_updated > database_updated
)
)
if should_update:
update_movie(connection, movie, site_updated)
updated += 1
else:
unchanged += 1
print(f"Poster i JSON: {len(movies)}")
print(f"Tillagda: {inserted}")
print(f"Uppdaterade: {updated}")
print(f"Oförändrade: {unchanged}")
if __name__ == "__main__":
main()

23
scripts/schema.sql Normal file
View File

@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS movie (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
aka TEXT,
nationality TEXT,
age INTEGER,
shoot_location TEXT,
shoot_date TEXT,
duration_seconds INTEGER,
description TEXT,
rating REAL,
web_url TEXT,
thumbnail TEXT,
published TEXT,
updated TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (age IS NULL OR age >= 0),
CHECK (rating IS NULL OR rating BETWEEN 0 AND 10),
CHECK (duration_seconds IS NULL OR duration_seconds >= 0)
);