Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/locallibrary/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ Windows builds are experimental and untested on hardware (see Platforms in the R
player where the drive has none, and written with `analyze --apply grid,phrases,waveform` (the
sidecar's `plugins.apply_analysis.plan/apply`), a grid setting the track's BPM; operations plan
the same edit with `analysis:plan`. Checked against rekordbox's files, not yet on players.
- **Drives built from a plugin's library source.** `collection export DRIVE --from PLUGIN:SOURCE
--grant FOLDER` builds a drive from a library a plugin reads (feature `sources`, capability
`fs:read`), as rekordbox's collection is written: the plugin is given only the folders granted,
and only audio files players play in them (links resolved) are copied, each read and hashed by
locallibrary itself and checked as it's copied; the rest is left out with a warning. Cues, beat
grids and waveforms a track brings go into analysis files made for it, read back first. The
player has "build from …" in the palette and a drive's menu, the sidecar
`plugins.source.plan/apply`. Not yet tried on players.
- **The library is the unit.** Each drive holding a library is a clone of it, and its name doesn't
matter much: a library's name works where a drive does (but in commands about one particular
drive) and means its lead clone, the newest of its drives plugged in (the primary when they're
Expand Down
1 change: 1 addition & 0 deletions apps/locallibrary/docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ its drives.
| `locallibrary diff <a> <b> --summary` | Which drive is newer, the evidence, and the differences (in both databases) |
| `locallibrary collection [info\|tracks\|playlists\|diff <library>]` | rekordbox's own collection on this computer (`master.db`), read only, and what a drive lacks against it, see [rekordbox's collection](other-software.md#rekordboxs-collection) |
| `locallibrary collection export <drive or folder> [--playlist P]...` | Write the collection to a drive or folder as rekordbox's Export does, Cloud Library Sync tracks included, see [Exporting it without rekordbox](other-software.md#exporting-it-without-rekordbox) |
| `locallibrary collection export <drive or folder> --from PLUGIN:SOURCE --grant FOLDER...` | Build it from a plugin's library source instead, copying only audio files in the folders granted, see [Library sources](plugins.md#library-sources) |
| `locallibrary export <library> [PLAYLIST] -o FILE` | A playlist, folder, history list or the whole library as M3U8, CSV or text |
| `locallibrary export <library> [PLAYLIST] --format traktor\|serato\|engine -o DIR [--option KEY=VALUE]... [--dry-run]` | The library for other DJ software (the interop plugin's), written on this computer, see [Other DJ software](other-software.md#other-dj-software) |
| `locallibrary export <library> [PLAYLIST] --format FORMAT -o PATH [--option KEY=VALUE]... [--dry-run] [--replace]` | A format a plugin adds; overwriting a file is asked about first unless `--replace`, see [Plugins](plugins.md) |
Expand Down
5 changes: 5 additions & 0 deletions apps/locallibrary/docs/player.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ listening to your library the way the players see it:
alone is chosen for you, and for each other track you pick one from a list (or none); then the
changes are confirmed like any edit. "provider settings…" in the palette sets how high (as
`plugin providers --auto-pick` does).
- **Library sources**: "build from …" in the palette (or a drive's `e` menu) builds the drive
plugged in from a plugin's library source: a form asks which folders it may read files in
(`~/Music` to begin with, several split by commas), whether to replace a library the drive
holds, and for the source's options; the plan says what it read and left out, and is confirmed
like any change.
- **Plugins' settings**: "Cover art plugin settings…" in the palette opens a plugin's settings in
the same form, as they're set now; `enter` saves what changed, and a field left empty goes back
to its default. A secret is never filled in: typed, it's kept in the system's keychain; left
Expand Down
21 changes: 19 additions & 2 deletions apps/locallibrary/docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ are:
`locallibrary plugin` lists them, what each may do (its capabilities, in plain words) and what it
adds. `locallibrary plugin info ID` has one in full: its commands, how to run them and which of
the player's menus offer them, the events it listens for, its analyzers' fields, its export
and import formats, its providers, its services and its settings.
and import formats, its providers, its services, its library sources and its settings.

A plugin whose id, commands or export or import formats clash with locallibrary's, or with another
plugin's, is left out: every command warns which and why, and the other plugins work. Two plugins
Expand Down Expand Up @@ -148,6 +148,21 @@ as the log names it; the same log imported again replaces its session. In the pl
menu and the palette have "import ...", a form asking for the file. An importer plans nothing
itself and needs no capability to: it only answers what it read.

## Library sources

A library source is a library a plugin reads from somewhere on this computer (another DJ
program's collection, a folder of tagged files), which locallibrary builds a drive from as it
writes rekordbox's collection: `locallibrary collection export DRIVE --from PLUGIN:SOURCE --grant
FOLDER` (`--grant` again for more folders, the source's options as `--option KEY=VALUE`,
`--dry-run` to see the plan). The plugin is given only the folders you grant it, for that build,
and locallibrary copies only audio files players play that are in them (their links resolved),
not on the drive itself: it reads each file's length and format itself, hashes it before copying
it, and checks the copy. What it leaves out (a file elsewhere, one that isn't audio, a cue in a
slot taken) is listed as a warning. Cues, beat grids and waveforms a track comes with go into
analysis files made for it, each read back first. Building again copies only what changed; a
library on the drive that wasn't built from that source is only replaced with `--replace`. In the
player, "build from …" in the palette and a drive's menu asks for the folders and options first.

## Panels

A panel shows something about a track under its details: `locallibrary track LIBRARY --track ID`
Expand Down Expand Up @@ -257,7 +272,9 @@ plugin's log and of the errors it's told (what a server answers is passed on as
Images and other large answers come back as files in a folder locallibrary makes for each request
(`plugins/ID/scratch/`), which it reads itself, never past the folder (nor a link, or a file with
another name) or more than 512 MiB of them, and removes; `store gc` removes those a locallibrary
that crashed left.
that crashed left. A library source names audio files by path, and locallibrary copies a file
only when it's in a folder you granted for that build, its links resolved (see
[Library sources](#library-sources)).

A plugin runs as you and isn't sandboxed, so this is what locallibrary does, not a wall your
system keeps: a plugin could still read any file you can.
61 changes: 57 additions & 4 deletions apps/locallibrary/docs/rpc/locallibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2486,6 +2486,36 @@ export type ChooseRole = {
analyzer?: string | null;
};

/** Building a drive from a plugin's library source. */
export type SourceExport = {
plugin: string;
/** One of its library sources, by name. */
source: string;
/** As the source's `options` schema says. */
options?: unknown;
/**
* The folders the plugin may read files in, absolute: granted for this read alone, and only
* audio files in them are copied.
*/
grants: string[];
/** The drive or folder to write to. */
drive: string;
/**
* Playlists or folders (paths) to export, with the tracks they use. Empty: every track and
* playlist, or the selection the target was last exported with.
*/
playlists?: string[];
/** Export every track and playlist, whatever the target was exported with last. */
all?: boolean;
/**
* Replace a library the target holds that wasn't exported from the collection, or the
* files on a target with no library.
*/
replace?: boolean;
/** Write to an archive or retired drive (see [`crate::fleet::check_target`]). */
overwrite_archive?: boolean;
};

/** What `initialize` answers. */
export type Initialized = {
/** Changes only when a change would break clients written for the previous one. */
Expand Down Expand Up @@ -7493,8 +7523,10 @@ export type DriveDiff = {
};

export type CollectionExportPlan = {
/** The `master.db` exported. */
/** The `master.db` exported (empty for a plugin's library source). */
collection: string;
/** The plugin's library source written instead, `<plugin>:<source>`. */
source?: string | null;
target: string;
target_root: string;
/** The playlists chosen; empty for the whole collection. */
Expand Down Expand Up @@ -7523,7 +7555,10 @@ export type CollectionExportPlan = {
* computer.
*/
syncing: string[];
/** Tracks with an analysis file that can't be read: exported without it. */
/**
* Tracks with an analysis file that can't be read, or (from a plugin's source) whose cues,
* beat grid or waveform can't be written: exported without it.
*/
unreadable: string[];
/**
* The target holds a library that wasn't exported from the collection, or files the export
Expand Down Expand Up @@ -7578,8 +7613,9 @@ export type Listed = {
version: string;
capabilities: Granted[];
/**
* What it adds: its commands, operations, panels, analyzers, providers, services and themes by
* name, the events it listens for, its export and import formats and its settings' keys.
* What it adds: its commands, operations, panels, analyzers, providers, services, library
* sources and themes by name, the events it listens for, its export and import formats and
* its settings' keys.
*/
commands: string[];
events: string[];
Expand All @@ -7590,6 +7626,7 @@ export type Listed = {
importers: string[];
providers: string[];
services: string[];
sources: string[];
themes: string[];
settings: string[];
/** It's running now: plugins start when they're first used, and stop once they're idle. */
Expand Down Expand Up @@ -7658,6 +7695,7 @@ export type Contributions = {
importers: Importer[];
providers: Provider[];
services: Service[];
sources: LibrarySource[];
themes: Theme[];
settings: Setting[];
};
Expand Down Expand Up @@ -7880,6 +7918,17 @@ export type Service = {
title: string;
};

/**
* Read with `library/read`, a page at a time, for the host to build a drive from. It's given the
* folders the user grants it, which takes `fs:read`.
*/
export type LibrarySource = {
name: string;
title: string;
/** A JSON Schema for its options (an object). */
options?: unknown;
};

/**
* `[[contributes.themes]]`: a theme's colours, by the names of [`theme::TOKENS`] as tables (`cues.hot =
* "#28e214"`, `ui = { accent = "#ff8800" }`), each `#rrggbb` (a gradient, `waveform.*`, a list
Expand Down Expand Up @@ -9223,6 +9272,10 @@ export interface Methods {
"plugins.roles": { params: NoParams; result: RoleChoice[] };
/** immediate method */
"plugins.roles.set": { params: ChooseRole & WriteOptions; result: RoleChoice[] };
/** plan method; paired with `plugins.source.apply`; long running: sends `progress`, can be cancelled; can delete or overwrite something */
"plugins.source.plan": { params: SourceExport & WriteOptions; result: CollectionExportPlan & PlanFields };
/** apply method; paired with `plugins.source.plan`; long running: sends `progress`, can be cancelled; can delete or overwrite something */
"plugins.source.apply": { params: ApplyPlan | (SourceExport & WriteOptions); result: CollectionExportReport };
}

export type MethodName = keyof Methods;
Expand Down
Loading
Loading