Skip to content

Commit

Permalink
Add sort tags and use them in search
Browse files Browse the repository at this point in the history
  • Loading branch information
deluan committed Apr 24, 2020
1 parent d7edbf9 commit 69c19e9
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 49 deletions.
64 changes: 64 additions & 0 deletions db/migration/20200423204116_add_sort_fields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package migration

import (
"database/sql"
"github.com/pressly/goose"
)

func init() {
goose.AddMigration(Up20200423204116, Down20200423204116)
}

func Up20200423204116(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table artist
add order_artist_name varchar(255) collate nocase;
alter table artist
add sort_artist_name varchar(255) collate nocase;
create index if not exists artist_order_artist_name
on artist (order_artist_name);
alter table album
add order_album_name varchar(255) collate nocase;
alter table album
add order_album_artist_name varchar(255) collate nocase;
alter table album
add sort_album_name varchar(255) collate nocase;
alter table album
add sort_artist_name varchar(255) collate nocase;
alter table album
add sort_album_artist_name varchar(255) collate nocase;
create index if not exists album_order_album_name
on album (order_album_name);
create index if not exists album_order_album_artist_name
on album (order_album_artist_name);
alter table media_file
add order_album_name varchar(255) collate nocase;
alter table media_file
add order_album_artist_name varchar(255) collate nocase;
alter table media_file
add order_artist_name varchar(255) collate nocase;
alter table media_file
add sort_album_name varchar(255) collate nocase;
alter table media_file
add sort_artist_name varchar(255) collate nocase;
alter table media_file
add sort_album_artist_name varchar(255) collate nocase;
alter table media_file
add sort_title varchar(255) collate nocase;
create index if not exists media_file_order_album_name
on media_file (order_album_name);
create index if not exists media_file_order_artist_name
on media_file (order_artist_name);
`)
if err != nil {
return err
}
notice(tx, "A full rescan will be performed to change the search behaviour")
return forceFullRescan(tx)
}

func Down20200423204116(tx *sql.Tx) error {
return nil
}
39 changes: 22 additions & 17 deletions model/album.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@ package model
import "time"

type Album struct {
ID string `json:"id" orm:"column(id)"`
Name string `json:"name"`
CoverArtPath string `json:"coverArtPath"`
CoverArtId string `json:"coverArtId"`
ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
Artist string `json:"artist"`
AlbumArtistID string `json:"albumArtistId" orm:"pk;column(album_artist_id)"`
AlbumArtist string `json:"albumArtist"`
MaxYear int `json:"maxYear"`
MinYear int `json:"minYear"`
Compilation bool `json:"compilation"`
SongCount int `json:"songCount"`
Duration float32 `json:"duration"`
Genre string `json:"genre"`
FullText string `json:"fullText"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID string `json:"id" orm:"column(id)"`
Name string `json:"name"`
CoverArtPath string `json:"coverArtPath"`
CoverArtId string `json:"coverArtId"`
ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
Artist string `json:"artist"`
AlbumArtistID string `json:"albumArtistId" orm:"pk;column(album_artist_id)"`
AlbumArtist string `json:"albumArtist"`
MaxYear int `json:"maxYear"`
MinYear int `json:"minYear"`
Compilation bool `json:"compilation"`
SongCount int `json:"songCount"`
Duration float32 `json:"duration"`
Genre string `json:"genre"`
FullText string `json:"fullText"`
SortAlbumName string `json:"sortAlbumName"`
SortArtistName string `json:"sortArtistName"`
SortAlbumArtistName string `json:"sortAlbumArtistName"`
OrderAlbumName string `json:"orderAlbumName"`
OrderAlbumArtistName string `json:"orderAlbumArtistName"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`

// Annotations
PlayCount int `json:"playCount" orm:"-"`
Expand Down
10 changes: 6 additions & 4 deletions model/artist.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package model
import "time"

type Artist struct {
ID string `json:"id" orm:"column(id)"`
Name string `json:"name"`
AlbumCount int `json:"albumCount" orm:"column(album_count)"`
FullText string `json:"fullText"`
ID string `json:"id" orm:"column(id)"`
Name string `json:"name"`
AlbumCount int `json:"albumCount" orm:"column(album_count)"`
FullText string `json:"fullText"`
SortArtistName string `json:"sortArtistName"`
OrderArtistName string `json:"orderArtistName"`

// Annotations
PlayCount int `json:"playCount" orm:"-"`
Expand Down
50 changes: 28 additions & 22 deletions model/mediafile.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,34 @@ import (
)

type MediaFile struct {
ID string `json:"id" orm:"pk;column(id)"`
Path string `json:"path"`
Title string `json:"title"`
Album string `json:"album"`
ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
Artist string `json:"artist"`
AlbumArtistID string `json:"albumArtistId"`
AlbumArtist string `json:"albumArtist"`
AlbumID string `json:"albumId" orm:"pk;column(album_id)"`
HasCoverArt bool `json:"hasCoverArt"`
TrackNumber int `json:"trackNumber"`
DiscNumber int `json:"discNumber"`
Year int `json:"year"`
Size int `json:"size"`
Suffix string `json:"suffix"`
Duration float32 `json:"duration"`
BitRate int `json:"bitRate"`
Genre string `json:"genre"`
FullText string `json:"fullText"`
Compilation bool `json:"compilation"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID string `json:"id" orm:"pk;column(id)"`
Path string `json:"path"`
Title string `json:"title"`
Album string `json:"album"`
ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
Artist string `json:"artist"`
AlbumArtistID string `json:"albumArtistId"`
AlbumArtist string `json:"albumArtist"`
AlbumID string `json:"albumId" orm:"pk;column(album_id)"`
HasCoverArt bool `json:"hasCoverArt"`
TrackNumber int `json:"trackNumber"`
DiscNumber int `json:"discNumber"`
Year int `json:"year"`
Size int `json:"size"`
Suffix string `json:"suffix"`
Duration float32 `json:"duration"`
BitRate int `json:"bitRate"`
Genre string `json:"genre"`
FullText string `json:"fullText"`
SortTitle string `json:"sortTitle"`
SortAlbumName string `json:"sortAlbumName"`
SortArtistName string `json:"sortArtistName"`
SortAlbumArtistName string `json:"sortAlbumArtistName"`
OrderAlbumName string `json:"orderAlbumName"`
OrderAlbumArtistName string `json:"orderAlbumArtistName"`
Compilation bool `json:"compilation"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`

// Annotations
PlayCount int `json:"playCount" orm:"-"`
Expand Down
5 changes: 4 additions & 1 deletion persistence/album_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ func (r *albumRepository) Refresh(ids ...string) error {
}
var albums []refreshAlbum
sel := Select(`album_id as id, album as name, f.artist, f.album_artist, f.artist_id, f.album_artist_id,
f.sort_album_name, f.sort_artist_name, f.sort_album_artist_name,
f.order_album_name, f.order_album_artist_name,
f.compilation, f.genre, max(f.year) as max_year, sum(f.duration) as duration,
count(*) as song_count, a.id as current_id, f.id as cover_art_id, f.path as cover_art_path, f.has_cover_art,
group_concat(f.artist, ' ') as song_artists, group_concat(f.year, ' ') as years`).
Expand Down Expand Up @@ -148,7 +150,8 @@ func (r *albumRepository) Refresh(ids ...string) error {
toInsert++
al.CreatedAt = time.Now()
}
al.FullText = getFullText(al.Name, al.Artist, al.AlbumArtist, al.SongArtists)
al.FullText = getFullText(al.Name, al.Artist, al.AlbumArtist, al.SongArtists,
al.SortAlbumName, al.SortArtistName, al.SortAlbumArtistName)
_, err := r.put(al.ID, al.Album)
if err != nil {
return err
Expand Down
6 changes: 4 additions & 2 deletions persistence/artist_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (r *artistRepository) getIndexKey(a *model.Artist) string {
}

func (r *artistRepository) Put(a *model.Artist) error {
a.FullText = getFullText(a.Name)
a.FullText = getFullText(a.Name, a.SortArtistName)
_, err := r.put(a.ID, a)
return err
}
Expand Down Expand Up @@ -111,7 +111,9 @@ func (r *artistRepository) Refresh(ids ...string) error {
CurrentId string
}
var artists []refreshArtist
sel := Select("f.album_artist_id as id", "f.album_artist as name", "count(*) as album_count", "a.id as current_id").
sel := Select("f.album_artist_id as id", "f.album_artist as name", "count(*) as album_count", "a.id as current_id",
"f.sort_album_artist_name as sort_artist_name",
"f.order_album_artist_name as order_artist_name").
From("album f").
LeftJoin("artist a on f.album_artist_id = a.id").
Where(Eq{"f.album_artist_id": ids}).
Expand Down
3 changes: 2 additions & 1 deletion persistence/mediafile_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func (r mediaFileRepository) Exists(id string) (bool, error) {
}

func (r mediaFileRepository) Put(m *model.MediaFile) error {
m.FullText = getFullText(m.Title, m.Album, m.Artist, m.AlbumArtist)
m.FullText = getFullText(m.Title, m.Album, m.Artist, m.AlbumArtist,
m.SortTitle, m.SortAlbumName, m.SortArtistName, m.SortAlbumArtistName)
_, err := r.put(m.ID, m)
return err
}
Expand Down
21 changes: 19 additions & 2 deletions scanner/metadata_ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scanner
import (
"bufio"
"errors"
"fmt"
"mime"
"os"
"os/exec"
Expand All @@ -27,7 +28,11 @@ type Metadata struct {
func (m *Metadata) Title() string { return m.getTag("title", "sort_name") }
func (m *Metadata) Album() string { return m.getTag("album", "sort_album") }
func (m *Metadata) Artist() string { return m.getTag("artist", "sort_artist") }
func (m *Metadata) AlbumArtist() string { return m.getTag("album_artist") }
func (m *Metadata) AlbumArtist() string { return m.getTag("album_artist", "albumartist") }
func (m *Metadata) SortTitle() string { return m.getSortTag("title", "name") }
func (m *Metadata) SortAlbum() string { return m.getSortTag("album") }
func (m *Metadata) SortArtist() string { return m.getSortTag("artist") }
func (m *Metadata) SortAlbumArtist() string { return m.getSortTag("albumartist", "album_artist") }
func (m *Metadata) Composer() string { return m.getTag("composer", "tcm", "sort_composer") }
func (m *Metadata) Genre() string { return m.getTag("genre") }
func (m *Metadata) Year() int { return m.parseYear("date") }
Expand Down Expand Up @@ -99,7 +104,7 @@ var (
inputRegex = regexp.MustCompile(`(?m)^Input #\d+,.*,\sfrom\s'(.*)'`)

// TITLE : Back In Black
tagsRx = regexp.MustCompile(`(?i)^\s{4,6}(\w+)\s*:(.*)`)
tagsRx = regexp.MustCompile(`(?i)^\s{4,6}([\w-]+)\s*:(.*)`)

// Duration: 00:04:16.00, start: 0.000000, bitrate: 995 kb/s`
durationRx = regexp.MustCompile(`^\s\sDuration: ([\d.:]+).*bitrate: (\d+)`)
Expand Down Expand Up @@ -230,6 +235,18 @@ func (m *Metadata) getTag(tags ...string) string {
return ""
}

func (m *Metadata) getSortTag(tags ...string) string {
formats := []string{"sort%s", "sort_%s", "sort-%s", "%ssort", "%s_sort", "%s-sort"}
var all []string
for _, tag := range tags {
for _, format := range formats {
name := fmt.Sprintf(format, tag)
all = append(all, name)
}
}
return m.getTag(all...)
}

func (m *Metadata) parseTuple(tags ...string) (int, int) {
for _, tagName := range tags {
if v, ok := m.tags[tagName]; ok {
Expand Down
24 changes: 24 additions & 0 deletions scanner/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,30 @@ Tracklist:
md, _ := extractMetadata("tests/fixtures/test.mp3", outputWithMultilineComment)
Expect(md.Comment()).To(Equal(expectedComment))
})

It("parses sort tags correctly", func() {
const output = `
Input #0, mp3, from '/Users/deluan/Downloads/椎名林檎 - 加爾基 精液 栗ノ花 - 2003/02 - ドツペルゲンガー.mp3':
Metadata:
title-sort : Dopperugengā
album : 加爾基 精液 栗ノ花
artist : 椎名林檎
album_artist : 椎名林檎
title : ドツペルゲンガー
albumsort : Kalk Samen Kuri No Hana
artist_sort : Shiina, Ringo
ALBUMARTISTSORT : Shiina, Ringo
`
md, _ := extractMetadata("tests/fixtures/test.mp3", output)
Expect(md.Title()).To(Equal("ドツペルゲンガー"))
Expect(md.Album()).To(Equal("加爾基 精液 栗ノ花"))
Expect(md.Artist()).To(Equal("椎名林檎"))
Expect(md.AlbumArtist()).To(Equal("椎名林檎"))
Expect(md.SortTitle()).To(Equal("Dopperugengā"))
Expect(md.SortAlbum()).To(Equal("Kalk Samen Kuri No Hana"))
Expect(md.SortArtist()).To(Equal("Shiina, Ringo"))
Expect(md.SortAlbumArtist()).To(Equal("Shiina, Ringo"))
})
})

Context("parseYear", func() {
Expand Down
4 changes: 4 additions & 0 deletions scanner/tag_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ func (s *TagScanner) toMediaFile(md *Metadata) model.MediaFile {
mf.Suffix = md.Suffix()
mf.Size = md.Size()
mf.HasCoverArt = md.HasPicture()
mf.SortTitle = md.SortTitle()
mf.SortAlbumName = md.SortAlbum()
mf.SortArtistName = md.SortArtist()
mf.SortAlbumArtistName = md.SortAlbumArtist()

// TODO Get Creation time. https://github.com/djherbis/times ?
mf.CreatedAt = md.ModificationTime()
Expand Down

0 comments on commit 69c19e9

Please sign in to comment.