Compare commits

...

10 Commits

Author SHA1 Message Date
cf5e87e62b feat: improve cover selection algorithm
Some checks failed
Release / Run Release Please (push) Blocked by required conditions
Release / Build, tag, and publish Docker image (push) Blocked by required conditions
Release / Notify IRC (push) Blocked by required conditions
Release / Lint and test (push) Has been cancelled
2024-06-11 18:26:44 +08:00
sentriz
0e45f5e84c feat(subsonic): expose replaygain tags 2024-05-30 11:43:45 +01:00
garfieldairlines.net
259be0edde docs: add example GONIC_EXCLUDE_PATTERN usage (#505)
+ regex examples for GONIC_EXCLUDE_PATTERN
2024-05-23 10:19:19 +00:00
Artem Tarasov
0d7d92d545 chore(docker): update to Alpine 3.19 (#502)
* update to Alpine 3.19

check out utfcpp (taglib2 dependency) from git, because Alpine 3.19
packages incompatible utfcpp 4.0, resulting in build failure

* use utfcpp package and specify the include path
2024-05-20 16:21:04 +00:00
sentriz
14c34c6052 2024-05-16 13:24:12 +02:00
sentriz
86fd590556 scanner: use create time if we have it 2024-05-15 17:07:44 +02:00
xxxserxxx
f5893ea5ea feat(playlist): assume playlists in the root dir without a user dir belong to admin (#499) 2024-05-01 15:32:51 +00:00
sentriz
559c9106b0 remove redundant handlerutil.Redirect 2024-04-28 13:32:41 +01:00
sentriz
6ba342c770 chore: bump deps 2024-04-28 13:32:40 +01:00
brian-doherty
93ce039963 feat(scanner): support full scan cleanups in watcher (#496)
* Added code to trigger rescan of entire tree upon file removal to clean as needed.

* Simplified ScanOptions initialization. Removed broken linter.

* gofmt fix
2024-04-21 00:04:53 +00:00
29 changed files with 557 additions and 277 deletions

View File

@@ -45,7 +45,6 @@ linters:
- makezero - makezero
- mirror - mirror
- misspell - misspell
- musttag
- nakedret - nakedret
- nestif - nestif
- nilerr - nilerr

View File

@@ -1,4 +1,4 @@
FROM alpine:3.18 AS builder-taglib FROM alpine:3.19 AS builder-taglib
WORKDIR /tmp WORKDIR /tmp
COPY alpine/taglib/APKBUILD . COPY alpine/taglib/APKBUILD .
RUN apk update && \ RUN apk update && \
@@ -26,7 +26,7 @@ RUN go mod download
COPY . . COPY . .
RUN GOOS=linux go build -o gonic cmd/gonic/gonic.go RUN GOOS=linux go build -o gonic cmd/gonic/gonic.go
FROM alpine:3.18 FROM alpine:3.19
LABEL org.opencontainers.image.source https://github.com/sentriz/gonic LABEL org.opencontainers.image.source https://github.com/sentriz/gonic
RUN apk add -U --no-cache \ RUN apk add -U --no-cache \
ffmpeg \ ffmpeg \

View File

@@ -67,7 +67,7 @@ password can then be changed from the web interface
| `GONIC_JUKEBOX_ENABLED` | `-jukebox-enabled` | **optional** whether the subsonic [jukebox api](https://airsonic.github.io/docs/jukebox/) should be enabled | | `GONIC_JUKEBOX_ENABLED` | `-jukebox-enabled` | **optional** whether the subsonic [jukebox api](https://airsonic.github.io/docs/jukebox/) should be enabled |
| `GONIC_JUKEBOX_MPV_EXTRA_ARGS` | `-jukebox-mpv-extra-args` | **optional** extra command line arguments to pass to the jukebox mpv daemon | | `GONIC_JUKEBOX_MPV_EXTRA_ARGS` | `-jukebox-mpv-extra-args` | **optional** extra command line arguments to pass to the jukebox mpv daemon |
| `GONIC_PODCAST_PURGE_AGE` | `-podcast-purge-age` | **optional** age (in days) to purge podcast episodes if not accessed | | `GONIC_PODCAST_PURGE_AGE` | `-podcast-purge-age` | **optional** age (in days) to purge podcast episodes if not accessed |
| `GONIC_EXCLUDE_PATTERN` | `-exclude-pattern` | **optional** files matching this regex pattern will not be imported | | `GONIC_EXCLUDE_PATTERN` | `-exclude-pattern` | **optional** files matching this regex pattern will not be imported. eg <code>@eaDir\|[aA]rtwork\|[cC]overs\|[sS]cans\|[sS]pectrals</code> |
| `GONIC_MULTI_VALUE_GENRE` | `-multi-value-genre` | **optional** setting for multi-valued genre tags when scanning ([see more](#multi-valued-tags-v016)) | | `GONIC_MULTI_VALUE_GENRE` | `-multi-value-genre` | **optional** setting for multi-valued genre tags when scanning ([see more](#multi-valued-tags-v016)) |
| `GONIC_MULTI_VALUE_ARTIST` | `-multi-value-artist` | **optional** setting for multi-valued artist tags when scanning ([see more](#multi-valued-tags-v016)) | | `GONIC_MULTI_VALUE_ARTIST` | `-multi-value-artist` | **optional** setting for multi-valued artist tags when scanning ([see more](#multi-valued-tags-v016)) |
| `GONIC_MULTI_VALUE_ALBUM_ARTIST` | `-multi-value-album-artist` | **optional** setting for multi-valued album artist tags when scanning ([see more](#multi-valued-tags-v016)) | | `GONIC_MULTI_VALUE_ALBUM_ARTIST` | `-multi-value-album-artist` | **optional** setting for multi-valued album artist tags when scanning ([see more](#multi-valued-tags-v016)) |
@@ -97,7 +97,7 @@ the available modes are:
gonic supports multiple music folders. this can be handy if you have your music separated by albums, compilations, singles. or maybe 70s, 80s, 90s. whatever. gonic supports multiple music folders. this can be handy if you have your music separated by albums, compilations, singles. or maybe 70s, 80s, 90s. whatever.
on top of that - if you don't decide your folder names, or simply do not want the same name in your subsonic client, on top of that - if you don't decide your folder names, or simply do not want the same name in your subsonic client,
gonic can parse aliases for the folder names with the optional `ALIAS->PATH` syntax gonic can parse aliases for the folder names with the optional `ALIAS->PATH` syntax
if you're running gonic with the command line, stack the `-music-path` arg if you're running gonic with the command line, stack the `-music-path` arg

View File

@@ -1,7 +1,7 @@
# Contributor: Leo <thinkabit.ukim@gmail.com> # Contributor: Leo <thinkabit.ukim@gmail.com>
# Maintainer: Natanael Copa <ncopa@alpinelinux.org> # Maintainer: Natanael Copa <ncopa@alpinelinux.org>
pkgname=taglib2 pkgname=taglib2
pkgver=2.0 pkgver=2.0.1
pkgrel=0 pkgrel=0
pkgdesc="Library for reading and editing metadata of several popular audio formats" pkgdesc="Library for reading and editing metadata of several popular audio formats"
url="https://taglib.github.io/" url="https://taglib.github.io/"
@@ -30,10 +30,11 @@ build() {
-DCMAKE_BUILD_TYPE=MinSizeRel \ -DCMAKE_BUILD_TYPE=MinSizeRel \
-DWITH_ZLIB=ON \ -DWITH_ZLIB=ON \
-DBUILD_SHARED_LIBS=ON \ -DBUILD_SHARED_LIBS=ON \
-DBUILD_EXAMPLES=ON \ -DBUILD_EXAMPLES=OFF \
-DBUILD_TESTING="$(want_check && echo ON || echo OFF)" \ -DBUILD_TESTING="$(want_check && echo ON || echo OFF)" \
-DVISIBILITY_HIDDEN=ON -DVISIBILITY_HIDDEN=ON
cmake --build build CPLUS_INCLUDE_PATH="/usr/include/utf8cpp" \
cmake --build build
} }
check() { check() {
@@ -51,5 +52,5 @@ _lib() {
} }
sha512sums=" sha512sums="
099d02b2eab033f5702a8cb03e70752d7523c6f8c2f3eebdd0bcd939eafbdca3f2a6c82452983904b5822cfa45f2707ed866c3419508df9d43bf5c0b3a476f6c taglib-2.0.tar.gz 25ee89293a96d7f8dca6276f822bdaef01fd98503b78c20ffeac8e1d9821de7273a5127146aa798d304c6a995cb2b7229a205aff1cc261b5d4fa9e499dda0439 taglib-2.0.1.tar.gz
" "

View File

@@ -273,7 +273,7 @@ func main() {
mux.Handle("/admin/", http.StripPrefix("/admin", chain(ctrlAdmin))) mux.Handle("/admin/", http.StripPrefix("/admin", chain(ctrlAdmin)))
mux.Handle("/rest/", http.StripPrefix("/rest", chain(trim(ctrlSubsonic)))) mux.Handle("/rest/", http.StripPrefix("/rest", chain(trim(ctrlSubsonic))))
mux.Handle("/ping", chain(handlerutil.Message("ok"))) mux.Handle("/ping", chain(handlerutil.Message("ok")))
mux.Handle("/", chain(handlerutil.Redirect(resolveProxyPath("/admin/home")))) mux.Handle("/", chain(http.RedirectHandler(resolveProxyPath("/admin/home"), http.StatusSeeOther)))
if *confExpvar { if *confExpvar {
mux.Handle("/debug/vars", expvar.Handler()) mux.Handle("/debug/vars", expvar.Handler())

View File

@@ -235,9 +235,15 @@ type Track struct {
TagTrackNumber int `sql:"default: null"` TagTrackNumber int `sql:"default: null"`
TagDiscNumber int `sql:"default: null"` TagDiscNumber int `sql:"default: null"`
TagBrainzID string `sql:"default: null"` TagBrainzID string `sql:"default: null"`
TrackStar *TrackStar
TrackRating *TrackRating ReplayGainTrackGain float32
AverageRating float64 `sql:"default: null"` ReplayGainTrackPeak float32
ReplayGainAlbumGain float32
ReplayGainAlbumPeak float32
TrackStar *TrackStar
TrackRating *TrackRating
AverageRating float64 `sql:"default: null"`
} }
func (t *Track) AudioLength() int { return t.Length } func (t *Track) AudioLength() int { return t.Length }

View File

@@ -72,6 +72,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
construct(ctx, "202311072309", migrateAlbumInfo), construct(ctx, "202311072309", migrateAlbumInfo),
construct(ctx, "202311082304", migrateTemporaryDisplayAlbumArtist), construct(ctx, "202311082304", migrateTemporaryDisplayAlbumArtist),
construct(ctx, "202312110003", migrateAddExtraIndexes), construct(ctx, "202312110003", migrateAddExtraIndexes),
construct(ctx, "202405301140", migrateAddReplayGainFields),
} }
return gormigrate. return gormigrate.
@@ -813,3 +814,7 @@ func migrateAddExtraIndexes(tx *gorm.DB, _ MigrationContext) error {
CREATE INDEX idx_artist_appearances_album_id ON "artist_appearances" (album_id); CREATE INDEX idx_artist_appearances_album_id ON "artist_appearances" (album_id);
`).Error `).Error
} }
func migrateAddReplayGainFields(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(Track{}).Error
}

22
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/andybalholm/cascadia v1.3.2 github.com/andybalholm/cascadia v1.3.2
github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37 github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/djherbis/times v1.6.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/fatih/structs v1.1.0 github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
@@ -15,20 +16,20 @@ require (
github.com/gorilla/securecookie v1.1.2 github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414 github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
github.com/josephburnett/jd v1.7.1 github.com/josephburnett/jd v1.8.1
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/mmcdole/gofeed v1.3.0 github.com/mmcdole/gofeed v1.3.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/philippta/go-template v0.0.0-20220911145045-4556aca435e4 github.com/philippta/go-template v0.0.0-20220911145045-4556aca435e4
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be
github.com/sentriz/audiotags v0.0.0-20240305214804-7a32981c18f8 github.com/sentriz/audiotags v0.0.0-20240401192409-5a8ac2a2974f
github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
go.senan.xyz/flagconf v0.1.7 go.senan.xyz/flagconf v0.1.8
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 golang.org/x/net v0.24.0
golang.org/x/net v0.22.0 golang.org/x/sync v0.7.0
golang.org/x/sync v0.6.0 golang.org/x/sys v0.19.0
gopkg.in/gormigrate.v1 v1.6.0 gopkg.in/gormigrate.v1 v1.6.0
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
) )
@@ -38,8 +39,8 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect
github.com/PuerkitoBio/goquery v1.9.1 // indirect github.com/PuerkitoBio/goquery v1.9.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
@@ -59,9 +60,8 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.22.0 // indirect
golang.org/x/image v0.15.0 // indirect golang.org/x/image v0.15.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

67
go.sum
View File

@@ -6,8 +6,6 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.9.0 h1:zgjKkdpRY9T97Q5DCtcXwfqkcylSFIVCocZmn2huTp8=
github.com/PuerkitoBio/goquery v1.9.0/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI= github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY= github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@@ -23,6 +21,8 @@ github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37 h1:s+qNFsO3VsdsKro
github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37/go.mod h1:CXCwawNJCtFDip7gvbaQVgw0cGjldpyHDIp7oA5prOg= github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37/go.mod h1:CXCwawNJCtFDip7gvbaQVgw0cGjldpyHDIp7oA5prOg=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
@@ -31,10 +31,10 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -75,8 +75,8 @@ github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josephburnett/jd v1.7.1 h1:oXBPMS+SNnILTMGj1fWLK9pexpeJUXtbVFfRku/PjBU= github.com/josephburnett/jd v1.8.1 h1:U4wae4kEvduCmf5mlXJ3uKnfHFmGhwttEFkQ6rsoDMk=
github.com/josephburnett/jd v1.7.1/go.mod h1:R8ZnZnLt2D4rhW4NvBc/USTo6mzyNT6fYNIIWOJA9GY= github.com/josephburnett/jd v1.8.1/go.mod h1:d9nEP87VBIx8SxhIVraVdEU/IwZ7JH6kHWjZMByRq2M=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -104,12 +104,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mmcdole/gofeed v1.2.1 h1:tPbFN+mfOLcM1kDF1x2c/N68ChbdBatkppdzf/vDe1s=
github.com/mmcdole/gofeed v1.2.1/go.mod h1:2wVInNpgmC85q16QTTuwbuKxtKkHLCDDtf0dCmnrNr4=
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
github.com/mmcdole/goxpp v1.1.0 h1:WwslZNF7KNAXTFuzRtn/OKZxFLJAAyOA9w82mDz2ZGI=
github.com/mmcdole/goxpp v1.1.0/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8= github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8=
github.com/mmcdole/goxpp v1.1.1/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= github.com/mmcdole/goxpp v1.1.1/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -132,36 +128,26 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sentriz/audiotags v0.0.0-20240202193907-618ae39d7743 h1:aecPwcrY8mYmZmd9XgQcG8aILRuhRxeQMSunnr6DQ3U= github.com/sentriz/audiotags v0.0.0-20240401192409-5a8ac2a2974f h1:Yio3vmGw+yf+gzjYLf1plSGEf/1IUTVY45n+qcGJEmk=
github.com/sentriz/audiotags v0.0.0-20240202193907-618ae39d7743/go.mod h1:Zoo4UP5t2ySbPwScJfoydAlLLBonoqntv4ovA1T91Z8= github.com/sentriz/audiotags v0.0.0-20240401192409-5a8ac2a2974f/go.mod h1:+pmkMFDEXJuu/u4h2OYJVfYF2qIhXJD7kqvWq6q5Zo0=
github.com/sentriz/audiotags v0.0.0-20240305214804-7a32981c18f8 h1:WBzwq2r567WlnfYravpwUdsAzaXedbWLypXyArLGgI4=
github.com/sentriz/audiotags v0.0.0-20240305214804-7a32981c18f8/go.mod h1:+pmkMFDEXJuu/u4h2OYJVfYF2qIhXJD7kqvWq6q5Zo0=
github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 h1:sLILANWN76ja66/K4k/mBqJuCjDZaM67w+Ru6rEB0s0= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 h1:sLILANWN76ja66/K4k/mBqJuCjDZaM67w+Ru6rEB0s0=
github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981/go.mod h1:Rx8XB1ck+so+41uu9VY1gMKs1CPQ2NTq0pzf+OCCQHo= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981/go.mod h1:Rx8XB1ck+so+41uu9VY1gMKs1CPQ2NTq0pzf+OCCQHo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.senan.xyz/flagconf v0.1.5 h1:5HTNpA5jzH1XnsyR79pClXf9T+V+6OL/IsESORMrExs= go.senan.xyz/flagconf v0.1.8 h1:0HadvAEXHYJOGGdO6cHz2Ok4vWawaM64m5ldSjLoVUw=
go.senan.xyz/flagconf v0.1.5/go.mod h1:CGD/sgYWiTacz1ojgsQRwErqLxtShWMpBxxnsJI6yaE= go.senan.xyz/flagconf v0.1.8/go.mod h1:NqOFfSwJvNWXOTUabcRZ8mPK9+sJmhStJhqtEt74wNQ=
go.senan.xyz/flagconf v0.1.7 h1:+o9Cg3WyzCG+KSfZAwOP61dTWSzGhfH3W+zz9mbNJOA=
go.senan.xyz/flagconf v0.1.7/go.mod h1:CGD/sgYWiTacz1ojgsQRwErqLxtShWMpBxxnsJI6yaE=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
@@ -177,28 +163,25 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -214,8 +197,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -56,12 +56,6 @@ func BasicCORS(next http.Handler) http.Handler {
}) })
} }
func Redirect(to string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, to, http.StatusSeeOther)
})
}
func Message(message string) http.Handler { func Message(message string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, message) fmt.Fprintln(w, message)

View File

@@ -11,6 +11,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -18,7 +19,6 @@ import (
"github.com/dexterlb/mpvipc" "github.com/dexterlb/mpvipc"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"golang.org/x/exp/slices"
) )
var ( var (

View File

@@ -362,8 +362,14 @@ func (i *TagInfo) Genres() []string { return []string{i.RawGenre} }
func (i *TagInfo) TrackNumber() int { return 1 } func (i *TagInfo) TrackNumber() int { return 1 }
func (i *TagInfo) DiscNumber() int { return 1 } func (i *TagInfo) DiscNumber() int { return 1 }
func (i *TagInfo) Year() int { return 2021 } func (i *TagInfo) Year() int { return 2021 }
func (i *TagInfo) Length() int { return firstInt(100, i.RawLength) }
func (i *TagInfo) Bitrate() int { return firstInt(100, i.RawBitrate) } func (i *TagInfo) ReplayGainTrackGain() float32 { return 0 }
func (i *TagInfo) ReplayGainTrackPeak() float32 { return 0 }
func (i *TagInfo) ReplayGainAlbumGain() float32 { return 0 }
func (i *TagInfo) ReplayGainAlbumPeak() float32 { return 0 }
func (i *TagInfo) Length() int { return firstInt(100, i.RawLength) }
func (i *TagInfo) Bitrate() int { return firstInt(100, i.RawBitrate) }
var _ tagcommon.Reader = (*tagReader)(nil) var _ tagcommon.Reader = (*tagReader)(nil)

View File

@@ -99,7 +99,7 @@ func (s *Store) Read(relPath string) (*Playlist, error) {
playlist.UserID, err = userIDFromPath(relPath) playlist.UserID, err = userIDFromPath(relPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("convert id to str: %w", err) playlist.UserID = 1
} }
playlist.Name = strings.TrimSuffix(filepath.Base(relPath), filepath.Ext(relPath)) playlist.Name = strings.TrimSuffix(filepath.Base(relPath), filepath.Ext(relPath))

View File

@@ -0,0 +1,83 @@
package coverresolve
import (
"regexp"
"sort"
"strconv"
"strings"
)
var DefaultKeywords = []string{
"cover",
"folder",
"front",
"albumart",
"album",
"artist",
"scan",
}
// Helper function to extract the number from the filename
func extractNumber(filename string) int {
re := regexp.MustCompile(`\d+`)
matches := re.FindAllString(filename, -1)
if len(matches) == 0 {
return 0
}
num, _ := strconv.Atoi(matches[0])
return num
}
type CoverAlternative struct {
Name string
Score int
}
func SelectCover(covers []string) string {
if len(covers) == 0 {
return ""
}
coverAlternatives := make([]CoverAlternative, 0)
for _, keyword := range DefaultKeywords {
if len(coverAlternatives) > 0 {
break
}
for _, cover := range covers {
if strings.Contains(strings.ToLower(cover), keyword) {
coverAlternatives = append(coverAlternatives, CoverAlternative{
Name: cover,
Score: 0,
})
}
}
}
// parse the integer from the filename
// eg. cover(1).jpg will have higher score than cover(114514).jpg
for i := range coverAlternatives {
coverAlternatives[i].Score -= extractNumber(coverAlternatives[i].Name)
}
// sort by score
sort.Slice(coverAlternatives, func(i, j int) bool {
return coverAlternatives[i].Score > coverAlternatives[j].Score
})
if len(coverAlternatives) == 0 {
return covers[0]
}
return coverAlternatives[0].Name
}
func IsCover(name string) bool {
for _, ext := range []string{"jpg", "jpeg", "png", "bmp", "gif"} {
if strings.HasSuffix(strings.ToLower(name), "."+ext) {
return true
}
}
return false
}

View File

@@ -0,0 +1,85 @@
package coverresolve
import (
"testing"
)
func TestIsCover(t *testing.T) {
tests := []struct {
name string
filename string
expected bool
}{
{"JPEG file", "Image.jpg", true},
{"JPEG file", "image.jpg", true},
{"PNG file", "picture.png", true},
{"BMP file", "photo.bmp", true},
{"GIF file", "animation.gif", true},
{"Non-image file", "document.pdf", false},
{"Empty file name", "", false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := IsCover(test.filename)
if result != test.expected {
t.Errorf("Expected IsCover(%q) to be %v, but got %v", test.filename, test.expected, result)
}
})
}
}
func TestSelectCover(t *testing.T) {
tests := []struct {
name string
covers []string
expected string
}{
{
name: "Empty covers slice",
covers: []string{},
expected: "",
},
{
name: "Covers without keywords or numbers case sensitive",
covers: []string{"Cover1.jpg", "cover2.png"},
expected: "Cover1.jpg",
},
{
name: "Covers without keywords or numbers",
covers: []string{"cover1.jpg", "cover2.png"},
expected: "cover1.jpg",
},
{
name: "Covers with keywords and numbers",
covers: []string{"cover12.jpg", "cover2.png", "special_cover1.jpg"},
expected: "special_cover1.jpg",
},
{
name: "Covers with keywords but without numbers",
covers: []string{"cover12.jpg", "cover_keyword.png"},
expected: "cover_keyword.png",
},
{
name: "Covers without keywords but with numbers",
covers: []string{"cover1.jpg", "cover12.png"},
expected: "cover1.jpg",
},
{
name: "Covers with same highest score",
covers: []string{"cover1.jpg", "cover2.jpg", "cover_special.jpg"},
expected: "cover_special.jpg",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Mock the DefaultScoreRules
result := SelectCover(test.covers)
if result != test.expected {
t.Errorf("Expected SelectCover(%v) to be %q, but got %q", test.covers, test.expected, result)
}
})
}
}

