[READ-ONLY] Mirror of https://github.com/probablykasper/mr-tagger. Music file tagging app for Mac, Linux and Windows
audio linux macos music tagger tauri windows
0

Configure Feed

Select the types of activity you want to include in your feed.

File opening ability

Kasper (Jul 28, 2021, 3:48 AM +0200) f25ea036 6dd83124

+170 -37
+29
src-tauri/Cargo.lock
··· 1118 1118 ] 1119 1119 1120 1120 [[package]] 1121 + name = "id3" 1122 + version = "0.6.4" 1123 + source = "registry+https://github.com/rust-lang/crates.io-index" 1124 + checksum = "bbe9b826aaa15b25bfb73c18ca9507a0ced87578a62376a7751dc6a510e3d244" 1125 + dependencies = [ 1126 + "bitflags", 1127 + "byteorder", 1128 + "flate2", 1129 + ] 1130 + 1131 + [[package]] 1121 1132 name = "ident_case" 1122 1133 version = "1.0.1" 1123 1134 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1368 1379 ] 1369 1380 1370 1381 [[package]] 1382 + name = "mp4ameta" 1383 + version = "0.11.0" 1384 + source = "registry+https://github.com/rust-lang/crates.io-index" 1385 + checksum = "eb23d62e8eb5299a3f79657c70ea9269eac8f6239a76952689bcd06a74057e81" 1386 + dependencies = [ 1387 + "lazy_static", 1388 + "mp4ameta_proc", 1389 + ] 1390 + 1391 + [[package]] 1392 + name = "mp4ameta_proc" 1393 + version = "0.6.0" 1394 + source = "registry+https://github.com/rust-lang/crates.io-index" 1395 + checksum = "07dcca13d1740c0a665f77104803360da0bdb3323ecce2e93fa2c959a6d52806" 1396 + 1397 + [[package]] 1371 1398 name = "mr-tagger" 1372 1399 version = "0.1.0" 1373 1400 dependencies = [ 1401 + "id3", 1402 + "mp4ameta", 1374 1403 "serde", 1375 1404 "serde_json", 1376 1405 "tauri",
+2 -1
src-tauri/Cargo.toml
··· 12 12 [dependencies] 13 13 serde_json = "1.0" 14 14 serde = { version = "1.0", features = ["derive"] } 15 - # tauri = { git = "https://github.com/tauri-apps/tauri", rev = "70a1941",features = ["dialog-open", "dialog-save", "menu", "shell-open"] } 16 15 tauri = { version = "1.0.0-beta.5", features = ["dialog-open", "dialog-save", "menu", "shell-open"] } 16 + id3 = "0.6.4" 17 + mp4ameta = "0.11.0" 17 18 18 19 [features] 19 20 default = [ "custom-protocol" ]
+85 -3
src-tauri/src/cmd.rs
··· 1 - use tauri::command; 1 + use serde::Serialize; 2 + use std::path::PathBuf; 3 + use std::sync::{Arc, Mutex}; 4 + use tauri::api::dialog; 5 + use tauri::{command, State}; 6 + 7 + use crate::throw; 8 + 9 + #[derive(Debug, Clone)] 10 + enum Metadata { 11 + Id3(id3::Tag), 12 + M4a(mp4ameta::Tag), 13 + } 14 + use Metadata::{Id3, M4a}; 15 + 16 + #[derive(Debug, Clone)] 17 + pub struct Item { 18 + path: PathBuf, 19 + metadata: Metadata, 20 + } 21 + 22 + #[derive(Debug, Default)] 23 + pub struct App { 24 + open_item: Option<Item>, 25 + } 26 + 27 + #[derive(Default)] 28 + pub struct Data(pub Arc<Mutex<App>>); 2 29 3 30 #[command] 4 - pub fn example() -> String { 5 - return "Hello world".to_string(); 31 + pub fn error_popup(msg: String) { 32 + println!("Error popup: {}", msg); 33 + dialog::message("Error", msg); 34 + } 35 + 36 + #[command] 37 + // pub async fn open(path: PathBuf, app: State<'_, Data>, win: Window) -> Result<(), String> { 38 + pub async fn open_dialog(app: State<'_, Data>) -> Result<Option<Info>, String> { 39 + let path = dialog::FileDialogBuilder::default() 40 + .add_filter("Audio file", &["mp3", "m4a", "wav", "aiff"]) 41 + .pick_file(); 42 + let path = match path { 43 + None => return Ok(None), 44 + Some(p) => p, 45 + }; 46 + return open(path, app).await; 47 + } 48 + 49 + #[derive(Serialize)] 50 + pub struct Info { 51 + path: PathBuf, 52 + title: String, 53 + } 54 + 55 + #[command] 56 + pub async fn open(path: PathBuf, app: State<'_, Data>) -> Result<Option<Info>, String> { 57 + let mut app = app.0.lock().unwrap(); 58 + let ext = path.extension().unwrap_or_default().to_string_lossy(); 59 + let metadata = match ext.as_ref() { 60 + "mp3" | "aiff" | "wav" => { 61 + let tag = match id3::Tag::read_from_path(&path) { 62 + Ok(tag) => tag, 63 + Err(_) => id3::Tag::new(), 64 + }; 65 + Metadata::Id3(tag) 66 + } 67 + "m4a" | "mp4" | "m4p" | "m4b" | "m4r" | "m4v" => { 68 + let tag = match mp4ameta::Tag::read_from_path(&path) { 69 + Ok(tag) => tag, 70 + Err(_) => throw!("No tags found"), 71 + }; 72 + Metadata::M4a(tag) 73 + } 74 + _ => throw!("Unsupported file type"), 75 + }; 76 + app.open_item = Some(Item { 77 + path: path.clone(), 78 + metadata: metadata.clone(), 79 + }); 80 + let info = Info { 81 + path: path, 82 + title: match metadata { 83 + Id3(tag) => tag.title().unwrap_or_default().to_string(), 84 + M4a(tag) => tag.title().unwrap_or_default().to_string(), 85 + }, 86 + }; 87 + Ok(Some(info)) 6 88 }
+15 -3
src-tauri/src/main.rs
··· 7 7 8 8 mod cmd; 9 9 10 + #[macro_export] 11 + macro_rules! throw { 12 + ($($arg:tt)*) => {{ 13 + return Err(format!($($arg)*)) 14 + }}; 15 + } 16 + 10 17 fn main() { 11 18 fn custom_menu(name: &str) -> CustomMenuItem { 12 19 let c = CustomMenuItem::new(name.to_string(), name); ··· 15 22 let menu = Menu::new() 16 23 .add_submenu(Submenu::new( 17 24 // on macOS first menu is always app name 18 - "Kryp", 25 + "Mr Tagger", 19 26 Menu::new() 20 - .add_native_item(MenuItem::About("Kryp".to_string())) 27 + .add_native_item(MenuItem::About("Mr Tagger".to_string())) 21 28 .add_native_item(MenuItem::Separator) 22 29 .add_native_item(MenuItem::Services) 23 30 .add_native_item(MenuItem::Separator) ··· 76 83 77 84 let ctx = tauri::generate_context!(); 78 85 tauri::Builder::default() 79 - .invoke_handler(tauri::generate_handler![cmd::example]) 86 + .invoke_handler(tauri::generate_handler![ 87 + cmd::open, 88 + cmd::open_dialog, 89 + cmd::error_popup 90 + ]) 80 91 .create_window("main", WindowUrl::default(), |win, webview| { 81 92 let win = win 82 93 .title("Mr Tagger") ··· 90 101 .fullscreen(false); 91 102 return (win, webview); 92 103 }) 104 + .manage(cmd::Data(Default::default())) 93 105 .menu(menu) 94 106 .on_menu_event(|event| match event.menu_item_id() { 95 107 "learn-more" => {
+1 -1
src-tauri/tauri.conf.json
··· 4 4 }, 5 5 "build": { 6 6 "distDir": "../build", 7 - "devPath": "http://localhost:6000", 7 + "devPath": "http://localhost:8000", 8 8 "beforeDevCommand": "npm run dev:web", 9 9 "beforeBuildCommand": "npm run build:web" 10 10 },
+18 -16
src/App.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount } from 'svelte' 3 - import Counter from './Counter.svelte' 4 - let count: number = 0 5 - onMount(() => { 6 - const interval = setInterval(() => count++, 1000) 7 - return () => clearInterval(interval) 8 - }) 2 + import { invoke } from '@tauri-apps/api' 3 + import { popup } from './scripts/helpers' 4 + import ItemView from './components/Item.svelte' 5 + import type { Item } from './components/Item.svelte' 6 + let item: Item | null = null 7 + async function open() { 8 + item = (await invoke('open_dialog').catch(popup)) as any 9 + } 9 10 </script> 10 11 11 12 <main> 12 - <h1>Hello world</h1> 13 - <p>Edit <code>src/Counter.svelte</code> to trigger HMR</p> 14 - <Counter {count} /> 13 + {#if item} 14 + <ItemView {item} /> 15 + {:else} 16 + <h1>Mr Tagger</h1> 17 + <button on:click={open}>Open</button> 18 + {/if} 15 19 </main> 16 20 17 21 <style lang="sass"> ··· 19 23 margin: 0 20 24 font-family: Arial, Helvetica, sans-serif 21 25 font-size: 18px 26 + background-color: #191B20 22 27 main 23 - text-align: center 24 - min-height: 100vh 25 28 display: flex 26 29 flex-direction: column 27 30 justify-content: center 28 - code 29 - background: #e6e6e6 30 - padding: 3px 6px 31 - border-radius: 4px 31 + align-items: center 32 + color: #e6e6e6 33 + min-height: 100vh 32 34 </style>
-12
src/Counter.svelte
··· 1 - <script lang="ts"> 2 - export let count: number 3 - </script> 4 - 5 - <p>Counter: <code>{count}</code></p> 6 - 7 - <style lang="sass"> 8 - code 9 - background: #e6e6e6 10 - padding: 3px 6px 11 - border-radius: 4px 12 - </style>
+14
src/components/Item.svelte
··· 1 + <script lang="ts" context="module"> 2 + export type Item = { 3 + path: string 4 + title: string 5 + } 6 + let x = 0 7 + </script> 8 + 9 + <script lang="ts"> 10 + export let item: Item 11 + </script> 12 + 13 + <p>path: {item.path}</p> 14 + <p>title: {item.title}</p>
+5
src/scripts/helpers.ts
··· 1 + import { invoke } from '@tauri-apps/api' 2 + 3 + export function popup(msg: string) { 4 + invoke('error_popup', { msg }) 5 + }
+1 -1
vite.config.js
··· 8 8 publicDir: '../public', 9 9 clearScreen: false, 10 10 server: { 11 - port: 4000, 11 + port: 8000, 12 12 strictPort: true, 13 13 }, 14 14 build: {