Commit Graph

76 Commits

Author SHA1 Message Date
60790a3af1 Merge pull request 'ai-feature-setup' (#20) from ai-feature-setup into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/20
2026-04-12 21:24:57 +00:00
Garret Patti
6c769b457f handle video tagging 2026-04-12 17:24:39 -04:00
Garret Patti
ad9920a448 limit tags sent and send applied tags to ai 2026-04-12 16:45:26 -04:00
Garret Patti
732e9134c3 ai starter implementation 2026-04-12 15:39:48 -04:00
Garret Patti
0238dbda7a Add AI-powered image tagging via local LLM
Adds automatic image tagging that runs as a post-scan phase, sending
thumbnails to an OpenAI-compatible vision API and applying matching
tags from the user-defined tag vocabulary.

- New ai-tagger module with batch processing, failure tolerance, and
  tag validation against existing vocabulary
- Admin settings page (Manage > AI Tagging) for endpoint, model, and
  enable toggle with connection testing
- DB migration for ai_tagged_at tracking column and AI config seeds
- Re-tag All support to queue items for reprocessing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 15:18:03 -04:00
9bff0f848a Merge pull request 'add individual library scanning' (#19) from scanner-improvements into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 53s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/19
2026-04-12 18:10:13 +00:00
Garret Patti
aae41e9803 add individual library scanning 2026-04-12 13:51:51 -04:00
7e9ba6e014 Merge pull request 'add-android-platform' (#18) from add-android-platform into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/18
2026-04-12 17:09:29 +00:00
Garret Patti
0091606e4d handle other archive types for linux 2026-04-12 13:09:07 -04:00
Garret Patti
080cc011b9 icon color and size tweaks 2026-04-12 12:41:42 -04:00
Garret Patti
d3e1bf049b handle android and swap to os icons 2026-04-12 11:53:27 -04:00
625539f35e Merge pull request 'game-enhancements' (#17) from game-enhancements into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m4s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/17
2026-04-12 14:19:51 +00:00
Garret Patti
84c65c7964 Add screenshots to game detail modal
- New /api/game-screenshots route handles GET (list), POST (upload), and DELETE (remove single file)
- Screenshots stored in a screenshots/ subfolder inside each game directory
- Images converted to JPEG via Sharp on upload, named shot-{timestamp}.jpg
- Modal shows a horizontal scrollable strip of 16:9 thumbnail tiles
- Hover a tile to reveal a delete button; uploading placeholders appear per in-flight upload
- Dashed + tile triggers multi-file picker for batch uploads
- Click any thumbnail to open a full-screen lightbox with prev/next arrows, counter, and keyboard nav (←/→/Esc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 10:18:38 -04:00
Garret Patti
53205d4a19 Add multi-platform game support with per-OS download detection
- Detect Windows (.zip), Linux (.tar.gz), and macOS (.dmg / .app bundle) game archives during scan
- Store GameFile[] with platform metadata in DB instead of plain zipFiles[]
- Stream .app bundles as on-the-fly zip archives via archiver
- Show WIN/LIN/MAC platform badge pills on GameCard and SeriesCard
- Auto-select the download matching the user's OS in GameDetailModal
- Persist cover URL to DB immediately on upload (no re-scan needed)
- Backward-compatible: legacy zipFiles entries map to platform 'windows'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 09:47:09 -04:00
ebc35d7184 Merge pull request 'add more management capabilities' (#16) from management into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 52s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/16
2026-04-11 22:34:38 +00:00
Garret Patti
768c49ef00 add more management capabilities 2026-04-11 18:33:03 -04:00
1ca90184f5 Merge pull request 'clean-up' (#15) from clean-up into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m42s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/15
2026-04-11 01:33:10 +00:00
Garret Patti
6c2443fa2c Filter non-browser-playable formats from Doom Scroll
Formats like .mkv, .avi, .wmv, .ts, .m2ts and .tiff are not natively
supported by browsers and would stall silently in Doom Scroll mode.

Add src/lib/browser-media.ts with BROWSER_VIDEO_EXTENSIONS (.mp4,
.webm, .mov, .m4v), BROWSER_IMAGE_EXTENSIONS (.jpg, .jpeg, .png, .gif,
.webp, .bmp), and an isBrowserPlayable() helper that extracts the
extension without importing Node's path module.

Filter doomScrollItems in MixedView, MoviesView, and TvView using this
helper so only natively renderable files are passed to DoomScrollView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:29:17 -04:00
Garret Patti
5d4d11512d Fix DoomScrollView going blank after 100 items
When history hit the 100-entry cap, setHistory sliced the array back to
indices 0–99 but setHistoryIndex still returned idx + 1 = 100, making
current = history[100] = undefined. Nothing rendered and no API calls
were made until the user went back (decrementing to index 99, which
held the newly-picked item).

Fix: cap the returned historyIndex at HISTORY_CAP - 1 so it always
points to a valid entry in the sliced array. Extract HISTORY_CAP = 100
as a named constant so the slice and the index cap stay in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:13:06 -04:00
Garret Patti
6f86750a99 Unify media_key and item_key — use item_key everywhere
media_key was a lossy shortening of item_key (libraryId:lastSegment) that
introduced a real collision bug: two TV episodes from different series with
the same filename would share the same media_key and each other's tags.

- DB migration converts existing media_tags rows from short format to full
  item_key by joining against media_items; ambiguous/orphaned rows are dropped
- media_tags column renamed media_key → item_key
- Removed itemKeyToMediaKey() from scanner; reconcileAndPrune now passes
  item_key directly to reKeyMediaItem
- DB reader functions (tv, movies, games) now expose item_key on returned
  entities; frontend components use entity.item_key instead of constructing
  the short libraryId:id form
- MixedView now constructs the full mixed_file: item_key format
- Tag API renamed mediaKey param → itemKey throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 18:04:29 -04:00
390ce8fcc6 Merge pull request 'performance-stability' (#14) from performance-stability into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 50s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/14
2026-04-07 00:22:00 +00:00
Garret Patti
f08950f456 Fix Doom Scroll mode bugs in TV libraries and video autoplay
TV library fix: the unfiltered Doom Scroll path was calling
/api/browse which explicitly rejects non-mixed libraries with a 400
error, leaving the item list empty. Replace it with the same TV API
hierarchy fetch already used by the filtered path (series → seasons →
episodes), matching how the rest of the TV library is loaded.

Autoplay fix (all library types): two interacting bugs caused videos
to silently stall on navigation. First, the play/pause effect had
current?.url in its deps, so navigating while paused would call
pause() on the freshly-mounted video element before the isPaused reset
could take effect. Second, browser autoplay policy blocks unmuted
play() calls and the rejection was silently swallowed with no recovery.
Fix by merging the isPaused reset and the play() call into one
navigation effect, and adding a muted fallback on rejection so playback
always starts — the user can unmute manually afterward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:21:27 -04:00
Garret Patti
4d75e74cab Fix post-scan CPU spike and improve scan performance at scale
- Remove fire-and-forget thumbnail pre-warming from scanMixed(): firing
  48k+ simultaneous unresolved getThumbnailPath() promises was saturating
  sharp and ffmpeg after scan completion, keeping CPU pegged. Mixed-library
  thumbnails are now generated on-demand by /api/thumbnail as before.
- Add incremental fingerprinting: load existing (item_key → fingerprint)
  map from DB before each walk; reuse stored fingerprint for unchanged paths
  instead of re-reading 64 KB per file. Stable re-scans now do ~0 bytes of
  fingerprint I/O.
- Wrap all bulk DB upsert and delete loops in db.transaction() in
  scanMovies(), scanTv(), scanMixed(), and reconcileAndPrune(). Reduces
  N auto-committed WAL writes to a single batch commit per scan phase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:58:05 -04:00
e5953100d6 Merge pull request 'file-fingerprinting' (#13) from file-fingerprinting into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/13
2026-04-06 23:06:22 +00:00
Garret Patti
58c5e424d9 Fix media_tags not updating when fingerprint move is detected
reKeyMediaItem was called with item_key values (e.g. "lib1:movie:Inception")
but media_tags stores keys in the shorter UI format (e.g. "lib1:Inception"),
so the UPDATE never matched any rows.

Add itemKeyToMediaKey() to extract the terminal segment of an item_key and
reconstruct the media_key format the UI uses:
  lib1:movie:Inception%20(2010)          → lib1:Inception%20(2010)
  lib1:tv_episode:Show:Season1:ep.mkv   → lib1:ep.mkv
  lib1:mixed_file:dir%2Ffile.mp4        → lib1:dir%2Ffile.mp4

Also skip the UPDATE when old and new media_keys are identical (e.g. a TV
episode moved between seasons keeps the same filename-based media_key).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:59:51 -04:00
Garret Patti
38a6886863 Add file fingerprinting for move-resilient media item identity
Computes a SHA-256 partial-content fingerprint (file size + first 64 KB) for
movies, TV episodes, and mixed files during scans. When a file is moved or
renamed within a library, the scan detects the fingerprint match, renames the
media_items row in-place, and updates media_tags.media_key to match — so tags
and NFO metadata survive the move transparently.

- src/lib/fingerprint.ts: new computeFingerprint() using sync FS reads
- src/lib/db.ts: fingerprint TEXT column + index migration
- src/lib/tags.ts: reKeyMediaItem() to update media_tags on rename
- src/lib/scanner.ts: replace clear+upsert with detectMoves/reconcileAndPrune
  for movies, TV episodes, and mixed files; games retain clear+upsert (v1)
- TV scan restructured to a single filesystem pass (no double-scanning)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:35:02 -04:00
Garret Patti
819748d1ff DB-first library reads, mixed library indexing, and manual NFO refresh
- API reads now serve from media_items cache instead of scanning the filesystem
  on every request; scans (manual or scheduled) remain the write path
- NFO metadata is no longer parsed automatically during scans; title falls back
  to folder/filename — metadata can be refreshed per-item via the kabob menu
- Mixed libraries are now indexed in media_items (new mixed_file item type)
  with file_path stored; scanMixed walks recursively and upserts all files
- Added file_path column to media_items and migrated item_type CHECK constraint
  to include mixed_file via safe table-recreation migration
- New POST /api/nfo-refresh endpoint reads the .nfo for a single item and
  patches its DB row (supports movie, tv_series, tv_episode)
- Added "Refresh metadata" button to movie and TV series kabob menus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:20:21 -04:00
01a4a1c0b7 Merge pull request 'fix search/filter bugs in game and TV libraries' (#12) from search-tweaks into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 52s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/12
2026-04-06 18:24:30 +00:00
Garret Patti
5d27ba351b fix search/filter bugs in game and TV libraries
- Game series: filter now checks child games for both search and tag matches instead of always passing series through
- TV episodes: tag selector no longer closes after picking a tag
- TV episodes: filter panel now filters episodes within a season view
- TV series list: series now appear when any of their episodes match the active tag filter (via new /api/tv/series-episode-tags endpoint backed by media_items)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:23:34 -04:00
957d884903 Merge pull request 'Reduce code duplication and update README' (#11) from cleanup-pass into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/11
2026-04-06 16:52:22 +00:00
Garret Patti
6b5ff81654 Reduce code duplication and update README
- Extract shared utilities (HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl,
  thumbnailApiUrl, findFile) into new src/lib/media-utils.ts, removing
  identical copies from games.ts, movies.ts, tv.ts, files.ts, and scanner.ts
- Add comment in files.ts clarifying why its VIDEO_EXTENSIONS set intentionally
  differs from the media library set (web-playable formats for the mixed browser)
- Rewrite README to reflect the current feature set: Movies/TV libraries, auth
  system, tag system, background scanner, updated project structure, folder
  conventions for all four library types, and a complete API reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:49:24 -04:00
80d922263e Merge pull request 'handle shows without season folders' (#10) from navigation into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 50s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/10
2026-04-06 02:18:58 +00:00
e46c8d026f Merge branch 'main' into navigation 2026-04-06 02:18:50 +00:00
Garret Patti
87a90a88bc handle shows without season folders 2026-04-05 22:18:08 -04:00
ffaa460324 Merge pull request 'navigation' (#9) from navigation into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 50s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/9
2026-04-06 01:48:01 +00:00
Garret Patti
0c234b691e add auto mode and controls 2026-04-05 21:47:44 -04:00
Garret Patti
4f54a7c888 add viewer navigation and doom scroll mode
- Add prev/next arrow buttons and ArrowLeft/ArrowRight keyboard shortcuts to ImageLightbox and VideoPlayerModal
- Wire prev/next navigation in MixedView (through filtered media entries), TvView (through season episodes), and MoviesView/MovieDetailModal (through filtered movie list)
- Add new DoomScrollView component: fullscreen random-media mode with scroll/swipe/keyboard navigation, 100-item back-history, and per-library mute settings
- Add Doom Scroll button to mixed, movies, and TV library views
- Doom scroll respects active filters: mixed uses filtered entries, movies uses filtered movie list, TV fetches episodes from matching series only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:34:32 -04:00
Garret Patti
334d62e3b3 fix docker auth
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m3s
2026-04-05 19:59:24 -04:00
612e20da8e Merge pull request 'library-scanning' (#8) from library-scanning into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m2s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/8
2026-04-05 23:24:49 +00:00
Garret Patti
6858c1e8cf add tagging to tv 2026-04-05 19:24:28 -04:00
Garret Patti
8829188c58 add scanning 2026-04-05 18:55:53 -04:00
c87a9b33bb Merge pull request 'login-user-settings' (#7) from login-user-settings into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m1s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/7
2026-04-05 22:17:53 +00:00
Garret Patti
5b5503b7a6 add user settings 2026-04-05 18:15:08 -04:00
Garret Patti
eecee9bc5f add auth 2026-04-05 17:44:24 -04:00
f0666c0649 Merge pull request 'viewer-improvements' (#6) from viewer-improvements into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 46s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/6
2026-04-05 20:32:17 +00:00
Garret Patti
ca4bea084a make FilterPanel hideable and responsive across all library views
Adds a toggle button to show/hide the filter panel in Movies, Games,
Mixed, and TV views. On mobile the layout stacks vertically (filter
above content); on md+ it returns to the side-by-side layout. The
toggle button highlights when filters are active so hidden filters
remain discoverable. Also fixes a layout bug where items-start on the
flex-col container caused MixedView thumbnails to collapse on narrow
screens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 16:29:49 -04:00
Garret Patti
bc77abbd8b scaling tweaks 2026-04-05 16:03:51 -04:00
Garret Patti
427aade21a add tag selector to image and video viewers 2026-04-05 14:01:32 -04:00
2ba887dba6 Merge pull request 'Add Movie and TV Library types.' (#5) from library-types into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 59s
Reviewed-on: http://gitea.lan/gpatti/MediaLore/pulls/5
2026-04-05 16:50:48 +00:00
Garret Patti
122d7aa332 add series grouping, cover upload, and multi-zip download to games library
- Series grouping: a top-level folder with no .zip but game subfolders is
  now treated as a GameSeries. Clicking a series drills into it with a
  breadcrumb; a game-count badge distinguishes series cards from game cards.
  Series fall back to the first game's cover when no series-level cover exists.
- Cover upload: new POST /api/game-cover endpoint writes cover.jpg or
  widecover.jpg directly into the game/series folder (re-encoded via sharp).
  A kebab menu on GameDetailModal opens an Edit Images panel showing previews
  and upload/replace buttons for both cover and wide cover.
- Multi-zip download: Game.zipFiles replaces zipPath and includes all .zip
  files in the folder. A single zip shows the existing download button; multiple
  zips render a split button — primary action downloads the first file, a
  dropdown arrow lists all files by name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:49:42 -04:00