Media Reviewer

A lightweight, mobile-first web application for reviewing and sorting large sets of images and videos — no database required.

View project on GitHub

Media Reviewer API Reference

GET /api/health

Returns a typed status payload used by the frontend shell to confirm API availability and expose baseline configuration values.

Response

{
  "status": "ok",
  "service": "mediareviewer-api",
  "settings": {
    "stateDirectory": "/home/example/.mediareviewer",
    "hiddenPickerPaths": ["/proc", "/sys"],
    "deletionWorkers": 2
  },
  "deletionQueue": {
    "max_workers": 2,
    "active_jobs": 0,
    "submitted_jobs": 0,
    "completed_jobs": 0,
    "failed_jobs": 0
  }
}

Notes

  • settings.hiddenPickerPaths exposes the current conservative hidden-path defaults for the folder picker.
  • deletionQueue is currently a scaffolded placeholder for the future async trash-empty workflow.

GET /api/review-paths

Returns known review paths from ~/.mediareviewer/config.yaml plus active hidden picker paths.

Response

{
  "knownPaths": ["/home/michaelmoore/trailcam"],
  "hiddenPickerPaths": ["/proc", "/sys"]
}

POST /api/review-paths

Adds and persists a known review path.

Request

{
  "path": "/home/michaelmoore/trailcam"
}

Success Response

{
  "addedPath": "/home/michaelmoore/trailcam",
  "knownPaths": ["/home/michaelmoore/trailcam"]
}

Errors

  • 400: request body missing or path invalid.
  • 403: path is under hidden picker policy.

DELETE /api/review-paths

Removes a known review path from ~/.mediareviewer/config.yaml. The directory is not required to exist on disk.

Request

{
  "path": "/home/michaelmoore/trailcam"
}

Success Response

{
  "removedPath": "/home/michaelmoore/trailcam",
  "knownPaths": []
}

Errors

  • 400: request body missing or path invalid.
  • 404: path is not a configured known review path.

GET /api/folders

Returns immediate child folders under a parent directory. Hidden folders (starting with .) are excluded.

Query Parameters

  • path (required): absolute path must be an existing directory.

Response

{
  "path": "/home/michaelmoore/reviews",
  "folders": [
    {
      "path": "/home/michaelmoore/reviews/trip-2025",
      "name": "trip-2025",
      "has_children": true
    },
    {
      "path": "/home/michaelmoore/reviews/archive-2024",
      "name": "archive-2024",
      "has_children": false
    }
  ]
}

Notes

  • Folders starting with . are hidden and excluded from results.
  • has_children indicates whether the folder contains any non-hidden subfolders.
  • Folders are sorted alphabetically by name.

Errors

  • 400: missing path parameter or path does not exist or is not a directory.
  • 403: path is hidden by picker policy (e.g., under /proc, /sys).

GET /api/folders/files

Returns paginated media files (images and videos only) in a single folder, non-recursive. Includes thumbnails.

Query Parameters

  • path (required): absolute folder path which must be under a configured known review path.
  • offset (optional): integer >= 0, default 0. Number of files to skip.
  • limit (optional): integer from 1 to 1000, default 100. Maximum files to return.

Response

{
  "path": "/home/michaelmoore/reviews/trip-2025",
  "offset": 0,
  "limit": 100,
  "count": 15,
  "ignoredCount": 3,
  "items": [
    {
      "path": "/home/michaelmoore/reviews/trip-2025/day1/photo001.jpg",
      "name": "photo001.jpg",
      "mediaType": "image",
      "thumbnailUrl": "/api/media-thumbnail?path=%2Fhome%2Fmichaelmoore%2Freviews%2Ftrip-2025%2Fday1%2Fphoto001.jpg&size=256",
      "sizeBytes": 2048576,
      "modifiedAt": "2025-03-15T14:30:22.000000+00:00",
      "createdAt": "2025-03-15T14:30:22.000000+00:00",
      "status": {
        "locked": false,
        "trashed": false,
        "seen": false
      },
      "metadata": {
        "width": 4096,
        "height": 3072,
        "duration_seconds": null
      }
    }
  ]
}

Notes

  • Only image and video files are returned; other file types and companion files (.lock, .trash, .seen) are ignored.
  • ignoredCount indicates non-media files encountered during scan.
  • Results are sorted alphabetically by filename.
  • Each item includes thumbnailUrl pointing to the cached thumbnail.
  • Pagination is supported via offset and limit parameters for large folders.
  • metadata.width and metadata.height are populated from Pillow for images and from ffprobe for videos. metadata.duration_seconds is populated from ffprobe for videos; null for images or when ffprobe is unavailable/times out.

Errors

  • 400: missing path, invalid offset (negative), or invalid limit (zero, negative, or > 1000).
  • 403: folder path is not under a configured known review path.

GET /api/media-items/stream

Scans a known review path recursively and streams image/video files as NDJSON (one JSON object per line). Companion state files, hidden directories, and non-media files are ignored. Supports offset-based pagination for infinite-scroll clients.

