tranxuanthang / lrcget

Utility for mass-downloading LRC synced lyrics for your offline music library.
MIT License
832 stars 26 forks source link

Package as Web App along with official Docker builds #87

Open GlassedSilver opened 5 months ago

GlassedSilver commented 5 months ago

Hey :)

Firstly I want to applaud you for showing this much love for LRC and lyrics in general! I think it's also thanks to you, so many media players have picked up LRC support in recent times.

My request would be - since the app is built using web technology anyhow - to offer a web app native solution. I bet I'm not the only one managing their music collection centralized on their NAS but accessing it from different machines, all of which run widely different operating systems.

It'd be great to have equal access to the UI on any of those and to run the app headless in the background.

nothing2obvi commented 5 months ago

I'd like to echo this sentiment. I really want to use this software but can't run it on my Mac (Intel Mac Mini 2018, Sonoma 14.5). However, I'm very familiar with Docker. If this was provided as a Docker container that would be very greatly appreciated.

ShivamAmin commented 4 months ago

I'd really appreciate this as well. Having it run on my Unraid server would be ideal as all my music lives there.

I can try to make this happen, though I'm not familiar with Tauri yet.

arthurmelton commented 3 months ago

@ShivamAmin the main problem is that if you look in the source code, like even in src-tauri/src/main.rs, you can see all of the #[tauri::command], you would have to be able to convert all of those to api endpoints. To my knowledge tauri does not support api endpoints, so that means you would have to make a wrapper for #[tauri::command] and something like rocket. If you did this the fancy rust way you could make your own macro that would make a temporary version of the function and give 2 versions of this temporary function, the tauri and rocket version. Then for the frontend you would have to make a wrapper for the @tauri-apps commands, and importantly the invoke command that would allow you to use either the api endpoint or the tauri commands biased on what its ran on. All in all, it would honestly require a lot of work. To be fair though, I have not coded in tauri or rust in a while so I don't know if there is a better solution.

arthurmelton commented 3 months ago

I don't really have the time to code this but this is just how I would go about doing this. You can use some macros like this (It is actually not complete, as the async stuff does not work perfectly right now and the rocket url path does not work.)

#[macro_export]
macro_rules! new_command {
    ( $name:ident, $arguments:tt, $return:ty, $code:expr ) => {
        paste::paste! {
            #[derive(serde::Serialize, serde::Deserialize)]
            struct [<Struct $name:camel>] $arguments

            async fn [<common_ $name>](input: [<Struct $name:camel>], f: impl for<'a> Fn([<Struct $name:camel>]) -> futures::future::BoxFuture<'static, $return>) -> $return {
                f(input).await
            }

            #[tauri::command]
            async fn [<tauri_ $name>](input: [<Struct $name:camel>]) -> $return {
                return [<common_ $name>](input, $code.boxed()).await;
            }

            #[cfg(feature = "webui")]
            #[rocket::get("/$name", format = "json", data = "<input>")]
            async fn [<rocket_ $name>](input: rocket::serde::json::Json<[<Struct $name:camel>]>) -> rocket::serde::json::Json<$return> {
                return rocket::serde::json::Json([<common_ $name>](input.into_inner(), $code.boxed()).await);
            }
        }
    };
}

#[macro_export]
macro_rules! tauri_commands {
    ( $( $x:expr ),* ) => {
        paste::paste! {
            tauri::generate_handler![
                $(
                    [<tauri_ $x>],
                )*
            ]
        }
    };
}

#[macro_export]
macro_rules! rocket_commands {
    ( $( $x:ident ),* ) => {
        paste::paste! {
            rocket::routes![
                $(
                    [<rocket_ $x>],
                )*
            ]
        }
    };
}

to allow you to be able to create these temporary functions for both rocket and tauri. You can then convert what used to be defined as

#[tauri::command]
async fn set_directories(directories: Vec<String>, app_state: State<'_, AppState>) -> Result<(), String> {
  let conn_guard = app_state.db.lock().unwrap();
  let conn = conn_guard.as_ref().unwrap();
  db::set_directories(directories, conn).map_err(|err| err.to_string())?;

  Ok(())
}

to

new_command!(set_directories, {directories: Vec<String>}, Result<(), String>, |input| async move {
    let app_state = APP_STATE.lock().unwrap();
    let conn_guard = app_state.db.lock().unwrap();
    let conn = conn_guard.as_ref().unwrap();
    db::set_directories(input.directories, conn).map_err(|err| err.to_string())?;

    Ok(())
});

To be able to hold on the the AppState in a new way you can use once_cell like this


pub static APP_STATE: Lazy<Arc<Mutex<AppState>>> = Lazy::new(|| Arc::new(Mutex::new(AppState{
    db: Mutex::new(None),
    player: Mutex::new(None),
})));

You would have to write a wrapper for all of the tauri javascript libraries to use endpoints when used and you would have to code a smart invoke command that checks if window.__TAURI__ exists and if it does use the tauri logic, but if it does not, then use the end points. You would also have to convert app_handle.emit_all to use websocks when not in tauri.

All in all, it would honestly take a lot of work and some ugly code to get this to work sadly. The best solution would probably to be to probably just create a cli tool that just watches the directory and just creates the files automatically.

Just for anyone else who wants this feature just remember that tranxuanthang does not owe the feature, so if you really want it, either attempt to code the feature yourself or sponsor tranxuanthang. If someone wants to try and pickup where I left off this is the diff of what I got: changes.diff.

tranxuanthang commented 3 months ago

@arthurmelton thank you for the direction! I don't really prefer the macro approach, perhaps because I'm not very familiar with it.

I think one way we can address this is by creating an lrcget-core package that contains all of LRCGET's features without any Tauri dependencies, preferably using the workspace feature of Cargo. Afterward, we can create other workspaces that utilize the core package, such as lrcget-tauri, lrcget-api, lrcget-webui, and lrcget-cli.

This will require some refactoring of the Rust codebase, but it should not be too terribly difficult.

Unfortunately, I probably won't have enough time to work on this feature in the near future. If anyone wants to pick up this issue, it would be greatly appreciated!