Skip to content

Repository files navigation

Media Manager

A desktop media library manager built with Electron, React, Vite, and TypeScript.

Use Media Manager for the product name, media-manager for package names, mediaManager for code identifiers, and MEDIA_MANAGER_ for environment variable prefixes.

Current app

Media Manager opens a persistent library. Add a media folder to discover movies and episodes, then open Needs review to resolve uncertain matches. Scans show progress and support pause, resume, and retry. Choose a match, search TMDB or TVDB, or enter details yourself. TV files can contain multiple episodes, including season 0 specials. Review the exact metadata changes before applying an import or correction.

Browse the library as a table or posters, and filter by folder or media type. Search titles, people, episode names, and filenames with Ctrl+K on Windows and Linux, or Command+K on macOS. Use Edit details or Change match in the inspector to correct an entry. Edited fields survive restarts, rescans, and later match changes. Use catalog values previews removal of a field override. Separate copies remain separate entries; Compare files lists files with the same match.

In the table, select individual files, a page, or all filtered results. Bulk editing keeps unchecked fields and supports adding, removing, or replacing genres. The preview lists every affected file and its before/after values, with 100 entries per page. Applying changes reports saved and failed counts. Retrying skips completed saves. Change history can undo a change for one file after review.

Media Manager saves review choices and unfinished previews automatically. Resume changes reopens a preview after restart. A stale preview refuses to overwrite a newer correction; use Review current values to prepare a fresh change. Discard remaining changes hides an unfinished preview without undoing saved changes. The file's history retains the record.

Unavailable drives leave their entries and metadata intact. Reconnect the drive and scan again, or use Locate folder if its location changed. Media Manager previews the new root, checks relative filenames and sizes, and shows missing or different files before reconnecting. It does not compare file hashes. Imports, edits, reconnection, and undo change the library database only. They never rename, move, write, or delete media files. Symlinks and overlapping media roots are skipped or rejected.

Settings

Open Settings at the bottom of the sidebar, or press Ctrl+, on Windows and Linux or Command+, on macOS. Back returns to the library with its view and search intact.

In Directories, folders appear in Movies and TV Shows sections on the same page. Select a subsection in the sidebar to scroll to it. Subsection labels brighten while their sections are visible. Each section has an Add folder button that uses that section's media type. Selecting a folder adds it and starts scanning immediately. The empty library's Choose folder action asks for the media type before adding. Existing folders with automatic detection appear under Automatic. Each folder has Scan folder and Remove buttons. Scanning folders show Pause scan, and offline folders also show Locate folder. Removing a directory removes its library entries and change history, but leaves every media file untouched.

In Appearance > Interface, choose Device, Light, or Dark. The choice persists across restarts. Dark is the default; Device follows the operating system's appearance.

Search settings by group name or setting, then select a result or press Enter to jump to the first match.

Metadata connection

Open Settings > Providers and choose the Default provider for new searches and scans. Changing the provider keeps saved matches and manual edits. Episode lookups for an existing match continue to use its original provider.

For TMDB, enter a TMDB API Read Access Token. Choose Connect to save it. For TVDB, enter a TVDB v4 API key. Add your subscriber PIN if your key uses the user-supported model, then choose Connect. TVDB supplies movie and TV metadata. Its popularity score is not shown as a viewer rating.

For fanart.tv, enter a personal API key and choose Connect. Choose fanart.tv as the artwork source in Scrape metadata for movie, show, and season posters. TMDB or TVDB supplies the details. TV artwork requires a TVDB show ID, and movie artwork requires a TMDB movie ID. The app uses fanart.tv API v3.2 with personal-key authentication.

Use Check connection to verify a connection, or enter replacement credentials and save them. Media Manager validates credentials and encrypts them with Electron's system credential storage. Linux requires an unlocked supported keyring. Developers can supply MEDIA_MANAGER_TMDB_TOKEN in the environment instead. The previous CINEVORE_TMDB_TOKEN and DECANT_TMDB_TOKEN names remain fallbacks, in that order, when MEDIA_MANAGER_TMDB_TOKEN is unset. For TVDB, developers can supply MEDIA_MANAGER_TVDB_API_KEY and an optional MEDIA_MANAGER_TVDB_PIN. For fanart.tv, developers can supply a personal key through MEDIA_MANAGER_FANART_API_KEY. Credentials stay outside the renderer except while entering them. Without a connection, manual entry, editing, browsing saved metadata, and folder scanning still work. Scan again to retry failed lookups after connecting. Provider artwork requires internet access; missing artwork uses a placeholder.