Query Parameters

  • path (required): absolute path that must already exist in knownPaths.
  • limit (optional): integer from 1 to 10000, default 50.
  • offset (optional): integer >= 0, default 0. Skips the first N matched items to support pagination.

Response

Response Content-Type is application/x-ndjson. Each line (except the last) is a media item:

{"path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/frame001.jpg", "name": "frame001.jpg", "mediaType": "image", "thumbnailUrl": "/api/media-thumbnail?path=...&size=256", "sizeBytes": 154323, "modifiedAt": "2026-04-12T21:50:19.123456+00:00", "createdAt": "2026-04-12T21:50:19.123456+00:00", "status": {"locked": false, "trashed": false, "seen": true}, "metadata": {"width": 1920, "height": 1080, "duration_seconds": null}}

The final line is always the done sentinel:

{"type": "done", "count": 2}

count reflects the number of item lines yielded in this page. When count < limit, the client can infer there are no more pages.

Notes

  • Items are sorted alphabetically by filename.
  • thumbnailUrl points to the disk-backed per-folder thumbnail cache.
  • Hidden directories (names starting with .) are excluded from the recursive walk.

Errors

  • 400: missing path, negative offset, or invalid limit (zero, negative, or > 10000).
  • 403: path is not in configured known review paths.

POST /api/media-actions

Applies a companion-file state action to a media file under a known review path.

Request

{
  "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip001.mp4",
  "action": "trash"
}

Actions

  • lock: creates .lock, creates .seen (lock implies seen).
  • unlock: removes .lock.
  • trash: creates .trash, creates .seen (trash implies seen). Rejected with 409 if the item is currently locked.
  • untrash: removes .trash.
  • seen: creates .seen.
  • unseen: removes .seen.

Success Response

{
  "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip001.mp4",
  "action": "trash",
  "status": {
    "locked": false,
    "trashed": true,
    "seen": true
  }
}

Errors

  • 400: request body invalid, unsupported action, or media path missing.
  • 403: media path is outside configured known review paths.
  • 409: trash action attempted on a locked item. Unlock first, or use unlock followed by trash.

POST /api/empty-trash

Permanently deletes all media files (and their companion files) that are marked as trashed across all configured known review paths. Locked-and-trashed items are skipped to protect them from accidental deletion. After deleting trashed files, orphaned thumbnails (whose source media was removed outside this application) are also pruned from each review path’s thumbnail cache.

The response is a streaming NDJSON body (application/x-ndjson). Each line is a JSON object with a type field. The caller should read the stream line by line until the done event is received or the connection is closed.

Request

No request body required.

Response Content-Type

application/x-ndjson — one JSON object per line, newline-terminated.

Event Schema

type Fields Description
deleting path File is about to be deleted.
deleted path File was successfully deleted.
skipped path, reason File was not deleted (reason: "locked").
error path, message File could not be deleted (OS error).
done deleted, errors Stream complete; summary counts.

Example Stream

{"type": "deleting", "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip001.mp4"}
{"type": "deleted", "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip001.mp4"}
{"type": "deleting", "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip002.mp4"}
{"type": "skipped", "path": "/home/michaelmoore/trailcam/DCIM/100MEDIA/clip002.mp4", "reason": "locked"}
{"type": "done", "deleted": 1, "errors": 0}

Client Abort

The client may abort the request at any time (e.g. via AbortController.abort()). The server will stop streaming when it detects the closed connection.

Errors

  • error events within the stream: an individual file could not be removed (permissions, concurrent modification). The stream continues for remaining files.

GET /api/media-file

Streams an image or video file under a configured review path for fullscreen review mode.

Query Parameters

  • path (required): absolute media file path under a configured known review root.

Behavior

  • Image and video files can be streamed directly for fullscreen review playback.
  • Access is denied for files outside configured review paths.

Errors

  • 400: missing path or file does not exist.
  • 403: file is outside configured known review paths.

GET /api/media-thumbnail

Serves a PNG thumbnail from the on-disk cache, generating it if needed.

Query Parameters

  • path (required): absolute media file path under a configured known review root.
  • size (optional): integer from 1 to 1024, default 256.

Behavior

  • Image files are resized into a square PNG thumbnail.
  • Video files have a frame extracted at the 2-second mark using ffmpeg. If ffmpeg is unavailable or the extraction fails, a labelled placeholder PNG is returned for that request and the failure is not permanently cached — the next request will retry ffmpeg.
  • Thumbnails are stored under <review_path>/.thumbnails/ using a content- addressed MD5 filename (following the freedesktop thumbnail specification). The thumbnail’s mtime is set to match the source media file so staleness is detected automatically on modification.
  • When the stream endpoint starts a new scan, a background daemon thread pre-generates thumbnails for every item in the path so that subsequent page loads and filter changes find thumbnails already cached on disk.

Errors

  • 400: missing path, invalid size, or file does not exist.
  • 403: file is outside configured known review paths.