View File

@@ -17,12 +17,14 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/djherbis/times"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/rainycape/unidecode" "github.com/rainycape/unidecode"
"go.senan.xyz/gonic/db" "go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/fileutil" "go.senan.xyz/gonic/fileutil"
"go.senan.xyz/gonic/scanner/coverresolve"
"go.senan.xyz/gonic/tags/tagcommon" "go.senan.xyz/gonic/tags/tagcommon"
) )
@@ -134,9 +136,18 @@ func (s *Scanner) ExecuteWatch(ctx context.Context) error {
} }
batchSeen := map[string]struct{}{} batchSeen := map[string]struct{}{}
batchClean := false
for { for {
select { select {
case <-batchT.C: case <-batchT.C:
if batchClean {
if _, err := s.ScanAndClean(ScanOptions{}); err != nil {
log.Printf("error scanning: %v", err)
}
clear(batchSeen)
batchClean = false
break
}
if !s.StartScanning() { if !s.StartScanning() {
break break
} }
@@ -164,6 +175,10 @@ func (s *Scanner) ExecuteWatch(ctx context.Context) error {
clear(batchSeen) clear(batchSeen)
case event := <-watcher.Events: case event := <-watcher.Events:
if event.Op&(fsnotify.Remove) == fsnotify.Remove {
batchClean = true
break
}
if event.Op&(fsnotify.Create|fsnotify.Write) == 0 { if event.Op&(fsnotify.Create|fsnotify.Write) == 0 {
break break
} }
@@ -252,7 +267,7 @@ func (s *Scanner) scanDir(tx *db.DB, st *State, absPath string) error {
} }
var tracks []string var tracks []string
var cover string var covers []string
for _, item := range items { for _, item := range items {
absPath := filepath.Join(absPath, item.Name()) absPath := filepath.Join(absPath, item.Name())
if s.excludePattern != nil && s.excludePattern.MatchString(absPath) { if s.excludePattern != nil && s.excludePattern.MatchString(absPath) {
@@ -263,8 +278,8 @@ func (s *Scanner) scanDir(tx *db.DB, st *State, absPath string) error {
continue continue
} }
if isCover(item.Name()) { if coverresolve.IsCover(item.Name()) {
cover = item.Name() covers = append(covers, item.Name())
continue continue
} }
if s.tagReader.CanRead(absPath) { if s.tagReader.CanRead(absPath) {
@@ -273,6 +288,8 @@ func (s *Scanner) scanDir(tx *db.DB, st *State, absPath string) error {
} }
} }
cover := coverresolve.SelectCover(covers)
pdir, pbasename := filepath.Split(filepath.Dir(relPath)) pdir, pbasename := filepath.Split(filepath.Dir(relPath))
var parent db.Album var parent db.Album
if err := tx.Where("root_dir=? AND left_path=? AND right_path=?", musicDir, pdir, pbasename).Assign(db.Album{RootDir: musicDir, LeftPath: pdir, RightPath: pbasename}).FirstOrCreate(&parent).Error; err != nil { if err := tx.Where("root_dir=? AND left_path=? AND right_path=?", musicDir, pdir, pbasename).Assign(db.Album{RootDir: musicDir, LeftPath: pdir, RightPath: pbasename}).FirstOrCreate(&parent).Error; err != nil {
@@ -301,9 +318,10 @@ func (s *Scanner) scanDir(tx *db.DB, st *State, absPath string) error {
} }
func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db.Album, basename string, absPath string) error { func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db.Album, basename string, absPath string) error {
stat, err := os.Stat(absPath) // useful to get the real create/birth time for filesystems and kernels which support it
timeSpec, err := times.Stat(absPath)
if err != nil { if err != nil {
return fmt.Errorf("stating %q: %w", basename, err) return fmt.Errorf("get times %q: %w", basename, err)
} }
var track db.Track var track db.Track
@@ -311,7 +329,7 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db
return fmt.Errorf("query track: %w", err) return fmt.Errorf("query track: %w", err)
} }
if !st.isFull && track.ID != 0 && stat.ModTime().Before(track.UpdatedAt) { if !st.isFull && track.ID != 0 && timeSpec.ModTime().Before(track.UpdatedAt) {
st.seenTracks[track.ID] = struct{}{} st.seenTracks[track.ID] = struct{}{}
return nil return nil
} }
@@ -350,7 +368,11 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db
return fmt.Errorf("populate track artists: %w", err) return fmt.Errorf("populate track artists: %w", err)
} }
if err := populateAlbum(tx, album, trags, stat.ModTime()); err != nil { modTime, createTime := timeSpec.ModTime(), timeSpec.ModTime()
if timeSpec.HasBirthTime() {
createTime = timeSpec.BirthTime()
}
if err := populateAlbum(tx, album, trags, modTime, createTime); err != nil {
return fmt.Errorf("populate album: %w", err) return fmt.Errorf("populate album: %w", err)
} }
@@ -359,6 +381,10 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db
} }
} }
stat, err := os.Stat(absPath)
if err != nil {
return fmt.Errorf("stating %q: %w", basename, err)
}
if err := populateTrack(tx, album, &track, trags, basename, int(stat.Size())); err != nil { if err := populateTrack(tx, album, &track, trags, basename, int(stat.Size())); err != nil {
return fmt.Errorf("process %q: %w", basename, err) return fmt.Errorf("process %q: %w", basename, err)
} }
@@ -389,7 +415,7 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, st *State, i int, album *db
return nil return nil
} }
func populateAlbum(tx *db.DB, album *db.Album, trags tagcommon.Info, modTime time.Time) error { func populateAlbum(tx *db.DB, album *db.Album, trags tagcommon.Info, modTime, createTime time.Time) error {
albumName := tagcommon.MustAlbum(trags) albumName := tagcommon.MustAlbum(trags)
album.TagTitle = albumName album.TagTitle = albumName
album.TagTitleUDec = decoded(albumName) album.TagTitleUDec = decoded(albumName)
@@ -398,8 +424,8 @@ func populateAlbum(tx *db.DB, album *db.Album, trags tagcommon.Info, modTime tim
album.TagYear = trags.Year() album.TagYear = trags.Year()
album.ModifiedAt = modTime album.ModifiedAt = modTime
if album.CreatedAt.After(modTime) { if album.CreatedAt.After(createTime) {
album.CreatedAt = modTime // reset created at to match filesytem for new albums album.CreatedAt = createTime // reset created at to match filesytem for new albums
} }
if err := tx.Save(&album).Error; err != nil { if err := tx.Save(&album).Error; err != nil {
@@ -446,8 +472,14 @@ func populateTrack(tx *db.DB, album *db.Album, track *db.Track, trags tagcommon.
track.TagDiscNumber = trags.DiscNumber() track.TagDiscNumber = trags.DiscNumber()
track.TagBrainzID = trags.BrainzID() track.TagBrainzID = trags.BrainzID()
track.Length = trags.Length() // these two should be calculated track.ReplayGainTrackGain = trags.ReplayGainTrackGain()
track.Bitrate = trags.Bitrate() // ...from the file instead of tags track.ReplayGainTrackPeak = trags.ReplayGainTrackPeak()
track.ReplayGainAlbumGain = trags.ReplayGainAlbumGain()
track.ReplayGainAlbumPeak = trags.ReplayGainAlbumPeak()
// these two are calculated from the file instead of tags
track.Length = trags.Length()
track.Bitrate = trags.Bitrate()
if err := tx.Save(&track).Error; err != nil { if err := tx.Save(&track).Error; err != nil {
return fmt.Errorf("saving track: %w", err) return fmt.Errorf("saving track: %w", err)
@@ -642,26 +674,6 @@ func (s *Scanner) cleanGenres(st *State) error { //nolint:unparam
return nil return nil
} }
//nolint:gochecknoglobals
var coverNames = map[string]struct{}{}
//nolint:gochecknoinits
func init() {
for _, name := range []string{"cover", "folder", "front", "albumart", "album", "artist"} {
for _, ext := range []string{"jpg", "jpeg", "png", "bmp", "gif"} {
coverNames[fmt.Sprintf("%s.%s", name, ext)] = struct{}{}
for i := 0; i < 3; i++ {
coverNames[fmt.Sprintf("%s.%d.%s", name, i, ext)] = struct{}{} // support beets extras
}
}
}
}
func isCover(name string) bool {
_, ok := coverNames[strings.ToLower(name)]
return ok
}
// decoded converts a string to it's latin equivalent. // decoded converts a string to it's latin equivalent.
// it will be used by the model's *UDec fields, and is only set if it // it will be used by the model's *UDec fields, and is only set if it
// differs from the original. the fields are used for searching. // differs from the original. the fields are used for searching.

View File

@@ -102,6 +102,14 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
for _, a := range t.Artists { for _, a := range t.Artists {
trCh.Artists = append(trCh.Artists, &ArtistRef{ID: a.SID(), Name: a.Name}) trCh.Artists = append(trCh.Artists, &ArtistRef{ID: a.SID(), Name: a.Name})
} }
if t.ReplayGainTrackGain != 0 || t.ReplayGainAlbumGain != 0 {
trCh.ReplayGain = &ReplayGain{
TrackGain: t.ReplayGainTrackGain,
TrackPeak: t.ReplayGainTrackPeak,
AlbumGain: t.ReplayGainAlbumGain,
AlbumPeak: t.ReplayGainAlbumPeak,
}
}
return trCh return trCh
} }

View File

@@ -112,6 +112,14 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
for _, a := range album.Artists { for _, a := range album.Artists {
ret.AlbumArtists = append(ret.AlbumArtists, &ArtistRef{ID: a.SID(), Name: a.Name}) ret.AlbumArtists = append(ret.AlbumArtists, &ArtistRef{ID: a.SID(), Name: a.Name})
} }
if t.ReplayGainTrackGain != 0 || t.ReplayGainAlbumGain != 0 {
ret.ReplayGain = &ReplayGain{
TrackGain: t.ReplayGainTrackGain,
TrackPeak: t.ReplayGainTrackPeak,
AlbumGain: t.ReplayGainAlbumGain,
AlbumPeak: t.ReplayGainAlbumPeak,
}
}
return ret return ret
} }

View File

@@ -169,6 +169,13 @@ type TranscodeMeta struct {
TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"` TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"`
} }
type ReplayGain struct {
TrackGain float32 `xml:"trackGain,attr" json:"trackGain"`
TrackPeak float32 `xml:"trackPeak,attr" json:"trackPeak"`
AlbumGain float32 `xml:"albumGain,attr" json:"albumGain"`
AlbumPeak float32 `xml:"albumPeak,attr" json:"albumPeak"`
}
// https://opensubsonic.netlify.app/docs/responses/child/ // https://opensubsonic.netlify.app/docs/responses/child/
type TrackChild struct { type TrackChild struct {
ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"` ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"`
@@ -211,6 +218,8 @@ type TrackChild struct {
UserRating int `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` UserRating int `xml:"userRating,attr,omitempty" json:"userRating,omitempty"`
AverageRating string `xml:"averageRating,attr,omitempty" json:"averageRating,omitempty"` AverageRating string `xml:"averageRating,attr,omitempty" json:"averageRating,omitempty"`
ReplayGain *ReplayGain `xml:"replayGain" json:"replayGain"`
TranscodeMeta TranscodeMeta
} }

View File

@@ -8,16 +8,16 @@
"albumList": { "albumList": {
"album": [ "album": [
{ {
"id": "al-9", "id": "al-5",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",
"artist": "artist-1", "artist": "artist-0",
"artists": null, "artists": null,
"displayArtist": "", "displayArtist": "",
"title": "album-2", "title": "album-2",
"album": "album-2", "album": "album-2",
"parent": "al-6", "parent": "al-2",
"isDir": true, "isDir": true,
"coverArt": "al-9", "coverArt": "al-5",
"name": "album-2", "name": "album-2",
"songCount": 3, "songCount": 3,
"duration": 300, "duration": 300,
@@ -40,48 +40,16 @@
"playCount": 0 "playCount": 0
}, },
{ {
"id": "al-4", "id": "al-13",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-1",
"album": "album-1",
"parent": "al-2",
"isDir": true,
"coverArt": "al-4",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-12",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",
"artist": "artist-2", "artist": "artist-2",
"artists": null, "artists": null,
"displayArtist": "", "displayArtist": "",
"title": "album-1",
"album": "album-1",
"parent": "al-10",
"isDir": true,
"coverArt": "al-12",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-5",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-2", "title": "album-2",
"album": "album-2", "album": "album-2",
"parent": "al-2", "parent": "al-10",
"isDir": true, "isDir": true,
"coverArt": "al-5", "coverArt": "al-13",
"name": "album-2", "name": "album-2",
"songCount": 3, "songCount": 3,
"duration": 300, "duration": 300,
@@ -103,6 +71,54 @@
"duration": 300, "duration": 300,
"playCount": 0 "playCount": 0
}, },
{
"id": "al-4",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-1",
"album": "album-1",
"parent": "al-2",
"isDir": true,
"coverArt": "al-4",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-9",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-1",
"artists": null,
"displayArtist": "",
"title": "album-2",
"album": "album-2",
"parent": "al-6",
"isDir": true,
"coverArt": "al-9",
"name": "album-2",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-12",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-2",
"artists": null,
"displayArtist": "",
"title": "album-1",
"album": "album-1",
"parent": "al-10",
"isDir": true,
"coverArt": "al-12",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{ {
"id": "al-8", "id": "al-8",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",
@@ -119,22 +135,6 @@
"duration": 300, "duration": 300,
"playCount": 0 "playCount": 0
}, },
{
"id": "al-13",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-2",
"artists": null,
"displayArtist": "",
"title": "album-2",
"album": "album-2",
"parent": "al-10",
"isDir": true,
"coverArt": "al-13",
"name": "album-2",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{ {
"id": "al-11", "id": "al-11",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",

View File

@@ -24,32 +24,16 @@
"year": 2021 "year": 2021
}, },
{ {
"id": "al-5", "id": "al-7",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-1",
"artist": "artist-0",
"artists": [{ "id": "ar-1", "name": "artist-0" }],
"displayArtist": "artist-0",
"title": "album-2",
"album": "album-2",
"coverArt": "al-5",
"name": "album-2",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{
"id": "al-8",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",
"artistId": "ar-2", "artistId": "ar-2",
"artist": "artist-1", "artist": "artist-1",
"artists": [{ "id": "ar-2", "name": "artist-1" }], "artists": [{ "id": "ar-2", "name": "artist-1" }],
"displayArtist": "artist-1", "displayArtist": "artist-1",
"title": "album-1", "title": "album-0",
"album": "album-1", "album": "album-0",
"coverArt": "al-8", "coverArt": "al-7",
"name": "album-1", "name": "album-0",
"songCount": 3, "songCount": 3,
"duration": 300, "duration": 300,
"playCount": 0, "playCount": 0,
@@ -71,38 +55,6 @@
"playCount": 0, "playCount": 0,
"year": 2021 "year": 2021
}, },
{
"id": "al-12",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-3",
"artist": "artist-2",
"artists": [{ "id": "ar-3", "name": "artist-2" }],
"displayArtist": "artist-2",
"title": "album-1",
"album": "album-1",
"coverArt": "al-12",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{
"id": "al-7",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-2",
"artist": "artist-1",
"artists": [{ "id": "ar-2", "name": "artist-1" }],
"displayArtist": "artist-1",
"title": "album-0",
"album": "album-0",
"coverArt": "al-7",
"name": "album-0",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{ {
"id": "al-13", "id": "al-13",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",
@@ -135,6 +87,54 @@
"playCount": 0, "playCount": 0,
"year": 2021 "year": 2021
}, },
{
"id": "al-12",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-3",
"artist": "artist-2",
"artists": [{ "id": "ar-3", "name": "artist-2" }],
"displayArtist": "artist-2",
"title": "album-1",
"album": "album-1",
"coverArt": "al-12",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{
"id": "al-5",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-1",
"artist": "artist-0",
"artists": [{ "id": "ar-1", "name": "artist-0" }],
"displayArtist": "artist-0",
"title": "album-2",
"album": "album-2",
"coverArt": "al-5",
"name": "album-2",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{
"id": "al-8",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-2",
"artist": "artist-1",
"artists": [{ "id": "ar-2", "name": "artist-1" }],
"displayArtist": "artist-1",
"title": "album-1",
"album": "album-1",
"coverArt": "al-8",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0,
"year": 2021
},
{ {
"id": "al-9", "id": "al-9",
"created": "2019-11-30T00:00:00Z", "created": "2019-11-30T00:00:00Z",

View File

@@ -48,7 +48,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-2", "id": "tr-2",
@@ -75,7 +76,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-3", "id": "tr-3",
@@ -102,7 +104,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -33,7 +33,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-2", "id": "tr-2",
@@ -58,7 +59,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-3", "id": "tr-3",
@@ -83,7 +85,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -23,7 +23,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-0", "title": "album-0",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-4", "id": "al-4",
@@ -38,7 +39,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-1", "title": "album-1",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-5", "id": "al-5",
@@ -53,7 +55,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-2", "title": "album-2",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -34,7 +34,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-2", "id": "tr-2",
@@ -63,7 +64,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-3", "id": "tr-3",
@@ -92,7 +94,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-4", "id": "tr-4",
@@ -121,7 +124,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-5", "id": "tr-5",
@@ -150,7 +154,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-6", "id": "tr-6",
@@ -179,7 +184,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-7", "id": "tr-7",
@@ -208,7 +214,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-8", "id": "tr-8",
@@ -237,7 +244,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-9", "id": "tr-9",
@@ -266,7 +274,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-10", "id": "tr-10",
@@ -295,7 +304,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-11", "id": "tr-11",
@@ -324,7 +334,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-12", "id": "tr-12",
@@ -353,7 +364,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-13", "id": "tr-13",
@@ -382,7 +394,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-14", "id": "tr-14",
@@ -411,7 +424,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-15", "id": "tr-15",
@@ -440,7 +454,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-16", "id": "tr-16",
@@ -469,7 +484,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-17", "id": "tr-17",
@@ -498,7 +514,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-18", "id": "tr-18",
@@ -527,7 +544,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-19", "id": "tr-19",
@@ -556,7 +574,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-20", "id": "tr-20",
@@ -585,7 +604,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -20,7 +20,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-0", "title": "album-0",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-4", "id": "al-4",
@@ -35,7 +36,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-1", "title": "album-1",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-5", "id": "al-5",
@@ -50,7 +52,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-2", "parent": "al-2",
"title": "album-2", "title": "album-2",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-7", "id": "al-7",
@@ -65,7 +68,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-6", "parent": "al-6",
"title": "album-0", "title": "album-0",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-8", "id": "al-8",
@@ -80,7 +84,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-6", "parent": "al-6",
"title": "album-1", "title": "album-1",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-9", "id": "al-9",
@@ -95,7 +100,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-6", "parent": "al-6",
"title": "album-2", "title": "album-2",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-11", "id": "al-11",
@@ -110,7 +116,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-10", "parent": "al-10",
"title": "album-0", "title": "album-0",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-12", "id": "al-12",
@@ -125,7 +132,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-10", "parent": "al-10",
"title": "album-1", "title": "album-1",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "al-13", "id": "al-13",
@@ -140,7 +148,8 @@
"isVideo": false, "isVideo": false,
"parent": "al-10", "parent": "al-10",
"title": "album-2", "title": "album-2",
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -30,7 +30,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-2", "id": "tr-2",
@@ -55,7 +56,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-3", "id": "tr-3",
@@ -80,7 +82,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-4", "id": "tr-4",
@@ -105,7 +108,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-5", "id": "tr-5",
@@ -130,7 +134,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-6", "id": "tr-6",
@@ -155,7 +160,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-7", "id": "tr-7",
@@ -180,7 +186,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-8", "id": "tr-8",
@@ -205,7 +212,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-9", "id": "tr-9",
@@ -230,7 +238,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-10", "id": "tr-10",
@@ -255,7 +264,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-11", "id": "tr-11",
@@ -280,7 +290,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-12", "id": "tr-12",
@@ -305,7 +316,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-13", "id": "tr-13",
@@ -330,7 +342,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-14", "id": "tr-14",
@@ -355,7 +368,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-15", "id": "tr-15",
@@ -380,7 +394,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-16", "id": "tr-16",
@@ -405,7 +420,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-17", "id": "tr-17",
@@ -430,7 +446,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-18", "id": "tr-18",
@@ -455,7 +472,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-19", "id": "tr-19",
@@ -480,7 +498,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
}, },
{ {
"id": "tr-20", "id": "tr-20",
@@ -505,7 +524,8 @@
"discNumber": 1, "discNumber": 1,
"type": "music", "type": "music",
"year": 2021, "year": 2021,
"musicBrainzId": "" "musicBrainzId": "",
"replayGain": null
} }
] ]
} }

View File

@@ -24,9 +24,15 @@ type Info interface {
Genres() []string Genres() []string
TrackNumber() int TrackNumber() int
DiscNumber() int DiscNumber() int
Year() int
ReplayGainTrackGain() float32
ReplayGainTrackPeak() float32
ReplayGainAlbumGain() float32
ReplayGainAlbumPeak() float32
Length() int Length() int
Bitrate() int Bitrate() int
Year() int
} }
const ( const (

View File

@@ -51,8 +51,14 @@ func (i *info) Genres() []string { return find(i.raw, "genres") }
func (i *info) TrackNumber() int { return intSep("/", first(find(i.raw, "tracknumber"))) } // eg. 5/12 func (i *info) TrackNumber() int { return intSep("/", first(find(i.raw, "tracknumber"))) } // eg. 5/12
func (i *info) DiscNumber() int { return intSep("/", first(find(i.raw, "discnumber"))) } // eg. 1/2 func (i *info) DiscNumber() int { return intSep("/", first(find(i.raw, "discnumber"))) } // eg. 1/2
func (i *info) Year() int { return intSep("-", first(find(i.raw, "originaldate", "date", "year"))) } // eg. 2023-12-01 func (i *info) Year() int { return intSep("-", first(find(i.raw, "originaldate", "date", "year"))) } // eg. 2023-12-01
func (i *info) Length() int { return i.props.Length }
func (i *info) Bitrate() int { return i.props.Bitrate } func (i *info) ReplayGainTrackGain() float32 { return dB(first(find(i.raw, "replaygain_track_gain"))) }
func (i *info) ReplayGainTrackPeak() float32 { return flt(first(find(i.raw, "replaygain_track_peak"))) }
func (i *info) ReplayGainAlbumGain() float32 { return dB(first(find(i.raw, "replaygain_album_gain"))) }
func (i *info) ReplayGainAlbumPeak() float32 { return flt(first(find(i.raw, "replaygain_album_peak"))) }
func (i *info) Length() int { return i.props.Length }
func (i *info) Bitrate() int { return i.props.Bitrate }
func first[T comparable](is []T) T { func first[T comparable](is []T) T {
var z T var z T
@@ -83,6 +89,17 @@ func filterStr(ss []string) []string {
return r return r
} }
func flt(in string) float32 {
f, _ := strconv.ParseFloat(in, 32)
return float32(f)
}
func dB(in string) float32 {
in = strings.ToLower(in)
in = strings.TrimSuffix(in, " db")
in = strings.TrimSuffix(in, "db")
return flt(in)
}
func intSep(sep, in string) int { func intSep(sep, in string) int {
start, _, _ := strings.Cut(in, sep) start, _, _ := strings.Cut(in, sep)
out, _ := strconv.Atoi(start) out, _ := strconv.Atoi(start)