Automatic suggestions require a unique exact title and year for movies, or a unique show with every parsed episode present for TV. These are suggestions, and imports still require a preview. Episode lookup uses TMDB's season numbering or TVDB's aired order. DVD and absolute orders require manual episode correction. API errors and an empty search are shown separately. Requests use timeouts, bounded caching, and rate-limit retries. See TMDB's API documentation. TVDB renews expired sessions automatically. See the TVDB v4 API documentation.

TVDB metadata is provided by TheTVDB. fanart.tv artwork is provided by fanart.tv. Its logo comes from the official fanart.tv wiki.

This product uses the TMDB API but is not endorsed or certified by TMDB. The official TMDB logo in the metadata credits comes from TMDB's logo guidelines.

The interface uses shadcn/ui's compact Mira style with Neutral theme colours. In dark mode, the sidebar and header darken native Mica on supported Windows versions and vibrancy on macOS with a 60% black fill. Light mode uses a white tint. The header tint extends behind the native window controls, whose symbols follow the theme. Linux and older Windows versions use a solid fallback. The library and inspector use black backgrounds in dark mode and white in light mode. The library toolbar shares the top edge with native window controls. Drag empty header space to move the window. The same header contains the back button when reviewing a match or comparing files. Drag the sidebar's right edge to change its width. The app remembers the width and keeps room for the library when the window shrinks. Double-click the divider to reset it. You can also focus the divider with Tab and use the left and right arrow keys. Home and End select the minimum and maximum widths.

The previous in-memory demo is available with Electron's --sample-library switch. Sample actions remain separate from the persistent library. Sample posters are bundled locally; their sources are listed in the artwork credits.

Run the app

Use Node.js 24 or newer and npm 12.0.2. From the repository root, install the dependencies:

npm install

The install step uses Electron's own installer to download the runtime for your operating system and CPU architecture. Use a native Node.js installation on Windows, macOS, or Linux. Use ARM64 Node.js on ARM64 machines, including Apple silicon.

Start the desktop app:

npm run dev

Vite updates the React renderer as you edit. Changes in apps/desktop/src/main restart Electron. The terminal prints the renderer server's actual loopback address and port.

Application data, including development and local preview state, uses Electron's default profile location:

OS Profile location
Windows %APPDATA%\Media Manager
macOS ~/Library/Application Support/Media Manager
Linux $XDG_CONFIG_HOME/Media Manager, or ~/.config/Media Manager when unset

Existing profiles in apps/desktop/.local/user-data are not moved automatically. To keep using an existing profile, pass its absolute path with --user-data-dir. To run an isolated instance, pass an absolute path for a disposable profile:

npm run dev --workspace @media-manager/desktop -- -- --user-data-dir=/absolute/path/to/disposable-profile

Use a Windows path such as C:/Temp/media-manager-profile on Windows.

The profile contains library.sqlite, its SQLite journal files, and the encrypted tmdb.bin and tvdb.bin connections. provider.json stores the default provider, and settings.json stores the theme preference. An interrupted scan reopens paused. Saves commit in batches of 100, with each entry's outcome and change history in the same transaction. An interrupted apply can resume without repeating completed saves. Database errors retain the original file. For a backup, close Media Manager and copy the entire profile. Restore only with Media Manager closed, keeping the damaged profile separately. Copying a running database file is not a consistent backup.

To start over, open Settings > Application data and select Reset application data.... Review what will be removed, then select Reset and restart. This clears the library, metadata edits, saved media folders, settings, saved provider connections, and cached app data. Your media files stay on disk. The reset cannot be undone. Back up the profile first if you want to keep its saved work.

An interrupted reset resumes on the next launch before the library opens. If cleanup fails, the app reports the failure and waits for another launch to retry. Credentials supplied through environment variables remain available after reset. Only one app process can open a given profile at a time.

Build and preview

Build the app bundles:

npm run build

The output is in apps/desktop/out. To build and open the app from those files:

npm run preview

These commands do not create an installer. Packaging, signing, and updates are not configured yet.

Check changes

Run the checks from the repository root:

npm run lint
npm run format
npm run check-types
npm test

The tests launch Electron with disposable profiles and media fixtures. They exercise folder import, review, individual and bulk edits, restart, rescan, folder reconnection, directory settings, and themes. Desktop test files run one at a time because concurrent windows can steal focus and cancel pointer drags. Service tests cover partial failure, stale previews, undo, duplicate copies, missing metadata, interruption, and provider responses. The retained demo tests check browsing, selection, comparisons, and keyboard navigation. Tests also check that React loads from the built files, profile data stays isolated, and the renderer blocks Node.js access, popups, and inline scripts. Platform tests cover the Mica version boundary and backdrop fallbacks. Linux runners need a display server, such as Xvfb.

