mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-03 12:37:37 +03:00
WIP
This commit is contained in:
parent
e4c1b8c5e0
commit
03d1870b9f
5 changed files with 251 additions and 32 deletions
144
resources/mappings.yaml
Normal file
144
resources/mappings.yaml
Normal 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 ]
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
55
scanner2/metadata/metadata.go
Normal file
55
scanner2/metadata/metadata.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue