├── data └── .keep ├── .gitignore ├── Cargo.toml ├── rustfmt.toml ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── LICENSE ├── README.md ├── src └── main.rs └── Cargo.lock /data/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .claude 3 | *.mp3 4 | docs 5 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "musix" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | rodio = "0.20" 8 | crossterm = "0.29" 9 | ratatui = "0.29" -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | max_width = 160 3 | newline_style = "Unix" 4 | use_field_init_shorthand = true 5 | use_try_shorthand = true 6 | tab_spaces = 4 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | cargo-tests: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 15 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v4 17 | 18 | - name: Install system dependencies 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get install -y libasound2-dev pkg-config 22 | 23 | - name: Install Rust toolchain 24 | uses: dtolnay/rust-toolchain@stable 25 | 26 | - name: Rust Cache 27 | uses: Swatinem/rust-cache@v2 28 | with: 29 | cache-on-failure: true 30 | 31 | - name: Run tests 32 | run: cargo test --all --all-features 33 | 34 | - name: Check formatting 35 | run: cargo fmt --all -- --check 36 | 37 | - name: Run clippy 38 | run: cargo clippy --all-targets --all-features -- -D warnings 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 B 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [created] 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | release: 11 | name: release ${{ matrix.target }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - target: x86_64-pc-windows-gnu 18 | os: ubuntu-latest 19 | archive: zip 20 | - target: x86_64-unknown-linux-musl 21 | os: ubuntu-latest 22 | archive: tar.gz 23 | - target: x86_64-apple-darwin 24 | os: macos-latest 25 | archive: zip 26 | - target: aarch64-apple-darwin 27 | os: macos-latest 28 | archive: zip 29 | steps: 30 | - name: Checkout sources 31 | uses: actions/checkout@v4 32 | 33 | - name: Install Rust 34 | uses: dtolnay/rust-toolchain@stable 35 | with: 36 | targets: ${{ matrix.target }} 37 | 38 | - name: Install dependencies (Linux) 39 | if: matrix.os == 'ubuntu-latest' 40 | run: | 41 | sudo apt-get update 42 | sudo apt-get install -y libasound2-dev pkg-config 43 | if [[ "${{ matrix.target }}" == *"musl"* ]]; then 44 | sudo apt-get install -y musl-tools 45 | fi 46 | 47 | - name: Build 48 | run: cargo build --release --target ${{ matrix.target }} 49 | 50 | - name: Create archive 51 | run: | 52 | mkdir -p release 53 | if [[ "${{ matrix.archive }}" == "zip" ]]; then 54 | if [[ "${{ matrix.target }}" == *"windows"* ]]; then 55 | cp target/${{ matrix.target }}/release/musix.exe release/ 56 | cd release && zip ../musix-${{ matrix.target }}.zip musix.exe 57 | else 58 | cp target/${{ matrix.target }}/release/musix release/ 59 | cd release && zip ../musix-${{ matrix.target }}.zip musix 60 | fi 61 | else 62 | cp target/${{ matrix.target }}/release/musix release/ 63 | cd release && tar -czf ../musix-${{ matrix.target }}.tar.gz musix 64 | fi 65 | 66 | - name: Upload release asset 67 | uses: actions/upload-release-asset@v1 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | with: 71 | upload_url: ${{ github.event.release.upload_url }} 72 | asset_path: ./musix-${{ matrix.target }}.${{ matrix.archive == 'zip' && 'zip' || 'tar.gz' }} 73 | asset_name: musix-${{ matrix.target }}.${{ matrix.archive == 'zip' && 'zip' || 'tar.gz' }} 74 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MUSIX 2 | 3 | A minimalist terminal-based MP3 music player built with Rust. 4 | 5 | ![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) 6 | ![Terminal](https://img.shields.io/badge/Terminal-UI-green?style=for-the-badge) 7 | ![Music](https://img.shields.io/badge/MP3-Player-orange?style=for-the-badge) 8 | 9 | [![asciicast](https://asciinema.org/a/730123.svg)](https://asciinema.org/a/730123) 10 | 11 | ## Features 12 | 13 | - **Beautiful TUI**: Clean terminal interface with cyberpunk green theme 14 | - **High-Quality Playback**: MP3 audio support with crystal-clear sound 15 | - **Visual Progress**: Real-time progress bar with time display 16 | - **Smart Controls**: Intuitive keyboard controls with popup help 17 | - **Smooth Seeking**: Instant seek without playback interruption 18 | - **Playback Modes**: Normal sequential and random shuffle 19 | - **Keyboard-Driven**: Lightning-fast keyboard-only interface 20 | - **Fuzzy Search**: Real-time search with `/` key - find songs instantly 21 | - **Vim-Style Navigation**: Full vim keybinding support (hjkl, gg/G, n/N, q) 22 | 23 | ## Quick Start 24 | 25 | ### Prerequisites 26 | 27 | - **Rust 1.70+** - [Install Rust](https://rustup.rs/) 28 | - **Audio Libraries** (Linux): `libasound2-dev pkg-config` 29 | 30 | ### Installation 31 | 32 | ```bash 33 | # Clone the repository 34 | git clone git@github.com:coolcode/musix.git 35 | cd musix 36 | 37 | # Build and run 38 | cargo run 39 | 40 | # Or build optimized release version 41 | cargo build --release 42 | ./target/release/musix 43 | ``` 44 | 45 | ### Quick Usage 46 | 1. **Start the player**: `cargo run` 47 | 2. **Navigate**: Use `j/k` or arrow keys to browse songs 48 | 3. **Search**: Press `/` and type to find songs instantly 49 | 4. **Play**: Press `Enter` or `Space` to play selected song 50 | 5. **Jump**: Use `gg` (first song) or `G` (last song) 51 | 6. **Help**: Press `x` to see all controls 52 | 7. **Quit**: Press `q` or `Esc` to exit 53 | 54 | ### Setup Music Files 55 | 56 | MUSIX automatically searches for MP3 files in these directories: 57 | 58 | 1. **`~/Music`** - Your system's Music directory 59 | 2. **`./data`** - Local data folder 60 | 61 | ```bash 62 | # Option 1: Use local data folder 63 | mkdir -p ./data 64 | cp /path/to/your/music/*.mp3 ./data/ 65 | 66 | # Option 2: Use system Music directory 67 | cp /path/to/your/music/*.mp3 ~/Music/ 68 | 69 | # Option 3: Create symbolic link 70 | ln -s /path/to/your/music ./data 71 | ``` 72 | 73 | ## Controls 74 | 75 | > **Tip**: Press **x** anytime to view the interactive controls popup! 76 | 77 | ### Essential Keys 78 | 79 | | Key | Action | 80 | |-----|--------| 81 | | **`Space/↵`** | **Smart Play** - Play selected song or pause current | 82 | | **`/`** | **Search Mode** - Enter fuzzy search | 83 | | **`x`** | **Show/Hide help popup** | 84 | | **`q/Esc`** | **Exit** | 85 | 86 | ### Navigation & Playback 87 | 88 | | Key | Action | 89 | |-----|--------| 90 | | `↑/↓` or `j/k` | Navigate songs (vim-style) | 91 | | `Space/↵` | Play/pause (same functionality) | 92 | | `←/→` or `h/l` | Play previous/next song | 93 | | `gg` / `G` | Jump to first/last song | 94 | | `,` / `.` | Seek backward/forward 5 seconds | 95 | | `<` / `>` | Same as above | 96 | | `r` | Toggle Random mode | 97 | 98 | ### Search Mode 99 | 100 | | Key | Action | 101 | |-----|--------| 102 | | **`/`** | Enter search mode | 103 | | `n` / `N` | Navigate to next/previous search result | 104 | | `↑/↓` or `j/k` | Navigate through filtered results | 105 | | `Enter` | Play selected song and exit search | 106 | | `Esc` | Exit search mode | 107 | | `Backspace` | Delete characters from search query | 108 | | `Any text` | Type to search (fuzzy matching) | 109 | 110 | ## Interface 111 | 112 | MUSIX features a clean, 4-panel interface that maximizes space for your music: 113 | 114 | ``` 115 | ┌─────────────────────────────────┐ 116 | │ MUSIX │ ← Title Bar 117 | ├─────────────────────────────────┤ 118 | │ Songs - Search: rock │ ← Song List (Search Mode) 119 | │ → ♪ 1. Rock Song │ or "Songs" (Normal Mode) 120 | │ 5. Another Rock Song │ (Scrollable, Filtered) 121 | │ 12. Rock Ballad │ 122 | │ More filtered results... │ 123 | ├─────────────────────────────────┤ 124 | │ ████████████████░░░░ 02:30/04:15│ ← Progress Bar 125 | ├─────────────────────────────────┤ 126 | │ Search Mode | Songs: 15/120 |.. │ ← Status & Search Info 127 | └─────────────────────────────────┘ 128 | ``` 129 | 130 | ### Interactive Controls Popup (Press **x**) 131 | 132 | ``` 133 | ┌─────────────────────────────────┐ 134 | │ CONTROLS │ 135 | │ │ 136 | │ ↑/↓ or j/k - Navigate songs │ 137 | │ Space/↵ - Play/Pause │ 138 | │ ←/→ or h/l - Play prev/next song│ 139 | │ gg/G - Jump to first/last │ 140 | │ / - Enter search mode │ 141 | │ n/N - Next/prev search │ 142 | │ ,/. - Seek ±5 seconds │ 143 | │ r - Toggle random mode │ 144 | │ q/Esc - Exit application │ 145 | │ x - Close this popup │ 146 | └─────────────────────────────────┘ 147 | ``` 148 | 149 | ## Smart Features 150 | 151 | ### Fuzzy Search 152 | - **Instant Search**: Press `/` to enter search mode 153 | - **Real-time Filtering**: Results update as you type 154 | - **Fuzzy Matching**: Finds songs even with partial or misspelled text 155 | - **Smart Scoring**: Prioritizes exact matches → substring matches → fuzzy matches 156 | - **Search Navigation**: Use `n/N` to quickly jump between results 157 | - **Quick Play**: Press Enter on any result to play immediately 158 | 159 | **Example**: Searching "btl" will match "Battle Song", "Beautiful", "Subtitle" 160 | 161 | ### Visual Indicators 162 | - **`→`** Currently selected song in the list 163 | - **`♪`** Currently playing song indicator 164 | - **Progress Bar** Real-time playback progress with time 165 | - **Search Title** Shows current search query in song list header 166 | - **Result Count** Displays filtered results count (e.g., "15/120 songs") 167 | 168 | ### Playback Modes 169 | - **Normal Mode**: Sequential playback through your playlist 170 | - **Random Mode**: Intelligent shuffle (excludes current song) 171 | 172 | ### Smart Space/Enter Key 173 | - **Initial state**: Plays the first selected song 174 | - **Different song selected**: Plays the selected song immediately 175 | - **Same song selected**: Toggles play/pause for current song 176 | 177 | ### Vim-Style Navigation 178 | - **Movement**: `hjkl` for navigation (h=left, j=down, k=up, l=right) 179 | - **Jumping**: `gg` jumps to first song, `G` jumps to last song 180 | - **Search Navigation**: `n/N` for next/previous search results 181 | - **Quit**: `q` as alternative to Escape 182 | 183 | ## Technical Details 184 | 185 | ### Architecture 186 | - **Player Engine**: State management with smart playback control 187 | - **Terminal UI**: Ratatui-powered responsive interface 188 | - **Audio Engine**: Rodio-based high-quality MP3 processing 189 | - **Performance**: Efficient seeking without playback interruption 190 | 191 | ### Core Dependencies 192 | - **`rodio`** - Professional audio playback and MP3 decoding 193 | - **`ratatui`** - Modern terminal user interface framework 194 | - **`crossterm`** - Cross-platform terminal control 195 | - **`rand`** - Cryptographically secure random shuffle 196 | 197 | ## Development 198 | 199 | ### Project Structure 200 | 201 | ``` 202 | musix/ 203 | ├── src/ 204 | │ └── main.rs # Complete application (~700 lines) 205 | ├── data/ # MP3 files (optional) 206 | ├── .github/workflows/ # CI/CD automation 207 | ├── Cargo.toml # Dependencies and metadata 208 | ├── rustfmt.toml # Code formatting rules 209 | └── README.md # Documentation 210 | ``` 211 | 212 | ### Building & Testing 213 | 214 | ```bash 215 | # Development build 216 | cargo build 217 | 218 | # Optimized release build 219 | cargo build --release 220 | 221 | # Run all tests 222 | cargo test 223 | 224 | # Code quality checks 225 | cargo clippy --all-targets --all-features -- -D warnings 226 | cargo fmt --all -- --check 227 | ``` 228 | 229 | ## Troubleshooting 230 | 231 | ### No Music Files Found 232 | 233 | **Issue**: `No MP3 files found in any accessible directory` 234 | 235 | **Solutions**: 236 | ```bash 237 | # Option 1: Copy files to data folder 238 | mkdir -p ./data 239 | cp /path/to/your/music/*.mp3 ./data/ 240 | 241 | # Option 2: Create symbolic link 242 | ln -s /path/to/your/music ./data 243 | 244 | # Option 3: Check permissions 245 | ls -la ~/Music 246 | ``` 247 | 248 | ### macOS Music Access 249 | 250 | **Issue**: Cannot access ~/Music directory on macOS 251 | 252 | **Solution**: Enable Full Disk Access for your terminal: 253 | 254 | 1. **System Settings** → **Privacy & Security** → **Full Disk Access** 255 | 2. Click lock icon to unlock settings 256 | 3. Click **+** and add your terminal app (Terminal/iTerm2) 257 | 4. Enable the checkbox 258 | 5. **Restart your terminal** 259 | 260 | ### Linux Audio Issues 261 | 262 | **Issue**: No audio output or initialization errors 263 | 264 | **Solutions**: 265 | ```bash 266 | # Install required audio libraries 267 | sudo apt-get update 268 | sudo apt-get install libasound2-dev pkg-config 269 | 270 | # For other distributions 271 | sudo pacman -S alsa-lib pkg-config # Arch 272 | sudo dnf install alsa-lib-devel pkgconf # Fedora 273 | ``` 274 | 275 | ## License 276 | 277 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 278 | 279 | ## Contributing 280 | 281 | 1. Fork the repository 282 | 2. Create a feature branch 283 | 3. Make your changes 284 | 4. Add tests if applicable 285 | 5. Submit a pull request 286 | 287 | ## Acknowledgments 288 | 289 | - **Rodio** team for excellent Rust audio library 290 | - **Ratatui** team for powerful TUI framework 291 | - **Rust** community for amazing ecosystem 292 | 293 | --- 294 | 295 | **Built with Claud Code** 296 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, io, 3 | path::PathBuf, 4 | sync::{Arc, Mutex}, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use crossterm::{ 9 | event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, 10 | execute, 11 | terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}, 12 | }; 13 | use ratatui::{ 14 | Frame, Terminal, 15 | backend::CrosstermBackend, 16 | layout::{Alignment, Constraint, Direction, Layout}, 17 | style::{Color, Modifier, Style}, 18 | text::{Line, Span}, 19 | widgets::{Block, Borders, Gauge, List, ListItem, ListState, Paragraph}, 20 | }; 21 | use rodio::{Decoder, OutputStream, Sink, Source}; 22 | 23 | #[derive(Clone)] 24 | struct Song { 25 | name: String, 26 | path: PathBuf, 27 | } 28 | 29 | const HIGHLIGHT_COLOR: Color = Color::Rgb(0, 255, 150); 30 | const PRIMARY_COLOR: Color = Color::LightGreen; 31 | 32 | struct Player { 33 | songs: Vec, 34 | current_index: usize, 35 | selected_index: usize, 36 | _stream: Option>, 37 | _stream_handle: Option>, 38 | sink: Option>>, 39 | is_playing: bool, 40 | loop_mode: bool, 41 | random_mode: bool, 42 | list_state: ListState, 43 | playback_start: Option, 44 | song_duration: Option, 45 | seek_offset: Duration, 46 | show_controls_popup: bool, 47 | search_mode: bool, 48 | search_query: String, 49 | filtered_songs: Vec, 50 | g_pressed: bool, 51 | } 52 | 53 | impl Player { 54 | fn update_terminal_title(&self) { 55 | if self.songs.is_empty() { 56 | return; 57 | } 58 | 59 | let title = if self.is_playing { 60 | format!("MUSIX - ♪ {}", self.songs[self.current_index].name) 61 | } else { 62 | format!("MUSIX - {} (Paused)", self.songs[self.current_index].name) 63 | }; 64 | 65 | let _ = execute!(io::stdout(), SetTitle(&title)); 66 | } 67 | fn new() -> Result> { 68 | let songs = load_mp3_files()?; 69 | if songs.is_empty() { 70 | return Err("No MP3 files found".into()); 71 | } 72 | 73 | let mut list_state = ListState::default(); 74 | list_state.select(Some(0)); 75 | 76 | // Initialize audio system with Rodio 0.20 API 77 | let (stream, stream_handle, sink) = match OutputStream::try_default() { 78 | Ok((stream, stream_handle)) => match Sink::try_new(&stream_handle) { 79 | Ok(sink) => ( 80 | Some(Box::new(stream) as Box), 81 | Some(Box::new(stream_handle) as Box), 82 | Some(Arc::new(Mutex::new(sink))), 83 | ), 84 | Err(e) => { 85 | eprintln!("Warning: Could not create audio sink: {e}"); 86 | ( 87 | Some(Box::new(stream) as Box), 88 | Some(Box::new(stream_handle) as Box), 89 | None, 90 | ) 91 | } 92 | }, 93 | Err(e) => { 94 | eprintln!("Warning: Could not initialize audio output: {e}"); 95 | eprintln!("The application will continue but audio playback may not work."); 96 | (None, None, None) 97 | } 98 | }; 99 | 100 | let filtered_songs: Vec = (0..songs.len()).collect(); 101 | 102 | let player = Player { 103 | songs, 104 | current_index: 0, 105 | selected_index: 0, 106 | _stream: stream, 107 | _stream_handle: stream_handle, 108 | sink, 109 | is_playing: false, 110 | loop_mode: true, 111 | random_mode: false, 112 | list_state, 113 | playback_start: None, 114 | song_duration: None, 115 | seek_offset: Duration::from_secs(0), 116 | show_controls_popup: false, 117 | search_mode: false, 118 | search_query: String::new(), 119 | filtered_songs, 120 | g_pressed: false, 121 | }; 122 | 123 | // Set initial terminal title 124 | if !player.songs.is_empty() { 125 | let _ = execute!(io::stdout(), SetTitle(&format!("MUSIX - {}", player.songs[0].name))); 126 | } else { 127 | let _ = execute!(io::stdout(), SetTitle("MUSIX")); 128 | } 129 | 130 | Ok(player) 131 | } 132 | 133 | fn play_song(&mut self, index: usize) -> Result<(), Box> { 134 | if index >= self.songs.len() { 135 | return Ok(()); 136 | } 137 | 138 | self.current_index = index; 139 | self.selected_index = index; 140 | self.list_state.select(Some(self.selected_index)); 141 | self.seek_offset = Duration::from_secs(0); 142 | if let Some(ref sink) = self.sink { 143 | let song = &self.songs[index]; 144 | match std::fs::File::open(&song.path) { 145 | Ok(file) => { 146 | match Decoder::new(file) { 147 | Ok(source) => { 148 | // Try to get duration from the source 149 | let total_duration = source.total_duration(); 150 | 151 | let sink = sink.lock().unwrap(); 152 | sink.stop(); 153 | 154 | // If we have a seek offset, we need to skip ahead 155 | if self.seek_offset > Duration::from_secs(0) { 156 | let skipped_source = source.skip_duration(self.seek_offset); 157 | sink.append(skipped_source); 158 | } else { 159 | sink.append(source); 160 | } 161 | 162 | sink.play(); 163 | self.is_playing = true; 164 | self.playback_start = Some(Instant::now()); 165 | self.song_duration = total_duration; 166 | self.update_terminal_title(); 167 | } 168 | Err(e) => { 169 | eprintln!("Warning: Could not decode audio file '{}': {e}", song.name); 170 | } 171 | } 172 | } 173 | Err(e) => { 174 | eprintln!("Warning: Could not open audio file '{}': {e}", song.name); 175 | } 176 | } 177 | } else { 178 | eprintln!("Warning: No audio sink available. Cannot play '{}'", self.songs[index].name); 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | fn play_or_pause(&mut self) -> Result<(), Box> { 185 | // If no songs are loaded, do nothing 186 | if self.songs.is_empty() { 187 | return Ok(()); 188 | } 189 | 190 | // If no song has ever been played (initial state), play the selected song 191 | if self.playback_start.is_none() && !self.is_playing { 192 | self.play_song(self.selected_index)?; 193 | return Ok(()); 194 | } 195 | 196 | // If selected song is different from current playing song, play the selected song 197 | if self.selected_index != self.current_index { 198 | self.play_song(self.selected_index)?; 199 | } else { 200 | // If selected song is the same as current playing song, toggle play/pause 201 | if self.is_playing { 202 | self.pause_playback(); 203 | self.update_terminal_title(); 204 | } else { 205 | self.resume_playback(); 206 | self.update_terminal_title(); 207 | } 208 | } 209 | Ok(()) 210 | } 211 | 212 | fn next_song(&mut self) -> Result<(), Box> { 213 | if self.songs.is_empty() { 214 | return Ok(()); 215 | } 216 | 217 | let next_index = if self.random_mode { 218 | // Simple random selection using timestamp 219 | let timestamp = std::time::SystemTime::now() 220 | .duration_since(std::time::UNIX_EPOCH) 221 | .unwrap_or_default() 222 | .as_nanos() as usize; 223 | let mut indices: Vec = (0..self.songs.len()).collect(); 224 | indices.retain(|&i| i != self.current_index); 225 | if indices.is_empty() { 226 | self.current_index 227 | } else { 228 | indices[timestamp % indices.len()] 229 | } 230 | } else if self.current_index + 1 >= self.songs.len() { 231 | if self.loop_mode { 0 } else { self.current_index } 232 | } else { 233 | self.current_index + 1 234 | }; 235 | 236 | self.play_song(next_index) 237 | } 238 | 239 | fn previous_song(&mut self) -> Result<(), Box> { 240 | if self.songs.is_empty() { 241 | return Ok(()); 242 | } 243 | 244 | let prev_index = if self.random_mode { 245 | // Simple random selection using timestamp 246 | let timestamp = std::time::SystemTime::now() 247 | .duration_since(std::time::UNIX_EPOCH) 248 | .unwrap_or_default() 249 | .as_nanos() as usize; 250 | let mut indices: Vec = (0..self.songs.len()).collect(); 251 | indices.retain(|&i| i != self.current_index); 252 | if indices.is_empty() { 253 | self.current_index 254 | } else { 255 | indices[timestamp % indices.len()] 256 | } 257 | } else if self.current_index == 0 { 258 | if self.loop_mode { self.songs.len() - 1 } else { 0 } 259 | } else { 260 | self.current_index - 1 261 | }; 262 | 263 | self.play_song(prev_index) 264 | } 265 | 266 | fn move_selection(&mut self, direction: i32) { 267 | if self.songs.is_empty() { 268 | return; 269 | } 270 | 271 | let len = self.songs.len(); 272 | if direction > 0 { 273 | self.selected_index = (self.selected_index + 1) % len; 274 | } else if direction < 0 { 275 | self.selected_index = if self.selected_index == 0 { len - 1 } else { self.selected_index - 1 }; 276 | } 277 | self.list_state.select(Some(self.selected_index)); 278 | } 279 | 280 | fn get_playback_progress(&self) -> (Duration, Option) { 281 | if let Some(start_time) = self.playback_start { 282 | let elapsed = start_time.elapsed() + self.seek_offset; 283 | (elapsed, self.song_duration) 284 | } else { 285 | (self.seek_offset, self.song_duration) 286 | } 287 | } 288 | 289 | fn format_duration(duration: Duration) -> String { 290 | let total_seconds = duration.as_secs(); 291 | let minutes = total_seconds / 60; 292 | let seconds = total_seconds % 60; 293 | format!("{minutes:02}:{seconds:02}") 294 | } 295 | 296 | fn pause_playback(&mut self) { 297 | if self.is_playing { 298 | // Store current progress before pausing 299 | if let Some(start_time) = self.playback_start { 300 | self.seek_offset += start_time.elapsed(); 301 | } 302 | 303 | if let Some(ref sink) = self.sink { 304 | let sink = sink.lock().unwrap(); 305 | sink.pause(); 306 | } 307 | self.is_playing = false; 308 | self.playback_start = None; 309 | self.update_terminal_title(); 310 | } 311 | } 312 | 313 | fn resume_playback(&mut self) { 314 | if !self.is_playing && !self.songs.is_empty() { 315 | if let Some(ref sink) = self.sink { 316 | let sink = sink.lock().unwrap(); 317 | sink.play(); 318 | self.is_playing = true; 319 | self.playback_start = Some(Instant::now()); 320 | self.update_terminal_title(); 321 | } 322 | } 323 | } 324 | 325 | fn seek(&mut self, offset_seconds: i32) { 326 | if !self.songs.is_empty() && self.is_playing { 327 | if let Some(ref sink) = self.sink { 328 | // Get current actual position (including elapsed time since playback start) 329 | let current_position = if let Some(start_time) = self.playback_start { 330 | self.seek_offset + start_time.elapsed() 331 | } else { 332 | self.seek_offset 333 | }; 334 | 335 | let seek_duration = Duration::from_secs(offset_seconds.unsigned_abs().into()); 336 | let new_position = if offset_seconds < 0 { 337 | // Seek backward 338 | if current_position > seek_duration { 339 | current_position - seek_duration 340 | } else { 341 | Duration::from_secs(0) 342 | } 343 | } else { 344 | // Seek forward 345 | current_position + seek_duration 346 | }; 347 | 348 | // Try to seek using rodio's try_seek method 349 | let sink = sink.lock().unwrap(); 350 | match sink.try_seek(new_position) { 351 | Ok(()) => { 352 | // Seeking succeeded, update our tracking variables 353 | self.seek_offset = new_position; 354 | self.playback_start = Some(Instant::now()); 355 | } 356 | Err(_) => { 357 | // Seeking failed, fall back to restarting from new position 358 | drop(sink); 359 | self.seek_offset = new_position; 360 | let _ = self.play_song(self.current_index); 361 | } 362 | } 363 | } 364 | } 365 | } 366 | 367 | fn fuzzy_search(&mut self, query: &str) { 368 | if query.is_empty() { 369 | self.filtered_songs = (0..self.songs.len()).collect(); 370 | } else { 371 | let query_lower = query.to_lowercase(); 372 | let mut matches: Vec<(usize, f32)> = self.songs 373 | .iter() 374 | .enumerate() 375 | .filter_map(|(index, song)| { 376 | let song_name_lower = song.name.to_lowercase(); 377 | let score = Self::fuzzy_match_score(&query_lower, &song_name_lower); 378 | if score > 0.0 { 379 | Some((index, score)) 380 | } else { 381 | None 382 | } 383 | }) 384 | .collect(); 385 | 386 | matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); 387 | self.filtered_songs = matches.into_iter().map(|(index, _)| index).collect(); 388 | } 389 | 390 | if !self.filtered_songs.is_empty() { 391 | self.selected_index = self.filtered_songs[0]; 392 | self.list_state.select(Some(0)); 393 | } 394 | } 395 | 396 | fn fuzzy_match_score(query: &str, text: &str) -> f32 { 397 | if query.is_empty() { 398 | return 1.0; 399 | } 400 | 401 | if text.contains(query) { 402 | let exact_match_bonus = if text == query { 2.0 } else { 1.5 }; 403 | let starts_with_bonus = if text.starts_with(query) { 1.2 } else { 1.0 }; 404 | return exact_match_bonus * starts_with_bonus; 405 | } 406 | 407 | let mut score = 0.0; 408 | let query_chars: Vec = query.chars().collect(); 409 | let text_chars: Vec = text.chars().collect(); 410 | let mut query_index = 0; 411 | 412 | for (text_index, text_char) in text_chars.iter().enumerate() { 413 | if query_index < query_chars.len() && *text_char == query_chars[query_index] { 414 | score += 1.0 / (text_index as f32 + 1.0); 415 | query_index += 1; 416 | } 417 | } 418 | 419 | if query_index == query_chars.len() { 420 | score / query_chars.len() as f32 421 | } else { 422 | 0.0 423 | } 424 | } 425 | 426 | fn enter_search_mode(&mut self) { 427 | self.search_mode = true; 428 | self.search_query.clear(); 429 | self.fuzzy_search(""); 430 | } 431 | 432 | fn exit_search_mode(&mut self) { 433 | self.search_mode = false; 434 | self.search_query.clear(); 435 | self.filtered_songs = (0..self.songs.len()).collect(); 436 | self.list_state.select(Some(self.selected_index)); 437 | } 438 | 439 | fn get_display_songs(&self) -> Vec<(usize, &Song)> { 440 | if self.search_mode { 441 | self.filtered_songs.iter().map(|&index| (index, &self.songs[index])).collect() 442 | } else { 443 | self.songs.iter().enumerate().collect() 444 | } 445 | } 446 | 447 | fn move_selection_in_search(&mut self, direction: i32) { 448 | if self.filtered_songs.is_empty() { 449 | return; 450 | } 451 | 452 | let current_filtered_index = self.filtered_songs 453 | .iter() 454 | .position(|&index| index == self.selected_index) 455 | .unwrap_or(0); 456 | 457 | let new_filtered_index = if direction > 0 { 458 | (current_filtered_index + 1) % self.filtered_songs.len() 459 | } else if direction < 0 { 460 | if current_filtered_index == 0 { 461 | self.filtered_songs.len() - 1 462 | } else { 463 | current_filtered_index - 1 464 | } 465 | } else { 466 | current_filtered_index 467 | }; 468 | 469 | self.selected_index = self.filtered_songs[new_filtered_index]; 470 | self.list_state.select(Some(new_filtered_index)); 471 | } 472 | 473 | fn jump_to_first(&mut self) { 474 | if self.songs.is_empty() { 475 | return; 476 | } 477 | 478 | if self.search_mode { 479 | if !self.filtered_songs.is_empty() { 480 | self.selected_index = self.filtered_songs[0]; 481 | self.list_state.select(Some(0)); 482 | } 483 | } else { 484 | self.selected_index = 0; 485 | self.list_state.select(Some(0)); 486 | } 487 | } 488 | 489 | fn jump_to_last(&mut self) { 490 | if self.songs.is_empty() { 491 | return; 492 | } 493 | 494 | if self.search_mode { 495 | if !self.filtered_songs.is_empty() { 496 | let last_index = self.filtered_songs.len() - 1; 497 | self.selected_index = self.filtered_songs[last_index]; 498 | self.list_state.select(Some(last_index)); 499 | } 500 | } else { 501 | self.selected_index = self.songs.len() - 1; 502 | self.list_state.select(Some(self.selected_index)); 503 | } 504 | } 505 | } 506 | 507 | fn load_mp3_files() -> Result, Box> { 508 | let mut songs = Vec::new(); 509 | 510 | // Try multiple directories in order of preference 511 | let potential_dirs = vec![ 512 | { 513 | // User's Music directory 514 | let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); 515 | PathBuf::from(format!("{home_dir}/Music")) 516 | }, 517 | PathBuf::from("./data"), 518 | ]; 519 | 520 | for data_dir in potential_dirs { 521 | if data_dir.exists() { 522 | match visit_dir(&data_dir, &mut songs) { 523 | Ok(_) => { 524 | //eprintln!("Loaded {} MP3 files from: {data_dir:?}", songs.len()); // break; 525 | } 526 | Err(e) => { 527 | eprintln!("Warning: Could not access directory {data_dir:?}: {e}"); 528 | continue; 529 | } 530 | } 531 | } 532 | } 533 | 534 | songs.sort_by(|a, b| a.name.cmp(&b.name)); 535 | Ok(songs) 536 | } 537 | 538 | fn visit_dir(dir: &PathBuf, songs: &mut Vec) -> Result<(), Box> { 539 | if dir.is_dir() { 540 | for entry in fs::read_dir(dir)? { 541 | let entry = entry?; 542 | let path = entry.path(); 543 | 544 | if path.is_dir() { 545 | visit_dir(&path, songs)?; 546 | } else if let Some(extension) = path.extension() { 547 | if extension.to_str().unwrap_or("").to_lowercase() == "mp3" { 548 | let name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("Unknown").to_string(); 549 | 550 | songs.push(Song { name, path: path.clone() }); 551 | } 552 | } 553 | } 554 | } 555 | Ok(()) 556 | } 557 | 558 | fn ui(f: &mut Frame, player: &Player) { 559 | let chunks = Layout::default() 560 | .direction(Direction::Vertical) 561 | .constraints([ 562 | Constraint::Length(3), // Title 563 | Constraint::Min(8), // Song list 564 | Constraint::Length(3), // Progress bar 565 | Constraint::Length(3), // Status 566 | ]) 567 | .split(f.area()); 568 | 569 | // Title 570 | let title = Paragraph::new("MUSIX") 571 | .style(Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)) 572 | .alignment(Alignment::Center) 573 | .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(PRIMARY_COLOR))); 574 | f.render_widget(title, chunks[0]); 575 | 576 | // Song list 577 | let display_songs = player.get_display_songs(); 578 | let items: Vec = display_songs 579 | .iter() 580 | .enumerate() 581 | .map(|(_display_index, &(actual_index, song))| { 582 | let playing_indicator = if actual_index == player.current_index && player.is_playing { "♪ " } else { " " }; 583 | 584 | let content = format!("{playing_indicator}{}. {}", actual_index + 1, song.name); 585 | 586 | let style = if actual_index == player.current_index && player.is_playing { 587 | Style::default().fg(HIGHLIGHT_COLOR).add_modifier(Modifier::BOLD) 588 | } else if actual_index == player.selected_index { 589 | Style::default().fg(PRIMARY_COLOR) 590 | } else { 591 | Style::default().fg(Color::White) 592 | }; 593 | 594 | ListItem::new(content).style(style) 595 | }) 596 | .collect(); 597 | 598 | let songs_title = if player.search_mode { 599 | format!("Songs - Search: {}", player.search_query) 600 | } else { 601 | "Songs".to_string() 602 | }; 603 | 604 | let songs_list = List::new(items) 605 | .block( 606 | Block::default() 607 | .borders(Borders::ALL) 608 | .title(songs_title) 609 | .border_style(Style::default().fg(PRIMARY_COLOR)), 610 | ) 611 | .highlight_style(Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)) 612 | .scroll_padding(1); 613 | 614 | f.render_stateful_widget(songs_list, chunks[1], &mut player.list_state.clone()); 615 | 616 | // Progress bar 617 | let (elapsed, total) = player.get_playback_progress(); 618 | let progress_ratio = if let Some(duration) = total { 619 | if duration.as_secs() > 0 { 620 | (elapsed.as_secs() as f64 / duration.as_secs() as f64).min(1.0) 621 | } else { 622 | 0.0 623 | } 624 | } else { 625 | 0.0 626 | }; 627 | 628 | 629 | let progress_label_text = if let Some(duration) = total { 630 | format!(" {}/{} ", Player::format_duration(elapsed), Player::format_duration(duration)) 631 | } else { 632 | format!(" {} ", Player::format_duration(elapsed)) 633 | }; 634 | 635 | let progress_bar_style = Style::default().fg(PRIMARY_COLOR).bg(Color::default()); 636 | let progress_label = Span::styled(progress_label_text, progress_bar_style); 637 | 638 | let progress_bar = Gauge::default() 639 | .block( 640 | Block::default() 641 | .borders(Borders::ALL) 642 | .title("Progress") 643 | .border_style(Style::default().fg(PRIMARY_COLOR)), 644 | ) 645 | .gauge_style(progress_bar_style) 646 | .ratio(progress_ratio) 647 | .label(progress_label); 648 | f.render_widget(progress_bar, chunks[2]); 649 | 650 | // Status 651 | let mode_text = if player.random_mode { "RANDOM" } else { "NORMAL" }; 652 | let song_count = if player.search_mode { 653 | format!("{}/{}", player.filtered_songs.len(), player.songs.len()) 654 | } else { 655 | player.songs.len().to_string() 656 | }; 657 | 658 | let status_content = if player.search_mode { 659 | vec![Line::from(vec![ 660 | Span::raw(format!(" Search Mode | Songs: {} | ", song_count)), 661 | Span::styled("Esc", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 662 | Span::raw(": Exit Search | "), 663 | Span::styled("Enter", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 664 | Span::raw(": Play "), 665 | ])] 666 | } else { 667 | vec![Line::from(vec![ 668 | Span::raw(format!(" Mode: {} | Songs: {} | ", mode_text, song_count)), 669 | Span::styled("/", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 670 | Span::raw(": Search | "), 671 | Span::styled("x", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 672 | Span::raw(": Help "), 673 | ])] 674 | }; 675 | 676 | let status = Paragraph::new(status_content) 677 | .alignment(Alignment::Left) 678 | .block( 679 | Block::default() 680 | .borders(Borders::ALL) 681 | .title("Status") 682 | .border_style(Style::default().fg(PRIMARY_COLOR)), 683 | ); 684 | f.render_widget(status, chunks[3]); 685 | 686 | // Controls popup 687 | if player.show_controls_popup { 688 | let popup_area = centered_rect(60, 60, f.area()); 689 | f.render_widget(ratatui::widgets::Clear, popup_area); 690 | 691 | let controls_popup = Paragraph::new(vec![ 692 | Line::from(""), 693 | Line::from(vec![Span::styled("CONTROLS", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD))]).alignment(Alignment::Center), 694 | Line::from(""), 695 | Line::from(vec![ 696 | Span::styled(" ↑/↓ or j/k", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 697 | Span::raw(" - Navigate songs"), 698 | ]), 699 | Line::from(vec![ 700 | Span::styled(" Space/↵ ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 701 | Span::raw(" - Play/Pause"), 702 | ]), 703 | Line::from(vec![ 704 | Span::styled(" ←/→ or h/l", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 705 | Span::raw(" - Play prev/next song"), 706 | ]), 707 | Line::from(vec![ 708 | Span::styled(" gg/G ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 709 | Span::raw(" - Jump to first/last"), 710 | ]), 711 | Line::from(vec![ 712 | Span::styled(" / ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 713 | Span::raw(" - Enter search mode"), 714 | ]), 715 | Line::from(vec![ 716 | Span::styled(" n/N ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 717 | Span::raw(" - Next/prev search"), 718 | ]), 719 | Line::from(vec![ 720 | Span::styled(" ,/. ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 721 | Span::raw(" - Seek ±5 seconds"), 722 | ]), 723 | Line::from(vec![ 724 | Span::styled(" R ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 725 | Span::raw(" - Toggle random mode"), 726 | ]), 727 | Line::from(vec![ 728 | Span::styled(" q/Esc ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 729 | Span::raw(" - Exit application"), 730 | ]), 731 | Line::from(vec![ 732 | Span::styled(" x ", Style::default().fg(PRIMARY_COLOR).add_modifier(Modifier::BOLD)), 733 | Span::raw(" - Close this popup"), 734 | ]), 735 | ]) 736 | .alignment(Alignment::Left) 737 | .block( 738 | Block::default() 739 | .borders(Borders::ALL) 740 | .title("Help") 741 | .border_style(Style::default().fg(PRIMARY_COLOR)), 742 | ); 743 | f.render_widget(controls_popup, popup_area); 744 | } 745 | } 746 | 747 | fn centered_rect(percent_x: u16, percent_y: u16, r: ratatui::prelude::Rect) -> ratatui::prelude::Rect { 748 | let popup_layout = Layout::default() 749 | .direction(Direction::Vertical) 750 | .constraints([ 751 | Constraint::Percentage((100 - percent_y) / 2), 752 | Constraint::Percentage(percent_y), 753 | Constraint::Percentage((100 - percent_y) / 2), 754 | ]) 755 | .split(r); 756 | 757 | Layout::default() 758 | .direction(Direction::Horizontal) 759 | .constraints([ 760 | Constraint::Percentage((100 - percent_x) / 2), 761 | Constraint::Percentage(percent_x), 762 | Constraint::Percentage((100 - percent_x) / 2), 763 | ]) 764 | .split(popup_layout[1])[1] 765 | } 766 | 767 | fn run_player() -> Result<(), Box> { 768 | let mut player = match Player::new() { 769 | Ok(p) => p, 770 | Err(e) => { 771 | eprintln!("Player initialization failed: {e}"); 772 | eprintln!("Error details: {e:?}"); 773 | std::process::exit(1); 774 | } 775 | }; 776 | 777 | if player.songs.is_empty() { 778 | println!("No MP3 files found in any accessible directory."); 779 | println!("MUSIX searched for MP3 files in:"); 780 | println!(" - ~/Music (user's music directory)"); 781 | println!(" - ./data (current directory)"); 782 | println!(); 783 | println!("To test MUSIX, you can:"); 784 | println!("Copy MP3 files to ./data directory"); 785 | return Ok(()); 786 | } 787 | 788 | match enable_raw_mode() { 789 | Ok(_) => {} 790 | Err(e) => { 791 | eprintln!("Failed to enable raw mode: {e}"); 792 | return Err(e.into()); 793 | } 794 | } 795 | 796 | let mut stdout = io::stdout(); 797 | match execute!(stdout, EnterAlternateScreen) { 798 | Ok(_) => {} 799 | Err(e) => { 800 | eprintln!("Failed to enter alternate screen: {e}"); 801 | return Err(e.into()); 802 | } 803 | } 804 | 805 | let backend = CrosstermBackend::new(stdout); 806 | let mut terminal = match Terminal::new(backend) { 807 | Ok(t) => t, 808 | Err(e) => { 809 | eprintln!("Failed to create terminal: {e}"); 810 | return Err(e.into()); 811 | } 812 | }; 813 | 814 | let result = main_loop(&mut terminal, &mut player); 815 | 816 | // Clean shutdown of audio to prevent warning messages 817 | if let Some(ref sink) = player.sink { 818 | let sink = sink.lock().unwrap(); 819 | sink.stop(); 820 | } 821 | 822 | disable_raw_mode()?; 823 | execute!(terminal.backend_mut(), LeaveAlternateScreen)?; 824 | terminal.show_cursor()?; 825 | 826 | // Reset terminal title 827 | let _ = execute!(io::stdout(), SetTitle("Terminal")); 828 | 829 | result 830 | } 831 | 832 | fn main_loop(terminal: &mut Terminal>, player: &mut Player) -> Result<(), Box> { 833 | loop { 834 | terminal.draw(|f| ui(f, player))?; 835 | 836 | if let Ok(true) = event::poll(Duration::from_millis(100)) { 837 | if let Ok(Event::Key(key)) = event::read() { 838 | // Reset g_pressed state for any key except 'g' 839 | if key.code != KeyCode::Char('g') || key.modifiers != KeyModifiers::NONE { 840 | player.g_pressed = false; 841 | } 842 | 843 | match key { 844 | KeyEvent { 845 | code: KeyCode::Esc, 846 | modifiers: KeyModifiers::NONE, 847 | .. 848 | } => { 849 | if player.show_controls_popup { 850 | player.show_controls_popup = false; 851 | } else if player.search_mode { 852 | player.exit_search_mode(); 853 | } else { 854 | break; 855 | } 856 | } 857 | 858 | KeyEvent { 859 | code: KeyCode::Char('c'), 860 | modifiers: KeyModifiers::CONTROL, 861 | .. 862 | } => break, 863 | 864 | KeyEvent { 865 | code: KeyCode::Up, 866 | modifiers: KeyModifiers::NONE, 867 | .. 868 | } | KeyEvent { 869 | code: KeyCode::Char('k'), 870 | modifiers: KeyModifiers::NONE, 871 | .. 872 | } => { 873 | if player.search_mode { 874 | player.move_selection_in_search(-1); 875 | } else { 876 | player.move_selection(-1); 877 | } 878 | } 879 | 880 | KeyEvent { 881 | code: KeyCode::Down, 882 | modifiers: KeyModifiers::NONE, 883 | .. 884 | } | KeyEvent { 885 | code: KeyCode::Char('j'), 886 | modifiers: KeyModifiers::NONE, 887 | .. 888 | } => { 889 | if player.search_mode { 890 | player.move_selection_in_search(1); 891 | } else { 892 | player.move_selection(1); 893 | } 894 | } 895 | 896 | KeyEvent { 897 | code: KeyCode::Enter, 898 | modifiers: KeyModifiers::NONE, 899 | .. 900 | } => { 901 | let _ = player.play_or_pause(); 902 | if player.search_mode { 903 | player.exit_search_mode(); 904 | } 905 | } 906 | 907 | KeyEvent { 908 | code: KeyCode::Char(' '), 909 | modifiers: KeyModifiers::NONE, 910 | .. 911 | } => { 912 | if player.search_mode { 913 | player.search_query.push(' '); 914 | let query = player.search_query.clone(); 915 | player.fuzzy_search(&query); 916 | } else { 917 | let _ = player.play_or_pause(); 918 | } 919 | } 920 | 921 | KeyEvent { 922 | code: KeyCode::Left, 923 | modifiers: KeyModifiers::NONE, 924 | .. 925 | } => { 926 | if !player.search_mode { 927 | player.previous_song()?; 928 | } 929 | } 930 | 931 | KeyEvent { 932 | code: KeyCode::Right, 933 | modifiers: KeyModifiers::NONE, 934 | .. 935 | } => { 936 | if !player.search_mode { 937 | player.next_song()?; 938 | } 939 | } 940 | 941 | KeyEvent { 942 | code: KeyCode::Char('h'), 943 | modifiers: KeyModifiers::NONE, 944 | .. 945 | } => { 946 | if player.search_mode { 947 | player.search_query.push('h'); 948 | let query = player.search_query.clone(); 949 | player.fuzzy_search(&query); 950 | } else { 951 | player.previous_song()?; 952 | } 953 | } 954 | 955 | KeyEvent { 956 | code: KeyCode::Char('l'), 957 | modifiers: KeyModifiers::NONE, 958 | .. 959 | } => { 960 | if player.search_mode { 961 | player.search_query.push('l'); 962 | let query = player.search_query.clone(); 963 | player.fuzzy_search(&query); 964 | } else { 965 | player.next_song()?; 966 | } 967 | } 968 | 969 | KeyEvent { 970 | code: KeyCode::Char('n'), 971 | modifiers: KeyModifiers::NONE, 972 | .. 973 | } => { 974 | if player.search_mode { 975 | player.move_selection_in_search(1); 976 | } 977 | // In normal mode, 'n' has no special meaning, fall through to default char handler 978 | } 979 | 980 | KeyEvent { 981 | code: KeyCode::Char('N'), 982 | modifiers: KeyModifiers::SHIFT, 983 | .. 984 | } => { 985 | if player.search_mode { 986 | player.move_selection_in_search(-1); 987 | } 988 | // In normal mode, 'N' has no special meaning, ignore 989 | } 990 | 991 | KeyEvent { 992 | code: KeyCode::Char('g'), 993 | modifiers: KeyModifiers::NONE, 994 | .. 995 | } => { 996 | if player.search_mode { 997 | player.search_query.push('g'); 998 | let query = player.search_query.clone(); 999 | player.fuzzy_search(&query); 1000 | } else { 1001 | if player.g_pressed { 1002 | // Second 'g' - jump to first song 1003 | player.jump_to_first(); 1004 | player.g_pressed = false; 1005 | } else { 1006 | // First 'g' - set flag and wait for second 'g' 1007 | player.g_pressed = true; 1008 | } 1009 | } 1010 | } 1011 | 1012 | KeyEvent { 1013 | code: KeyCode::Char('G'), 1014 | modifiers: KeyModifiers::SHIFT, 1015 | .. 1016 | } => { 1017 | if player.search_mode { 1018 | player.search_query.push('G'); 1019 | let query = player.search_query.clone(); 1020 | player.fuzzy_search(&query); 1021 | } else { 1022 | player.jump_to_last(); 1023 | player.g_pressed = false; // Reset g_pressed state 1024 | } 1025 | } 1026 | 1027 | KeyEvent { 1028 | code: KeyCode::Char('q'), 1029 | modifiers: KeyModifiers::NONE, 1030 | .. 1031 | } => { 1032 | if player.search_mode { 1033 | player.search_query.push('q'); 1034 | let query = player.search_query.clone(); 1035 | player.fuzzy_search(&query); 1036 | } else { 1037 | break; // Quit the application 1038 | } 1039 | } 1040 | 1041 | KeyEvent { 1042 | code: KeyCode::Char('r'), 1043 | modifiers: KeyModifiers::NONE, 1044 | .. 1045 | } => { 1046 | if player.search_mode { 1047 | player.search_query.push('r'); 1048 | let query = player.search_query.clone(); 1049 | player.fuzzy_search(&query); 1050 | } else { 1051 | player.random_mode = !player.random_mode; 1052 | } 1053 | } 1054 | 1055 | KeyEvent { 1056 | code: KeyCode::Char('x'), 1057 | modifiers: KeyModifiers::NONE, 1058 | .. 1059 | } => { 1060 | if player.search_mode { 1061 | player.search_query.push('x'); 1062 | let query = player.search_query.clone(); 1063 | player.fuzzy_search(&query); 1064 | } else { 1065 | player.show_controls_popup = !player.show_controls_popup; 1066 | } 1067 | } 1068 | 1069 | KeyEvent { 1070 | code: KeyCode::Char('<') | KeyCode::Char(','), 1071 | modifiers: KeyModifiers::NONE, 1072 | .. 1073 | } => { 1074 | if player.search_mode { 1075 | let c = if key.code == KeyCode::Char('<') { '<' } else { ',' }; 1076 | player.search_query.push(c); 1077 | let query = player.search_query.clone(); 1078 | player.fuzzy_search(&query); 1079 | } else { 1080 | player.seek(-5); // Seek backward 5 seconds 1081 | } 1082 | } 1083 | 1084 | KeyEvent { 1085 | code: KeyCode::Char('>') | KeyCode::Char('.'), 1086 | modifiers: KeyModifiers::NONE, 1087 | .. 1088 | } => { 1089 | if player.search_mode { 1090 | let c = if key.code == KeyCode::Char('>') { '>' } else { '.' }; 1091 | player.search_query.push(c); 1092 | let query = player.search_query.clone(); 1093 | player.fuzzy_search(&query); 1094 | } else { 1095 | player.seek(5); // Seek forward 5 seconds 1096 | } 1097 | } 1098 | 1099 | KeyEvent { 1100 | code: KeyCode::Char('/'), 1101 | modifiers: KeyModifiers::NONE, 1102 | .. 1103 | } => { 1104 | if !player.search_mode { 1105 | player.enter_search_mode(); 1106 | } else { 1107 | player.search_query.push('/'); 1108 | let query = player.search_query.clone(); 1109 | player.fuzzy_search(&query); 1110 | } 1111 | } 1112 | 1113 | KeyEvent { 1114 | code: KeyCode::Backspace, 1115 | modifiers: KeyModifiers::NONE, 1116 | .. 1117 | } => { 1118 | if player.search_mode { 1119 | player.search_query.pop(); 1120 | let query = player.search_query.clone(); 1121 | player.fuzzy_search(&query); 1122 | } 1123 | } 1124 | 1125 | KeyEvent { 1126 | code: KeyCode::Char(c), 1127 | modifiers: KeyModifiers::NONE, 1128 | .. 1129 | } => { 1130 | if player.search_mode { 1131 | player.search_query.push(c); 1132 | let query = player.search_query.clone(); 1133 | player.fuzzy_search(&query); 1134 | } 1135 | } 1136 | 1137 | _ => {} 1138 | } 1139 | } 1140 | } 1141 | 1142 | // Check if current song finished and auto-play next 1143 | if player.is_playing { 1144 | if let Some(ref sink) = player.sink { 1145 | let sink = sink.lock().unwrap(); 1146 | if sink.empty() { 1147 | drop(sink); 1148 | player.is_playing = false; 1149 | player.playback_start = None; 1150 | player.seek_offset = Duration::from_secs(0); 1151 | player.next_song()?; 1152 | } 1153 | } 1154 | } 1155 | } 1156 | 1157 | Ok(()) 1158 | } 1159 | 1160 | fn main() { 1161 | if let Err(e) = run_player() { 1162 | eprintln!("Error: {e}"); 1163 | std::process::exit(1); 1164 | } 1165 | } 1166 | 1167 | #[cfg(test)] 1168 | mod tests { 1169 | use super::*; 1170 | 1171 | #[test] 1172 | fn test_format_duration() { 1173 | assert_eq!(Player::format_duration(Duration::from_secs(0)), "00:00"); 1174 | assert_eq!(Player::format_duration(Duration::from_secs(30)), "00:30"); 1175 | assert_eq!(Player::format_duration(Duration::from_secs(60)), "01:00"); 1176 | assert_eq!(Player::format_duration(Duration::from_secs(125)), "02:05"); 1177 | } 1178 | } 1179 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "allocator-api2" 16 | version = "0.2.21" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 19 | 20 | [[package]] 21 | name = "alsa" 22 | version = "0.9.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" 25 | dependencies = [ 26 | "alsa-sys", 27 | "bitflags 2.9.1", 28 | "cfg-if", 29 | "libc", 30 | ] 31 | 32 | [[package]] 33 | name = "alsa-sys" 34 | version = "0.3.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" 37 | dependencies = [ 38 | "libc", 39 | "pkg-config", 40 | ] 41 | 42 | [[package]] 43 | name = "arrayvec" 44 | version = "0.7.6" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 47 | 48 | [[package]] 49 | name = "autocfg" 50 | version = "1.5.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 53 | 54 | [[package]] 55 | name = "bindgen" 56 | version = "0.72.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" 59 | dependencies = [ 60 | "bitflags 2.9.1", 61 | "cexpr", 62 | "clang-sys", 63 | "itertools", 64 | "proc-macro2", 65 | "quote", 66 | "regex", 67 | "rustc-hash", 68 | "shlex", 69 | "syn", 70 | ] 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "1.3.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 77 | 78 | [[package]] 79 | name = "bitflags" 80 | version = "2.9.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 83 | 84 | [[package]] 85 | name = "bumpalo" 86 | version = "3.19.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 89 | 90 | [[package]] 91 | name = "bytemuck" 92 | version = "1.23.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" 95 | 96 | [[package]] 97 | name = "byteorder" 98 | version = "1.5.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 101 | 102 | [[package]] 103 | name = "bytes" 104 | version = "1.10.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 107 | 108 | [[package]] 109 | name = "cassowary" 110 | version = "0.3.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 113 | 114 | [[package]] 115 | name = "castaway" 116 | version = "0.2.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 119 | dependencies = [ 120 | "rustversion", 121 | ] 122 | 123 | [[package]] 124 | name = "cc" 125 | version = "1.2.30" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" 128 | dependencies = [ 129 | "jobserver", 130 | "libc", 131 | "shlex", 132 | ] 133 | 134 | [[package]] 135 | name = "cesu8" 136 | version = "1.1.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 139 | 140 | [[package]] 141 | name = "cexpr" 142 | version = "0.6.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 145 | dependencies = [ 146 | "nom", 147 | ] 148 | 149 | [[package]] 150 | name = "cfg-if" 151 | version = "1.0.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 154 | 155 | [[package]] 156 | name = "clang-sys" 157 | version = "1.8.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 160 | dependencies = [ 161 | "glob", 162 | "libc", 163 | "libloading", 164 | ] 165 | 166 | [[package]] 167 | name = "claxon" 168 | version = "0.4.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" 171 | 172 | [[package]] 173 | name = "combine" 174 | version = "4.6.7" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 177 | dependencies = [ 178 | "bytes", 179 | "memchr", 180 | ] 181 | 182 | [[package]] 183 | name = "compact_str" 184 | version = "0.8.1" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 187 | dependencies = [ 188 | "castaway", 189 | "cfg-if", 190 | "itoa", 191 | "rustversion", 192 | "ryu", 193 | "static_assertions", 194 | ] 195 | 196 | [[package]] 197 | name = "convert_case" 198 | version = "0.7.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 201 | dependencies = [ 202 | "unicode-segmentation", 203 | ] 204 | 205 | [[package]] 206 | name = "core-foundation-sys" 207 | version = "0.8.7" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 210 | 211 | [[package]] 212 | name = "coreaudio-rs" 213 | version = "0.11.3" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" 216 | dependencies = [ 217 | "bitflags 1.3.2", 218 | "core-foundation-sys", 219 | "coreaudio-sys", 220 | ] 221 | 222 | [[package]] 223 | name = "coreaudio-sys" 224 | version = "0.2.17" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" 227 | dependencies = [ 228 | "bindgen", 229 | ] 230 | 231 | [[package]] 232 | name = "cpal" 233 | version = "0.15.3" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" 236 | dependencies = [ 237 | "alsa", 238 | "core-foundation-sys", 239 | "coreaudio-rs", 240 | "dasp_sample", 241 | "jni", 242 | "js-sys", 243 | "libc", 244 | "mach2", 245 | "ndk", 246 | "ndk-context", 247 | "oboe", 248 | "wasm-bindgen", 249 | "wasm-bindgen-futures", 250 | "web-sys", 251 | "windows", 252 | ] 253 | 254 | [[package]] 255 | name = "crossterm" 256 | version = "0.28.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 259 | dependencies = [ 260 | "bitflags 2.9.1", 261 | "crossterm_winapi", 262 | "mio", 263 | "parking_lot", 264 | "rustix 0.38.44", 265 | "signal-hook", 266 | "signal-hook-mio", 267 | "winapi", 268 | ] 269 | 270 | [[package]] 271 | name = "crossterm" 272 | version = "0.29.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 275 | dependencies = [ 276 | "bitflags 2.9.1", 277 | "crossterm_winapi", 278 | "derive_more", 279 | "document-features", 280 | "mio", 281 | "parking_lot", 282 | "rustix 1.0.8", 283 | "signal-hook", 284 | "signal-hook-mio", 285 | "winapi", 286 | ] 287 | 288 | [[package]] 289 | name = "crossterm_winapi" 290 | version = "0.9.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 293 | dependencies = [ 294 | "winapi", 295 | ] 296 | 297 | [[package]] 298 | name = "darling" 299 | version = "0.20.11" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 302 | dependencies = [ 303 | "darling_core", 304 | "darling_macro", 305 | ] 306 | 307 | [[package]] 308 | name = "darling_core" 309 | version = "0.20.11" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 312 | dependencies = [ 313 | "fnv", 314 | "ident_case", 315 | "proc-macro2", 316 | "quote", 317 | "strsim", 318 | "syn", 319 | ] 320 | 321 | [[package]] 322 | name = "darling_macro" 323 | version = "0.20.11" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 326 | dependencies = [ 327 | "darling_core", 328 | "quote", 329 | "syn", 330 | ] 331 | 332 | [[package]] 333 | name = "dasp_sample" 334 | version = "0.11.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" 337 | 338 | [[package]] 339 | name = "derive_more" 340 | version = "2.0.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 343 | dependencies = [ 344 | "derive_more-impl", 345 | ] 346 | 347 | [[package]] 348 | name = "derive_more-impl" 349 | version = "2.0.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 352 | dependencies = [ 353 | "convert_case", 354 | "proc-macro2", 355 | "quote", 356 | "syn", 357 | ] 358 | 359 | [[package]] 360 | name = "document-features" 361 | version = "0.2.11" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 364 | dependencies = [ 365 | "litrs", 366 | ] 367 | 368 | [[package]] 369 | name = "either" 370 | version = "1.15.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 373 | 374 | [[package]] 375 | name = "encoding_rs" 376 | version = "0.8.35" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 379 | dependencies = [ 380 | "cfg-if", 381 | ] 382 | 383 | [[package]] 384 | name = "equivalent" 385 | version = "1.0.2" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 388 | 389 | [[package]] 390 | name = "errno" 391 | version = "0.3.13" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 394 | dependencies = [ 395 | "libc", 396 | "windows-sys 0.60.2", 397 | ] 398 | 399 | [[package]] 400 | name = "fnv" 401 | version = "1.0.7" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 404 | 405 | [[package]] 406 | name = "foldhash" 407 | version = "0.1.5" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 410 | 411 | [[package]] 412 | name = "getrandom" 413 | version = "0.3.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 416 | dependencies = [ 417 | "cfg-if", 418 | "libc", 419 | "r-efi", 420 | "wasi 0.14.2+wasi-0.2.4", 421 | ] 422 | 423 | [[package]] 424 | name = "glob" 425 | version = "0.3.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 428 | 429 | [[package]] 430 | name = "hashbrown" 431 | version = "0.15.4" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 434 | dependencies = [ 435 | "allocator-api2", 436 | "equivalent", 437 | "foldhash", 438 | ] 439 | 440 | [[package]] 441 | name = "heck" 442 | version = "0.5.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 445 | 446 | [[package]] 447 | name = "hound" 448 | version = "3.5.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" 451 | 452 | [[package]] 453 | name = "ident_case" 454 | version = "1.0.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 457 | 458 | [[package]] 459 | name = "indexmap" 460 | version = "2.10.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 463 | dependencies = [ 464 | "equivalent", 465 | "hashbrown", 466 | ] 467 | 468 | [[package]] 469 | name = "indoc" 470 | version = "2.0.6" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 473 | 474 | [[package]] 475 | name = "instability" 476 | version = "0.3.9" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" 479 | dependencies = [ 480 | "darling", 481 | "indoc", 482 | "proc-macro2", 483 | "quote", 484 | "syn", 485 | ] 486 | 487 | [[package]] 488 | name = "itertools" 489 | version = "0.13.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 492 | dependencies = [ 493 | "either", 494 | ] 495 | 496 | [[package]] 497 | name = "itoa" 498 | version = "1.0.15" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 501 | 502 | [[package]] 503 | name = "jni" 504 | version = "0.21.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 507 | dependencies = [ 508 | "cesu8", 509 | "cfg-if", 510 | "combine", 511 | "jni-sys", 512 | "log", 513 | "thiserror", 514 | "walkdir", 515 | "windows-sys 0.45.0", 516 | ] 517 | 518 | [[package]] 519 | name = "jni-sys" 520 | version = "0.3.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 523 | 524 | [[package]] 525 | name = "jobserver" 526 | version = "0.1.33" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 529 | dependencies = [ 530 | "getrandom", 531 | "libc", 532 | ] 533 | 534 | [[package]] 535 | name = "js-sys" 536 | version = "0.3.77" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 539 | dependencies = [ 540 | "once_cell", 541 | "wasm-bindgen", 542 | ] 543 | 544 | [[package]] 545 | name = "lazy_static" 546 | version = "1.5.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 549 | 550 | [[package]] 551 | name = "lewton" 552 | version = "0.10.2" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" 555 | dependencies = [ 556 | "byteorder", 557 | "ogg", 558 | "tinyvec", 559 | ] 560 | 561 | [[package]] 562 | name = "libc" 563 | version = "0.2.174" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 566 | 567 | [[package]] 568 | name = "libloading" 569 | version = "0.8.8" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 572 | dependencies = [ 573 | "cfg-if", 574 | "windows-targets 0.53.2", 575 | ] 576 | 577 | [[package]] 578 | name = "linux-raw-sys" 579 | version = "0.4.15" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 582 | 583 | [[package]] 584 | name = "linux-raw-sys" 585 | version = "0.9.4" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 588 | 589 | [[package]] 590 | name = "litrs" 591 | version = "0.4.2" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" 594 | 595 | [[package]] 596 | name = "lock_api" 597 | version = "0.4.13" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 600 | dependencies = [ 601 | "autocfg", 602 | "scopeguard", 603 | ] 604 | 605 | [[package]] 606 | name = "log" 607 | version = "0.4.27" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 610 | 611 | [[package]] 612 | name = "lru" 613 | version = "0.12.5" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 616 | dependencies = [ 617 | "hashbrown", 618 | ] 619 | 620 | [[package]] 621 | name = "mach2" 622 | version = "0.4.3" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" 625 | dependencies = [ 626 | "libc", 627 | ] 628 | 629 | [[package]] 630 | name = "memchr" 631 | version = "2.7.5" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 634 | 635 | [[package]] 636 | name = "minimal-lexical" 637 | version = "0.2.1" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 640 | 641 | [[package]] 642 | name = "mio" 643 | version = "1.0.4" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 646 | dependencies = [ 647 | "libc", 648 | "log", 649 | "wasi 0.11.1+wasi-snapshot-preview1", 650 | "windows-sys 0.59.0", 651 | ] 652 | 653 | [[package]] 654 | name = "musix" 655 | version = "0.1.0" 656 | dependencies = [ 657 | "crossterm 0.29.0", 658 | "ratatui", 659 | "rodio", 660 | ] 661 | 662 | [[package]] 663 | name = "ndk" 664 | version = "0.8.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" 667 | dependencies = [ 668 | "bitflags 2.9.1", 669 | "jni-sys", 670 | "log", 671 | "ndk-sys", 672 | "num_enum", 673 | "thiserror", 674 | ] 675 | 676 | [[package]] 677 | name = "ndk-context" 678 | version = "0.1.1" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 681 | 682 | [[package]] 683 | name = "ndk-sys" 684 | version = "0.5.0+25.2.9519653" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" 687 | dependencies = [ 688 | "jni-sys", 689 | ] 690 | 691 | [[package]] 692 | name = "nom" 693 | version = "7.1.3" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 696 | dependencies = [ 697 | "memchr", 698 | "minimal-lexical", 699 | ] 700 | 701 | [[package]] 702 | name = "num-derive" 703 | version = "0.4.2" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 706 | dependencies = [ 707 | "proc-macro2", 708 | "quote", 709 | "syn", 710 | ] 711 | 712 | [[package]] 713 | name = "num-traits" 714 | version = "0.2.19" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 717 | dependencies = [ 718 | "autocfg", 719 | ] 720 | 721 | [[package]] 722 | name = "num_enum" 723 | version = "0.7.4" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" 726 | dependencies = [ 727 | "num_enum_derive", 728 | "rustversion", 729 | ] 730 | 731 | [[package]] 732 | name = "num_enum_derive" 733 | version = "0.7.4" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" 736 | dependencies = [ 737 | "proc-macro-crate", 738 | "proc-macro2", 739 | "quote", 740 | "syn", 741 | ] 742 | 743 | [[package]] 744 | name = "oboe" 745 | version = "0.6.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" 748 | dependencies = [ 749 | "jni", 750 | "ndk", 751 | "ndk-context", 752 | "num-derive", 753 | "num-traits", 754 | "oboe-sys", 755 | ] 756 | 757 | [[package]] 758 | name = "oboe-sys" 759 | version = "0.6.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" 762 | dependencies = [ 763 | "cc", 764 | ] 765 | 766 | [[package]] 767 | name = "ogg" 768 | version = "0.8.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" 771 | dependencies = [ 772 | "byteorder", 773 | ] 774 | 775 | [[package]] 776 | name = "once_cell" 777 | version = "1.21.3" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 780 | 781 | [[package]] 782 | name = "parking_lot" 783 | version = "0.12.4" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 786 | dependencies = [ 787 | "lock_api", 788 | "parking_lot_core", 789 | ] 790 | 791 | [[package]] 792 | name = "parking_lot_core" 793 | version = "0.9.11" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 796 | dependencies = [ 797 | "cfg-if", 798 | "libc", 799 | "redox_syscall", 800 | "smallvec", 801 | "windows-targets 0.52.6", 802 | ] 803 | 804 | [[package]] 805 | name = "paste" 806 | version = "1.0.15" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 809 | 810 | [[package]] 811 | name = "pkg-config" 812 | version = "0.3.32" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 815 | 816 | [[package]] 817 | name = "proc-macro-crate" 818 | version = "3.3.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" 821 | dependencies = [ 822 | "toml_edit", 823 | ] 824 | 825 | [[package]] 826 | name = "proc-macro2" 827 | version = "1.0.95" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 830 | dependencies = [ 831 | "unicode-ident", 832 | ] 833 | 834 | [[package]] 835 | name = "quote" 836 | version = "1.0.40" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 839 | dependencies = [ 840 | "proc-macro2", 841 | ] 842 | 843 | [[package]] 844 | name = "r-efi" 845 | version = "5.3.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 848 | 849 | [[package]] 850 | name = "ratatui" 851 | version = "0.29.0" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 854 | dependencies = [ 855 | "bitflags 2.9.1", 856 | "cassowary", 857 | "compact_str", 858 | "crossterm 0.28.1", 859 | "indoc", 860 | "instability", 861 | "itertools", 862 | "lru", 863 | "paste", 864 | "strum", 865 | "unicode-segmentation", 866 | "unicode-truncate", 867 | "unicode-width 0.2.0", 868 | ] 869 | 870 | [[package]] 871 | name = "redox_syscall" 872 | version = "0.5.16" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" 875 | dependencies = [ 876 | "bitflags 2.9.1", 877 | ] 878 | 879 | [[package]] 880 | name = "regex" 881 | version = "1.11.1" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 884 | dependencies = [ 885 | "aho-corasick", 886 | "memchr", 887 | "regex-automata", 888 | "regex-syntax", 889 | ] 890 | 891 | [[package]] 892 | name = "regex-automata" 893 | version = "0.4.9" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 896 | dependencies = [ 897 | "aho-corasick", 898 | "memchr", 899 | "regex-syntax", 900 | ] 901 | 902 | [[package]] 903 | name = "regex-syntax" 904 | version = "0.8.5" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 907 | 908 | [[package]] 909 | name = "rodio" 910 | version = "0.20.1" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" 913 | dependencies = [ 914 | "claxon", 915 | "cpal", 916 | "hound", 917 | "lewton", 918 | "symphonia", 919 | ] 920 | 921 | [[package]] 922 | name = "rustc-hash" 923 | version = "2.1.1" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 926 | 927 | [[package]] 928 | name = "rustix" 929 | version = "0.38.44" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 932 | dependencies = [ 933 | "bitflags 2.9.1", 934 | "errno", 935 | "libc", 936 | "linux-raw-sys 0.4.15", 937 | "windows-sys 0.59.0", 938 | ] 939 | 940 | [[package]] 941 | name = "rustix" 942 | version = "1.0.8" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 945 | dependencies = [ 946 | "bitflags 2.9.1", 947 | "errno", 948 | "libc", 949 | "linux-raw-sys 0.9.4", 950 | "windows-sys 0.60.2", 951 | ] 952 | 953 | [[package]] 954 | name = "rustversion" 955 | version = "1.0.21" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 958 | 959 | [[package]] 960 | name = "ryu" 961 | version = "1.0.20" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 964 | 965 | [[package]] 966 | name = "same-file" 967 | version = "1.0.6" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 970 | dependencies = [ 971 | "winapi-util", 972 | ] 973 | 974 | [[package]] 975 | name = "scopeguard" 976 | version = "1.2.0" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 979 | 980 | [[package]] 981 | name = "shlex" 982 | version = "1.3.0" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 985 | 986 | [[package]] 987 | name = "signal-hook" 988 | version = "0.3.18" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 991 | dependencies = [ 992 | "libc", 993 | "signal-hook-registry", 994 | ] 995 | 996 | [[package]] 997 | name = "signal-hook-mio" 998 | version = "0.2.4" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1001 | dependencies = [ 1002 | "libc", 1003 | "mio", 1004 | "signal-hook", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "signal-hook-registry" 1009 | version = "1.4.5" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1012 | dependencies = [ 1013 | "libc", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "smallvec" 1018 | version = "1.15.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1021 | 1022 | [[package]] 1023 | name = "static_assertions" 1024 | version = "1.1.0" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1027 | 1028 | [[package]] 1029 | name = "strsim" 1030 | version = "0.11.1" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1033 | 1034 | [[package]] 1035 | name = "strum" 1036 | version = "0.26.3" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1039 | dependencies = [ 1040 | "strum_macros", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "strum_macros" 1045 | version = "0.26.4" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1048 | dependencies = [ 1049 | "heck", 1050 | "proc-macro2", 1051 | "quote", 1052 | "rustversion", 1053 | "syn", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "symphonia" 1058 | version = "0.5.4" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" 1061 | dependencies = [ 1062 | "lazy_static", 1063 | "symphonia-bundle-mp3", 1064 | "symphonia-core", 1065 | "symphonia-metadata", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "symphonia-bundle-mp3" 1070 | version = "0.5.4" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" 1073 | dependencies = [ 1074 | "lazy_static", 1075 | "log", 1076 | "symphonia-core", 1077 | "symphonia-metadata", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "symphonia-core" 1082 | version = "0.5.4" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" 1085 | dependencies = [ 1086 | "arrayvec", 1087 | "bitflags 1.3.2", 1088 | "bytemuck", 1089 | "lazy_static", 1090 | "log", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "symphonia-metadata" 1095 | version = "0.5.4" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" 1098 | dependencies = [ 1099 | "encoding_rs", 1100 | "lazy_static", 1101 | "log", 1102 | "symphonia-core", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "syn" 1107 | version = "2.0.104" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 1110 | dependencies = [ 1111 | "proc-macro2", 1112 | "quote", 1113 | "unicode-ident", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "thiserror" 1118 | version = "1.0.69" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1121 | dependencies = [ 1122 | "thiserror-impl", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "thiserror-impl" 1127 | version = "1.0.69" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1130 | dependencies = [ 1131 | "proc-macro2", 1132 | "quote", 1133 | "syn", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "tinyvec" 1138 | version = "1.9.0" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1141 | dependencies = [ 1142 | "tinyvec_macros", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "tinyvec_macros" 1147 | version = "0.1.1" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1150 | 1151 | [[package]] 1152 | name = "toml_datetime" 1153 | version = "0.6.11" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 1156 | 1157 | [[package]] 1158 | name = "toml_edit" 1159 | version = "0.22.27" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 1162 | dependencies = [ 1163 | "indexmap", 1164 | "toml_datetime", 1165 | "winnow", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "unicode-ident" 1170 | version = "1.0.18" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1173 | 1174 | [[package]] 1175 | name = "unicode-segmentation" 1176 | version = "1.12.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1179 | 1180 | [[package]] 1181 | name = "unicode-truncate" 1182 | version = "1.1.0" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1185 | dependencies = [ 1186 | "itertools", 1187 | "unicode-segmentation", 1188 | "unicode-width 0.1.14", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "unicode-width" 1193 | version = "0.1.14" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1196 | 1197 | [[package]] 1198 | name = "unicode-width" 1199 | version = "0.2.0" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1202 | 1203 | [[package]] 1204 | name = "walkdir" 1205 | version = "2.5.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1208 | dependencies = [ 1209 | "same-file", 1210 | "winapi-util", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "wasi" 1215 | version = "0.11.1+wasi-snapshot-preview1" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1218 | 1219 | [[package]] 1220 | name = "wasi" 1221 | version = "0.14.2+wasi-0.2.4" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1224 | dependencies = [ 1225 | "wit-bindgen-rt", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "wasm-bindgen" 1230 | version = "0.2.100" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1233 | dependencies = [ 1234 | "cfg-if", 1235 | "once_cell", 1236 | "rustversion", 1237 | "wasm-bindgen-macro", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "wasm-bindgen-backend" 1242 | version = "0.2.100" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1245 | dependencies = [ 1246 | "bumpalo", 1247 | "log", 1248 | "proc-macro2", 1249 | "quote", 1250 | "syn", 1251 | "wasm-bindgen-shared", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "wasm-bindgen-futures" 1256 | version = "0.4.50" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1259 | dependencies = [ 1260 | "cfg-if", 1261 | "js-sys", 1262 | "once_cell", 1263 | "wasm-bindgen", 1264 | "web-sys", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "wasm-bindgen-macro" 1269 | version = "0.2.100" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1272 | dependencies = [ 1273 | "quote", 1274 | "wasm-bindgen-macro-support", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "wasm-bindgen-macro-support" 1279 | version = "0.2.100" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1282 | dependencies = [ 1283 | "proc-macro2", 1284 | "quote", 1285 | "syn", 1286 | "wasm-bindgen-backend", 1287 | "wasm-bindgen-shared", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "wasm-bindgen-shared" 1292 | version = "0.2.100" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1295 | dependencies = [ 1296 | "unicode-ident", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "web-sys" 1301 | version = "0.3.77" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1304 | dependencies = [ 1305 | "js-sys", 1306 | "wasm-bindgen", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "winapi" 1311 | version = "0.3.9" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1314 | dependencies = [ 1315 | "winapi-i686-pc-windows-gnu", 1316 | "winapi-x86_64-pc-windows-gnu", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "winapi-i686-pc-windows-gnu" 1321 | version = "0.4.0" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1324 | 1325 | [[package]] 1326 | name = "winapi-util" 1327 | version = "0.1.9" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1330 | dependencies = [ 1331 | "windows-sys 0.59.0", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "winapi-x86_64-pc-windows-gnu" 1336 | version = "0.4.0" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1339 | 1340 | [[package]] 1341 | name = "windows" 1342 | version = "0.54.0" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" 1345 | dependencies = [ 1346 | "windows-core", 1347 | "windows-targets 0.52.6", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "windows-core" 1352 | version = "0.54.0" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" 1355 | dependencies = [ 1356 | "windows-result", 1357 | "windows-targets 0.52.6", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "windows-result" 1362 | version = "0.1.2" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" 1365 | dependencies = [ 1366 | "windows-targets 0.52.6", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "windows-sys" 1371 | version = "0.45.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1374 | dependencies = [ 1375 | "windows-targets 0.42.2", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "windows-sys" 1380 | version = "0.59.0" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1383 | dependencies = [ 1384 | "windows-targets 0.52.6", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "windows-sys" 1389 | version = "0.60.2" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1392 | dependencies = [ 1393 | "windows-targets 0.53.2", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "windows-targets" 1398 | version = "0.42.2" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1401 | dependencies = [ 1402 | "windows_aarch64_gnullvm 0.42.2", 1403 | "windows_aarch64_msvc 0.42.2", 1404 | "windows_i686_gnu 0.42.2", 1405 | "windows_i686_msvc 0.42.2", 1406 | "windows_x86_64_gnu 0.42.2", 1407 | "windows_x86_64_gnullvm 0.42.2", 1408 | "windows_x86_64_msvc 0.42.2", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "windows-targets" 1413 | version = "0.52.6" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1416 | dependencies = [ 1417 | "windows_aarch64_gnullvm 0.52.6", 1418 | "windows_aarch64_msvc 0.52.6", 1419 | "windows_i686_gnu 0.52.6", 1420 | "windows_i686_gnullvm 0.52.6", 1421 | "windows_i686_msvc 0.52.6", 1422 | "windows_x86_64_gnu 0.52.6", 1423 | "windows_x86_64_gnullvm 0.52.6", 1424 | "windows_x86_64_msvc 0.52.6", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "windows-targets" 1429 | version = "0.53.2" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" 1432 | dependencies = [ 1433 | "windows_aarch64_gnullvm 0.53.0", 1434 | "windows_aarch64_msvc 0.53.0", 1435 | "windows_i686_gnu 0.53.0", 1436 | "windows_i686_gnullvm 0.53.0", 1437 | "windows_i686_msvc 0.53.0", 1438 | "windows_x86_64_gnu 0.53.0", 1439 | "windows_x86_64_gnullvm 0.53.0", 1440 | "windows_x86_64_msvc 0.53.0", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "windows_aarch64_gnullvm" 1445 | version = "0.42.2" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1448 | 1449 | [[package]] 1450 | name = "windows_aarch64_gnullvm" 1451 | version = "0.52.6" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1454 | 1455 | [[package]] 1456 | name = "windows_aarch64_gnullvm" 1457 | version = "0.53.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1460 | 1461 | [[package]] 1462 | name = "windows_aarch64_msvc" 1463 | version = "0.42.2" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1466 | 1467 | [[package]] 1468 | name = "windows_aarch64_msvc" 1469 | version = "0.52.6" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1472 | 1473 | [[package]] 1474 | name = "windows_aarch64_msvc" 1475 | version = "0.53.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1478 | 1479 | [[package]] 1480 | name = "windows_i686_gnu" 1481 | version = "0.42.2" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1484 | 1485 | [[package]] 1486 | name = "windows_i686_gnu" 1487 | version = "0.52.6" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1490 | 1491 | [[package]] 1492 | name = "windows_i686_gnu" 1493 | version = "0.53.0" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1496 | 1497 | [[package]] 1498 | name = "windows_i686_gnullvm" 1499 | version = "0.52.6" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1502 | 1503 | [[package]] 1504 | name = "windows_i686_gnullvm" 1505 | version = "0.53.0" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1508 | 1509 | [[package]] 1510 | name = "windows_i686_msvc" 1511 | version = "0.42.2" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1514 | 1515 | [[package]] 1516 | name = "windows_i686_msvc" 1517 | version = "0.52.6" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1520 | 1521 | [[package]] 1522 | name = "windows_i686_msvc" 1523 | version = "0.53.0" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1526 | 1527 | [[package]] 1528 | name = "windows_x86_64_gnu" 1529 | version = "0.42.2" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1532 | 1533 | [[package]] 1534 | name = "windows_x86_64_gnu" 1535 | version = "0.52.6" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1538 | 1539 | [[package]] 1540 | name = "windows_x86_64_gnu" 1541 | version = "0.53.0" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1544 | 1545 | [[package]] 1546 | name = "windows_x86_64_gnullvm" 1547 | version = "0.42.2" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1550 | 1551 | [[package]] 1552 | name = "windows_x86_64_gnullvm" 1553 | version = "0.52.6" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1556 | 1557 | [[package]] 1558 | name = "windows_x86_64_gnullvm" 1559 | version = "0.53.0" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1562 | 1563 | [[package]] 1564 | name = "windows_x86_64_msvc" 1565 | version = "0.42.2" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1568 | 1569 | [[package]] 1570 | name = "windows_x86_64_msvc" 1571 | version = "0.52.6" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1574 | 1575 | [[package]] 1576 | name = "windows_x86_64_msvc" 1577 | version = "0.53.0" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1580 | 1581 | [[package]] 1582 | name = "winnow" 1583 | version = "0.7.12" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" 1586 | dependencies = [ 1587 | "memchr", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "wit-bindgen-rt" 1592 | version = "0.39.0" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1595 | dependencies = [ 1596 | "bitflags 2.9.1", 1597 | ] 1598 | --------------------------------------------------------------------------------