Skip to content

Commit

Permalink
0.4.0 - library manager
Browse files Browse the repository at this point in the history
  • Loading branch information
ronanru committed Aug 11, 2023
1 parent a411ff4 commit 829de97
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Get the AppImage [here](https://github.com/ronanru/ronix/releases/tag/v0.2.0)
- [x] Plays music (All traditional music player features)
- [x] Fuzzy search
- [x] Multiple themes
- [ ] Song manager (Ability to edit song tags, delete songs)
- [x] Song manager (Ability to edit song tags, delete songs)
- [x] Song downloader with yt-dlp
- [x] Publish on the AUR
- [ ] Publish on flathub
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ronix"
version = "0.3.0"
version = "0.4.0"
description = "Music Player and Library Manager"
authors = ["Matvey Ryabchikov"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn get_router() -> RouterBuilder<Context> {
.current_dir(&folders[0])
.status();
if !sacad.map(|s| s.success()).unwrap_or(false) {
return "Failed to convert video with sacad_r";
return "Failed to download cover art with sacad_r";
}
*ctx.library.lock().unwrap() = library::read_from_dirs(&folders);
"Download successful"
Expand Down
64 changes: 62 additions & 2 deletions src-tauri/src/library.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use crate::{Context, PlayerScope};
use fuse_rust::Fuse;
use lofty::{Accessor, AudioFile, MimeType, Probe, TaggedFileExt};
use lofty::{Accessor, AudioFile, MimeType, Probe, TagExt, TaggedFileExt};
use nanoid::nanoid;
use rand::prelude::*;
use rspc::{Router, RouterBuilder, Type};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs::{self, create_dir_all, File},
hash::Hash,
io::Write,
path::PathBuf,
process::Command,
};
use walkdir::WalkDir;

Expand Down Expand Up @@ -187,6 +188,14 @@ struct SearchResults {
songs: Vec<String>,
}

#[derive(Deserialize, Type)]
struct EditSongInput {
id: String,
title: String,
album: String,
artist: String,
}

pub fn get_router() -> RouterBuilder<Context> {
Router::<Context>::new()
.query("get", |t| {
Expand Down Expand Up @@ -243,4 +252,55 @@ pub fn get_router() -> RouterBuilder<Context> {
}
})
})
.mutation("editSong", |t| {
t(|ctx, input: EditSongInput| {
let mut library = ctx.library.lock().unwrap();
match library.songs.get(&input.id) {
Some(song) => {
match Probe::open(&song.path)
.ok()
.map(|f| f.read().ok())
.flatten()
{
Some(mut tagged_file) => {
let tags_option = match tagged_file.primary_tag_mut() {
Some(primary_tag) => Some(primary_tag),
None => tagged_file.first_tag_mut(),
};
match tags_option {
Some(tags) => {
tags.set_title(input.title);
tags.set_album(input.album);
tags.set_artist(input.artist);
for i in 0..tags.picture_count() {
tags.remove_picture(i as usize);
}
if tags.save_to_path(&song.path).is_err() {
return "Failed to edit song";
}
let sacad = Command::new("sacad_r")
.args(["-f", ".", "600", "+"])
.current_dir(&song.path.parent().unwrap())
.status();
if !sacad.map(|s| s.success()).unwrap_or(false) {
return "Failed to download cover art with sacad_r";
}
*library = read_from_dirs(&ctx.config.lock().unwrap().music_folders);
"Successfully edited"
}
None => "Could not edit song",
}
}
None => "Could not find song to edit",
}
}
None => "Could not find song to edit",
}
})
})
.mutation("refresh", |t| {
t(|ctx, _: ()| {
*ctx.library.lock().unwrap() = read_from_dirs(&ctx.config.lock().unwrap().music_folders);
})
})
}
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"package": {
"productName": "ronix",
"version": "0.3.0"
"version": "0.4.0"
},
"tauri": {
"allowlist": {
Expand Down
16 changes: 15 additions & 1 deletion src/components/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { api } from '@/api';
import { refetchLibrary } from '@/library';
import { navigate } from '@/router';
import { FolderIcon, MenuIcon, PlusIcon, SettingsIcon } from 'lucide-solid';
import {
FolderIcon,
MenuIcon,
PlusIcon,
RefreshCcwIcon,
SettingsIcon,
} from 'lucide-solid';
import {
For,
Show,
Expand Down Expand Up @@ -36,6 +44,12 @@ const Menu: Component<{
},
id: 'downloadSong',
},
{
icon: RefreshCcwIcon,
name: 'Refresh library',
onClick: () => api.mutation(['library.refresh']).then(refetchLibrary),
id: 'refreshLibrary',
},
{
icon: FolderIcon,
name: 'Library Manager',
Expand Down
30 changes: 30 additions & 0 deletions src/components/ui/textInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
ComponentProps,
createUniqueId,
splitProps,
type Component,
} from 'solid-js';

const TextInput: Component<
{
label: string;
} & ComponentProps<'input'>
> = (props) => {
const id = createUniqueId();

const [local, otherProps] = splitProps(props, ['label']);

return (
<div>
<label for={id}>{local.label}</label>
<input
class="mt-1 block w-full rounded-md bg-primary-800 px-2 py-1"
type="text"
id={id}
{...otherProps}
/>
</div>
);
};

export default TextInput;
12 changes: 8 additions & 4 deletions src/gen/tauri-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type Procedures = {
mutations:
{ key: "config.set", input: Config, result: null } |
{ key: "library.deleteSong", input: string, result: string } |
{ key: "library.editSong", input: EditSongInput, result: string } |
{ key: "library.refresh", input: never, result: null } |
{ key: "player.nextSong", input: never, result: null } |
{ key: "player.playSong", input: PlaySongInput, result: null } |
{ key: "player.previousSong", input: never, result: null } |
Expand All @@ -25,15 +27,17 @@ export type Procedures = {

export type RepeatMode = "None" | "One" | "All"

export type PlaySongInput = { song_id: string; scope: PlayerScope }
export type Song = { title: string; path: string; duration: number; album: string }

export type Artist = { name: string }
export type PlaySongInput = { song_id: string; scope: PlayerScope }

export type MainColor = "Slate" | "Gray" | "Zinc" | "Neutral" | "Stone"

export type SearchResults = { artists: string[]; albums: string[]; songs: string[] }

export type Library = { artists: { [key: string]: Artist }; albums: { [key: string]: Album }; songs: { [key: string]: Song } }

export type Song = { title: string; path: string; duration: number; album: string }
export type EditSongInput = { id: string; title: string; album: string; artist: string }

export type CurrentSongData = { current_song: string | null; song_started_at: number; paused_at: number | null; volume: number }

Expand All @@ -43,6 +47,6 @@ export type Config = { music_folders: string[]; dark_mode: boolean; main_color:

export type PlayerScope = "Library" | { Album: string } | { Artist: string }

export type SearchResults = { artists: string[]; albums: string[]; songs: string[] }
export type Artist = { name: string }

export type AccentColor = "Red" | "Orange" | "Amber" | "Yellow" | "Lime" | "Green" | "Emerald" | "Teal" | "Cyan" | "Blue" | "Indigo" | "Violet" | "Purple" | "Fuchsia" | "Pink" | "Rose"
12 changes: 9 additions & 3 deletions src/songButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PencilIcon, Trash2Icon } from 'lucide-solid';
import { Show, type Component } from 'solid-js';
import CoverArt from './components/coverArt';
import Button from './components/ui/button';
import { Trash2Icon } from 'lucide-solid';

const SongButton: Component<{
title: string;
Expand All @@ -13,6 +13,7 @@ const SongButton: Component<{
onClick?: (e: MouseEvent) => void;
isManager?: boolean;
onDelete?: () => void;
onEdit?: () => void;
}> = (props) => {
return (
<button
Expand All @@ -31,13 +32,18 @@ const SongButton: Component<{
<button class="block truncate">{props.artist}</button>
</Show>
</div>
<Show when={props.isManager}
<Show
when={props.isManager}
fallback={
<p class="flex-shrink-0">
{Math.floor(props.duration / 60)}:
{(props.duration % 60).toString().padStart(2, '0')}
</p>}
</p>
}
>
<Button variant="accent" size="icon" onClick={props.onEdit}>
<PencilIcon />
</Button>
<Button variant="danger" size="icon" onClick={props.onDelete}>
<Trash2Icon />
</Button>
Expand Down
Loading

0 comments on commit 829de97

Please sign in to comment.