feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709)

* fix(server): more race conditions when updating artist/album from external sources

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scanner): add .gitignore syntax to .ndignore. Resolves #1394

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): null

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): pass configfile option to child process

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): resume interrupted fullScans

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): remove old scanner code

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): rename old metadata package

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): move old metadata package

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: tests

Signed-off-by: Deluan <deluan@navidrome.org>

* chore(deps): update Go to 1.23.4

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: logs

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(test):

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: log level

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: remove log message

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add config for scanner watcher

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: children playlists

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: replace `interface{}` with `any`

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: smart playlists with genres

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: allow any tags in smart playlists

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: artist names in playlists

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: smart playlist's sort by tags

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add moods to child

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add moods to AlbumID3

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(subsonic): use generic JSONArray for OS arrays

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(subsonic): use https in test

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add releaseTypes to AlbumID3

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add recordLabels to AlbumID3

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(subsonic): rename JSONArray to Array

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add artists to AlbumID3

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add artists to Child

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(scanner): do not pre-populate smart playlists

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): implement a simplified version of ArtistID3.

See https://github.com/opensubsonic/open-subsonic-api/discussions/120

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add artists to album child

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add contributors to mediafile Child

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add albumArtists to mediafile Child

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add displayArtist and displayAlbumArtist

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add displayComposer to Child

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add roles to ArtistID3

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(subsonic): use " • " separator for displayComposer

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor:

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(subsonic):

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(subsonic): respect `PreferSortTags` config option

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(subsonic):

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: optimize purging non-unused tags

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: don't run 'refresh artist stats' concurrently with other transactions

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor:

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: log message

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Scanner.ScanOnStartup config option, default true

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: better json parsing error msg when importing NSPs

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't update album's imported_time when updating external_metadata

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: handle interrupted scans and full scans after migrations

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: run `analyze` when migration requires a full rescan

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: run `PRAGMA optimize` at the end of the scan

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't update artist's updated_at when updating external_metadata

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: handle multiple artists and roles in smart playlists

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): dim missing tracks

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: album missing logic

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: error encoding in gob

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: separate warnings from errors

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: mark albums as missing if they were contained in a deleted folder

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: add participant names to media_file and album tables

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: use participations in criteria, instead of m2m relationship

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: rename participations to participants

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add moods to album child

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: albumartist role case

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scanner): run scanner as an external process by default

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): show albumArtist names

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): dim out missing albums

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: flaky test

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(server): scrobble buffer mapping. fix #3583

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: more participations renaming

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: listenbrainz scrobbling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: send release_group_mbid to listenbrainz

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): implement OpenSubsonic explicitStatus field (#3597)

* feat: implement OpenSubsonic explicitStatus field

* fix(subsonic): fix failing snapshot tests

* refactor: create helper for setting explicitStatus

* fix: store smaller values for explicit-status on database

* test: ToAlbum explicitStatus

* refactor: rename explicitStatus helper function

---------

Co-authored-by: Deluan Quintão <deluan@navidrome.org>

* fix: handle album and track tags in the DB based on the mappings.yaml file

Signed-off-by: Deluan <deluan@navidrome.org>

* save similar artists as JSONB

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: getAlbumList byGenre

Signed-off-by: Deluan <deluan@navidrome.org>

* detect changes in PID configuration

Signed-off-by: Deluan <deluan@navidrome.org>

* set default album PID to legacy_pid

Signed-off-by: Deluan <deluan@navidrome.org>

* fix tests

Signed-off-by: Deluan <deluan@navidrome.org>

* fix SIGSEGV

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't lose album stars/ratings when migrating

Signed-off-by: Deluan <deluan@navidrome.org>

* store full PID conf in properties

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: keep album annotations when changing PID.Album config

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: reassign album annotations

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: use (display) albumArtist and add links to each artist

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: not showing albums by albumartist

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: error msgs

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: hide PID from Native API

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: album cover art resolution

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: trim participant names

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: reduce watcher log spam

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: panic when initializing the watcher

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: various artists

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't store empty lyrics in the DB

Signed-off-by: Deluan <deluan@navidrome.org>

* remove unused methods

Signed-off-by: Deluan <deluan@navidrome.org>

* drop full_text indexes, as they are not being used by SQLite

Signed-off-by: Deluan <deluan@navidrome.org>

* keep album created_at when upgrading

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): null pointer

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: album artwork cache

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't expose missing files in Subsonic API

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: searchable interface

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: filter out missing items from subsonic search

* fix: filter out missing items from playlists

* fix: filter out missing items from shares

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): add filter by artist role

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): only return albumartists in getIndexes and getArtists endpoints

Signed-off-by: Deluan <deluan@navidrome.org>

* sort roles alphabetically

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: artist playcounts

Signed-off-by: Deluan <deluan@navidrome.org>

* change default Album PID conf

Signed-off-by: Deluan <deluan@navidrome.org>

* fix albumartist link when it does not match any albumartists values

Signed-off-by: Deluan <deluan@navidrome.org>

* fix `Ignoring filter not whitelisted` (role) message

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: trim any names/titles being imported

Signed-off-by: Deluan <deluan@navidrome.org>

* remove unused genre code

Signed-off-by: Deluan <deluan@navidrome.org>

* serialize calls to Last.fm's getArtist

Signed-off-by: Deluan <deluan@navidrome.org>

xxx

Signed-off-by: Deluan <deluan@navidrome.org>

* add counters to genres

Signed-off-by: Deluan <deluan@navidrome.org>

* nit: fix migration `notice` message

Signed-off-by: Deluan <deluan@navidrome.org>

* optimize similar artists query

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: last.fm.getInfo when mbid does not exist

Signed-off-by: Deluan <deluan@navidrome.org>

* ui only show missing items for admins

Signed-off-by: Deluan <deluan@navidrome.org>

* don't allow interaction with missing items

Signed-off-by: Deluan <deluan@navidrome.org>

* Add Missing Files view (WIP)

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: merged tag_counts into tag table

Signed-off-by: Deluan <deluan@navidrome.org>

* add option to completely disable automatic scanner

Signed-off-by: Deluan <deluan@navidrome.org>

* add delete missing files functionality

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: playlists not showing for regular users

Signed-off-by: Deluan <deluan@navidrome.org>

* reduce updateLastAccess frequency to once every minute

Signed-off-by: Deluan <deluan@navidrome.org>

* reduce update player frequency to once every minute

Signed-off-by: Deluan <deluan@navidrome.org>

* add timeout when updating player

Signed-off-by: Deluan <deluan@navidrome.org>

* remove dead code

Signed-off-by: Deluan <deluan@navidrome.org>

* fix duplicated roles in stats

Signed-off-by: Deluan <deluan@navidrome.org>

* add `; ` to artist splitters

Signed-off-by: Deluan <deluan@navidrome.org>

* fix stats query

Signed-off-by: Deluan <deluan@navidrome.org>

* more logs

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: support legacy clients (DSub) by removing OpenSubsonic extra fields - WIP

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: support legacy clients (DSub) by removing OpenSubsonic extra fields - WIP

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: support legacy clients (DSub) by removing OpenSubsonic extra fields - WIP

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: support legacy clients (DSub) by removing OpenSubsonic extra fields - WIP

Signed-off-by: Deluan <deluan@navidrome.org>

* add record label filter

Signed-off-by: Deluan <deluan@navidrome.org>

* add release type filter

Signed-off-by: Deluan <deluan@navidrome.org>

* fix purgeUnused tags

Signed-off-by: Deluan <deluan@navidrome.org>

* add grouping filter to albums

Signed-off-by: Deluan <deluan@navidrome.org>

* allow any album tags to be used in as filters in the API

Signed-off-by: Deluan <deluan@navidrome.org>

* remove empty tags from album info

Signed-off-by: Deluan <deluan@navidrome.org>

* comments in the migration

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: Cannot read properties of undefined

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: listenbrainz scrobbling (#3640)

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: remove duplicated tag values

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: don't ignore the taglib folder!

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: show track subtitle tag

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: show artists stats based on selected role

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: inspect

Signed-off-by: Deluan <deluan@navidrome.org>

* add media type to album info/filters

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: change format of subtitle in the UI

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: subtitle in Subsonic API and search

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: subtitle in UI's player

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: split strings should be case-insensitive

Signed-off-by: Deluan <deluan@navidrome.org>

* disable ScanSchedule

Signed-off-by: Deluan <deluan@navidrome.org>

* increase default sessiontimeout

Signed-off-by: Deluan <deluan@navidrome.org>

* add sqlite command line tool to docker image

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: resources override

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: album PID conf

Signed-off-by: Deluan <deluan@navidrome.org>

* change migration to mark current artists as albumArtists

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): Allow filtering on multiple genres (#3679)

* feat(ui): Allow filtering on multiple genres

Signed-off-by: Henrik Nordvik <henrikno@gmail.com>
Signed-off-by: Deluan <deluan@navidrome.org>

* add multi-genre filter in Album list

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Henrik Nordvik <henrikno@gmail.com>
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Henrik Nordvik <henrikno@gmail.com>

* add more multi-valued tag filters to Album and Song views

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): unselect missing files after removing

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): song filter

Signed-off-by: Deluan <deluan@navidrome.org>

* fix sharing tracks. fix #3687

Signed-off-by: Deluan <deluan@navidrome.org>

* use rowids when using search for sync (ex: Symfonium)

Signed-off-by: Deluan <deluan@navidrome.org>

* fix "Report Real Paths" option for subsonic clients

Signed-off-by: Deluan <deluan@navidrome.org>

* fix "Report Real Paths" option for subsonic clients for search

Signed-off-by: Deluan <deluan@navidrome.org>

* add libraryPath to Native API /songs endpoint

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(subsonic): add album version

Signed-off-by: Deluan <deluan@navidrome.org>

* made all tags lowercase as they are case-insensitive anyways.

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): Show full paths, extended properties for album/song (#3691)

* feat(ui): Show full paths, extended properties for album/song

- uses library path + os separator + path
- show participants (album/song) and tags (song)
- make album/participant clickable in show info

* add source to path

* fix pathSeparator in UI

Signed-off-by: Deluan <deluan@navidrome.org>

* fix local artist artwork (#3695)

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: parse vorbis performers

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: clean function into smaller functions

Signed-off-by: Deluan <deluan@navidrome.org>

* fix translations for en and pt

Signed-off-by: Deluan <deluan@navidrome.org>

* add trace log to show annotations reassignment

Signed-off-by: Deluan <deluan@navidrome.org>

* add trace log to show annotations reassignment

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: allow performers without instrument/subrole

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: metadata clean function again

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: optimize split function

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: split function is now a method of TagConf

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: humanize Artist total size

Signed-off-by: Deluan <deluan@navidrome.org>

* add album version to album details

Signed-off-by: Deluan <deluan@navidrome.org>

* don't display album-level tags in SongInfo

Signed-off-by: Deluan <deluan@navidrome.org>

* fix genre clicking in Album Page

Signed-off-by: Deluan <deluan@navidrome.org>

* don't use mbids in Last.fm api calls.

From 1337574018:

With MBID:
```
GET https://ws.audioscrobbler.com/2.0/?api_key=XXXX&artist=Van+Morrison&format=json&lang=en&mbid=a41ac10f-0a56-4672-9161-b83f9b223559&method=artist.getInfo

{
artist: {
name: "Bee Gees",
mbid: "bf0f7e29-dfe1-416c-b5c6-f9ebc19ea810",
url: "https://www.last.fm/music/Bee+Gees",
}
```

Without MBID:
```
GET https://ws.audioscrobbler.com/2.0/?api_key=XXXX&artist=Van+Morrison&format=json&lang=en&method=artist.getInfo

{
artist: {
name: "Van Morrison",
mbid: "a41ac10f-0a56-4672-9161-b83f9b223559",
url: "https://www.last.fm/music/Van+Morrison",
}
```

Signed-off-by: Deluan <deluan@navidrome.org>

* better logging for when the artist folder is not found

Signed-off-by: Deluan <deluan@navidrome.org>

* fix various issues with artist image resolution

Signed-off-by: Deluan <deluan@navidrome.org>

* hide "Additional Tags" header if there are none.

Signed-off-by: Deluan <deluan@navidrome.org>

* simplify tag rendering

Signed-off-by: Deluan <deluan@navidrome.org>

* enhance logging for artist folder detection

Signed-off-by: Deluan <deluan@navidrome.org>

* make folderID consistent for relative and absolute folderPaths

Signed-off-by: Deluan <deluan@navidrome.org>

* handle more folder paths scenarios

Signed-off-by: Deluan <deluan@navidrome.org>

* filter out other roles when SubsonicArtistParticipations = true

Signed-off-by: Deluan <deluan@navidrome.org>

* fix "Cannot read properties of undefined"

Signed-off-by: Deluan <deluan@navidrome.org>

* fix lyrics and comments being truncated (#3701)

* fix lyrics and comments being truncated

* specifically test for lyrics and comment length

* reorder assertions

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>

* fix(server): Expose library_path for playlist (#3705)

Allows showing absolute path for UI, and makes "report real path" work for playlists (Subsonic)

* fix BFR on Windows (#3704)

* fix potential reflected cross-site scripting vulnerability

Signed-off-by: Deluan <deluan@navidrome.org>

* hack to make it work on Windows

* ignore windows executables

* try fixing the pipeline

Signed-off-by: Deluan <deluan@navidrome.org>

* allow MusicFolder in other drives

* move windows local drive logic to local storage implementation

---------

Signed-off-by: Deluan <deluan@navidrome.org>

* increase pagination sizes for missing files

Signed-off-by: Deluan <deluan@navidrome.org>

* reduce level of "already scanning" watcher log message

Signed-off-by: Deluan <deluan@navidrome.org>

* only count folders with audio files in it

See https://github.com/navidrome/navidrome/discussions/3676#discussioncomment-11990930

Signed-off-by: Deluan <deluan@navidrome.org>

* add album version and catalog number to search

Signed-off-by: Deluan <deluan@navidrome.org>

* add `organization` alias for `recordlabel`

Signed-off-by: Deluan <deluan@navidrome.org>

* remove mbid from Last.fm agent

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: support inspect in ui (#3726)

* inspect in ui

* address round 1

* add catalogNum to AlbumInfo

Signed-off-by: Deluan <deluan@navidrome.org>

* remove dependency on metadata_old (deprecated) package

Signed-off-by: Deluan <deluan@navidrome.org>

* add `RawTags` to model

Signed-off-by: Deluan <deluan@navidrome.org>

* support parsing MBIDs for roles (from the https://github.com/kgarner7/picard-all-mbids plugin) (#3698)


* parse standard roles, vorbis/m4a work for now

* fix djmixer

* working roles, use DJ-mix

* add performers to file

* map mbids

* add a few more tests

* add test

Signed-off-by: Deluan <deluan@navidrome.org>

* try to simplify the performers logic

Signed-off-by: Deluan <deluan@navidrome.org>

* stylistic changes

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>

* remove param mutation

Signed-off-by: Deluan <deluan@navidrome.org>

* run automated SQLite optimizations

Signed-off-by: Deluan <deluan@navidrome.org>

* fix playlists import/export on Windows

* fix import playlists

* fix export playlists

* better handling of Windows volumes

Signed-off-by: Deluan <deluan@navidrome.org>

* handle more album ID reassignments

Signed-off-by: Deluan <deluan@navidrome.org>

* allow adding/overriding tags in the config file

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): Fix playlist track id, handle missing tracks better (#3734)

- Use `mediaFileId` instead of `id` for playlist tracks
- Only fetch if the file is not missing
- If extractor fails to get the file, also error (rather than panic)

* optimize DB after each scan.

Signed-off-by: Deluan <deluan@navidrome.org>

* remove sortable from AlbumSongs columns

Signed-off-by: Deluan <deluan@navidrome.org>

* simplify query to get missing tracks

Signed-off-by: Deluan <deluan@navidrome.org>

* mark Scanner.Extractor as deprecated

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Signed-off-by: Henrik Nordvik <henrikno@gmail.com>
Co-authored-by: Caio Cotts <caio@cotts.com.br>
Co-authored-by: Henrik Nordvik <henrikno@gmail.com>
Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
This commit is contained in:
Deluan Quintão 2025-02-19 17:35:17 -08:00 committed by GitHub
parent 46a963a02a
commit c795bcfcf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
329 changed files with 16586 additions and 5852 deletions

View file

@ -5,7 +5,6 @@ import (
"io/fs"
"os"
"path"
"sync"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/utils/merge"
@ -14,9 +13,9 @@ import (
//go:embed *
var embedFS embed.FS
var FS = sync.OnceValue(func() fs.FS {
func FS() fs.FS {
return merge.FS{
Base: embedFS,
Overlay: os.DirFS(path.Join(conf.Server.DataFolder, "resources")),
}
})
}

View file

@ -1,468 +1,510 @@
{
"languageName": "Português",
"resources": {
"song": {
"name": "Música |||| Músicas",
"fields": {
"albumArtist": "Artista",
"duration": "Duração",
"trackNumber": "#",
"playCount": "Execuções",
"title": "Título",
"artist": "Artista",
"album": "Álbum",
"path": "Arquivo",
"genre": "Gênero",
"compilation": "Coletânea",
"year": "Ano",
"size": "Tamanho",
"updatedAt": "Últ. Atualização",
"bitRate": "Bitrate",
"discSubtitle": "Sub-título do disco",
"starred": "Favorita",
"comment": "Comentário",
"rating": "Classificação",
"quality": "Qualidade",
"bpm": "BPM",
"playDate": "Últ. Execução",
"channels": "Canais",
"createdAt": "Adiconado em"
},
"actions": {
"addToQueue": "Adicionar à fila",
"playNow": "Tocar agora",
"addToPlaylist": "Adicionar à playlist",
"shuffleAll": "Aleatório",
"download": "Baixar",
"playNext": "Toca a seguir",
"info": "Detalhes"
}
},
"album": {
"name": "Álbum |||| Álbuns",
"fields": {
"albumArtist": "Artista",
"artist": "Artista",
"duration": "Duração",
"songCount": "Músicas",
"playCount": "Execuções",
"name": "Nome",
"genre": "Gênero",
"compilation": "Coletânea",
"year": "Ano",
"updatedAt": "Últ. Atualização",
"comment": "Comentário",
"rating": "Classificação",
"createdAt": "Adicionado em",
"size": "Tamanho",
"originalDate": "Original",
"releaseDate": "Data de Lançamento",
"releases": "Versão||||Versões",
"released": "Lançado"
},
"actions": {
"playAll": "Tocar",
"playNext": "Tocar em seguida",
"addToQueue": "Adicionar à fila",
"shuffle": "Aleatório",
"addToPlaylist": "Adicionar à playlist",
"download": "Baixar",
"info": "Detalhes",
"share": "Compartilhar"
},
"lists": {
"all": "Todos",
"random": "Aleatório",
"recentlyAdded": "Recém-adicionados",
"recentlyPlayed": "Recém-tocados",
"mostPlayed": "Mais tocados",
"starred": "Favoritos",
"topRated": "Melhor classificados"
}
},
"artist": {
"name": "Artista |||| Artistas",
"fields": {
"name": "Nome",
"albumCount": "Total de Álbuns",
"songCount": "Total de Músicas",
"playCount": "Execuções",
"rating": "Classificação",
"genre": "Gênero",
"size": "Tamanho"
}
},
"user": {
"name": "Usuário |||| Usuários",
"fields": {
"userName": "Usuário",
"isAdmin": "Admin?",
"lastLoginAt": "Últ. Login",
"lastAccessAt": "Últ. Acesso",
"updatedAt": "Últ. Atualização",
"name": "Nome",
"password": "Senha",
"createdAt": "Data de Criação",
"changePassword": "Trocar Senha?",
"currentPassword": "Senha Atual",
"newPassword": "Nova Senha",
"token": "Token"
},
"helperTexts": {
"name": "Alterações no seu nome só serão refletidas no próximo login"
},
"notifications": {
"created": "Novo usuário criado",
"updated": "Usuário atualizado com sucesso",
"deleted": "Usuário deletado com sucesso"
},
"message": {
"listenBrainzToken": "Entre seu token do ListenBrainz",
"clickHereForToken": "Clique aqui para obter seu token"
}
},
"player": {
"name": "Tocador |||| Tocadores",
"fields": {
"name": "Nome",
"transcodingId": "Conversão",
"maxBitRate": "Bitrate máx",
"client": "Cliente",
"userName": "Usuário",
"lastSeen": "Últ. acesso",
"reportRealPath": "Use paths reais",
"scrobbleEnabled": "Enviar scrobbles para serviços externos"
}
},
"transcoding": {
"name": "Conversão |||| Conversões",
"fields": {
"name": "Nome",
"targetFormat": "Formato",
"defaultBitRate": "Bitrate padrão",
"command": "Comando"
}
},
"playlist": {
"name": "Playlist |||| Playlists",
"fields": {
"name": "Nome",
"duration": "Duração",
"ownerName": "Dono",
"public": "Pública",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação",
"songCount": "Músicas",
"comment": "Comentário",
"sync": "Auto-importar",
"path": "Importar de"
},
"actions": {
"selectPlaylist": "Selecione a playlist:",
"addNewPlaylist": "Criar \"%{name}\"",
"export": "Exportar",
"makePublic": "Pública",
"makePrivate": "Pessoal"
},
"message": {
"duplicate_song": "Adicionar músicas duplicadas",
"song_exist": "Algumas destas músicas já existem na playlist. Você quer adicionar as duplicadas ou ignorá-las?"
}
},
"radio": {
"name": "Rádio |||| Rádios",
"fields": {
"name": "Nome",
"streamUrl": "Endereço de stream",
"homePageUrl": "Home Page",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação"
},
"actions": {
"playNow": "Tocar agora"
}
},
"share": {
"name": "Compartilhamento |||| Compartilhamentos",
"fields": {
"username": "Compartilhado por",
"url": "Link",
"description": "Descrição",
"contents": "Conteúdo",
"expiresAt": "Dt. Expiração",
"lastVisitedAt": "Última visita",
"visitCount": "Visitas",
"format": "Formato",
"maxBitRate": "Bitrate máx",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação",
"downloadable": "Permitir Baixar?"
}
}
"languageName": "Português",
"resources": {
"song": {
"name": "Música |||| Músicas",
"fields": {
"albumArtist": "Artista",
"duration": "Duração",
"trackNumber": "#",
"playCount": "Execuções",
"title": "Título",
"artist": "Artista",
"album": "Álbum",
"path": "Arquivo",
"genre": "Gênero",
"compilation": "Coletânea",
"year": "Ano",
"size": "Tamanho",
"updatedAt": "Últ. Atualização",
"bitRate": "Bitrate",
"discSubtitle": "Sub-título do disco",
"starred": "Favorita",
"comment": "Comentário",
"rating": "Classificação",
"quality": "Qualidade",
"bpm": "BPM",
"playDate": "Últ. Execução",
"channels": "Canais",
"createdAt": "Adiconado em",
"grouping": "Agrupamento",
"mood": "Mood",
"participants": "Outros Participantes",
"tags": "Outras Tags"
},
"actions": {
"addToQueue": "Adicionar à fila",
"playNow": "Tocar agora",
"addToPlaylist": "Adicionar à playlist",
"shuffleAll": "Aleatório",
"download": "Baixar",
"playNext": "Toca a seguir",
"info": "Detalhes"
}
},
"ra": {
"auth": {
"welcome1": "Obrigado por instalar Navidrome!",
"welcome2": "Para iniciar, crie um usuário admin",
"confirmPassword": "Confirme a senha",
"buttonCreateAdmin": "Criar Admin",
"auth_check_error": "Por favor, faça login para continuar",
"user_menu": "Perfil",
"username": "Usuário",
"password": "Senha",
"sign_in": "Entrar",
"sign_in_error": "Erro na autenticação, tente novamente.",
"logout": "Sair",
"insightsCollectionNote": "Navidrome coleta dados de uso anônimos para\najudar a melhorar o projeto. Clique [aqui] para\nsaber mais e para desativar se desejar"
},
"validation": {
"invalidChars": "Somente use letras e numeros",
"passwordDoesNotMatch": "Senha não confere",
"required": "Obrigatório",
"minLength": "Deve ser ter no mínimo %{min} caracteres",
"maxLength": "Deve ter no máximo %{max} caracteres",
"minValue": "Deve ser %{min} ou maior",
"maxValue": "Deve ser %{max} ou menor",
"number": "Deve ser um número",
"email": "Deve ser um email válido",
"oneOf": "Deve ser uma das seguintes opções: %{options}",
"regex": "Deve ter o formato específico (regexp): %{pattern}",
"unique": "Deve ser único",
"url": "URL inválida"
},
"action": {
"add_filter": "Adicionar Filtro",
"add": "Adicionar",
"back": "Voltar",
"bulk_actions": "1 item selecionado |||| %{smart_count} itens selecionados",
"cancel": "Cancelar",
"clear_input_value": "Limpar campo",
"clone": "Duplicar",
"confirm": "Confirmar",
"create": "Novo",
"delete": "Deletar",
"edit": "Editar",
"export": "Exportar",
"list": "Listar",
"refresh": "Atualizar",
"remove_filter": "Cancelar filtro",
"remove": "Excluir",
"save": "Salvar",
"search": "Buscar",
"show": "Exibir",
"sort": "Ordenar",
"undo": "Desfazer",
"expand": "Expandir",
"close": "Fechar",
"open_menu": "Abrir menu",
"close_menu": "Fechar menu",
"unselect": "Deselecionar",
"skip": "Ignorar",
"bulk_actions_mobile": "1 |||| %{smart_count}",
"share": "Compartilhar",
"download": "Baixar"
},
"boolean": {
"true": "Sim",
"false": "Não"
},
"page": {
"create": "Criar %{name}",
"dashboard": "Painel de Controle",
"edit": "%{name} #%{id}",
"error": "Um erro ocorreu",
"list": "Listar %{name}",
"loading": "Carregando",
"not_found": "Não encontrado",
"show": "%{name} #%{id}",
"empty": "Ainda não há nenhum registro em %{name}",
"invite": "Gostaria de criar um novo?"
},
"input": {
"file": {
"upload_several": "Arraste alguns arquivos para fazer o upload, ou clique para selecioná-los.",
"upload_single": "Arraste o arquivo para fazer o upload, ou clique para selecioná-lo."
},
"image": {
"upload_several": "Arraste algumas imagens para fazer o upload ou clique para selecioná-las",
"upload_single": "Arraste um arquivo para upload ou clique em selecionar arquivo."
},
"references": {
"all_missing": "Não foi possível encontrar os dados das referencias.",
"many_missing": "Pelo menos uma das referências passadas não está mais disponível.",
"single_missing": "A referência passada aparenta não estar mais disponível."
},
"password": {
"toggle_visible": "Esconder senha",
"toggle_hidden": "Mostrar senha"
}
},
"message": {
"about": "Sobre",
"are_you_sure": "Tem certeza?",
"bulk_delete_content": "Você tem certeza que deseja excluir %{name}? |||| Você tem certeza que deseja excluir estes %{smart_count} itens?",
"bulk_delete_title": "Excluir %{name} |||| Excluir %{smart_count} %{name} itens",
"delete_content": "Você tem certeza que deseja excluir?",
"delete_title": "Excluir %{name} #%{id}",
"details": "Detalhes",
"error": "Um erro ocorreu e a sua requisição não pôde ser completada.",
"invalid_form": "Este formulário não está valido. Certifique-se de corrigir os erros",
"loading": "A página está carregando. Um momento, por favor",
"no": "Não",
"not_found": "Foi digitada uma URL inválida, ou o link pode estar quebrado.",
"yes": "Sim",
"unsaved_changes": "Algumas das suas mudanças não foram salvas, deseja realmente ignorá-las?"
},
"navigation": {
"no_results": "Nenhum resultado encontrado",
"no_more_results": "A página numero %{page} está fora dos limites. Tente a página anterior.",
"page_out_of_boundaries": "Página %{page} fora do limite",
"page_out_from_end": "Não é possível ir após a última página",
"page_out_from_begin": "Não é possível ir antes da primeira página",
"page_range_info": "%{offsetBegin}-%{offsetEnd} de %{total}",
"page_rows_per_page": "Resultados por página:",
"next": "Próximo",
"prev": "Anterior",
"skip_nav": "Pular para o conteúdo"
},
"notification": {
"updated": "Item atualizado com sucesso |||| %{smart_count} itens foram atualizados com sucesso",
"created": "Item criado com sucesso",
"deleted": "Item removido com sucesso! |||| %{smart_count} itens foram removidos com sucesso",
"bad_item": "Item incorreto",
"item_doesnt_exist": "Esse item não existe mais",
"http_error": "Erro na comunicação com servidor",
"data_provider_error": "Erro interno do servidor. Entre em contato",
"i18n_error": "Não foi possível carregar as traduções para o idioma especificado",
"canceled": "Ação cancelada",
"logged_out": "Sua sessão foi encerrada. Por favor, reconecte",
"new_version": "Nova versão disponível! Por favor recarregue esta janela."
},
"toggleFieldsMenu": {
"columnsToDisplay": "Colunas visíveis",
"layout": "Layout",
"grid": "Grade",
"table": "Tabela"
}
"album": {
"name": "Álbum |||| Álbuns",
"fields": {
"albumArtist": "Artista",
"artist": "Artista",
"duration": "Duração",
"songCount": "Músicas",
"playCount": "Execuções",
"name": "Nome",
"genre": "Gênero",
"compilation": "Coletânea",
"year": "Ano",
"updatedAt": "Últ. Atualização",
"comment": "Comentário",
"rating": "Classificação",
"createdAt": "Adicionado em",
"size": "Tamanho",
"originalDate": "Original",
"releaseDate": "Data de Lançamento",
"releases": "Versão||||Versões",
"released": "Lançado",
"recordLabel": "Selo",
"catalogNum": "Nr. Catálogo",
"releaseType": "Tipo",
"grouping": "Agrupamento",
"media": "Mídia",
"mood": "Mood"
},
"actions": {
"playAll": "Tocar",
"playNext": "Tocar em seguida",
"addToQueue": "Adicionar à fila",
"shuffle": "Aleatório",
"addToPlaylist": "Adicionar à playlist",
"download": "Baixar",
"info": "Detalhes",
"share": "Compartilhar"
},
"lists": {
"all": "Todos",
"random": "Aleatório",
"recentlyAdded": "Recém-adicionados",
"recentlyPlayed": "Recém-tocados",
"mostPlayed": "Mais tocados",
"starred": "Favoritos",
"topRated": "Melhor classificados"
}
},
"message": {
"note": "ATENÇÃO",
"transcodingDisabled": "Por questão de segurança, esta tela de configuração está desabilitada. Se você quiser alterar estas configurações, reinicie o servidor com a opção %{config}",
"transcodingEnabled": "Navidrome está sendo executado com a opção %{config}. Isto permite que potencialmente se execute comandos do sistema pela interface Web. É recomendado que vc mantenha esta opção desabilitada, e só a habilite quando precisar configurar opções de Conversão",
"songsAddedToPlaylist": "Música adicionada à playlist |||| %{smart_count} músicas adicionadas à playlist",
"noPlaylistsAvailable": "Nenhuma playlist",
"delete_user_title": "Excluir usuário '%{name}'",
"delete_user_content": "Você tem certeza que deseja excluir o usuário e todos os seus dados (incluindo suas playlists e preferências)?",
"notifications_blocked": "Você bloqueou notificações para este site nas configurações do seu browser",
"notifications_not_available": "Este navegador não suporta notificações",
"lastfmLinkSuccess": "Sua conta no Last.fm foi conectada com sucesso",
"lastfmLinkFailure": "Sua conta no Last.fm não pode ser conectada",
"lastfmUnlinkSuccess": "Sua conta no Last.fm foi desconectada",
"lastfmUnlinkFailure": "Sua conta no Last.fm não pode ser desconectada",
"openIn": {
"lastfm": "Abrir em Last.fm",
"musicbrainz": "Abrir em MusicBrainz"
},
"lastfmLink": "Leia mais",
"listenBrainzLinkSuccess": "Sua conta no ListenBrainz foi conectada com sucesso",
"listenBrainzLinkFailure": "Sua conta no ListenBrainz não pode ser conectada",
"listenBrainzUnlinkSuccess": "Sua conta no ListenBrainz foi desconectada",
"listenBrainzUnlinkFailure": "Sua conta no ListenBrainz não pode ser desconectada",
"downloadOriginalFormat": "Baixar no formato original",
"shareOriginalFormat": "Compartilhar no formato original",
"shareDialogTitle": "Compartilhar %{resource} '%{name}'",
"shareBatchDialogTitle": "Compartilhar 1 %{resource} |||| Compartilhar %{smart_count} %{resource}",
"shareSuccess": "Link copiado para o clipboard : %{url}",
"shareFailure": "Erro ao copiar o link %{url} para o clipboard",
"downloadDialogTitle": "Baixar %{resource} '%{name}' (%{size})",
"shareCopyToClipboard": "Copie para o clipboard: Ctrl+C, Enter"
"artist": {
"name": "Artista |||| Artistas",
"fields": {
"name": "Nome",
"albumCount": "Total de Álbuns",
"songCount": "Total de Músicas",
"playCount": "Execuções",
"rating": "Classificação",
"genre": "Gênero",
"size": "Tamanho",
"role": "Role"
},
"roles": {
"albumartist": "Artista do Álbum |||| Artistas do Álbum",
"artist": "Artista |||| Artistas",
"composer": "Compositor |||| Compositores",
"conductor": "Maestro |||| Maestros",
"lyricist": "Letrista |||| Letristas",
"arranger": "Arranjador |||| Arranjadores",
"producer": "Produtor |||| Produtores",
"director": "Diretor |||| Diretores",
"engineer": "Engenheiro |||| Engenheiros",
"mixer": "Mixador |||| Mixadores",
"remixer": "Remixador |||| Remixadores",
"djmixer": "DJ Mixer |||| DJ Mixers",
"performer": "Músico |||| Músicos"
}
},
"menu": {
"library": "Biblioteca",
"settings": "Configurações",
"version": "Versão",
"theme": "Tema",
"personal": {
"name": "Pessoal",
"options": {
"theme": "Tema",
"language": "Língua",
"defaultView": "Tela inicial",
"desktop_notifications": "Notificações",
"lastfmNotConfigured": "A API-Key do Last.fm não está configurada",
"lastfmScrobbling": "Enviar scrobbles para Last.fm",
"listenBrainzScrobbling": "Enviar scrobbles para ListenBrainz",
"replaygain": "Modo ReplayGain",
"preAmp": "PreAmp ReplayGain (dB)",
"gain": {
"none": "Desligado",
"album": "Usar ganho do álbum",
"track": "Usar ganho do faixa"
}
}
},
"albumList": "Álbuns",
"about": "Info",
"playlists": "Playlists",
"sharedPlaylists": "Compartilhadas"
"user": {
"name": "Usuário |||| Usuários",
"fields": {
"userName": "Usuário",
"isAdmin": "Admin?",
"lastLoginAt": "Últ. Login",
"lastAccessAt": "Últ. Acesso",
"updatedAt": "Últ. Atualização",
"name": "Nome",
"password": "Senha",
"createdAt": "Data de Criação",
"changePassword": "Trocar Senha?",
"currentPassword": "Senha Atual",
"newPassword": "Nova Senha",
"token": "Token"
},
"helperTexts": {
"name": "Alterações no seu nome só serão refletidas no próximo login"
},
"notifications": {
"created": "Novo usuário criado",
"updated": "Usuário atualizado com sucesso",
"deleted": "Usuário deletado com sucesso"
},
"message": {
"listenBrainzToken": "Entre seu token do ListenBrainz",
"clickHereForToken": "Clique aqui para obter seu token"
}
},
"player": {
"playListsText": "Fila de Execução",
"openText": "Abrir",
"closeText": "Fechar",
"notContentText": "Nenhum música",
"clickToPlayText": "Clique para tocar",
"clickToPauseText": "Clique para pausar",
"nextTrackText": "Próxima faixa",
"previousTrackText": "Faixa anterior",
"reloadText": "Recarregar",
"volumeText": "Volume",
"toggleLyricText": "Letra",
"toggleMiniModeText": "Minimizar",
"destroyText": "Destruir",
"downloadText": "Baixar",
"removeAudioListsText": "Limpar fila de execução",
"clickToDeleteText": "Clique para remover %{name}",
"emptyLyricText": "Letra não disponível",
"playModeText": {
"order": "Em ordem",
"orderLoop": "Repetir tudo",
"singleLoop": "Repetir",
"shufflePlay": "Aleatório"
}
"name": "Tocador |||| Tocadores",
"fields": {
"name": "Nome",
"transcodingId": "Conversão",
"maxBitRate": "Bitrate máx",
"client": "Cliente",
"userName": "Usuário",
"lastSeen": "Últ. acesso",
"reportRealPath": "Use paths reais",
"scrobbleEnabled": "Enviar scrobbles para serviços externos"
}
},
"about": {
"links": {
"homepage": "Website",
"source": "Código fonte",
"featureRequests": "Solicitar funcionalidade",
"lastInsightsCollection": "Última coleta de dados",
"insights": {
"disabled": "Desligado",
"waiting": "Aguardando"
}
}
"transcoding": {
"name": "Conversão |||| Conversões",
"fields": {
"name": "Nome",
"targetFormat": "Formato",
"defaultBitRate": "Bitrate padrão",
"command": "Comando"
}
},
"activity": {
"title": "Atividade",
"totalScanned": "Total de pastas analisadas",
"quickScan": "Scan rápido",
"fullScan": "Scan completo",
"serverUptime": "Uptime do servidor",
"serverDown": "DESCONECTADO"
"playlist": {
"name": "Playlist |||| Playlists",
"fields": {
"name": "Nome",
"duration": "Duração",
"ownerName": "Dono",
"public": "Pública",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação",
"songCount": "Músicas",
"comment": "Comentário",
"sync": "Auto-importar",
"path": "Importar de"
},
"actions": {
"selectPlaylist": "Selecione a playlist:",
"addNewPlaylist": "Criar \"%{name}\"",
"export": "Exportar",
"makePublic": "Pública",
"makePrivate": "Pessoal"
},
"message": {
"duplicate_song": "Adicionar músicas duplicadas",
"song_exist": "Algumas destas músicas já existem na playlist. Você quer adicionar as duplicadas ou ignorá-las?"
}
},
"help": {
"title": "Teclas de atalho",
"hotkeys": {
"show_help": "Mostra esta janela",
"toggle_menu": "Mostra o menu lateral",
"toggle_play": "Tocar / pausar",
"prev_song": "Música anterior",
"next_song": "Próxima música",
"vol_up": "Aumenta volume",
"vol_down": "Diminui volume",
"toggle_love": "Marcar/desmarcar favorita",
"current_song": "Vai para música atual"
}
"radio": {
"name": "Rádio |||| Rádios",
"fields": {
"name": "Nome",
"streamUrl": "Endereço de stream",
"homePageUrl": "Home Page",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação"
},
"actions": {
"playNow": "Tocar agora"
}
},
"share": {
"name": "Compartilhamento |||| Compartilhamentos",
"fields": {
"username": "Compartilhado por",
"url": "Link",
"description": "Descrição",
"contents": "Conteúdo",
"expiresAt": "Dt. Expiração",
"lastVisitedAt": "Última visita",
"visitCount": "Visitas",
"format": "Formato",
"maxBitRate": "Bitrate máx",
"updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação",
"downloadable": "Permitir Baixar?"
}
},
"missing": {
"name": "Arquivo ausente |||| Arquivos ausentes",
"fields": {
"path": "Caminho",
"size": "Tamanho",
"updatedAt": "Desaparecido em"
},
"actions": {
"remove": "Remover"
},
"notifications": {
"removed": "Arquivo(s) ausente(s) removido(s)"
}
}
},
"ra": {
"auth": {
"welcome1": "Obrigado por instalar Navidrome!",
"welcome2": "Para iniciar, crie um usuário admin",
"confirmPassword": "Confirme a senha",
"buttonCreateAdmin": "Criar Admin",
"auth_check_error": "Por favor, faça login para continuar",
"user_menu": "Perfil",
"username": "Usuário",
"password": "Senha",
"sign_in": "Entrar",
"sign_in_error": "Erro na autenticação, tente novamente.",
"logout": "Sair",
"insightsCollectionNote": "Navidrome coleta dados de uso anônimos para\najudar a melhorar o projeto. Clique [aqui] para\nsaber mais e para desativar se desejar"
},
"validation": {
"invalidChars": "Somente use letras e numeros",
"passwordDoesNotMatch": "Senha não confere",
"required": "Obrigatório",
"minLength": "Deve ser ter no mínimo %{min} caracteres",
"maxLength": "Deve ter no máximo %{max} caracteres",
"minValue": "Deve ser %{min} ou maior",
"maxValue": "Deve ser %{max} ou menor",
"number": "Deve ser um número",
"email": "Deve ser um email válido",
"oneOf": "Deve ser uma das seguintes opções: %{options}",
"regex": "Deve ter o formato específico (regexp): %{pattern}",
"unique": "Deve ser único",
"url": "URL inválida"
},
"action": {
"add_filter": "Adicionar Filtro",
"add": "Adicionar",
"back": "Voltar",
"bulk_actions": "1 item selecionado |||| %{smart_count} itens selecionados",
"cancel": "Cancelar",
"clear_input_value": "Limpar campo",
"clone": "Duplicar",
"confirm": "Confirmar",
"create": "Novo",
"delete": "Deletar",
"edit": "Editar",
"export": "Exportar",
"list": "Listar",
"refresh": "Atualizar",
"remove_filter": "Cancelar filtro",
"remove": "Remover",
"save": "Salvar",
"search": "Buscar",
"show": "Exibir",
"sort": "Ordenar",
"undo": "Desfazer",
"expand": "Expandir",
"close": "Fechar",
"open_menu": "Abrir menu",
"close_menu": "Fechar menu",
"unselect": "Deselecionar",
"skip": "Ignorar",
"bulk_actions_mobile": "1 |||| %{smart_count}",
"share": "Compartilhar",
"download": "Baixar"
},
"boolean": {
"true": "Sim",
"false": "Não"
},
"page": {
"create": "Criar %{name}",
"dashboard": "Painel de Controle",
"edit": "%{name} #%{id}",
"error": "Um erro ocorreu",
"list": "Listar %{name}",
"loading": "Carregando",
"not_found": "Não encontrado",
"show": "%{name} #%{id}",
"empty": "Ainda não há nenhum registro em %{name}",
"invite": "Gostaria de criar um novo?"
},
"input": {
"file": {
"upload_several": "Arraste alguns arquivos para fazer o upload, ou clique para selecioná-los.",
"upload_single": "Arraste o arquivo para fazer o upload, ou clique para selecioná-lo."
},
"image": {
"upload_several": "Arraste algumas imagens para fazer o upload ou clique para selecioná-las",
"upload_single": "Arraste um arquivo para upload ou clique em selecionar arquivo."
},
"references": {
"all_missing": "Não foi possível encontrar os dados das referencias.",
"many_missing": "Pelo menos uma das referências passadas não está mais disponível.",
"single_missing": "A referência passada aparenta não estar mais disponível."
},
"password": {
"toggle_visible": "Esconder senha",
"toggle_hidden": "Mostrar senha"
}
},
"message": {
"about": "Sobre",
"are_you_sure": "Tem certeza?",
"bulk_delete_content": "Você tem certeza que deseja excluir %{name}? |||| Você tem certeza que deseja excluir estes %{smart_count} itens?",
"bulk_delete_title": "Excluir %{name} |||| Excluir %{smart_count} %{name} itens",
"delete_content": "Você tem certeza que deseja excluir?",
"delete_title": "Excluir %{name} #%{id}",
"details": "Detalhes",
"error": "Um erro ocorreu e a sua requisição não pôde ser completada.",
"invalid_form": "Este formulário não está valido. Certifique-se de corrigir os erros",
"loading": "A página está carregando. Um momento, por favor",
"no": "Não",
"not_found": "Foi digitada uma URL inválida, ou o link pode estar quebrado.",
"yes": "Sim",
"unsaved_changes": "Algumas das suas mudanças não foram salvas, deseja realmente ignorá-las?"
},
"navigation": {
"no_results": "Nenhum resultado encontrado",
"no_more_results": "A página numero %{page} está fora dos limites. Tente a página anterior.",
"page_out_of_boundaries": "Página %{page} fora do limite",
"page_out_from_end": "Não é possível ir após a última página",
"page_out_from_begin": "Não é possível ir antes da primeira página",
"page_range_info": "%{offsetBegin}-%{offsetEnd} de %{total}",
"page_rows_per_page": "Resultados por página:",
"next": "Próximo",
"prev": "Anterior",
"skip_nav": "Pular para o conteúdo"
},
"notification": {
"updated": "Item atualizado com sucesso |||| %{smart_count} itens foram atualizados com sucesso",
"created": "Item criado com sucesso",
"deleted": "Item removido com sucesso! |||| %{smart_count} itens foram removidos com sucesso",
"bad_item": "Item incorreto",
"item_doesnt_exist": "Esse item não existe mais",
"http_error": "Erro na comunicação com servidor",
"data_provider_error": "Erro interno do servidor. Entre em contato",
"i18n_error": "Não foi possível carregar as traduções para o idioma especificado",
"canceled": "Ação cancelada",
"logged_out": "Sua sessão foi encerrada. Por favor, reconecte",
"new_version": "Nova versão disponível! Por favor recarregue esta janela."
},
"toggleFieldsMenu": {
"columnsToDisplay": "Colunas visíveis",
"layout": "Layout",
"grid": "Grade",
"table": "Tabela"
}
},
"message": {
"note": "ATENÇÃO",
"transcodingDisabled": "Por questão de segurança, esta tela de configuração está desabilitada. Se você quiser alterar estas configurações, reinicie o servidor com a opção %{config}",
"transcodingEnabled": "Navidrome está sendo executado com a opção %{config}. Isto permite que potencialmente se execute comandos do sistema pela interface Web. É recomendado que vc mantenha esta opção desabilitada, e só a habilite quando precisar configurar opções de Conversão",
"songsAddedToPlaylist": "Música adicionada à playlist |||| %{smart_count} músicas adicionadas à playlist",
"noPlaylistsAvailable": "Nenhuma playlist",
"delete_user_title": "Excluir usuário '%{name}'",
"delete_user_content": "Você tem certeza que deseja excluir o usuário e todos os seus dados (incluindo suas playlists e preferências)?",
"remove_missing_title": "Remover arquivos ausentes",
"remove_missing_content": "Você tem certeza que deseja remover os arquivos selecionados do banco de dados? Isso removerá permanentemente qualquer referência a eles, incluindo suas contagens de reprodução e classificações.",
"notifications_blocked": "Você bloqueou notificações para este site nas configurações do seu browser",
"notifications_not_available": "Este navegador não suporta notificações",
"lastfmLinkSuccess": "Sua conta no Last.fm foi conectada com sucesso",
"lastfmLinkFailure": "Sua conta no Last.fm não pode ser conectada",
"lastfmUnlinkSuccess": "Sua conta no Last.fm foi desconectada",
"lastfmUnlinkFailure": "Sua conta no Last.fm não pode ser desconectada",
"openIn": {
"lastfm": "Abrir em Last.fm",
"musicbrainz": "Abrir em MusicBrainz"
},
"lastfmLink": "Leia mais",
"listenBrainzLinkSuccess": "Sua conta no ListenBrainz foi conectada com sucesso",
"listenBrainzLinkFailure": "Sua conta no ListenBrainz não pode ser conectada",
"listenBrainzUnlinkSuccess": "Sua conta no ListenBrainz foi desconectada",
"listenBrainzUnlinkFailure": "Sua conta no ListenBrainz não pode ser desconectada",
"downloadOriginalFormat": "Baixar no formato original",
"shareOriginalFormat": "Compartilhar no formato original",
"shareDialogTitle": "Compartilhar %{resource} '%{name}'",
"shareBatchDialogTitle": "Compartilhar 1 %{resource} |||| Compartilhar %{smart_count} %{resource}",
"shareSuccess": "Link copiado para o clipboard : %{url}",
"shareFailure": "Erro ao copiar o link %{url} para o clipboard",
"downloadDialogTitle": "Baixar %{resource} '%{name}' (%{size})",
"shareCopyToClipboard": "Copie para o clipboard: Ctrl+C, Enter"
},
"menu": {
"library": "Biblioteca",
"settings": "Configurações",
"version": "Versão",
"theme": "Tema",
"personal": {
"name": "Pessoal",
"options": {
"theme": "Tema",
"language": "Língua",
"defaultView": "Tela inicial",
"desktop_notifications": "Notificações",
"lastfmNotConfigured": "A API-Key do Last.fm não está configurada",
"lastfmScrobbling": "Enviar scrobbles para Last.fm",
"listenBrainzScrobbling": "Enviar scrobbles para ListenBrainz",
"replaygain": "Modo ReplayGain",
"preAmp": "PreAmp ReplayGain (dB)",
"gain": {
"none": "Desligado",
"album": "Usar ganho do álbum",
"track": "Usar ganho do faixa"
}
}
},
"albumList": "Álbuns",
"about": "Info",
"playlists": "Playlists",
"sharedPlaylists": "Compartilhadas"
},
"player": {
"playListsText": "Fila de Execução",
"openText": "Abrir",
"closeText": "Fechar",
"notContentText": "Nenhum música",
"clickToPlayText": "Clique para tocar",
"clickToPauseText": "Clique para pausar",
"nextTrackText": "Próxima faixa",
"previousTrackText": "Faixa anterior",
"reloadText": "Recarregar",
"volumeText": "Volume",
"toggleLyricText": "Letra",
"toggleMiniModeText": "Minimizar",
"destroyText": "Destruir",
"downloadText": "Baixar",
"removeAudioListsText": "Limpar fila de execução",
"clickToDeleteText": "Clique para remover %{name}",
"emptyLyricText": "Letra não disponível",
"playModeText": {
"order": "Em ordem",
"orderLoop": "Repetir tudo",
"singleLoop": "Repetir",
"shufflePlay": "Aleatório"
}
},
"about": {
"links": {
"homepage": "Website",
"source": "Código fonte",
"featureRequests": "Solicitar funcionalidade",
"lastInsightsCollection": "Última coleta de dados",
"insights": {
"disabled": "Desligado",
"waiting": "Aguardando"
}
}
},
"activity": {
"title": "Atividade",
"totalScanned": "Total de pastas analisadas",
"quickScan": "Scan rápido",
"fullScan": "Scan completo",
"serverUptime": "Uptime do servidor",
"serverDown": "DESCONECTADO"
},
"help": {
"title": "Teclas de atalho",
"hotkeys": {
"show_help": "Mostra esta janela",
"toggle_menu": "Mostra o menu lateral",
"toggle_play": "Tocar / pausar",
"prev_song": "Música anterior",
"next_song": "Próxima música",
"vol_up": "Aumenta volume",
"vol_down": "Diminui volume",
"toggle_love": "Marcar/desmarcar favorita",
"current_song": "Vai para música atual"
}
}
}

248
resources/mappings.yaml Normal file
View file

@ -0,0 +1,248 @@
#file: noinspection SpellCheckingInspection
# Tag mapping adapted from https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
#
# This file contains the mapping between the tags in your music files and the fields in Navidrome.
# You can add new tags, change the aliases, or add new split characters to the existing tags.
# The artists and roles keys are used to define how to split the tag values into multiple values.
# The tags are divided into two categories: main and additional.
# The main tags are handled directly by Navidrome, while the additional tags are available as fields for smart playlists.
#
# Applies to single valued ARTIST and ALBUMARTIST tags. Won't be applied if the tag is multivalued or the multivalued
# versions are available (ARTISTS and ALBUMARTISTS)
artists:
split: [" / ", " feat. ", " feat ", " ft. ", " ft ", "; "]
# Applies to all remaining single-valued role tags (composer, lyricist, arranger...)
roles:
split: ["/", ";"]
# These tags are handled directly by Navidrome. You can add/remove/reorder aliases, but changing the tag name
# may require code changes
main:
title:
aliases: [ tit2, title, ©nam, inam ]
titlesort:
aliases: [ tsot, titlesort, sonm, wm/titlesortorder ]
artist:
aliases: [ tpe1, artist, ©art, author, iart ]
artistsort:
aliases: [ tsop, artistsort, artistsort, soar, wm/artistsortorder ]
artists:
aliases: [ txxx:artists, artists, ----:com.apple.itunes:artists, wm/artists ]
artistssort:
aliases: [ artistssort ]
arranger:
aliases: [ tipl:arranger, ipls:arranger, arranger ]
composer:
aliases: [ tcom, composer, ©wrt, wm/composer, imus,
writer, txxx:writer, iwri,
# If you need writer separated from composer, remove these tagss from the line above
# and uncomment the two lines below
]
#writer:
# aliases: [ WRITER, TXXX:Writer, IWRI ]
composersort:
aliases: [ tsoc, txxx:composersort, composersort, soco, wm/composersortorder ]
lyricist:
aliases: [ text, lyricist, ----:com.apple.itunes:lyricist, wm/writer ]
lyricistsort:
aliases: [ lyricistsort ]
conductor:
aliases: [ tpe3, conductor, ----:com.apple.itunes:conductor, wm/conductor ]
director:
aliases: [ txxx:director, director, ©dir, wm/director ]
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 ]
engineer:
aliases: [ tipl:engineer, ipls:engineer, engineer, ----:com.apple.itunes:engineer, wm/engineer, ieng ]
producer:
aliases: [ tipl:producer, ipls:producer, producer, ----:com.apple.itunes:producer, wm/producer, ipro ]
remixer:
aliases: [ tpe4, remixer, mixartist, ----:com.apple.itunes:remixer, wm/modifiedby ]
albumartist:
aliases: [ tpe2, albumartist, album artist, aart, wm/albumartist ]
albumartistsort:
aliases: [ tso2, txxx:albumartistsort, albumartistsort, soaa, wm/albumartistsortorder ]
albumartists:
aliases: [ txxx:album artists, albumartists ]
albumartistssort:
aliases: [ albumartistssort ]
album:
aliases: [ talb, album, ©alb, wm/albumtitle, iprd ]
albumsort:
aliases: [ tsoa, albumsort, soal, wm/albumsortorder ]
albumversion:
aliases: [albumversion, musicbrainz_albumcomment, musicbrainz album comment, version]
album: true
genre:
aliases: [ tcon, genre, ©gen, wm/genre, ignr ]
split: [ ";", "/", "," ]
album: true
mood:
aliases: [ tmoo, mood, ----:com.apple.itunes:mood, wm/mood ]
split: [ ";", "/", "," ]
album: true
compilation:
aliases: [ tcmp, compilation, cpil, wm/iscompilation ]
track:
aliases: [ track, trck, tracknumber, trkn, wm/tracknumber, itrk ]
tracktotal:
aliases: [ tracktotal, totaltracks ]
album: true
disc:
aliases: [ tpos, disc, discnumber, disk, wm/partofset ]
disctotal:
aliases: [ disctotal, totaldiscs ]
album: true
discsubtitle:
aliases: [ tsst, discsubtitle, ----:com.apple.itunes:discsubtitle, wm/setsubtitle ]
bpm:
aliases: [ tbpm, bpm, tmpo, wm/beatsperminute ]
lyrics:
aliases: [ uslt:description, lyrics, ©lyr, wm/lyrics ]
maxLength: 32768
type: pair # ex: lyrics:eng, lyrics:xxx
comment:
aliases: [ comm:description, comment, ©cmt, description, icmt ]
maxLength: 4096
originaldate:
aliases: [ tdor, originaldate, ----:com.apple.itunes:originaldate, wm/originalreleasetime, tory, originalyear, ----:com.apple.itunes:originalyear, wm/originalreleaseyear ]
type: date
recordingdate:
aliases: [ tdrc, date, icrd, ©day, wm/year, year ]
type: date
releasedate:
aliases: [ tdrl, releasedate ]
type: date
catalognumber:
aliases: [ txxx:catalognumber, catalognumber, ----:com.apple.itunes:catalognumber, wm/catalogno ]
musicbrainz_artistid:
aliases: [ txxx:musicbrainz artist id, musicbrainz_artistid, musicbrainz artist id, ----:com.apple.itunes:musicbrainz artist id, musicbrainz/artist id ]
type: uuid
musicbrainz_recordingid:
aliases: [ ufid:http://musicbrainz.org, musicbrainz_trackid, musicbrainz track id, ----:com.apple.itunes:musicbrainz track id, musicbrainz/track id ]
type: uuid
musicbrainz_trackid:
aliases: [txxx:musicbrainz release track id, musicbrainz_releasetrackid, ----:com.apple.itunes:musicbrainz release track id, musicbrainz/release track id]
type: uuid
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 ]
type: uuid
musicbrainz_albumid:
aliases: [ txxx:musicbrainz album id, musicbrainz_albumid, musicbrainz album id, ----:com.apple.itunes:musicbrainz album id, musicbrainz/album id ]
type: uuid
musicbrainz_releasegroupid:
aliases: [ txxx:musicbrainz release group id, musicbrainz_releasegroupid, ----:com.apple.itunes:musicbrainz release group id, musicbrainz/release group id ]
type: uuid
musicbrainz_composerid:
aliases: [ txxx:musicbrainz composer id, musicbrainz_composerid, musicbrainz_composer_id, ----:com.apple.itunes:musicbrainz composer id, musicbrainz/composer id ]
type: uuid
musicbrainz_lyricistid:
aliases: [ txxx:musicbrainz lyricist id, musicbrainz_lyricistid, musicbrainz_lyricist_id, ----:com.apple.itunes:musicbrainz lyricist id, musicbrainz/lyricist id ]
type: uuid
musicbrainz_directorid:
aliases: [ txxx:musicbrainz director id, musicbrainz_directorid, musicbrainz_director_id, ----:com.apple.itunes:musicbrainz director id, musicbrainz/director id ]
type: uuid
musicbrainz_producerid:
aliases: [ txxx:musicbrainz producer id, musicbrainz_producerid, musicbrainz_producer_id, ----:com.apple.itunes:musicbrainz producer id, musicbrainz/producer id ]
type: uuid
musicbrainz_engineerid:
aliases: [ txxx:musicbrainz engineer id, musicbrainz_engineerid, musicbrainz_engineer_id, ----:com.apple.itunes:musicbrainz engineer id, musicbrainz/engineer id ]
type: uuid
musicbrainz_mixerid:
aliases: [ txxx:musicbrainz mixer id, musicbrainz_mixerid, musicbrainz_mixer_id, ----:com.apple.itunes:musicbrainz mixer id, musicbrainz/mixer id ]
type: uuid
musicbrainz_remixerid:
aliases: [ txxx:musicbrainz remixer id, musicbrainz_remixerid, musicbrainz_remixer_id, ----:com.apple.itunes:musicbrainz remixer id, musicbrainz/remixer id ]
type: uuid
musicbrainz_djmixerid:
aliases: [ txxx:musicbrainz djmixer id, musicbrainz_djmixerid, musicbrainz_djmixer_id, ----:com.apple.itunes:musicbrainz djmixer id, musicbrainz/djmixer id ]
type: uuid
musicbrainz_conductorid:
aliases: [ txxx:musicbrainz conductor id, musicbrainz_conductorid, musicbrainz_conductor_id, ----:com.apple.itunes:musicbrainz conductor id, musicbrainz/conductor id ]
type: uuid
musicbrainz_arrangerid:
aliases: [ txxx:musicbrainz arranger id, musicbrainz_arrangerid, musicbrainz_arranger_id, ----:com.apple.itunes:musicbrainz arranger id, musicbrainz/arranger id ]
type: uuid
releasetype:
aliases: [ txxx:musicbrainz album type, releasetype, musicbrainz_albumtype, ----:com.apple.itunes:musicbrainz album type, musicbrainz/album type ]
album: true
split: [ "," ]
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 ]
r128_album_gain:
aliases: [r128_album_gain]
r128_track_gain:
aliases: [r128_track_gain]
performer:
aliases: [performer]
type: pair
musicbrainz_performerid:
aliases: [ txxx:musicbrainz performer id, musicbrainz_performerid, musicbrainz_performer_id, ----:com.apple.itunes:musicbrainz performer id, musicbrainz/performer id ]
type: pair
explicitstatus:
aliases: [ itunesadvisory, rtng ]
# Additional tags. You can add new tags without the need to modify the code. They will be available as fields
# for smart playlists
additional:
asin:
aliases: [ txxx:asin, asin, ----:com.apple.itunes:asin ]
barcode:
aliases: [ txxx:barcode, barcode, ----:com.apple.itunes:barcode, wm/barcode ]
copyright:
aliases: [ tcop, copyright, cprt, icop ]
encodedby:
aliases: [ tenc, encodedby, ©too, wm/encodedby, ienc ]
encodersettings:
aliases: [ tsse, encodersettings, ----:com.apple.itunes:encodersettings, wm/encodingsettings ]
grouping:
aliases: [ grp1, grouping, ©grp, wm/contentgroupdescription ]
album: true
key:
aliases: [ tkey, key, ----: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 ]
media:
aliases: [ tmed, media, ----:com.apple.itunes:media, wm/media, imed ]
album: true
movementname:
aliases: [ mvnm, movementname, ©mvn ]
movementtotal:
aliases: [ movementtotal, mvc ]
movement:
aliases: [ mvin, movement, mvi ]
recordlabel:
aliases: [ tpub, label, publisher, ----:com.apple.itunes:label, wm/publisher, organization ]
album: true
musicbrainz_discid:
aliases: [ txxx:musicbrainz disc id, musicbrainz_discid, musicbrainz disc id, ----:com.apple.itunes:musicbrainz disc id, musicbrainz/disc id ]
type: uuid
musicbrainz_workid:
aliases: [ txxx:musicbrainz work id, musicbrainz_workid, musicbrainz work id, ----:com.apple.itunes:musicbrainz work id, musicbrainz/work id ]
type: uuid
releasecountry:
aliases: [ txxx:musicbrainz album release country, releasecountry, ----:com.apple.itunes:musicbrainz album release country, musicbrainz/album release country, icnt ]
album: true
releasestatus:
aliases: [ txxx:musicbrainz album status, releasestatus, musicbrainz_albumstatus, ----:com.apple.itunes:musicbrainz album status, musicbrainz/album status ]
album: true
script:
aliases: [ txxx:script, script, ----:com.apple.itunes:script, wm/script ]
subtitle:
aliases: [ tit3, subtitle, ----:com.apple.itunes:subtitle, wm/subtitle ]
website:
aliases: [ woar, website, weblink, wm/authorurl ]
work:
aliases: [ txxx:work, tit1, work, ©wrk, wm/work ]