Add few more fields to song information (#141)

* [meta] Add ignore paths to vscode settings

* [feature] Add few more fields to song information

Fields include lyricist, composer, genre, category
and label.
This commit is contained in:
pmphfm 2021-05-20 22:08:43 -07:00 committed by GitHub
parent 4c25195deb
commit f104355076
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 512 additions and 470 deletions

10
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
"files.watcherExclude": {
"**/target/**": true,
"**/test-output/**": true
},
"files.exclude": {
"**/target": true,
"**/test-output": true
}
}

836
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ lewton = "0.10.1"
log = "0.4.5"
metaflac = "0.2.3"
mp3-duration = "0.1.9"
mp4ameta = "0.7.1"
mp4ameta = "0.10.0"
num_cpus = "1.13.0"
opus_headers = "0.1.2"
percent-encoding = "2.1"
@ -52,7 +52,7 @@ url = "2.1"
[dependencies.diesel]
version = "1.4.5"
default_features = false
features = ["libsqlite3-sys", "r2d2", "sqlite"]
features = ["libsqlite3-sys", "r2d2", "sqlite", "64-column-tables"]
[dependencies.image]
version = "0.23.12"

View file

@ -1417,6 +1417,22 @@
"duration": {
"type": "integer",
"example": 571
},
"lyricist": {
"type": "string",
"example": "Timo Tolkki"
},
"composer": {
"type": "string",
"example": "Timo Tolkki"
},
"genre": {
"type": "string",
"example": "Genre"
},
"label": {
"type": "string",
"example": "Noise Records"
}
}
},
@ -1492,4 +1508,4 @@
"callbacks": {}
},
"security": []
}
}

View file

@ -0,0 +1,20 @@
CREATE TEMPORARY TABLE songs_backup(id, path, parent, track_number, disc_number, title, artist, album_artist, year, album, artwork, duration);
INSERT INTO songs_backup SELECT id, path, parent, track_number, disc_number, title, artist, album_artist, year, album, artwork, duration FROM songs;
DROP TABLE songs;
CREATE TABLE songs (
id INTEGER PRIMARY KEY NOT NULL,
path TEXT NOT NULL,
parent TEXT NOT NULL,
track_number INTEGER,
disc_number INTEGER,
title TEXT,
artist TEXT,
album_artist TEXT,
year INTEGER,
album TEXT,
artwork TEXT,
duration INTEGER,
UNIQUE(path) ON CONFLICT REPLACE
);
INSERT INTO songs SELECT * FROM songs_backup;
DROP TABLE songs_backup;

View file

@ -0,0 +1,4 @@
ALTER TABLE songs ADD COLUMN lyricist TEXT;
ALTER TABLE songs ADD COLUMN composer TEXT;
ALTER TABLE songs ADD COLUMN genre TEXT;
ALTER TABLE songs ADD COLUMN label TEXT;

View file

