syntax = "proto3"; package crabidy.v1; service CrabidyService { // System rpc Init(InitRequest) returns (InitResponse); // Library rpc GetLibraryNode(GetLibraryNodeRequest) returns (GetLibraryNodeResponse); // Creates a child node under a creatable parent (LibraryNode.is_creatable). // What creation means is provider-defined; under /tidal/search the title is // a search term and the created node holds its results. Idempotent: an // existing title returns the existing node. rpc CreateLibraryNode(CreateLibraryNodeRequest) returns (CreateLibraryNodeResponse); // Renames a node whose listing entry sets is_editable. For a search term // the title is the query, so a rename re-runs the search; the node's path // changes with the title. Renaming onto an existing sibling title merges // with it. Returns the renamed node at its new path. rpc RenameLibraryNode(RenameLibraryNodeRequest) returns (RenameLibraryNodeResponse); // Deletes a node whose listing entry sets is_deletable, or a track // whose parent node sets tracks_deletable. On filesystem-backed stores // this removes the data from disk: a folder is deleted recursively, a // track loses its metadata file and its local audio. Idempotent: // deleting an already-gone node succeeds. Returns the refreshed parent. rpc DeleteLibraryNode(DeleteLibraryNodeRequest) returns (DeleteLibraryNodeResponse); // Captures the queueable subtree at `path` as the bookmark `name`: a // structure-preserving snapshot under /bookmarks/ (folders per // child node, link track files per track). Overwrites an existing // bookmark of the same name; a *download* capture instead merges into an // existing /captures/, resuming what is not yet downloaded. // Returns once the capture is accepted (name, store, and download // blessing validated); the walk runs server-side and reports through // CaptureProgress updates on GetUpdateStream, ending in one event with // `finished` set (and `error` on failure). rpc CaptureLibraryNode(CaptureLibraryNodeRequest) returns (CaptureLibraryNodeResponse); // Queue rpc Queue(QueueRequest) returns (QueueResponse); rpc Replace(ReplaceRequest) returns (ReplaceResponse); rpc Append(AppendRequest) returns (AppendResponse); rpc Remove(RemoveRequest) returns (RemoveResponse); rpc Insert(InsertRequest) returns (InsertResponse); rpc ClearQueue(ClearQueueRequest) returns (ClearQueueResponse); rpc SetCurrent(SetCurrentRequest) returns (SetCurrentResponse); rpc ToggleShuffle(ToggleShuffleRequest) returns (ToggleShuffleResponse); rpc ToggleRepeat(ToggleRepeatRequest) returns (ToggleRepeatResponse); rpc GetUpdateStream(GetUpdateStreamRequest) returns (stream GetUpdateStreamResponse); rpc SaveQueue(SaveQueueRequest) returns (SaveQueueResponse); // Playback rpc TogglePlay(TogglePlayRequest) returns (TogglePlayResponse); rpc Stop(StopRequest) returns (StopResponse); rpc ChangeVolume(ChangeVolumeRequest) returns (ChangeVolumeResponse); rpc ToggleMute(ToggleMuteRequest) returns (ToggleMuteResponse); rpc Next(NextRequest) returns (NextResponse); rpc Prev(PrevRequest) returns (PrevResponse); rpc RestartTrack(RestartTrackRequest) returns (RestartTrackResponse); // Moves the playing position inside the current track. Relative, never // absolute: the server adds the offset to the live position, so repeated // presses of a seek key compose instead of all computing from the same // (already stale) position update. rpc Seek(SeekRequest) returns (SeekResponse); } // System message InitRequest {} message InitResponse { Queue queue = 1; QueueModifiers mods = 2; QueueTrack queue_track = 3; PlayState play_state = 4; float volume = 5; bool mute = 6; TrackPosition position = 7; // Whether the server has any credentials configured (the auth on/off // switch). Reachable anonymously, so a client that connected as the // unauthenticated fallback role can learn a higher role is available // and offer a login (architecture/roles-auth.md). bool auth_enabled = 8; } // Library message GetLibraryNodeRequest { string path = 1; } message GetLibraryNodeResponse { LibraryNode node = 1; } message CreateLibraryNodeRequest { // Path of the creatable parent, e.g. /tidal/search. string parent_path = 1; // Human-entered name; becomes the node title. The path segment is a // percent-encoded form of it, chosen by the provider. string title = 2; } message CreateLibraryNodeResponse { LibraryNode node = 1; } message RenameLibraryNodeRequest { // Path of the node to rename. string path = 1; // New human-entered title; the provider derives the new path segment. string new_title = 2; } message RenameLibraryNodeResponse { // The renamed node, at its (possibly changed) path. LibraryNode node = 1; } message DeleteLibraryNodeRequest { // Path of the node to delete. string path = 1; } message DeleteLibraryNodeResponse { // The parent node with the deleted child gone — what a client should // display after the delete, without a follow-up GetLibraryNode. LibraryNode parent = 1; } message CaptureLibraryNodeRequest { // Path of the queueable node (or track) to capture. string path = 1; // Capture name; becomes the top-level folder under /bookmarks (or // /captures when download is set). string name = 2; // Download every track's audio into the capture instead of writing // link files; the source node must set is_downloadable. bool download = 3; } message CaptureLibraryNodeResponse {} // Queue message QueueRequest { repeated string paths = 1; } message QueueResponse {} message ReplaceRequest { repeated string paths = 1; } message ReplaceResponse {} message AppendRequest { repeated string paths = 1; } message AppendResponse {} message RemoveRequest { repeated uint32 positions = 1; } message RemoveResponse {} message InsertRequest { // Index to insert **at**: the entry currently there, and everything after // it, shifts down. 0 inserts at the front, a position at or past the end // appends. "After entry N" is therefore N + 1. uint32 position = 1; repeated string paths = 2; } message InsertResponse {} message SetCurrentRequest { uint32 position = 1; } message SetCurrentResponse {} message ToggleShuffleRequest {} message ToggleShuffleResponse {} message ToggleRepeatRequest {} message ToggleRepeatResponse {} message SaveQueueRequest { string name = 1; } message SaveQueueResponse {} message ClearQueueRequest { bool exclude_current = 1; } message ClearQueueResponse {} // Stream message GetUpdateStreamRequest {} message GetUpdateStreamResponse { oneof update { Queue queue = 1; QueueModifiers mods = 2; QueueTrack queue_track = 3; PlayState play_state = 4; float volume = 5; bool mute = 6; TrackPosition position = 7; CaptureProgress capture_progress = 8; SpectrumFrame spectrum = 9; } } // One frame of the audio frequency spectrum (architecture/spectrum.md). // Broadcast by the server at a low frame rate while audio is playing; an // all-zero frame signals silence. Clients render `bins` as bars; they do // no signal processing themselves. message SpectrumFrame { // Normalized magnitudes in [0, 1], low frequency first, log-spaced. // The count is the server's bin resolution (a handful of bars). repeated float bins = 1; } // Progress of a running capture (CaptureLibraryNode). Broadcast after each // processed track; exactly one event per capture sets `finished` (with // `error` on failure). message CaptureProgress { // The capture (or bookmark) name the user chose. string name = 1; // True for a download capture (W), false for a bookmark (w). bool download = 2; // Tracks settled so far (reused, downloaded, linked, or recorded as // skipped) — reaches tracks_total on success. uint32 tracks_done = 3; // Total tracks discovered by the enumeration; 0 until it completes. uint32 tracks_total = 4; // Of the settled tracks, how many were recorded as skipped // (uncapturable source) this run. uint32 tracks_skipped = 5; // Terminal event: the capture is over. bool finished = 6; // Why it failed; empty on success. Never carries URLs or file contents. string error = 7; } // Playback message TogglePlayRequest {} message TogglePlayResponse {} message StopRequest {} message StopResponse {} message ChangeVolumeRequest { float delta = 1; } message ChangeVolumeResponse {} message ToggleMuteRequest {} message ToggleMuteResponse {} message NextRequest {} message NextResponse {} message PrevRequest {} message PrevResponse {} message RestartTrackRequest {} message RestartTrackResponse {} message SeekRequest { // Signed offset from the current position; negative seeks backwards. // Milliseconds, like TrackPosition, so a client is free to pick any step. // // Clamped by the server: backwards saturates at the start of the track (it // never steps into the previous one), and forwards stops just short of the // end, so an overshooting seek lets the track finish and the queue advance. // A track whose duration is unknown (a length-less stream) has no upper // clamp and the decoder may refuse the seek. // // Fire-and-forget, like the other playback rpcs: the response says the // command was accepted, not that the seek happened. A source that cannot // seek at all (SoundCloud's HLS streams) leaves the position unchanged; the // position updates on the stream remain the truth. sint32 delta_millis = 1; } message SeekResponse {} // Data types message LibraryNodeChild { string path = 1; string title = 2; bool is_queable = 3; // Children may be created under this node (see CreateLibraryNode). bool is_creatable = 4; // This node may be renamed (see RenameLibraryNode). bool is_editable = 5; // This node may be deleted (see DeleteLibraryNode). bool is_deletable = 6; // This node allows download captures (CaptureLibraryNode with download). bool is_downloadable = 7; // Every track and child node below this child is captured (fully local in // the content store). Clients mark captured rows. bool is_captured = 8; } message QueueModifiers { bool shuffle = 1; bool repeat = 2; } message Queue { uint64 timestamp = 1; uint32 current_position = 2; // Without album repeated Track tracks = 3; // True while the server is still resolving queued paths into tracks: more // tracks will arrive in subsequent Queue updates. Clients may show a // loading indicator until an update carries resolving = false. bool resolving = 4; } message QueueTrack { uint32 queue_position = 1; // With album optional Track track = 2; } enum PlayState { PLAY_STATE_UNSPECIFIED = 0; PLAY_STATE_STOPPED = 1; PLAY_STATE_LOADING = 2; PLAY_STATE_PLAYING = 3; PLAY_STATE_PAUSED = 4; } message TrackPosition { uint32 duration = 1; uint32 position = 2; } message Album { string title = 1; optional string release_date = 2; } message Track { // Full library path including provider string path = 1; string artist = 2; string title = 3; optional uint32 duration = 4; optional Album album = 5; // The track has no playable audio (a capture recorded its source as // uncapturable). Clients mark it; playback skips it. bool is_skipped = 6; // Provider-internal id that identifies this item inside its provider, // independent of the path it was reached by (playlist, search, album…). // Set by the owning provider; empty when unknown. Keys the content store. string provider_item_id = 7; // The content store already holds this track (by provider id or, once // captured, as a store-backed playable). Set at listing time; clients // mark captured rows. See architecture/crabidy-store.md. bool is_captured = 8; } message LibraryNode { // Full library path including provider string path = 1; string title = 2; repeated LibraryNodeChild children = 3; optional string parent = 4; repeated Track tracks = 5; bool is_queable = 6; // Children may be created under this node (see CreateLibraryNode). bool is_creatable = 7; // This node allows download captures; its listed tracks inherit the // flag (CaptureLibraryNode with download). bool is_downloadable = 8; // This node's listed tracks may be deleted (see DeleteLibraryNode) — // like is_downloadable, tracks inherit the node's flag. bool tracks_deletable = 9; // Every track and child node below this node is captured (fully local in // the content store). Clients mark captured nodes. See // architecture/crabidy-store.md. bool is_captured = 10; }