To test a build outside the repository, set MEDIA_MANAGER_TEST_APP_DIR to the directory containing its package.json and out folder, then run npm run test --workspace @media-manager/desktop.

Set MEDIA_MANAGER_TEST_ARTIFACTS to a directory to capture the import test's final window and visible text, plus screenshots of the settings pages. Set MEDIA_MANAGER_UI_LIBRARY=10000 and run node --test apps/desktop/tests/catalog-ui.test.ts against a built app to exercise filtered selection, bulk editing, and episode correction with 10,001 entries. For the slower collection check, set MEDIA_MANAGER_LARGE_LIBRARY to 10000 or 100000, then run:

node --test packages/library/tests/large-library.test.ts

This check creates disposable movie, multi-episode, duplicate, ambiguous, and unmatched files. It imports and bulk-edits all entries, restarts, rescans, disconnects and reconnects the folder, and prints timings and process memory. Its deterministic metadata provider avoids live API traffic. Normal test runs skip this check. The reported times do not measure live TMDB throughput or artwork downloads.

The desktop CI workflow runs a fresh install, build, and launch test on Windows, macOS, and Ubuntu, using both x64 and ARM64 runners. It also checks each platform's window lifecycle. Linux CI uses Xvfb and configures Electron's sandbox helper so tests keep sandboxing enabled.

Run npm run format:fix to format the workspace.

Work on the app

Put desktop lifecycle code in apps/desktop/src/main and React code in apps/desktop/src/renderer/src. The renderer uses Electron's sandbox and context isolation. A narrow preload bridge validates library commands with Zod; the main process verifies the sender and owns native folder selection and credentials. The renderer has no Node.js access and cannot supply arbitrary filesystem paths to the bridge. The renderer separates shared controls in components/ui, the shell in components/layout, browsing in components/library, media details and workflows in components/media, and settings in components/settings. Component styles live beside their owners. styles/global.css contains resets and shadcn's Neutral theme tokens. apps/desktop/components.json selects the radix-mira style. Add shadcn components from apps/desktop. The renderer uses Tailwind's Vite plugin for the shared controls and CSS files for application layouts. packages/library exports browser-safe types, schemas, and metadata helpers from @repo/library. Its Node.js entry points are @repo/library/service for scanning, SQLite storage, saved corrections, and change previews, and @repo/library/filename for filename parsing. packages/providers exports the TMDB adapter from @repo/providers/tmdb and depends on the library's shared types. The library accepts a Catalog and has no dependency on a provider implementation. Put OS-specific behavior in packages/native. @repo/native provides browser-safe platform helpers. @repo/native/electron owns application-data paths, native window behavior, and secure-storage checks. @repo/native/paths handles filesystem path comparison without loading Electron. @repo/native/window-controls.css reserves space for native window controls. Each package owns its tests. Run npm test --workspace @repo/library, npm test --workspace @repo/providers, or npm test --workspace @repo/native to check a package independently. These packages export TypeScript source. The desktop build bundles them, including the library worker, so they need no separate build or development watcher. apps/desktop/src/shared/library-api.ts defines the desktop bridge, theme types, and native folder choices. library-commands.ts validates library commands. IPC handlers, native dialogs, credential storage, and worker startup stay in the desktop app. components/library/catalog-app.tsx owns the real library workflow. data/sample-library.ts and lib/library.ts support the optional in-memory demo. packages/native/src/window-appearance.ts selects native backdrops and title bar options. Keep the renderer root transparent. The sidebar and header use --shell-background, a 60% black fill over the native material in dark mode and an 80% white fill in light mode. Settings shares the library's header and sidebar search. Keep the native window control background transparent so the header tint applies only once. Keep the library and inspector opaque using the background token, with no outer gutter or rounded workspace container. Use the foreground token's colour for native window control symbols. The shell reserves space for native controls using Electron's title bar geometry. Header buttons and inputs must stay outside draggable regions. macOS reserves space on the left for its traffic lights. Keep new native operations behind explicit methods in src/preload/index.ts. src/main/appearance-settings.ts persists the theme preference and updates native appearance. lib/theme.tsx keeps the renderer in sync, including system changes while Device is selected. Folder selection returns a temporary choice ID. Adding that choice with a media type registers the directory and starts scanning. Directories uses the section's type. The empty library asks for it. The renderer cannot use that ID to register another filesystem path.

electron-vite builds the main process, library worker, preload, and renderer. The renderer uses Vite 7, which electron-vite 5 supports.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages