Merge branch 'master' into msi-insights-detection

This commit is contained in:
Deluan Quintão 2025-03-22 12:43:09 -04:00 committed by GitHub
commit d342a9a225
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 111 additions and 51 deletions

View File

@ -138,7 +138,6 @@ ENV GODEBUG="asyncpreemptoff=1"
RUN touch /.nddockerenv
EXPOSE ${ND_PORT}
HEALTHCHECK CMD wget -O- http://localhost:${ND_PORT}/ping || exit 1
WORKDIR /app
ENTRYPOINT ["/app/navidrome"]

View File

@ -29,7 +29,7 @@ func New() FFmpeg {
}
const (
extractImageCmd = "ffmpeg -i %s -an -vcodec copy -f image2pipe -"
extractImageCmd = "ffmpeg -i %s -map 0:v -map -0:V -vcodec copy -f image2pipe -"
probeCmd = "ffmpeg %s -f ffmetadata"
)

View File

@ -176,7 +176,11 @@ func (md Metadata) getRoleValues(role model.TagName) []string {
if len(values) == 0 {
return nil
}
if conf := model.TagRolesConf(); len(conf.Split) > 0 {
conf := model.TagMainMappings()[role]
if conf.Split == nil {
conf = model.TagRolesConf()
}
if len(conf.Split) > 0 {
values = conf.SplitTagValue(values)
return filterDuplicatedOrEmptyValues(values)
}
@ -193,7 +197,11 @@ func (md Metadata) getArtistValues(single, multi model.TagName) []string {
if len(vSingle) != 1 {
return vSingle
}
if conf := model.TagArtistsConf(); len(conf.Split) > 0 {
conf := model.TagMainMappings()[single]
if conf.Split == nil {
conf = model.TagArtistsConf()
}
if len(conf.Split) > 0 {
vSingle = conf.SplitTagValue(vSingle)
return filterDuplicatedOrEmptyValues(vSingle)
}

View File

@ -120,7 +120,7 @@ func (md Metadata) first(key model.TagName) string {
func float(value string, def ...float64) float64 {
v, err := strconv.ParseFloat(value, 64)
if err != nil || v == math.Inf(-1) || v == math.Inf(1) {
if err != nil || v == math.Inf(-1) || math.IsInf(v, 1) || math.IsNaN(v) {
if len(def) > 0 {
return def[0]
}

View File

@ -264,6 +264,7 @@ var _ = Describe("Metadata", func() {
Entry("1.2dB", "1.2dB", 1.2),
Entry("Infinity", "Infinity", 0.0),
Entry("Invalid value", "INVALID VALUE", 0.0),
Entry("NaN", "NaN", 0.0),
)
DescribeTable("Peak",
func(tagValue string, expected float64) {
@ -275,6 +276,7 @@ var _ = Describe("Metadata", func() {
Entry("Invalid dB suffix", "0.7dB", 1.0),
Entry("Infinity", "Infinity", 1.0),
Entry("Invalid value", "INVALID VALUE", 1.0),
Entry("NaN", "NaN", 1.0),
)
DescribeTable("getR128GainValue",
func(tagValue string, expected float64) {

View File

@ -201,7 +201,7 @@ func loadTagMappings() {
aliases = oldValue.Aliases
}
split := cfg.Split
if len(split) == 0 {
if split == nil {
split = oldValue.Split
}
c := TagConf{

View File

@ -216,6 +216,7 @@
"username": "Partekatzailea:",
"url": "URLa",
"description": "Deskribapena",
"downloadable": "Deskargatzea ahalbidetu?",
"contents": "Edukia",
"expiresAt": "Iraungitze-data:",
"lastVisitedAt": "Azkenekoz bisitatu zen:",
@ -223,22 +224,24 @@
"format": "Formatua",
"maxBitRate": "Gehienezko bit tasa",
"updatedAt": "Eguneratze-data:",
"createdAt": "Sortze-data:",
"downloadable": "Deskargatzea ahalbidetu?"
}
"createdAt": "Sortze-data:"
},
"notifications": {},
"actions": {}
},
"missing": {
"name": "",
"name": "Fitxategia falta da|||| Fitxategiak falta dira",
"empty": "Ez da fitxategirik falta",
"fields": {
"path": "",
"size": "",
"updatedAt": ""
"path": "Bidea",
"size": "Tamaina",
"updatedAt": "Desagertze-data:"
},
"actions": {
"remove": ""
"remove": "Kendu"
},
"notifications": {
"removed": ""
"removed": "Faltan zeuden fitxategiak kendu dira"
}
}
},
@ -509,4 +512,4 @@
"current_song": "Uneko abestia"
}
}
}
}

View File

@ -25,8 +25,13 @@
"quality": "Qualité",
"bpm": "BPM",
"playDate": "Derniers joués",
"channels": "Canaux",
"createdAt": "Date d'ajout"
"createdAt": "Date d'ajout",
"grouping": "Regroupement",
"mood": "Humeur",
"participants": "Participants supplémentaires",
"tags": "Étiquettes supplémentaires",
"mappedTags": "Étiquettes correspondantes",
"rawTags": "Étiquettes brutes"
},
"actions": {
"addToQueue": "Ajouter à la file",
@ -46,29 +51,35 @@
"duration": "Durée",
"songCount": "Nombre de pistes",
"playCount": "Nombre d'écoutes",
"size": "Taille",
"name": "Nom",
"genre": "Genre",
"compilation": "Compilation",
"year": "Année",
"originalDate": "Original",
"releaseDate": "Sortie",
"releases": "Sortie |||| Sorties",
"released": "Sortie",
"updatedAt": "Mis à jour le",
"comment": "Commentaire",
"rating": "Classement",
"createdAt": "Date d'ajout",
"size": "Taille",
"originalDate": "Original",
"releaseDate": "Sortie",
"releases": "Sortie |||| Sorties",
"released": "Sortie"
"recordLabel": "Label",
"catalogNum": "Numéro de catalogue",
"releaseType": "Type",
"grouping": "Regroupement",
"media": "Média",
"mood": "Humeur"
},
"actions": {
"playAll": "Lire",
"playNext": "Lire ensuite",
"addToQueue": "Ajouter à la file",
"share": "Partager",
"shuffle": "Mélanger",
"addToPlaylist": "Ajouter à la playlist",
"download": "Télécharger",
"info": "Plus d'informations",
"share": "Partager"
"info": "Plus d'informations"
},
"lists": {
"all": "Tous",
@ -86,10 +97,26 @@
"name": "Nom",
"albumCount": "Nombre d'albums",
"songCount": "Nombre de pistes",
"size": "Taille",
"playCount": "Lectures",
"rating": "Classement",
"genre": "Genre",
"size": "Taille"
"role": "Rôle"
},
"roles": {
"albumartist": "Artiste de l'album |||| Artistes de l'album",
"artist": "Artiste |||| Artistes",
"composer": "Compositeur |||| Compositeurs",
"conductor": "Chef d'orchestre |||| Chefs d'orchestre",
"lyricist": "Parolier |||| Paroliers",
"arranger": "Arrangeur |||| Arrangeurs",
"producer": "Producteur |||| Producteurs",
"director": "Réalisateur |||| Réalisateurs",
"engineer": "Ingénieur |||| Ingénieurs",
"mixer": "Mixeur |||| Mixeurs",
"remixer": "Remixeur |||| Remixeurs",
"djmixer": "Mixeur DJ |||| Mixeurs DJ",
"performer": "Interprète |||| Interprètes"
}
},
"user": {
@ -98,6 +125,7 @@
"userName": "Nom d'utilisateur",
"isAdmin": "Administrateur",
"lastLoginAt": "Dernière connexion",
"lastAccessAt": "Dernier accès",
"updatedAt": "Dernière mise à jour",
"name": "Nom",
"password": "Mot de passe",
@ -105,8 +133,7 @@
"changePassword": "Changer le mot de passe ?",
"currentPassword": "Mot de passe actuel",
"newPassword": "Nouveau mot de passe",
"token": "Token",
"lastAccessAt": "Dernier accès"
"token": "Token"
},
"helperTexts": {
"name": "Les changements liés à votre nom ne seront reflétés qu'à la prochaine connexion"
@ -152,7 +179,7 @@
"public": "Publique",
"updatedAt": "Mise à jour le",
"createdAt": "Créée le",
"songCount": "Titres",
"songCount": "Morceaux",
"comment": "Commentaire",
"sync": "Import automatique",
"path": "Importer depuis"
@ -188,6 +215,7 @@
"username": "Partagé(e) par",
"url": "Lien URL",
"description": "Description",
"downloadable": "Autoriser les téléchargements ?",
"contents": "Contenu",
"expiresAt": "Expire le",
"lastVisitedAt": "Visité pour la dernière fois",
@ -195,8 +223,24 @@
"format": "Format",
"maxBitRate": "Bitrate maximum",
"updatedAt": "Mis à jour le",
"createdAt": "Créé le",
"downloadable": "Autoriser les téléchargements ?"
"createdAt": "Créé le"
},
"notifications": {},
"actions": {}
},
"missing": {
"name": "Fichier manquant|||| Fichiers manquants",
"empty": "Aucun fichier manquant",
"fields": {
"path": "Chemin",
"size": "Taille",
"updatedAt": "A disparu le"
},
"actions": {
"remove": "Supprimer"
},
"notifications": {
"removed": "Fichier(s) manquant(s) supprimé(s)"
}
}
},
@ -235,6 +279,7 @@
"add": "Ajouter",
"back": "Retour",
"bulk_actions": "%{smart_count} sélectionné |||| %{smart_count} sélectionnés",
"bulk_actions_mobile": "1 |||| %{smart_count}",
"cancel": "Annuler",
"clear_input_value": "Vider le champ",
"clone": "Dupliquer",
@ -258,7 +303,6 @@
"close_menu": "Fermer le menu",
"unselect": "Désélectionner",
"skip": "Ignorer",
"bulk_actions_mobile": "1 |||| %{smart_count}",
"share": "Partager",
"download": "Télécharger"
},
@ -273,10 +317,10 @@
"error": "Un problème est survenu",
"list": "%{name}",
"loading": "Chargement",
"not_found": "Page manquante",
"not_found": "Introuvable",
"show": "%{name} #%{id}",
"empty": "Pas encore de %{name}.",
"invite": "Voulez-vous en créer ?"
"invite": "Voulez-vous en créer un ?"
},
"input": {
"file": {
@ -353,29 +397,31 @@
"noPlaylistsAvailable": "Aucune playlist",
"delete_user_title": "Supprimer l'utilisateur '%{name}'",
"delete_user_content": "Êtes-vous sûr(e) de vouloir supprimer cet utilisateur et ses données associées (y compris ses playlists et préférences) ?",
"remove_missing_title": "Supprimer les fichiers manquants",
"remove_missing_content": "Êtes-vous sûr(e) de vouloir supprimer les fichiers manquants sélectionnés de la base de données ? Ceci supprimera définiviement toute référence à ceux-ci, y compris leurs nombres d'écoutes et leurs notations.",
"notifications_blocked": "Votre navigateur bloque les notifications de ce site",
"notifications_not_available": "Votre navigateur ne permet pas d'afficher les notifications sur le bureau ou vous n'accédez pas à Navidrome via HTTPS",
"lastfmLinkSuccess": "Last.fm a été correctement relié et le scrobble a été activé",
"lastfmLinkFailure": "Last.fm n'a pas pu être correctement relié",
"lastfmUnlinkSuccess": "Last.fm n'est plus relié et le scrobble a été désactivé",
"lastfmUnlinkFailure": "Erreur pendant la suppression du lien avec Last.fm",
"listenBrainzLinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant activés pour l'utilisateur : %{user}",
"listenBrainzLinkFailure": "Échec lors de la liaison avec ListenBrainz : %{error}",
"listenBrainzUnlinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant désactivés",
"listenBrainzUnlinkFailure": "Échec lors de la désactivation de la liaison avec ListenBrainz",
"openIn": {
"lastfm": "Ouvrir dans Last.fm",
"musicbrainz": "Ouvrir dans MusicBrainz"
},
"lastfmLink": "Lire plus...",
"listenBrainzLinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant activés pour l'utilisateur : %{user}",
"listenBrainzLinkFailure": "Échec lors de la liaison avec ListenBrainz : %{error}",
"listenBrainzUnlinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant désactivés",
"listenBrainzUnlinkFailure": "Échec lors de la désactivation de la liaison avec ListenBrainz",
"downloadOriginalFormat": "Télécharger au format original",
"shareOriginalFormat": "Partager avec le format original",
"shareDialogTitle": "Partager %{resource} '%{name}'",
"shareBatchDialogTitle": "Partager 1 %{resource} |||| Partager %{smart_count} %{resource}",
"shareCopyToClipboard": "Copier vers le presse-papier : Ctrl+C, Enter",
"shareSuccess": "Lien copié vers le presse-papier : %{url}",
"shareFailure": "Erreur en copiant le lien %{url} vers le presse-papier",
"downloadDialogTitle": "Télécharger %{resource} '%{name}' (%{size})",
"shareCopyToClipboard": "Copier vers le presse-papier : Ctrl+C, Enter"
"downloadOriginalFormat": "Télécharger au format original"
},
"menu": {
"library": "Bibliothèque",
@ -389,6 +435,7 @@
"language": "Langue",
"defaultView": "Vue par défaut",
"desktop_notifications": "Notifications de bureau",
"lastfmNotConfigured": "La clef API de Last.fm n'est pas configurée",
"lastfmScrobbling": "Scrobbler vers Last.fm",
"listenBrainzScrobbling": "Scrobbler vers ListenBrainz",
"replaygain": "Mode ReplayGain",
@ -397,14 +444,13 @@
"none": "Désactivé",
"album": "Utiliser le gain de l'album",
"track": "Utiliser le gain des pistes"
},
"lastfmNotConfigured": "La clef API de Last.fm n'est pas configurée"
}
}
},
"albumList": "Albums",
"about": "À propos",
"playlists": "Playlists",
"sharedPlaylists": "Playlists partagées"
"sharedPlaylists": "Playlists partagées",
"about": "À propos"
},
"player": {
"playListsText": "File de lecture",
@ -459,10 +505,10 @@
"toggle_play": "Lecture/Pause",
"prev_song": "Morceau précédent",
"next_song": "Morceau suivant",
"current_song": "Aller à la chanson en cours",
"vol_up": "Augmenter le volume",
"vol_down": "Baisser le volume",
"toggle_love": "Ajouter/Enlever le morceau des favoris",
"current_song": "Aller à la chanson en cours"
"toggle_love": "Ajouter/Enlever le morceau des favoris"
}
}
}
}