@ -25,6 +25,10 @@ pub struct SongTags {
pub album: Option<String>,
pub year: Option<i32>,
pub has_artwork: bool,
pub lyricist: Option<String>,
pub composer: Option<String>,
pub genre: Option<String>,
pub label: Option<String>,
}
impl From<id3::Tag> for SongTags {
@ -42,6 +46,10 @@ impl From<id3::Tag> for SongTags {
.or_else(|| tag.date_released().map(|d| d.year))
.or_else(|| tag.date_recorded().map(|d| d.year));
let has_artwork = tag.pictures().count() > 0;
let lyricist = tag.get_text("TEXT");
let composer = tag.get_text("TCOM");
let genre = tag.genre().map(|s| s.to_string());
let label = tag.get_text("TPUB");
SongTags {
artist,
@ -53,6 +61,10 @@ impl From<id3::Tag> for SongTags {
track_number,
year,
has_artwork,
lyricist,
composer,
genre,
label,
}
}
}
@ -80,6 +92,22 @@ pub fn read(path: &Path) -> Option<SongTags> {
}
}
trait FrameContent {
/// Returns the value stored, if any, in the Frame.
/// Say "TCOM" returns composer field.
fn get_text(&self, key: &str) -> Option<String>;
}
impl FrameContent for id3::Tag {
fn get_text(&self, key: &str) -> Option<String> {
let frame = self.get(key)?;
match frame.content() {
id3::Content::Text(value) => Some(value.to_string()),
_ => None,
}
}
}
fn read_mp3(path: &Path) -> Result<SongTags> {
let tag = id3::Tag::read_from_path(&path).or_else(|error| {
if let Some(tag) = error.partial_tag {
@ -159,9 +187,14 @@ fn read_ape(path: &Path) -> Result<SongTags> {
let year = tag.item("Year").and_then(read_ape_i32);
let disc_number = tag.item("Disc").and_then(read_ape_x_of_y);
let track_number = tag.item("Track").and_then(read_ape_x_of_y);
let lyricist = tag.item("LYRICIST").and_then(read_ape_string);
let composer = tag.item("COMPOSER").and_then(read_ape_string);
let genre = tag.item("GENRE").and_then(read_ape_string);
let label = tag.item("PUBLISHER").and_then(read_ape_string);
Ok(SongTags {
artist,
album_artist,
//
artist, //
album_artist, //
album,
title,
duration: None,
@ -169,6 +202,10 @@ fn read_ape(path: &Path) -> Result<SongTags> {
track_number,
year,
has_artwork: false,
lyricist,
composer,
genre,
label,
})
}
@ -186,6 +223,10 @@ fn read_vorbis(path: &Path) -> Result<SongTags> {
track_number: None,
year: None,
has_artwork: false,
lyricist: None,
composer: None,
genre: None,
label: None,
};
for (key, value) in source.comment_hdr.comment_list {
@ -198,6 +239,10 @@ fn read_vorbis(path: &Path) -> Result<SongTags> {
"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
"DATE" => tags.year = value.parse::<i32>().ok(),
"LYRICIST" => tags.lyricist = Some(value),
"COMPOSER" => tags.composer = Some(value),
"GENRE" => tags.genre = Some(value),
"PUBLISHER" => tags.label = Some(value),
_ => (),
}
}
@ -219,6 +264,10 @@ fn read_opus(path: &Path) -> Result<SongTags> {
track_number: None,
year: None,
has_artwork: false,
lyricist: None,
composer: None,
genre: None,
label: None,
};
for (key, value) in headers.comments.user_comments {
@ -231,6 +280,10 @@ fn read_opus(path: &Path) -> Result<SongTags> {
"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
"DATE" => tags.year = value.parse::<i32>().ok(),
"LYRICIST" => tags.lyricist = Some(value),
"COMPOSER" => tags.composer = Some(value),
"GENRE" => tags.genre = Some(value),
"PUBLISHER" => tags.label = Some(value),
_ => (),
}
}
@ -267,22 +320,31 @@ fn read_flac(path: &Path) -> Result<SongTags> {
track_number: vorbis.track(),
year,
has_artwork,
lyricist: vorbis.get("LYRICIST").map(|v| v[0].clone()),
composer: vorbis.get("COMPOSER").map(|v| v[0].clone()),
genre: vorbis.get("GENRE").map(|v| v[0].clone()),
label: vorbis.get("PUBLISHER").map(|v| v[0].clone()),
})
}
fn read_mp4(path: &Path) -> Result<SongTags> {
let mut tag = mp4ameta::Tag::read_from_path(path)?;
let label_ident = mp4ameta::FreeformIdent::new("com.apple.iTunes", "Label");
Ok(SongTags {
artist: tag.take_artist(),
album_artist: tag.take_album_artist(),
album: tag.take_album(),
title: tag.take_title(),
duration: tag.duration().map(|v| v as u32),
duration: tag.duration().map(|v| v.as_secs() as u32),
disc_number: tag.disc_number().map(|d| d as u32),
track_number: tag.track_number().map(|d| d as u32),
year: tag.year().and_then(|v| v.parse::<i32>().ok()),
has_artwork: tag.artwork().is_some(),
lyricist: tag.take_lyricist(),
composer: tag.take_composer(),
genre: tag.take_genre(),
label: tag.take_string(&label_ident).next(),
})
}
@ -298,6 +360,10 @@ fn reads_file_metadata() {
duration: None,
year: Some(2016),
has_artwork: false,
lyricist: Some("TEST LYRICIST".into()),
composer: Some("TEST COMPOSER".into()),
genre: Some("TEST GENRE".into()),
label: Some("TEST LABEL".into()),
};
let flac_sample_tag = SongTags {
duration: Some(0),

View file

@ -27,6 +27,10 @@ pub struct Song {
pub album: Option<String>,
pub artwork: Option<String>,
pub duration: Option<i32>,
pub lyricist: Option<String>,
pub composer: Option<String>,
pub genre: Option<String>,
pub label: Option<String>,
}
impl Song {

View file

@ -88,6 +88,10 @@ impl Collector {
album: tags.album,
year: tags.year,
artwork: artwork_path,
lyricist: tags.lyricist,
composer: tags.composer,
genre: tags.genre,
label: tags.label,
})) {
error!("Error while sending song from collector: {}", e);
}

View file

@ -22,6 +22,10 @@ pub struct Song {
pub album: Option<String>,
pub artwork: Option<String>,
pub duration: Option<i32>,
pub lyricist: Option<String>,
pub composer: Option<String>,
pub genre: Option<String>,
pub label: Option<String>,
}
#[derive(Debug, Insertable)]

View file

@ -164,7 +164,7 @@ impl Manager {
// Select songs. Not using Diesel because we need to LEFT JOIN using a custom column
let query = diesel::sql_query(
r#"
SELECT s.id, s.path, s.parent, s.track_number, s.disc_number, s.title, s.artist, s.album_artist, s.year, s.album, s.artwork, s.duration
SELECT s.id, s.path, s.parent, s.track_number, s.disc_number, s.title, s.artist, s.album_artist, s.year, s.album, s.artwork, s.duration, s.lyricist, s.composer, s.genre, s.label
FROM playlist_songs ps
LEFT JOIN songs s ON ps.path = s.path
WHERE ps.playlist = ?

View file

@ -68,6 +68,10 @@ table! {
album -> Nullable<Text>,
artwork -> Nullable<Text>,
duration -> Nullable<Integer>,
lyricist -> Nullable<Text>,
composer -> Nullable<Text>,
genre -> Nullable<Text>,
label -> Nullable<Text>,
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.