Compare commits
8 Commits
e6769164d6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 029e192f6a | |||
| 064b385687 | |||
| 81b4bed8eb | |||
| f0a35c1aa2 | |||
| cabcc5fd34 | |||
| dcd1a233e8 | |||
| 6cf0820a1f | |||
| 6b45477a9d |
41
README.md
41
README.md
@@ -3,8 +3,19 @@
|
|||||||
**a music manager to finally solve this huge mess of music management.**
|
**a music manager to finally solve this huge mess of music management.**
|
||||||
|
|
||||||
> ⚠️ **Timbre is in extreme alpha.** Right now it can create a library, import
|
> ⚠️ **Timbre is in extreme alpha.** Right now it can create a library, import
|
||||||
> and organize your music, scan it, and repair itself. Everything else, CD
|
> and organize your music, scan it, repair itself, and adopt (rip) CDs to
|
||||||
> ripping, automatic tagging, and more, is the planned future of the project.
|
> tagged, lossless FLAC with cover art. More is planned. It works, but expect
|
||||||
|
> rough edges and breaking changes, so use it on music you have backed up
|
||||||
|
> elsewhere.
|
||||||
|
|
||||||
|
## 📑 Contents
|
||||||
|
|
||||||
|
- [Info](#-info)
|
||||||
|
- [Installation](#-installation)
|
||||||
|
- [Usage](#️-usage)
|
||||||
|
- [Building](#️-building)
|
||||||
|
- [The Cat](#-the-cat)
|
||||||
|
- [License](#-license)
|
||||||
|
|
||||||
## 📋 Info
|
## 📋 Info
|
||||||
|
|
||||||
@@ -16,6 +27,32 @@ features in one app. Rip your CDs (or bring your own media files!), auto-organiz
|
|||||||
them, and play them back in your favorite music player, all with one person's
|
them, and play them back in your favorite music player, all with one person's
|
||||||
opinion instead of tons, seamlessly working, in a fast Rust app.
|
opinion instead of tons, seamlessly working, in a fast Rust app.
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
You will need the following commands on your system for adopting a CD:
|
||||||
|
- `cdparanoia`
|
||||||
|
- `flac`
|
||||||
|
|
||||||
|
Here is some ways to get them depending on your system:
|
||||||
|
|
||||||
|
Arch-based systems
|
||||||
|
```
|
||||||
|
sudo pacman -S cdparanoia flac
|
||||||
|
```
|
||||||
|
Debian-based systems
|
||||||
|
```
|
||||||
|
sudo apt install cdparanoia flac
|
||||||
|
```
|
||||||
|
Fedora-based systems
|
||||||
|
```
|
||||||
|
sudo dnf install cdparanoia flac
|
||||||
|
```
|
||||||
|
|
||||||
|
Most are the same, you should be able to figure it out.
|
||||||
|
Afterwards, download the binary from the latest release and put it somewhere on path, usually `~/.local/bin/`.
|
||||||
|
Then run timbre init and get started.
|
||||||
|
|
||||||
|
Timbre is also in the AUR as the package [`timbre-bin`](https://aur.archlinux.org/packages/timbre-bin).
|
||||||
|
|
||||||
## ⌨️ Usage
|
## ⌨️ Usage
|
||||||
|
|
||||||
- `timbre init` - give Timbre a home (`~/Music/Timbre/`)
|
- `timbre init` - give Timbre a home (`~/Music/Timbre/`)
|
||||||
|
|||||||
12
src/adopt.rs
12
src/adopt.rs
@@ -9,13 +9,12 @@ use std::{
|
|||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{import, musicbrainz::get_disc_info, tagging};
|
use crate::{coverartarchive::fetch_cover_art, import, musicbrainz::get_disc_info, tagging};
|
||||||
|
|
||||||
// adopt = rip a disc but ripping sounds like destroying so i'm not using it
|
// adopt = rip a disc but ripping sounds like destroying so i'm not using it
|
||||||
pub fn adopt(timbre_dir: &Path) {
|
pub fn adopt(timbre_dir: &Path) {
|
||||||
let disc = get_disc_info("http://catgpt-server:5000");
|
let disc = get_disc_info("https://musicbrainz.org");
|
||||||
let release = &disc.releases[0];
|
let release = &disc.releases[0];
|
||||||
println!("{}", release.id);
|
|
||||||
let tmp_dir = timbre_dir.join(format!(
|
let tmp_dir = timbre_dir.join(format!(
|
||||||
".tmp/{}",
|
".tmp/{}",
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
@@ -79,12 +78,12 @@ pub fn adopt(timbre_dir: &Path) {
|
|||||||
}
|
}
|
||||||
child.wait().expect("cdparanoia didn't finish cleanly :(\n");
|
child.wait().expect("cdparanoia didn't finish cleanly :(\n");
|
||||||
|
|
||||||
let flac_name = format!("track{track_num:02}.flac");
|
let wav_path = tmp_dir.join(format!("track{track_num:02}.wav"));
|
||||||
let flac_path = tmp_dir.join(&flac_name);
|
let flac_path = tmp_dir.join(format!("track{track_num:02}.flac"));
|
||||||
|
|
||||||
Command::new("flac")
|
Command::new("flac")
|
||||||
.arg("--best") // max compression, lossless
|
.arg("--best") // max compression, lossless
|
||||||
.arg(&flac_path) // input WAV
|
.arg(&wav_path) // input WAV
|
||||||
.output()
|
.output()
|
||||||
.expect("flac wouldn't encode :( is it installed?\n");
|
.expect("flac wouldn't encode :( is it installed?\n");
|
||||||
|
|
||||||
@@ -94,6 +93,7 @@ pub fn adopt(timbre_dir: &Path) {
|
|||||||
¤t_track.artist_credit[0].name,
|
¤t_track.artist_credit[0].name,
|
||||||
&release.title,
|
&release.title,
|
||||||
track_num as u32,
|
track_num as u32,
|
||||||
|
fetch_cover_art(&release.id).as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
import::import_file(&flac_path, timbre_dir);
|
import::import_file(&flac_path, timbre_dir);
|
||||||
|
|||||||
19
src/coverartarchive.rs
Normal file
19
src/coverartarchive.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
pub fn fetch_cover_art(release_id: &str) -> Option<Vec<u8>> {
|
||||||
|
let url = format!("https://coverartarchive.org/release/{}/front", release_id);
|
||||||
|
let response = ureq::get(&url)
|
||||||
|
.header("User-Agent", "Timbre/0.1 ( ...your repo... )")
|
||||||
|
.call();
|
||||||
|
match response {
|
||||||
|
Ok(resp) => {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
resp.into_body()
|
||||||
|
.into_reader()
|
||||||
|
.read_to_end(&mut bytes)
|
||||||
|
.expect("i couldn't read the cover art :(\n");
|
||||||
|
Some(bytes)
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main.rs
11
src/main.rs
@@ -1,10 +1,12 @@
|
|||||||
mod adopt;
|
mod adopt;
|
||||||
|
mod coverartarchive;
|
||||||
mod disc_id;
|
mod disc_id;
|
||||||
mod import;
|
mod import;
|
||||||
mod musicbrainz;
|
mod musicbrainz;
|
||||||
mod tagging;
|
mod tagging;
|
||||||
|
|
||||||
use clap::{
|
use clap::{
|
||||||
Parser, Subcommand,
|
Command, Parser, Subcommand,
|
||||||
builder::styling::{AnsiColor, Effects, Styles},
|
builder::styling::{AnsiColor, Effects, Styles},
|
||||||
};
|
};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -67,6 +69,8 @@ enum Commands {
|
|||||||
Repair,
|
Repair,
|
||||||
/// import music files
|
/// import music files
|
||||||
Import,
|
Import,
|
||||||
|
/// Adopt a CD, import it into your library, also getting metadata
|
||||||
|
Adopt,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_audio(path: &Path) -> bool {
|
fn is_audio(path: &Path) -> bool {
|
||||||
@@ -141,7 +145,7 @@ fn main() {
|
|||||||
let timbre_dir = Path::new(&std::env::var("HOME").expect("NERD!!!"))
|
let timbre_dir = Path::new(&std::env::var("HOME").expect("NERD!!!"))
|
||||||
.join("Music")
|
.join("Music")
|
||||||
.join("Timbre");
|
.join("Timbre");
|
||||||
adopt::adopt(&timbre_dir);
|
|
||||||
if cli.command != Commands::Init && cli.command != Commands::Repair {
|
if cli.command != Commands::Init && cli.command != Commands::Repair {
|
||||||
if timbre_dir.exists() {
|
if timbre_dir.exists() {
|
||||||
hashcat(&timbre_dir.join("cat_01.jpg")); // meow
|
hashcat(&timbre_dir.join("cat_01.jpg")); // meow
|
||||||
@@ -257,5 +261,8 @@ fn main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Commands::Adopt => {
|
||||||
|
adopt::adopt(&timbre_dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
use lofty::{config::WriteOptions, prelude::*, probe::Probe, tag::Tag};
|
use lofty::{
|
||||||
|
config::WriteOptions,
|
||||||
|
picture::{MimeType, Picture, PictureType},
|
||||||
|
prelude::*,
|
||||||
|
probe::Probe,
|
||||||
|
tag::Tag,
|
||||||
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn write_tags(path: &Path, title: &str, artist: &str, album: &str, track_num: u32) {
|
pub fn write_tags(
|
||||||
|
path: &Path,
|
||||||
|
title: &str,
|
||||||
|
artist: &str,
|
||||||
|
album: &str,
|
||||||
|
track_num: u32,
|
||||||
|
cover: Option<&[u8]>,
|
||||||
|
) {
|
||||||
let mut tagged_file = Probe::open(path)
|
let mut tagged_file = Probe::open(path)
|
||||||
.expect("couldn't open the flac :(\n")
|
.expect("couldn't open the flac :(\n")
|
||||||
.read()
|
.read()
|
||||||
@@ -24,6 +37,14 @@ pub fn write_tags(path: &Path, title: &str, artist: &str, album: &str, track_num
|
|||||||
tag.set_album(album.to_string());
|
tag.set_album(album.to_string());
|
||||||
tag.set_track(track_num);
|
tag.set_track(track_num);
|
||||||
|
|
||||||
|
if let Some(image_bytes) = cover {
|
||||||
|
let picture = Picture::unchecked(image_bytes.to_vec())
|
||||||
|
.pic_type(PictureType::CoverFront)
|
||||||
|
.mime_type(MimeType::Jpeg)
|
||||||
|
.build();
|
||||||
|
tag.set_picture(0, picture);
|
||||||
|
}
|
||||||
|
|
||||||
tag.save_to_path(path, WriteOptions::default())
|
tag.save_to_path(path, WriteOptions::default())
|
||||||
.expect("couldn't save the tags :(\n");
|
.expect("couldn't save the tags :(\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user