View File

@ -33,6 +33,8 @@ func (s *scannerExternal) scanAll(ctx context.Context, fullScan bool, progress c
cmd := exec.CommandContext(ctx, exe, "scan",
"--nobanner", "--subprocess",
"--configfile", conf.Server.ConfigFile,
"--datafolder", conf.Server.DataFolder,
"--cachefolder", conf.Server.CacheFolder,
If(fullScan, "--full", ""))
in, out := io.Pipe()

View File

@ -424,7 +424,7 @@ func (api *Router) buildArtist(r *http.Request, artist *model.Artist) (*response
return nil, err
}
a.Album = slice.MapWithArg(albums, ctx, childFromAlbum)
a.Album = slice.MapWithArg(albums, ctx, buildAlbumID3)
return a, nil
}

View File

@ -48,11 +48,11 @@ func AlbumsByArtist() Options {
func AlbumsByArtistID(artistId string) Options {
filters := []Sqlizer{
persistence.Exists("json_tree(Participants, '$.albumartist')", Eq{"value": artistId}),
persistence.Exists("json_tree(participants, '$.albumartist')", Eq{"value": artistId}),
}
if conf.Server.Subsonic.ArtistParticipations {
filters = append(filters,
persistence.Exists("json_tree(Participants, '$.artist')", Eq{"value": artistId}),
persistence.Exists("json_tree(participants, '$.artist')", Eq{"value": artistId}),
)
}
return addDefaultFilters(Options{

View File

@ -284,7 +284,7 @@ type OpenSubsonicAlbumID3 struct {
type ArtistWithAlbumsID3 struct {
ArtistID3
Album []Child `xml:"album" json:"album,omitempty"`
Album []AlbumID3 `xml:"album" json:"album,omitempty"`
}
type AlbumWithSongsID3 struct {