This commit is contained in:
Deluan 2024-04-27 19:57:21 -04:00
parent e4c1b8c5e0
commit 03d1870b9f
5 changed files with 251 additions and 32 deletions

144
resources/mappings.yaml Normal file
View file

@ -0,0 +1,144 @@
# Adapted from https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
---
acoustid_id:
aliases: [ TXXX:Acoustid Id, ACOUSTID_ID, ----:com.apple.iTunes:Acoustid Id, Acoustid/Id ]
acoustid_fingerprint:
aliases: [ TXXX:Acoustid Fingerprint, ACOUSTID_FINGERPRINT, ----:com.apple.iTunes:Acoustid Fingerprint, Acoustid/Fingerprint ]
album:
aliases: [ TALB, ALBUM, ©alb, WM/AlbumTitle, IPRD ]
albumartist:
aliases: [ TPE2, ALBUMARTIST, Album Artist, aART, WM/AlbumArtist ]
albumartistsort:
aliases: [ TSO2, TXXX:ALBUMARTISTSORT, ALBUMARTISTSORT, soaa, WM/AlbumArtistSortOrder ]
albumsort:
aliases: [ TSOA, ALBUMSORT, soal, WM/AlbumSortOrder ]
arranger:
aliases: [ TIPL:arranger, IPLS:arranger, ARRANGER ]
artist:
aliases: [ TPE1, ARTIST, ©ART, Author, IART ]
artists:
aliases: [ TXXX:ARTISTS, ARTISTS, ----:com.apple.iTunes:ARTISTS, WM/ARTISTS ]
asin:
aliases: [ TXXX:ASIN, ASIN, ----:com.apple.iTunes:ASIN ]
barcode:
aliases: [ TXXX:BARCODE, BARCODE, ----:com.apple.iTunes:BARCODE, WM/Barcode ]
bpm:
aliases: [ TBPM, BPM, tmpo, WM/BeatsPerMinute ]
catalognumber:
aliases: [ TXXX:CATALOGNUMBER, CATALOGNUMBER, ----:com.apple.iTunes:CATALOGNUMBER, WM/CatalogNo ]
comment:
aliases: [ COMM:description, COMMENT, ©cmt, Description, ICMT ]
compilation:
aliases: [ TCMP, COMPILATION, cpil, WM/IsCompilation ]
composer:
aliases: [ TCOM, COMPOSER, ©wrt, WM/Composer, IMUS ]
composersort:
aliases: [ TSOC, TXXX:COMPOSERSORT, COMPOSERSORT, soco, WM/ComposerSortOrder ]
conductor:
aliases: [ TPE3, CONDUCTOR, ----:com.apple.iTunes:CONDUCTOR, WM/Conductor ]
copyright:
aliases: [ TCOP, COPYRIGHT, cprt, ICOP ]
director:
aliases: [ TXXX:DIRECTOR, DIRECTOR, ©dir, WM/Director ]
discnumber:
aliases: [ TPOS, DISCNUMBER, disk, WM/PartOfSet ]
split: totaldiscs
discsubtitle:
aliases: [ TSST, DISCSUBTITLE, ----:com.apple.iTunes:DISCSUBTITLE, WM/SetSubTitle ]
encodedby:
aliases: [ TENC, ENCODEDBY, ©too, WM/EncodedBy, IENC ]
encodersettings:
aliases: [ TSSE, ENCODERSETTINGS, ----:com.apple.iTunes:ENCODERSETTINGS, WM/EncodingSettings ]
engineer:
aliases: [ TIPL:engineer, IPLS:engineer, ENGINEER, ----:com.apple.iTunes:ENGINEER, WM/Engineer, IENG ]
gapless:
aliases: [ gapless, pgap ]
genre:
aliases: [ TCON, GENRE, ©gen, WM/Genre, IGNR ]
grouping:
aliases: [ GRP1, GROUPING, ©grp, WM/ContentGroupDescription ]
key:
aliases: [ TKEY, ----:com.apple.iTunes:initialkey, WM/InitialKey ]
isrc:
aliases: [ TSRC, ISRC, ----:com.apple.iTunes:ISRC, WM/ISRC ]
language:
aliases: [ TLAN, LANGUAGE, ----:com.apple.iTunes:LANGUAGE, WM/Language, ILNG ]
license:
aliases: [ WCOP, TXXX:LICENSE, LICENSE, ----:com.apple.iTunes:LICENSE ]
lyricist:
aliases: [ TEXT, LYRICIST, ----:com.apple.iTunes:LYRICIST, WM/Writer ]
lyrics:
aliases: [ USLT:description, LYRICS, ©lyr, WM/Lyrics ]
media:
aliases: [ TMED, MEDIA, ----:com.apple.iTunes:MEDIA, WM/Media, IMED ]
djmixer:
aliases: [ TIPL:DJ-mix, IPLS:DJ-mix, DJMIXER, ----:com.apple.iTunes:DJMIXER, WM/DJMixer ]
mixer:
aliases: [ TIPL:mix, IPLS:mix, MIXER, ----:com.apple.iTunes:MIXER, WM/Mixer ]
mood:
aliases: [ TMOO, MOOD, ----:com.apple.iTunes:MOOD, WM/Mood ]
movement:
aliases: [ MVNM, MOVEMENTNAME, ©mvn ]
movementtotal:
aliases: [ MOVEMENTTOTAL, mvc ]
movementnumber:
aliases: [ MVIN(1), MOVEMENT, mvi ]
musicbrainz_artistid:
aliases: [ TXXX:MusicBrainz Artist Id, MUSICBRAINZ_ARTISTID, MUSICBRAINZ ARTIST ID, ----:com.apple.iTunes:MusicBrainz Artist Id, MusicBrainz/Artist Id ]
musicbrainz_discid:
aliases: [ TXXX:MusicBrainz Disc Id, MUSICBRAINZ_DISCID, MUSICBRAINZ DISC ID, ----:com.apple.iTunes:MusicBrainz Disc Id, MusicBrainz/Disc Id ]
musicbrainz_recordingid:
aliases: [ UFID:http://musicbrainz.org, MUSICBRAINZ_TRACKID, MUSICBRAINZ TRACK ID, ----:com.apple.iTunes:MusicBrainz Track Id, MusicBrainz/Track Id ]
musicbrainz_albumartistid:
aliases: [ TXXX:MusicBrainz Album Artist Id, MUSICBRAINZ_ALBUMARTISTID, MUSICBRAINZ ALBUM ARTIST ID, ----:com.apple.iTunes:MusicBrainz Album Artist Id, MusicBrainz/Album Artist Id ]
musicbrainz_albumid:
aliases: [ TXXX:MusicBrainz Album Id, MUSICBRAINZ_ALBUMID, MUSICBRAINZ ALBUM ID, ----:com.apple.iTunes:MusicBrainz Album Id, MusicBrainz/Album Id ]
musicbrainz_workid:
aliases: [ TXXX:MusicBrainz Work Id, MUSICBRAINZ_WORKID, MUSICBRAINZ WORK ID, ----:com.apple.iTunes:MusicBrainz Work Id, MusicBrainz/Work Id ]
originaldate:
aliases: [ TDOR, TORY, ORIGINALDATE, ----:com.apple.iTunes:originaldate, WM/OriginalReleaseTime ]
originalyear:
aliases: [ ORIGINALYEAR, ----:com.apple.iTunes:originalyear, WM/OriginalReleaseYear ]
producer:
aliases: [ TIPL:producer, IPLS:producer, PRODUCER, ----:com.apple.iTunes:PRODUCER, WM/Producer, IPRO ]
label:
aliases: [ TPUB, LABEL, ----:com.apple.iTunes:LABEL, WM/Publisher ]
releasecountry:
aliases: [ TXXX:MusicBrainz Album Release Country, RELEASECOUNTRY, ----:com.apple.iTunes:MusicBrainz Album Release Country, MusicBrainz/Album Release Country, ICNT ]
releasedate:
aliases: [ TDRC, TYER, TDAT, DATE, Year, ©day, WM/Year, ICRD ]
releasestatus:
aliases: [ TXXX:MusicBrainz Album Status, RELEASESTATUS, MUSICBRAINZ_ALBUMSTATUS, ----:com.apple.iTunes:MusicBrainz Album Status, MusicBrainz/Album Status ]
releasetype:
aliases: [ TXXX:MusicBrainz Album Type, RELEASETYPE, MUSICBRAINZ_ALBUMTYPE, ----:com.apple.iTunes:MusicBrainz Album Type, MusicBrainz/Album Type ]
remixer:
aliases: [ TPE4, REMIXER, MixArtist, ----:com.apple.iTunes:REMIXER, WM/ModifiedBy ]
replaygain_album_gain:
aliases: [ TXXX:REPLAYGAIN_ALBUM_GAIN, REPLAYGAIN_ALBUM_GAIN, ----:com.apple.iTunes:REPLAYGAIN_ALBUM_GAIN ]
replaygain_album_peak:
aliases: [ TXXX:REPLAYGAIN_ALBUM_PEAK, REPLAYGAIN_ALBUM_PEAK, ----:com.apple.iTunes:REPLAYGAIN_ALBUM_PEAK ]
replaygain_track_gain:
aliases: [ TXXX:REPLAYGAIN_TRACK_GAIN, REPLAYGAIN_TRACK_GAIN, ----:com.apple.iTunes:REPLAYGAIN_TRACK_GAIN ]
replaygain_track_peak:
aliases: [ TXXX:REPLAYGAIN_TRACK_PEAK, REPLAYGAIN_TRACK_PEAK, ----:com.apple.iTunes:REPLAYGAIN_TRACK_PEAK ]
script:
aliases: [ TXXX:SCRIPT, SCRIPT, ----:com.apple.iTunes:SCRIPT, WM/Script ]
subtitle:
aliases: [ TIT3, SUBTITLE, ----:com.apple.iTunes:SUBTITLE, WM/SubTitle ]
totaldiscs:
aliases: [ DISCTOTAL, TOTALDISCS ]
totaltracks:
aliases: [ TRACKTOTAL, TOTALTRACKS ]
tracknumber:
aliases: [ TRCK, TRACKNUMBER, trkn, WM/TrackNumber, ITRK ]
split: totaltracks
title:
aliases: [ TIT2, TITLE, ©nam, INAM ]
titlesort:
aliases: [ TSOT, TITLESORT, sonm, WM/TitleSortOrder ]
website:
aliases: [ WOAR, WEBSITE, Weblink, WM/AuthorURL ]
work:
aliases: [ TXXX:WORK, TIT1, WORK, ©wrk, WM/Work ]
writer:
aliases: [ TXXX:Writer, WRITER, IWRI ]

View file

@ -53,8 +53,8 @@ func (e *Extractor) extractMetadata(filePath string) (metadata.ParsedTags, error
delete(tags, "lengthinmilliseconds")
}
// Adjust some ID3 tags
parseTIPL(tags)
delete(tags, "tmcl") // TMCL is already parsed by TagLib
//parseTIPL(tags)
//delete(tags, "tmcl") // TMCL is already parsed by TagLib
return tags, nil
}

