Compare commits

..

No commits in common. "8faaa3cf917ed75a5bcabc5efa218ea4bd2d14a3" and "be12c12b2842099c56db385488c9fc0197a39f30" have entirely different histories.

26 changed files with 117 additions and 514 deletions

View File

@ -103,7 +103,6 @@ type configOptions struct {
type scannerOptions struct {
Extractor string
GenreSeparators string
GroupAlbumReleases bool
}
type lastfmOptions struct {
@ -298,7 +297,6 @@ func init() {
viper.SetDefault("scanner.extractor", consts.DefaultScannerExtractor)
viper.SetDefault("scanner.genreseparators", ";/,")
viper.SetDefault("scanner.groupalbumreleases", true)
viper.SetDefault("agents", "lastfm,spotify")
viper.SetDefault("lastfm.enabled", true)

View File

@ -94,19 +94,19 @@ var (
"name": "mp3 audio",
"targetFormat": "mp3",
"defaultBitRate": 192,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -f mp3 -",
"command": "ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -f mp3 -",
},
{
"name": "opus audio",
"targetFormat": "opus",
"defaultBitRate": 128,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -c:a libopus -f opus -",
"command": "ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -c:a libopus -f opus -",
},
{
"name": "aac audio",
"targetFormat": "aac",
"defaultBitRate": 256,
"command": "ffmpeg -i %s -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -",
"command": "ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -c:a aac -f adts -",
},
}

View File

@ -1,49 +0,0 @@
package migrations
import (
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigration(upAddRelRecYear, downAddRelRecYear)
}
func upAddRelRecYear(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table media_file
add date varchar(255) default '' not null;
alter table media_file
add original_year int default 0 not null;
alter table media_file
add original_date varchar(255) default '' not null;
alter table media_file
add release_year int default 0 not null;
alter table media_file
add release_date varchar(255) default '' not null;
alter table album
add date varchar(255) default '' not null;
alter table album
add min_original_year int default 0 not null;
alter table album
add max_original_year int default 0 not null;
alter table album
add original_date varchar(255) default '' not null;
alter table album
add release_date varchar(255) default '' not null;
alter table album
add releases integer default 0 not null;
`)
if err != nil {
return err
}
notice(tx, "A full rescan needs to be performed to import more tags")
return forceFullRescan(tx)
}
func downAddRelRecYear(tx *sql.Tx) error {
return nil
}

View File

@ -20,12 +20,6 @@ type Album struct {
AllArtistIDs string `structs:"all_artist_ids" json:"allArtistIds" orm:"column(all_artist_ids)"`
MaxYear int `structs:"max_year" json:"maxYear"`
MinYear int `structs:"min_year" json:"minYear"`
Date string `structs:"date" json:"date,omitempty"`
MaxOriginalYear int `structs:"max_original_year" json:"maxOriginalYear"`
MinOriginalYear int `structs:"min_original_year" json:"minOriginalYear"`
OriginalDate string `structs:"original_date" json:"originalDate,omitempty"`
ReleaseDate string `structs:"release_date" json:"releaseDate,omitempty"`
Releases int `structs:"releases" json:"releases"`
Compilation bool `structs:"compilation" json:"compilation"`
Comment string `structs:"comment" json:"comment,omitempty"`
SongCount int `structs:"song_count" json:"songCount"`
@ -62,7 +56,6 @@ func (a Album) CoverArtID() ArtworkID {
type DiscID struct {
AlbumID string `json:"albumId"`
ReleaseDate string `json:"releaseDate"`
DiscNumber int `json:"discNumber"`
}

View File

@ -15,11 +15,6 @@ var fieldMap = map[string]*mappedField{
"tracknumber": {field: "media_file.track_number"},
"discnumber": {field: "media_file.disc_number"},
"year": {field: "media_file.year"},
"date": {field: "media_file.date"},
"originalyear": {field: "media_file.original_year"},
"originaldate": {field: "media_file.original_date"},
"releaseyear": {field: "media_file.release_year"},
"releasedate": {field: "media_file.release_date"},
"size": {field: "media_file.size"},
"compilation": {field: "media_file.compilation"},
"dateadded": {field: "media_file.created_at"},

View File

@ -3,7 +3,6 @@ package model
import (
"mime"
"path/filepath"
"sort"
"strings"
"time"
@ -33,11 +32,6 @@ type MediaFile struct {
DiscNumber int `structs:"disc_number" json:"discNumber"`
DiscSubtitle string `structs:"disc_subtitle" json:"discSubtitle,omitempty"`
Year int `structs:"year" json:"year"`
Date string `structs:"date" json:"date,omitempty"`
OriginalYear int `structs:"original_year" json:"originalYear"`
OriginalDate string `structs:"original_date" json:"originalDate,omitempty"`
ReleaseYear int `structs:"release_year" json:"releaseYear"`
ReleaseDate string `structs:"release_date" json:"releaseDate,omitempty"`
Size int64 `structs:"size" json:"size"`
Suffix string `structs:"suffix" json:"suffix"`
Duration float32 `structs:"duration" json:"duration"`
@ -114,11 +108,6 @@ func (mfs MediaFiles) ToAlbum() Album {
var songArtistIds []string
var mbzAlbumIds []string
var comments []string
var years []int
var dates []string
var originalYears []int
var originalDates []string
var releaseDates []string
for _, m := range mfs {
// We assume these attributes are all the same for all songs on an album
a.ID = m.AlbumID
@ -141,11 +130,12 @@ func (mfs MediaFiles) ToAlbum() Album {
// Calculated attributes based on aggregations
a.Duration += m.Duration
a.Size += m.Size
years = append(years, m.Year)
dates = append(dates, m.Date)
originalYears = append(originalYears, m.OriginalYear)
originalDates = append(originalDates, m.OriginalDate)
releaseDates = append(releaseDates, m.ReleaseDate)
if a.MinYear == 0 {
a.MinYear = m.Year
} else if m.Year > 0 {
a.MinYear = number.Min(a.MinYear, m.Year)
}
a.MaxYear = number.Max(a.MaxYear, m.Year)
a.UpdatedAt = newer(a.UpdatedAt, m.UpdatedAt)
a.CreatedAt = older(a.CreatedAt, m.CreatedAt)
a.Genres = append(a.Genres, m.Genres...)
@ -161,15 +151,11 @@ func (mfs MediaFiles) ToAlbum() Album {
a.EmbedArtPath = m.Path
}
}
a.Paths = strings.Join(mfs.Dirs(), consts.Zwsp)
a.Date, _ = allOrNothing(dates)
a.OriginalDate, _ = allOrNothing(originalDates)
a.ReleaseDate, a.Releases = allOrNothing(releaseDates)
a.MinYear, a.MaxYear = minMax(years)
a.MinOriginalYear, a.MaxOriginalYear = minMax(originalYears)
a.Comment, _ = allOrNothing(comments)
a.Comment, _ = allOrNothing(comments)
comments = slices.Compact(comments)
if len(comments) == 1 {
a.Comment = comments[0]
}
a.Genre = slice.MostFrequent(a.Genres).Name
slices.SortFunc(a.Genres, func(a, b Genre) bool { return a.ID < b.ID })
a.Genres = slices.Compact(a.Genres)
@ -183,32 +169,6 @@ func (mfs MediaFiles) ToAlbum() Album {
return a
}
func allOrNothing(items []string) (string, int) {
items = slices.Compact(items)
if len(items) == 1 {
return items[0], 1
}
if len(items) > 1 {
sort.Strings(items)
return "", len(slices.Compact(items))
}
return "", 0
}
func minMax(items []int) (int, int) {
var max int = items[0]
var min int = items[0]
for _, value := range items {
max = number.Max(max, value)
if min == 0 {
min = value
} else if value > 0 {
min = number.Min(min, value)
}
}
return min, max
}
func newer(t1, t2 time.Time) time.Time {
if t1.After(t2) {
return t1

View File

@ -26,8 +26,8 @@ func NewMediaFileRepository(ctx context.Context, o orm.QueryExecutor) *mediaFile
r.ormer = o
r.tableName = "media_file"
r.sortMappings = map[string]string{
"artist": "order_artist_name asc, order_album_name asc, release_date asc, disc_number asc, track_number asc",
"album": "order_album_name asc, release_date asc, disc_number asc, track_number asc, order_artist_name asc, title asc",
"artist": "order_artist_name asc, order_album_name asc, disc_number asc, track_number asc",
"album": "order_album_name asc, disc_number asc, track_number asc, order_artist_name asc, title asc",
"random": "RANDOM()",
}
r.filterMappings = map[string]filterFunc{

View File

@ -123,7 +123,7 @@ func (r *playlistTrackRepository) Add(mediaFileIds []string) (int, error) {
}
func (r *playlistTrackRepository) addMediaFileIds(cond Sqlizer) (int, error) {
sq := Select("id").From("media_file").Where(cond).OrderBy("album_artist, album, release_date, disc_number, track_number")
sq := Select("id").From("media_file").Where(cond).OrderBy("album_artist, album, disc_number, track_number")
var ids []string
err := r.queryAll(sq, &ids)
if err != nil {
@ -147,7 +147,7 @@ func (r *playlistTrackRepository) AddDiscs(discs []model.DiscID) (int, error) {
}
var clauses Or
for _, d := range discs {
clauses = append(clauses, And{Eq{"album_id": d.AlbumID}, Eq{"release_date": d.ReleaseDate}, Eq{"disc_number": d.DiscNumber}})
clauses = append(clauses, And{Eq{"album_id": d.AlbumID}, Eq{"disc_number": d.DiscNumber}})
}
return r.addMediaFileIds(clauses)
}

View File

@ -32,10 +32,9 @@ func newMediaFileMapper(rootFolder string, genres model.GenreRepository) *mediaF
func (s mediaFileMapper) toMediaFile(md metadata.Tags) model.MediaFile {
mf := &model.MediaFile{}
mf.ID = s.trackID(md)
mf.Year, mf.Date, mf.OriginalYear, mf.OriginalDate, mf.ReleaseYear, mf.ReleaseDate = s.mapDates(md)
mf.Title = s.mapTrackTitle(md)
mf.Album = md.Album()
mf.AlbumID = s.albumID(md, mf.ReleaseDate)
mf.AlbumID = s.albumID(md)
mf.Album = s.mapAlbumName(md)
mf.ArtistID = s.artistID(md)
mf.Artist = s.mapArtistName(md)
@ -43,6 +42,7 @@ func (s mediaFileMapper) toMediaFile(md metadata.Tags) model.MediaFile {
mf.AlbumArtist = s.mapAlbumArtistName(md)
mf.Genre, mf.Genres = s.mapGenres(md.Genres())
mf.Compilation = md.Compilation()
mf.Year = md.Year()
mf.TrackNumber, _ = md.TrackNumber()
mf.DiscNumber, _ = md.DiscNumber()
mf.DiscSubtitle = md.DiscSubtitle()
@ -128,13 +128,8 @@ func (s mediaFileMapper) trackID(md metadata.Tags) string {
return fmt.Sprintf("%x", md5.Sum([]byte(md.FilePath())))
}
func (s mediaFileMapper) albumID(md metadata.Tags, releaseDate string) string {
func (s mediaFileMapper) albumID(md metadata.Tags) string {
albumPath := strings.ToLower(fmt.Sprintf("%s\\%s", s.mapAlbumArtistName(md), s.mapAlbumName(md)))
if !conf.Server.Scanner.GroupAlbumReleases {
if len(releaseDate) != 0 {
albumPath = fmt.Sprintf("%s\\%s", albumPath, releaseDate)
}
}
return fmt.Sprintf("%x", md5.Sum([]byte(albumPath)))
}
@ -174,18 +169,3 @@ func (s mediaFileMapper) mapGenres(genres []string) (string, model.Genres) {
}
return result[0].Name, result
}
func (s mediaFileMapper) mapDates(md metadata.Tags) (int, string, int, string, int, string) {
year, date := md.Date()
originalYear, originalDate := md.OriginalDate()
releaseYear, releaseDate := md.ReleaseDate()
// MusicBrainz Picard writes the Release Date of an album to the Date tag, and leaves the Release Date tag empty
taggedLikePicard := (originalYear != 0) &&
(releaseYear == 0) &&
(year >= originalYear)
if taggedLikePicard {
return originalYear, originalDate, originalYear, originalDate, year, date
}
return year, date, originalYear, originalDate, releaseYear, releaseDate
}

View File

@ -99,9 +99,7 @@ func (t Tags) SortAlbum() string { return t.getSortTag("", "album") }
func (t Tags) SortArtist() string { return t.getSortTag("", "artist") }
func (t Tags) SortAlbumArtist() string { return t.getSortTag("tso2", "albumartist", "album_artist") }
func (t Tags) Genres() []string { return t.getAllTagValues("genre") }
func (t Tags) Date() (int, string) { return t.getDate("date") }
func (t Tags) OriginalDate() (int, string) { return t.getDate("originaldate") }
func (t Tags) ReleaseDate() (int, string) { return t.getDate("releasedate") }
func (t Tags) Year() int { return t.getYear("date") }
func (t Tags) Comment() string { return t.getFirstTagValue("comment") }
func (t Tags) Lyrics() string { return t.getFirstTagValue("lyrics", "lyrics-eng") }
func (t Tags) Compilation() bool { return t.getBool("tcmp", "compilation") }
@ -219,38 +217,18 @@ func (t Tags) getSortTag(originalTag string, tagNames ...string) string {
var dateRegex = regexp.MustCompile(`([12]\d\d\d)`)
func (t Tags) getDate(tagNames ...string) (int, string) {
func (t Tags) getYear(tagNames ...string) int {
tag := t.getFirstTagValue(tagNames...)
if len(tag) < 4 {
return 0, ""
if tag == "" {
return 0
}
// first get just the year
match := dateRegex.FindStringSubmatch(tag)
if len(match) == 0 {
log.Warn("Error parsing "+tagNames[0]+" field for year", "file", t.filePath, "date", tag)
return 0, ""
log.Warn("Error parsing year date field", "file", t.filePath, "date", tag)
return 0
}
year, _ := strconv.Atoi(match[1])
if len(tag) < 5 {
return year, match[1]
}
//then try YYYY-MM-DD
if len(tag) > 10 {
tag = tag[:10]
}
layout := "2006-01-02"
_, err := time.Parse(layout, tag)
if err != nil {
layout = "2006-01"
_, err = time.Parse(layout, tag)
if err != nil {
log.Warn("Error parsing "+tagNames[0]+" field for month + day", "file", t.filePath, "date", tag)
return year, match[1]
}
}
return year, tag
return year
}
func (t Tags) getBool(tagNames ...string) bool {

View File

@ -6,25 +6,31 @@ import (
)
var _ = Describe("Tags", func() {
DescribeTable("getDate",
func(tag string, expectedYear int, expectedDate string) {
Describe("getYear", func() {
It("parses the year correctly", func() {
var examples = map[string]int{
"1985": 1985,
"2002-01": 2002,
"1969.06": 1969,
"1980.07.25": 1980,
"2004-00-00": 2004,
"2013-May-12": 2013,
"May 12, 2016": 2016,
"01/10/1990": 1990,
}
for tag, expected := range examples {
md := &Tags{}
md.tags = map[string][]string{"date": {tag}}
testYear, testDate := md.Date()
Expect(testYear).To(Equal(expectedYear))
Expect(testDate).To(Equal(expectedDate))
},
Entry(nil, "1985", 1985, "1985"),
Entry(nil, "2002-01", 2002, "2002-01"),
Entry(nil, "1969.06", 1969, "1969"),
Entry(nil, "1980.07.25", 1980, "1980"),
Entry(nil, "2004-00-00", 2004, "2004"),
Entry(nil, "2016-12-31", 2016, "2016-12-31"),
Entry(nil, "2013-May-12", 2013, "2013"),
Entry(nil, "May 12, 2016", 2016, "2016"),
Entry(nil, "01/10/1990", 1990, "1990"),
Entry(nil, "invalid", 0, ""),
)
Expect(md.Year()).To(Equal(expected))
}
})
It("returns 0 if year is invalid", func() {
md := &Tags{}
md.tags = map[string][]string{"date": {"invalid"}}
Expect(md.Year()).To(Equal(0))
})
})
Describe("getMbzID", func() {
It("return a valid MBID", func() {

View File

@ -27,15 +27,7 @@ var _ = Describe("Tags", func() {
Expect(m.AlbumArtist()).To(Equal("Album Artist"))
Expect(m.Compilation()).To(BeTrue())
Expect(m.Genres()).To(Equal([]string{"Rock"}))
y, d := m.Date()
Expect(y).To(Equal(2014))
Expect(d).To(Equal("2014-05-21"))
y, d = m.OriginalDate()
Expect(y).To(Equal(1996))
Expect(d).To(Equal("1996-11-21"))
y, d = m.ReleaseDate()
Expect(y).To(Equal(2020))
Expect(d).To(Equal("2020-12-31"))
Expect(m.Year()).To(Equal(2014))
n, t := m.TrackNumber()
Expect(n).To(Equal(2))
Expect(t).To(Equal(10))
@ -48,7 +40,7 @@ var _ = Describe("Tags", func() {
Expect(m.Channels()).To(Equal(2))
Expect(m.FilePath()).To(Equal("tests/fixtures/test.mp3"))
Expect(m.Suffix()).To(Equal("mp3"))
Expect(m.Size()).To(Equal(int64(51876)))
Expect(m.Size()).To(Equal(int64(52050)))
Expect(m.RGAlbumGain()).To(Equal(3.21518))
Expect(m.RGAlbumPeak()).To(Equal(0.9125))
Expect(m.RGTrackGain()).To(Equal(-1.48))

View File

@ -41,9 +41,7 @@ var _ = Describe("Extractor", func() {
Expect(m).To(HaveKeyWithValue("albumartist", []string{"Album Artist"}))
Expect(m).To(HaveKeyWithValue("tcmp", []string{"1"})) // Compilation
Expect(m).To(HaveKeyWithValue("genre", []string{"Rock"}))
Expect(m).To(HaveKeyWithValue("date", []string{"2014-05-21", "2014"}))
Expect(m).To(HaveKeyWithValue("originaldate", []string{"1996-11-21"}))
Expect(m).To(HaveKeyWithValue("releasedate", []string{"2020-12-31"}))
Expect(m).To(HaveKeyWithValue("date", []string{"2014", "2014"}))
Expect(m).To(HaveKeyWithValue("discnumber", []string{"1/2"}))
Expect(m).To(HaveKeyWithValue("has_picture", []string{"true"}))
Expect(m).To(HaveKeyWithValue("duration", []string{"1.02"}))
@ -70,8 +68,8 @@ var _ = Describe("Extractor", func() {
Expect(m["bitrate"][0]).To(BeElementOf("18", "39", "40"))
})
DescribeTable("ReplayGain",
func(file, albumGain, albumPeak, trackGain, trackPeak string) {
Context("ReplayGain", func() {
testGain := func(file, albumGain, albumPeak, trackGain, trackPeak string) {
file = "tests/fixtures/" + file
mds, err := e.Parse(file)
Expect(err).NotTo(HaveOccurred())
@ -83,11 +81,20 @@ var _ = Describe("Extractor", func() {
Expect(m).To(HaveKeyWithValue("replaygain_album_peak", []string{albumPeak}))
Expect(m).To(HaveKeyWithValue("replaygain_track_gain", []string{trackGain}))
Expect(m).To(HaveKeyWithValue("replaygain_track_peak", []string{trackPeak}))
},
Entry("Correctly parses m4a (aac) gain tags", "01 Invisible (RED) Edit Version.m4a", "0.37", "0.48", "0.37", "0.48"),
Entry("correctly parses mp3 tags", "test.mp3", "+3.21518 dB", "0.9125", "-1.48 dB", "0.4512"),
Entry("correctly parses ogg (vorbis) tags", "test.ogg", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506"),
)
}
It("Correctly parses m4a (aac) gain tags", func() {
testGain("01 Invisible (RED) Edit Version.m4a", "0.37", "0.48", "0.37", "0.48")
})
It("correctly parses mp3 tags", func() {
testGain("test.mp3", "+3.21518 dB", "0.9125", "-1.48 dB", "0.4512")
})
It("correctly parses ogg (vorbis) tags", func() {
testGain("test.ogg", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506")
})
})
})
Context("Error Checking", func() {

Binary file not shown.

View File

@ -25,7 +25,6 @@ import {
ArtistLinkField,
DurationField,
formatRange,
FormatFullDate,
SizeField,
LoveButton,
RatingField,
@ -196,59 +195,8 @@ const Details = (props) => {
details.push(<span key={`detail-${record.id}-${id}`}>{obj}</span>)
}
const originalYearRange = formatRange(record, 'originalYear')
const originalDate = record.originalDate
? FormatFullDate(record.originalDate)
: originalYearRange
const yearRange = formatRange(record, 'year')
const date = record.date ? FormatFullDate(record.date) : yearRange
const releaseDate = record.releaseDate
? FormatFullDate(record.releaseDate)
: date
const showReleaseDate = date !== releaseDate && releaseDate.length > 3
const showOriginalDate =
date !== originalDate &&
originalDate !== releaseDate &&
originalDate.length > 3
showOriginalDate &&
!isXsmall &&
addDetail(
<>
{[translate('resources.album.fields.originalDate'), originalDate].join(
' '
)}
</>
)
yearRange && addDetail(<>{['♫', !isXsmall ? date : yearRange].join(' ')}</>)
showReleaseDate &&
addDetail(
<>
{(!isXsmall
? [translate('resources.album.fields.releaseDate'), releaseDate]
: ['○', record.releaseDate.substring(0, 4)]
).join(' ')}
</>
)
const showReleases = record.releases > 1
showReleases &&
addDetail(
<>
{!isXsmall
? [
record.releases,
translate('resources.album.fields.releases', {
smart_count: record.releases,
}),
].join(' ')
: ['(', record.releases, ')))'].join(' ')}
</>
)
const year = formatRange(record, 'year')
year && addDetail(<>{year}</>)
addDetail(
<>
{record.songCount +

View File

@ -17,7 +17,7 @@ import {
AlbumContextMenu,
PlayButton,
ArtistLinkField,
RangeDoubleField,
RangeField,
} from '../common'
import { DraggableTypes } from '../consts'
@ -161,12 +161,9 @@ const AlbumGridTile = ({ showArtist, record, basePath, ...props }) => {
{showArtist ? (
<ArtistLinkField record={record} className={classes.albumSubtitle} />
) : (
<RangeDoubleField
<RangeField
record={record}
source={'year'}
symbol1={'♫'}
symbol2={'○'}
separator={' · '}
sortBy={'max_year'}
sortByOrder={'DESC'}
className={classes.albumSubtitle}

View File

@ -99,7 +99,7 @@ const AlbumSongs = (props) => {
trackNumber: isDesktop && (
<TextField
source="trackNumber"
sortBy="releaseDate asc, discNumber asc, trackNumber asc"
sortBy="discNumber asc, trackNumber asc"
label="#"
sortable={false}
/>
@ -172,7 +172,6 @@ const AlbumSongs = (props) => {
{...props}
hasBulkActions={true}
showDiscSubtitles={true}
showReleaseDivider={true}
contextAlwaysVisible={!isDesktop}
classes={{ row: classes.row }}
>
@ -208,6 +207,7 @@ export const removeAlbumCommentsFromSongs = ({ album, data }) => {
const SanitizedAlbumSongs = (props) => {
removeAlbumCommentsFromSongs(props)
const { loaded, loading, total, ...rest } = useListContext(props)
return <>{loaded && <AlbumSongs {...rest} actions={props.actions} />}</>
}

View File

@ -60,7 +60,7 @@ const AlbumShowLayout = (props) => {
addLabel={false}
reference="album"
target="artist_id"
sort={{ field: 'max_year asc,date asc', order: 'ASC' }}
sort={{ field: 'max_year', order: 'ASC' }}
filter={{ artist_id: record?.id }}
perPage={0}
pagination={null}

View File

@ -200,12 +200,8 @@ export const AlbumContextMenu = (props) =>
resource={'album'}
songQueryParams={{
pagination: { page: 1, perPage: -1 },
sort: { field: 'releaseDate, discNumber, trackNumber', order: 'ASC' },
filter: {
album_id: props.record.id,
release_date: props.releaseDate,
disc_number: props.discNumber,
},
sort: { field: 'discNumber, trackNumber', order: 'ASC' },
filter: { album_id: props.record.id, disc_number: props.discNumber },
}}
/>
) : null
@ -230,10 +226,7 @@ export const ArtistContextMenu = (props) =>
resource={'artist'}
songQueryParams={{
pagination: { page: 1, perPage: 200 },
sort: {
field: 'album, releaseDate, discNumber, trackNumber',
order: 'ASC',
},
sort: { field: 'album, discNumber, trackNumber', order: 'ASC' },
filter: { album_artist_id: props.record.id },
}}
/>

View File

@ -1,29 +0,0 @@
export const FormatFullDate = (date) => {
const dashes = date.split('-').length - 1
let options = {
year: 'numeric',
}
switch (dashes) {
case 2:
options = {
year: 'numeric',
month: 'long',
day: 'numeric',
}
return new Date(date).toLocaleDateString(undefined, options)
case 1:
options = {
year: 'numeric',
month: 'long',
}
return new Date(date).toLocaleDateString(undefined, options)
case 0:
if (date.length === 4) {
return new Date(date).toLocaleDateString(undefined, options)
} else {
return ''
}
default:
return ''
}
}

View File

@ -21,12 +21,8 @@ export const PlayButton = ({ record, size, className }) => {
dataProvider
.getList('song', {
pagination: { page: 1, perPage: -1 },
sort: { field: 'releaseDate, discNumber, trackNumber', order: 'ASC' },
filter: {
album_id: record.id,
release_date: record.releaseDate,
disc_number: record.discNumber,
},
sort: { field: 'discNumber, trackNumber', order: 'ASC' },
filter: { album_id: record.id, disc_number: record.discNumber },
})
.then((response) => {
let { data, ids } = extractSongsData(response)

View File

@ -1,50 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useRecordContext } from 'react-admin'
import { formatRange } from '../common'
export const RangeDoubleField = ({
className,
source,
symbol1,
symbol2,
separator,
...rest
}) => {
const record = useRecordContext(rest)
const yearRange = formatRange(record, source).toString()
const releases = [record.releases]
const releaseDate = [record.releaseDate]
const releaseYear = releaseDate.toString().substring(0, 4)
let subtitle = yearRange
if (releases > 1) {
subtitle = [
[yearRange && symbol1, yearRange].join(' '),
['(', releases, ')))'].join(' '),
].join(separator)
}
if (
yearRange !== releaseYear &&
yearRange.length > 0 &&
releaseYear.length > 0
) {
subtitle = [
[yearRange && symbol1, yearRange].join(' '),
[symbol2, releaseYear].join(' '),
].join(separator)
}
return <span className={className}>{subtitle}</span>
}
RangeDoubleField.propTypes = {
label: PropTypes.string,
record: PropTypes.object,
source: PropTypes.string.isRequired,
}
RangeDoubleField.defaultProps = {
addLabel: true,
}

View File

@ -1,11 +1,6 @@
import React, { isValidElement, useMemo, useCallback, forwardRef } from 'react'
import { useDispatch } from 'react-redux'
import {
Datagrid,
PureDatagridBody,
PureDatagridRow,
useTranslate,
} from 'react-admin'
import { Datagrid, PureDatagridBody, PureDatagridRow } from 'react-admin'
import {
TableCell,
TableRow,
@ -18,7 +13,7 @@ import AlbumIcon from '@material-ui/icons/Album'
import clsx from 'clsx'
import { useDrag } from 'react-dnd'
import { playTracks } from '../actions'
import { AlbumContextMenu, FormatFullDate } from '../common'
import { AlbumContextMenu } from '../common'
import { DraggableTypes } from '../consts'
const useStyles = makeStyles({
@ -54,57 +49,12 @@ const useStyles = makeStyles({
},
})
const ReleaseRow = forwardRef(
({ record, onClick, colSpan, contextAlwaysVisible }, ref) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const classes = useStyles({ isDesktop })
const translate = useTranslate()
const handlePlaySubset = (releaseDate) => () => {
onClick(releaseDate)
}
let releaseTitle = []
if (record.releaseDate) {
releaseTitle.push(translate('resources.album.fields.released'))
releaseTitle.push(FormatFullDate(record.releaseDate))
if (record.catalogNum && isDesktop) {
releaseTitle.push('· Cat #')
releaseTitle.push(record.catalogNum)
}
}
return (
<TableRow
hover
ref={ref}
onClick={handlePlaySubset(record.releaseDate)}
className={classes.row}
>
<TableCell colSpan={colSpan}>
<Typography variant="h6" className={classes.subtitle}>
{releaseTitle.join(' ')}
</Typography>
</TableCell>
<TableCell>
<AlbumContextMenu
record={{ id: record.albumId }}
releaseDate={record.releaseDate}
showLove={false}
className={classes.contextMenu}
visible={contextAlwaysVisible}
/>
</TableCell>
</TableRow>
)
}
)
const DiscSubtitleRow = forwardRef(
({ record, onClick, colSpan, contextAlwaysVisible }, ref) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const classes = useStyles({ isDesktop })
const handlePlaySubset = (releaseDate, discNumber) => () => {
onClick(releaseDate, discNumber)
const handlePlayDisc = (discNumber) => () => {
onClick(discNumber)
}
let subtitle = []
@ -119,7 +69,7 @@ const DiscSubtitleRow = forwardRef(
<TableRow
hover
ref={ref}
onClick={handlePlaySubset(record.releaseDate, record.discNumber)}
onClick={handlePlayDisc(record.discNumber)}
className={classes.row}
>
<TableCell colSpan={colSpan}>
@ -132,7 +82,6 @@ const DiscSubtitleRow = forwardRef(
<AlbumContextMenu
record={{ id: record.albumId }}
discNumber={record.discNumber}
releaseDate={record.releaseDate}
showLove={false}
className={classes.contextMenu}
visible={contextAlwaysVisible}
@ -146,10 +95,9 @@ const DiscSubtitleRow = forwardRef(
export const SongDatagridRow = ({
record,
children,
firstTracksOfDiscs,
firstTracksOfReleases,
firstTracks,
contextAlwaysVisible,
onClickSubset,
onClickDiscSubtitle,
className,
...rest
}) => {
@ -162,13 +110,7 @@ export const SongDatagridRow = ({
() => ({
type: DraggableTypes.DISC,
item: {
discs: [
{
albumId: record?.albumId,
releaseDate: record?.releaseDate,
discNumber: record?.discNumber,
},
],
discs: [{ albumId: record?.albumId, discNumber: record?.discNumber }],
},
options: { dropEffect: 'copy' },
}),
@ -191,20 +133,11 @@ export const SongDatagridRow = ({
const childCount = fields.length
return (
<>
{firstTracksOfReleases.has(record.id) && (
<ReleaseRow
ref={dragDiscRef}
record={record}
onClick={onClickSubset}
contextAlwaysVisible={contextAlwaysVisible}
colSpan={childCount + (rest.expand ? 1 : 0)}
/>
)}
{firstTracksOfDiscs.has(record.id) && (
{firstTracks.has(record.id) && (
<DiscSubtitleRow
ref={dragDiscRef}
record={record}
onClick={onClickSubset}
onClick={onClickDiscSubtitle}
contextAlwaysVisible={contextAlwaysVisible}
colSpan={childCount + (rest.expand ? 1 : 0)}
/>
@ -224,43 +157,32 @@ export const SongDatagridRow = ({
SongDatagridRow.propTypes = {
record: PropTypes.object,
children: PropTypes.node,
firstTracksOfDiscs: PropTypes.instanceOf(Set),
firstTracksOfReleases: PropTypes.instanceOf(Set),
firstTracks: PropTypes.instanceOf(Set),
contextAlwaysVisible: PropTypes.bool,
onClickSubset: PropTypes.func,
onClickDiscSubtitle: PropTypes.func,
}
SongDatagridRow.defaultProps = {
onClickSubset: () => {},
onClickDiscSubtitle: () => {},
}
const SongDatagridBody = ({
contextAlwaysVisible,
showDiscSubtitles,
showReleaseDivider,
...rest
}) => {
const dispatch = useDispatch()
const { ids, data } = rest
const playSubset = useCallback(
(releaseDate, discNumber) => {
let idsToPlay = []
if (discNumber !== undefined) {
idsToPlay = ids.filter(
(id) =>
data[id].releaseDate === releaseDate &&
data[id].discNumber === discNumber
)
} else {
idsToPlay = ids.filter((id) => data[id].releaseDate === releaseDate)
}
const playDisc = useCallback(
(discNumber) => {
const idsToPlay = ids.filter((id) => data[id].discNumber === discNumber)
dispatch(playTracks(data, idsToPlay))
},
[dispatch, data, ids]
)
const firstTracksOfDiscs = useMemo(() => {
const firstTracks = useMemo(() => {
if (!ids) {
return new Set()
}
@ -273,8 +195,7 @@ const SongDatagridBody = ({
foundSubtitle = foundSubtitle || data[id].discSubtitle
if (
acc.length === 0 ||
(last && data[id].discNumber !== data[last].discNumber) ||
(last && data[id].releaseDate !== data[last].releaseDate)
(last && data[id].discNumber !== data[last].discNumber)
) {
acc.push(id)
}
@ -287,39 +208,14 @@ const SongDatagridBody = ({
return set
}, [ids, data, showDiscSubtitles])
const firstTracksOfReleases = useMemo(() => {
if (!ids) {
return new Set()
}
const set = new Set(
ids
.filter((i) => data[i])
.reduce((acc, id) => {
const last = acc && acc[acc.length - 1]
if (
acc.length === 0 ||
(last && data[id].releaseDate !== data[last].releaseDate)
) {
acc.push(id)
}
return acc
}, [])
)
if (!showReleaseDivider || set.size < 2) {
set.clear()
}
return set
}, [ids, data, showReleaseDivider])
return (
<PureDatagridBody
{...rest}
row={
<SongDatagridRow
firstTracksOfDiscs={firstTracksOfDiscs}
firstTracksOfReleases={firstTracksOfReleases}
firstTracks={firstTracks}
contextAlwaysVisible={contextAlwaysVisible}
onClickSubset={playSubset}
onClickDiscSubtitle={playDisc}
/>
}
/>
@ -329,7 +225,6 @@ const SongDatagridBody = ({
export const SongDatagrid = ({
contextAlwaysVisible,
showDiscSubtitles,
showReleaseDivider,
...rest
}) => {
const classes = useStyles()
@ -341,7 +236,6 @@ export const SongDatagrid = ({
<SongDatagridBody
contextAlwaysVisible={contextAlwaysVisible}
showDiscSubtitles={showDiscSubtitles}
showReleaseDivider={showReleaseDivider}
/>
}
/>
@ -351,6 +245,5 @@ export const SongDatagrid = ({
SongDatagrid.propTypes = {
contextAlwaysVisible: PropTypes.bool,
showDiscSubtitles: PropTypes.bool,
showReleaseDivider: PropTypes.bool,
classes: PropTypes.object,
}

View File

@ -4,7 +4,6 @@ export * from './BatchPlayButton'
export * from './BitrateField'
export * from './ContextMenus'
export * from './DateField'
export * from './FormatFullDate'
export * from './DocLink'
export * from './DurationField'
export * from './List'
@ -13,7 +12,6 @@ export * from './Pagination'
export * from './PlayButton'
export * from './QuickFilter'
export * from './RangeField'
export * from './RangeDoubleField'
export * from './ShuffleAllButton'
export * from './SimpleList'
export * from './SizeField'

View File

@ -52,9 +52,6 @@
"genre": "Genre",
"compilation": "Compilation",
"year": "Year",
"originalDate": "Original",
"releaseDate": "Released",
"releases": "Release |||| Releases",
"updatedAt": "Updated at",
"comment": "Comment",
"rating": "Rating",

View File

@ -102,7 +102,7 @@ const SongList = (props) => {
<AlbumLinkField
source="album"
sortBy={
'album, order_album_artist_name, release_date, disc_number, track_number, title'
'album, order_album_artist_name, disc_number, track_number, title'
}
sortByOrder={'ASC'}
/>