Krolyxon
Commit cf9c69a

rmptuisrctags.rs

112 lines · 3.3 KB · Rust

1//! Reading and writing tags of local files for the tag editor.
2
3use std::{collections::BTreeMap, path::PathBuf};
4
5use lofty::{config::WriteOptions, prelude::*, tag::Tag};
6
7/// The tags the editor can change.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub enum TagField {
10 Title,
11 Artist,
12 AlbumArtist,
13 Album,
14 Track,
15 Disc,
16 Date,
17 Genre,
18 Comment,
19}
20
21impl TagField {
22 pub const ALL: [Self; 9] = [
23 Self::Title,
24 Self::Artist,
25 Self::AlbumArtist,
26 Self::Album,
27 Self::Track,
28 Self::Disc,
29 Self::Date,
30 Self::Genre,
31 Self::Comment,
32 ];
33
34 pub fn label(self) -> &'static str {
35 match self {
36 Self::Title => "Title",
37 Self::Artist => "Artist",
38 Self::AlbumArtist => "Album Artist",
39 Self::Album => "Album",
40 Self::Track => "Track",
41 Self::Disc => "Disc",
42 Self::Date => "Date",
43 Self::Genre => "Genre",
44 Self::Comment => "Comment",
45 }
46 }
47
48 /// The tag name MPD reports for this field.
49 pub fn mpd_tag(self) -> &'static str {
50 match self {
51 Self::Title => "Title",
52 Self::Artist => "Artist",
53 Self::AlbumArtist => "AlbumArtist",
54 Self::Album => "Album",
55 Self::Track => "Track",
56 Self::Disc => "Disc",
57 Self::Date => "Date",
58 Self::Genre => "Genre",
59 Self::Comment => "Comment",
60 }
61 }
62
63 fn item_key(self) -> ItemKey {
64 match self {
65 Self::Title => ItemKey::TrackTitle,
66 Self::Artist => ItemKey::TrackArtist,
67 Self::AlbumArtist => ItemKey::AlbumArtist,
68 Self::Album => ItemKey::AlbumTitle,
69 Self::Track => ItemKey::TrackNumber,
70 Self::Disc => ItemKey::DiscNumber,
71 Self::Date => ItemKey::RecordingDate,
72 Self::Genre => ItemKey::Genre,
73 Self::Comment => ItemKey::Comment,
74 }
75 }
76}
77
78/// Pending changes for one file. An empty value removes the tag.
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct TagWrite {
81 pub path: PathBuf,
82 /// Library-relative path, for reporting and rescanning.
83 pub uri: String,
84 pub changes: BTreeMap<TagField, String>,
85}
86
87/// Writes `changes` into the file, creating its primary tag if needed.
88pub fn write(write: &TagWrite) -> Result<(), String> {
89 let describe = |error: &dyn std::fmt::Display| format!("{}: {error}", write.uri);
90 let mut file = lofty::read_from_path(&write.path).map_err(|e| describe(&e))?;
91 if file.primary_tag().is_none() {
92 file.insert_tag(Tag::new(file.primary_tag_type()));
93 }
94 let tag = file
95 .primary_tag_mut()
96 .ok_or_else(|| format!("{}: cannot create a tag", write.uri))?;
97 for (field, value) in &write.changes {
98 let value = value.trim();
99 if value.is_empty() {
100 tag.remove_key(field.item_key());
101 } else if !tag.insert_text(field.item_key(), value.to_owned()) {
102 return Err(format!(
103 "{}: the {:?} tag cannot hold {}",
104 write.uri,
105 tag.tag_type(),
106 field.label()
107 ));
108 }
109 }
110 tag.save_to_path(&write.path, WriteOptions::default())
111 .map_err(|e| describe(&e))
112}