View file

@ -115,39 +115,58 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) {
go_map_put_lyrics(id, language, val);
}
} else if (kv.first == "SYLT") {
for (const auto &tag: kv.second) {
TagLib::ID3v2::SynchronizedLyricsFrame *frame = dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame *>(tag);
if (frame == NULL) continue;
// } else if (kv.first == "SYLT") {
// for (const auto &tag: kv.second) {
// TagLib::ID3v2::SynchronizedLyricsFrame *frame = dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame *>(tag);
// if (frame == NULL) continue;
//
// const auto bv = frame->language();
// char language[4] = {'x', 'x', 'x', '\0'};
// if (bv.size() == 3) {
// strncpy(language, bv.data(), 3);
// }
//
// const auto format = frame->timestampFormat();
// if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds) {
//
// for (const auto &line: frame->synchedText()) {
// char *text = (char *)line.text.toCString(true);
// go_map_put_lyric_line(id, language, text, line.time);
// }
// } else if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames) {
// const int sampleRate = props->sampleRate();
//
// if (sampleRate != 0) {
// for (const auto &line: frame->synchedText()) {
// const int timeInMs = (line.time * 1000) / sampleRate;
// char *text = (char *)line.text.toCString(true);
// go_map_put_lyric_line(id, language, text, timeInMs);
// }
// }
// }
// }
const auto bv = frame->language();
char language[4] = {'x', 'x', 'x', '\0'};
if (bv.size() == 3) {
strncpy(language, bv.data(), 3);
}
const auto format = frame->timestampFormat();
if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds) {
for (const auto &line: frame->synchedText()) {
char *text = (char *)line.text.toCString(true);
go_map_put_lyric_line(id, language, text, line.time);
}
} else if (format == TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames) {
const int sampleRate = props->sampleRate();
if (sampleRate != 0) {
for (const auto &line: frame->synchedText()) {
const int timeInMs = (line.time * 1000) / sampleRate;
char *text = (char *)line.text.toCString(true);
go_map_put_lyric_line(id, language, text, timeInMs);
}
}
}
}
} else {
if (!kv.second.isEmpty()) {
tags.insert(kv.first, kv.second.front()->toString());
// tags.insert(kv.first, kv.second.front()->toString());
// char *key = ::strdup(kv.first.data());
// for (const auto &v: kv.second) {
// char *val = ::strdup(v->toString().toCString(true));
// go_map_put_str(id, key, val);
// free(val);
// }
// free(key);
char *key = ::strdup(kv.first.data());
for (const auto &second: kv.second) {
for (const auto &v: second->toStringList()) {
char *val = ::strdup(v.toCString(true));
go_map_put_str(id, key, val);
free(val);
}
}
free(key);
}
}
}

