commit 8293c6e6e517aa54c44653a26d23158db1a1743c Author: h7x4 Date: Tue Nov 5 22:47:35 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f68e052 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +result +result-* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5f73f80 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "empidee" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7e96ce4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "empidee" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0.210" diff --git a/examples/mpd-client/main.rs b/examples/mpd-client/main.rs new file mode 100644 index 0000000..c7f97dc --- /dev/null +++ b/examples/mpd-client/main.rs @@ -0,0 +1,3 @@ +fn main() { + todo!() +} \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..585446f --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1729256560, + "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729477859, + "narHash": "sha256-r0VyeJxy4O4CgTB/PNtfQft9fPfN1VuGvnZiCxDArvg=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "ada8266712449c4c0e6ee6fcbc442b3c217c79e1", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ee976b9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,45 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + rust-overlay.url = "github:oxalica/rust-overlay"; + rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { self, nixpkgs, rust-overlay }: + let + inherit (nixpkgs) lib; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + "armv7l-linux" + ]; + + forAllSystems = f: lib.genAttrs systems (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [ + (import rust-overlay) + ]; + }; + + rust-bin = rust-overlay.lib.mkRustBin { } pkgs.buildPackages; + toolchain = rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "rust-std" ]; + }; + in f system pkgs toolchain); + in { + devShells = forAllSystems (system: pkgs: toolchain: { + default = pkgs.mkShell { + nativeBuildInputs = [ + toolchain + ]; + + RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; + }; + }); + }; +} diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..86ca375 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,77 @@ + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Tag { + /// The artist name. Its meaning is not well-defined; see “composer” and “performer” for more specific tags. + Artist(String), + /// Same as artist, but for sorting. This usually omits prefixes such as “The”. + ArtistSort(String), + /// The album name. + Album(String), + /// Same as album, but for sorting. + AlbumSort(String), + /// On multi-artist albums, this is the artist name which shall be used for the whole album. The exact meaning of this tag is not well-defined. + AlbumArtist(String), + /// Same as albumartist, but for sorting. + AlbumArtistSort(String), + /// The song title. + Title(String), + /// Same as title, but for sorting. + TitleSort(String), + /// The decimal track number within the album. + Track(String), + /// A name for this song. This is not the song title. The exact meaning of this tag is not well-defined. It is often used by badly configured internet radio stations with broken tags to squeeze both the artist name and the song title in one tag. + Name(String), + /// The music genre. + Genre(String), + /// The mood of the audio with a few keywords. + Mood(String), + /// The song’s release date. This is usually a 4-digit year. + Date(String), + /// The song’s original release date. + OriginalDate(String), + /// The artist who composed the song. + Composer(String), + /// Same as composer, but for sorting. + ComposerSort(String), + /// The artist who performed the song. + Performer(String), + /// The conductor who conducted the song. + Conductor(String), + /// “a work is a distinct intellectual or artistic creation, which can be expressed in the form of one or more audio recordings” + Work(String), + /// The ensemble performing this song, e.g. “Wiener Philharmoniker”. + Ensemble(String), + /// Name of the movement, e.g. “Andante con moto”. + Movement(String), + /// Movement number, e.g. “2” or “II”. + MovementNumber(String), + /// If this tag is set to “1” players supporting this tag will display the work, movement, and movementnumber` instead of the track title. + ShowMovement(String), + /// Location of the recording, e.g. “Royal Albert Hall”. + Location(String), + /// “used if the sound belongs to a larger category of sounds/music” (from the IDv2.4.0 TIT1 description). + Grouping(String), + /// A human-readable comment about this song. The exact meaning of this tag is not well-defined. + Comment(String), + /// The decimal disc number in a multi-disc album. + Disc(String), + /// The name of the label or publisher. + Label(String), + /// The artist id in the MusicBrainz database. + MusicBrainzArtistId(String), + /// The album id in the MusicBrainz database. + MusicBrainzAlbumId(String), + /// The album artist id in the MusicBrainz database. + MusicBrainzAlbumArtistId(String), + /// The track id in the MusicBrainz database. + MusicBrainzTrackId(String), + /// The release group id in the MusicBrainz database. + MusicBrainzReleaseGroupId(String), + /// The release track id in the MusicBrainz database. + MusicBrainzReleaseTrackId(String), + /// The work id in the MusicBrainz database. + MusicBrainzWorkId(String), + + /// Other tags not covered by the above + Other(String, String), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bf27c5e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +///! This library contains structs and parsing for the mpd protocol +///! as described in https://mpd.readthedocs.io/en/latest/protocol.html#protocol-overview +///! +///! It does not provide any client or server implementation + +mod common; +mod request; +mod response; + +pub use request::Request; +pub use response::Response; diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..2549f9b --- /dev/null +++ b/src/request.rs @@ -0,0 +1,302 @@ +type SongPosition = u32; +type SongId = u32; +type Seconds = u32; +type TimeWithFractions = f64; +type OneOrRange = (SongPosition, Option); +type WindowRange = (Option, Option); +type Priority = u8; +type PlaylistName = String; +type Offset = u32; + +// TODO: use a proper types +type TagName = String; +type TagValue = String; +type Uri = String; +type Path = String; +type Filter = String; +type Sort = String; +type Version = String; +type Tag = String; +type Feature = String; +type PartitionName = String; +type AudioOutputId = String; +type ChannelName = String; +type StickerType = String; + +#[derive(Debug, Clone, PartialEq)] +pub enum Request { + // -- Query Commands -- // + ClearError, + CurrentSong, + Idle(Option>), + Status, + Stats, + + // -- Playback Commands -- // + Consume(ConsumeState), + Crossfade(Seconds), + MixRampDb(f32), + MixRampDelay(Seconds), + Random(bool), + Repeat(bool), + SetVol(Volume), + GetVol, + Single(SingleState), + ReplayGainMode(ReplayGainMode), + ReplayGainStatus, + Volume(Volume), + + // -- Playback Control Commands -- // + Next, + Pause(Option), + Play(SongPosition), + PlayId(SongId), + Previous, + Seek(SongPosition, TimeWithFractions), + SeekId(SongId, TimeWithFractions), + SeekCur(SeekMode, TimeWithFractions), + Stop, + + // -- Queue Commands -- // + // TODO: relative mode + Add(String, Option), + // TODO: relative mode + AddId(String, Option), + Clear, + Delete(OneOrRange), + DeleteId(SongId), + Move(OneOrRange, SongPosition), + MoveId(SongId, SongPosition), + Playlist, + PlaylistFind(Filter, Option, Option), + PlaylistId(SongId), + PlaylistInfo(OneOrRange), + PlaylistSearch(Filter, Option, Option), + // TODO: which type of range? + PlChanges(Version, Option), + // TODO: which type of range? + PlChangesPosId(Version, Option), + // TODO: which type of range? + Prio(Priority, WindowRange), + PrioId(Priority, Vec), + RangeId(SongId, WindowRange), + Shuffle(Option), + Swap(SongPosition, SongPosition), + SwapId(SongId, SongId), + AddTagId(SongId, TagName, TagValue), + ClearTagId(SongId, TagName), + + // -- Stored Playlist Commands -- // + ListPlaylist(PlaylistName, Option), + ListPlaylistInfo(PlaylistName, Option), + SearchPlaylist(PlaylistName, Filter, Option), + ListPlaylists, + Load(PlaylistName, Option, SongPosition), + PlaylistAdd(PlaylistName, Uri, SongPosition), + PlaylistClear(PlaylistName), + PlaylistDelete(PlaylistName, OneOrRange), + PlaylistLength(PlaylistName), + // TODO: which type of range? + PlaylistMove(PlaylistName, OneOrRange, SongPosition), + Rename(PlaylistName, PlaylistName), + Rm(PlaylistName), + Save(PlaylistName, Option), + + // -- Music Database Commands -- // + AlbumArt(Uri, Offset), + Count(Filter, Option), + GetFingerprint(Uri), + Find(Filter, Option, Option), + FindAdd(Filter, Option, Option, Option), + List(Tag, Filter, Option), + #[deprecated] + ListAll(Option), + #[deprecated] + ListAllInfo(Option), + ListFiles(Uri), + LsInfo(Option), + ReadComments(Uri), + ReadPicture(Uri, Offset), + Search(Filter, Option, Option), + SearchAdd(Filter, Option, Option, Option), + SearchAddPl(Filter, Option, Option, Option), + SearchCount(Filter, Option), + Update(Option), + Rescan(Option), + + // -- Mount and Neighbor Commands -- // + Mount(Option, Option), + Unmount(Path), + ListMounts, + ListNeighbors, + + // -- Sticker Commands -- // + StickerGet(StickerType, Uri, String), + StickerSet(StickerType, Uri, String, String), + StickerDelete(StickerType, Uri, String), + StickerList(StickerType, Uri), + StickerFind(StickerType, Uri, String, Option, Option), + StickerFindValue(StickerType, Uri, String, String, Option, Option), + StickerNames, + StickerTypes, + StickerNamesTypes(Option), + + // -- Connection Commands -- // + Close, + Kill, + Password(String), + Ping, + BinaryLimit(u64), + TagTypes, + TagTypesDisable(Vec), + TagTypesEnable(Vec), + TagTypesClear, + TagTypesAll, + TagTypesAvailable, + TagTypesReset(Vec), + Protocol, + ProtocolDisable(Vec), + ProtocolEnable(Vec), + ProtocolClear, + ProtocolAll, + ProtocolAvailable, + + // -- Partition Commands -- // + Partition(PartitionName), + ListPartitions, + NewPartition(PartitionName), + DelPartition(PartitionName), + MoveOutput(String), + + // -- Audio Output Commands -- // + DisableOutput(AudioOutputId), + EnableOutput(AudioOutputId), + ToggleOutput(AudioOutputId), + Outputs, + OutputSet(AudioOutputId, String, String), + + // -- Reflection Commands -- // + Config, + Commands, + NotCommands, + UrlHandlers, + Decoders, + + // -- Client to Client Commands -- // + Subscribe(ChannelName), + Unsubscribe(ChannelName), + Channels, + ReadMessages, + SendMessage(ChannelName, String), +} + +impl Into> for Request { + fn into(self) -> Vec { + todo!() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SaveMode { + Create, + Append, + Replace, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SeekMode { + Relative, + RelativeReverse, + Absolute, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SubSystem { + /// The song database has been modified after update. + Database, + /// A database update has started or finished. If the database was modified during the update, the database event is also emitted. + Update, + /// A stored playlist has been modified, renamed, created or deleted + StoredPlaylist, + /// The queue (i.e. the current playlist) has been modified + Playlist, + /// The player has been started, stopped or seeked or tags of the currently playing song have changed (e.g. received from stream) + Player, + /// The volume has been changed + Mixer, + /// An audio output has been added, removed or modified (e.g. renamed, enabled or disabled) + Output, + /// Options like repeat, random, crossfade, replay gain + Options, + /// A partition was added, removed or changed + Partition, + /// The sticker database has been modified. + Sticker, + /// A client has subscribed or unsubscribed to a channel + Subscription, + /// A message was received on a channel this client is subscribed to; this event is only emitted when the client’s message queue is empty + Message, + /// A neighbor was found or lost + Neighbor, + /// The mount list has changed + Mount, + + /// Other subsystems not covered by the above + Other(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConsumeState { + On, + Off, + Oneshot, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SingleState { + On, + Off, + Oneshot, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ReplayGainMode { + Off, + Track, + Album, + Auto, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Volume(u32); +impl Volume { + pub fn new(volume: u32) -> Result { + match volume { + 0..=100 => Ok(Self(volume)), + _ => Err(()), + } + } +} +impl Into for Volume { + fn into(self) -> u32 { + self.0 + } +} + +// TODO: fill out +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GroupType { + Artist, + Album, + AlbumArtist, + Date, + Genre, + Track, + Composer, + Performer, + Conductor, + Comment, + Disc, + Filename, + Any, +} \ No newline at end of file diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..3e7eede --- /dev/null +++ b/src/response.rs @@ -0,0 +1,5 @@ +// See https://github.com/MusicPlayerDaemon/MPD/blob/7774c3369e1484dc5dec6d7d9572e0a57e9c5302/src/command/AllCommands.cxx#L67-L209 +pub enum Response { + Ok, + GenericError(String), +}