220 lines
5.3 KiB
Python
Executable File
220 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Process all movies waiting for OCR in the WCX SQLite index.
|
|
|
|
The script queries the movie table for records whose OCR status is "pending"
|
|
and invokes check_ocr.py once for each movie ID.
|
|
|
|
check_ocr.py performs the actual OCR request, parsing, validation, and database
|
|
update. This batch script only coordinates those individual executions.
|
|
|
|
Processing continues if one movie fails. A final summary reports how many
|
|
movies completed successfully and how many failed or require manual review.
|
|
|
|
By default, all pending movies are processed. Use --limit to restrict the
|
|
number of movies during testing.
|
|
|
|
Usage:
|
|
process_pending_ocr.py
|
|
process_pending_ocr.py --limit 1
|
|
process_pending_ocr.py --database /path/to/wcx-test.db
|
|
|
|
Requirements:
|
|
GOOGLE_VISION_API_KEY must be available in the environment.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
DEFAULT_DATABASE_FILE = Path(
|
|
"/storage/disk1/WCX/database/wcx.db"
|
|
)
|
|
CHECK_OCR_SCRIPT = Path(
|
|
"/storage/disk1/WCX/scripts/check_ocr.py"
|
|
)
|
|
|
|
|
|
def parse_arguments() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Process movies whose OCR status is pending."
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--database",
|
|
type=Path,
|
|
default=DEFAULT_DATABASE_FILE,
|
|
help=f"SQLite database; default: {DEFAULT_DATABASE_FILE}",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--limit",
|
|
type=int,
|
|
help="Process at most N pending movies.",
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def get_pending_movies(
|
|
database_file: Path,
|
|
limit: int | None,
|
|
) -> list[tuple[str, str]]:
|
|
sql = """
|
|
SELECT id, name
|
|
FROM movie
|
|
WHERE ocr_status = 'pending'
|
|
ORDER BY published, id
|
|
"""
|
|
|
|
parameters: tuple[int, ...] = ()
|
|
|
|
if limit is not None:
|
|
sql += "\nLIMIT ?"
|
|
parameters = (limit,)
|
|
|
|
with sqlite3.connect(database_file) as connection:
|
|
rows = connection.execute(sql, parameters).fetchall()
|
|
|
|
return [
|
|
(str(movie_id), str(name))
|
|
for movie_id, name in rows
|
|
]
|
|
|
|
|
|
def get_ocr_status(
|
|
database_file: Path,
|
|
movie_id: str,
|
|
) -> tuple[str | None, str | None]:
|
|
with sqlite3.connect(database_file) as connection:
|
|
row = connection.execute(
|
|
"""
|
|
SELECT ocr_status, ocr_error
|
|
FROM movie
|
|
WHERE id = ?
|
|
""",
|
|
(movie_id,),
|
|
).fetchone()
|
|
|
|
if row is None:
|
|
return None, "Movie disappeared from the database."
|
|
|
|
return row[0], row[1]
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_arguments()
|
|
|
|
if args.limit is not None and args.limit <= 0:
|
|
raise ValueError("--limit must be greater than zero.")
|
|
|
|
if not args.database.is_file():
|
|
raise FileNotFoundError(
|
|
f"Database file does not exist: {args.database}"
|
|
)
|
|
|
|
if not CHECK_OCR_SCRIPT.is_file():
|
|
raise FileNotFoundError(
|
|
f"OCR processing script does not exist: {CHECK_OCR_SCRIPT}"
|
|
)
|
|
|
|
if not os.environ.get("GOOGLE_VISION_API_KEY"):
|
|
raise ValueError(
|
|
"GOOGLE_VISION_API_KEY is not set in the environment."
|
|
)
|
|
|
|
pending_movies = get_pending_movies(
|
|
database_file=args.database,
|
|
limit=args.limit,
|
|
)
|
|
|
|
if not pending_movies:
|
|
print("No movies are waiting for OCR.")
|
|
return
|
|
|
|
print(f"Movies waiting for OCR: {len(pending_movies)}")
|
|
print()
|
|
|
|
completed = 0
|
|
manual_review = 0
|
|
failed = 0
|
|
unexpected = 0
|
|
|
|
for position, (movie_id, name) in enumerate(
|
|
pending_movies,
|
|
start=1,
|
|
):
|
|
print(
|
|
f"[{position}/{len(pending_movies)}] "
|
|
f"{movie_id} - {name}"
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(CHECK_OCR_SCRIPT),
|
|
movie_id,
|
|
"--database",
|
|
str(args.database),
|
|
],
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
if result.stdout.strip():
|
|
print(result.stdout.strip())
|
|
|
|
if result.stderr.strip():
|
|
print(result.stderr.strip(), file=sys.stderr)
|
|
|
|
status, error_message = get_ocr_status(
|
|
database_file=args.database,
|
|
movie_id=movie_id,
|
|
)
|
|
|
|
if status == "completed":
|
|
completed += 1
|
|
print("Result: completed")
|
|
elif status == "manual_review":
|
|
manual_review += 1
|
|
print("Result: manual_review")
|
|
elif status == "failed":
|
|
failed += 1
|
|
print("Result: failed")
|
|
else:
|
|
unexpected += 1
|
|
print(f"Result: unexpected status {status!r}")
|
|
|
|
if error_message:
|
|
print(f"Error: {error_message}")
|
|
|
|
print()
|
|
|
|
print("OCR batch summary")
|
|
print("-----------------")
|
|
print(f"Selected: {len(pending_movies)}")
|
|
print(f"Completed: {completed}")
|
|
print(f"Manual review: {manual_review}")
|
|
print(f"Failed: {failed}")
|
|
print(f"Unexpected: {unexpected}")
|
|
|
|
if failed or unexpected:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except (
|
|
FileNotFoundError,
|
|
ValueError,
|
|
sqlite3.Error,
|
|
) as error:
|
|
print(f"Error: {error}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|