View file

@ -0,0 +1,55 @@
package metadata
import (
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/resources"
"gopkg.in/yaml.v3"
)
type mapping struct {
internalName string
split string
}
var mappings = map[string]mapping{}
// Load resources/metadata.yaml into memory
func Load() error {
yml, err := resources.FS().Open("mappings.yaml")
if err != nil {
return err
}
defer yml.Close()
data := map[string]struct {
Aliases []string `yaml:"aliases"`
Split string `yaml:"split"`
}{}
decoder := yaml.NewDecoder(yml)
err = decoder.Decode(data)
if err != nil {
return err
}
for k, v := range data {
internalName := strings.ToLower(k)
for _, alias := range v.Aliases {
alias = strings.ToLower(strings.TrimSpace(alias))
if old, ok := mappings[alias]; ok {
log.Warn("Duplicate alias in resources/mappings.yaml", "alias", alias, "internalName", internalName, "previousInternalName", old.internalName)
}
mappings[alias] = mapping{internalName, v.Split}
}
}
return nil
}
func init() {
conf.AddHook(func() {
if err := Load(); err != nil {
panic(err)
}
})
}

View file

@ -9,6 +9,7 @@ import (
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/scanner"
_ "github.com/navidrome/navidrome/scanner2/metadata"
)
type scanner2 struct {