add song album_artist metadata (#673)

This commit is contained in:
Jason Dove
2022-03-04 18:44:54 -06:00
committed by GitHub
parent 58697496fa
commit ea339a1622
14 changed files with 7865 additions and 4 deletions
+2
View File
@@ -13,10 +13,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- Add automated error reporting via Bugsnag - Add automated error reporting via Bugsnag
- This can be disabled by editing the `appsettings.json` file or by setting the `Bugsnag:Enable` environment variable to `false` - This can be disabled by editing the `appsettings.json` file or by setting the `Bugsnag:Enable` environment variable to `false`
- Add `album_artist` to song metadata and to search index
### Changed ### Changed
- Framerate normalization will never normalize framerate below 24fps - Framerate normalization will never normalize framerate below 24fps
- Instead, content with a lower framerate will be normalized up to 24fps - Instead, content with a lower framerate will be normalized up to 24fps
- `Shuffle In Order` will group songs by album artist instead of by track artist
## [0.4.2-alpha] - 2022-02-26 ## [0.4.2-alpha] - 2022-02-26
### Fixed ### Fixed
@@ -4,6 +4,7 @@ public class SongMetadata : Metadata
{ {
public string Album { get; set; } public string Album { get; set; }
public string Artist { get; set; } public string Artist { get; set; }
public string AlbumArtist { get; set; }
public string Date { get; set; } public string Date { get; set; }
public string Track { get; set; } public string Track { get; set; }
public int SongId { get; set; } public int SongId { get; set; }
@@ -232,6 +232,11 @@ public class LocalMetadataProvider : ILocalMetadataProvider
result.Artist = artist; result.Artist = artist;
} }
if (tags.TryGetValue(MetadataFormatTag.AlbumArtist, out string albumArtist))
{
result.AlbumArtist = albumArtist;
}
if (tags.TryGetValue(MetadataFormatTag.Date, out string date)) if (tags.TryGetValue(MetadataFormatTag.Date, out string date))
{ {
result.Date = date; result.Date = date;
@@ -766,6 +771,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
{ {
existing.Title = metadata.Title; existing.Title = metadata.Title;
existing.Artist = metadata.Artist; existing.Artist = metadata.Artist;
existing.AlbumArtist = metadata.AlbumArtist;
existing.Album = metadata.Album; existing.Album = metadata.Album;
existing.Date = metadata.Date; existing.Date = metadata.Date;
existing.Track = metadata.Track; existing.Track = metadata.Track;
@@ -88,9 +88,20 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
result.Add(MetadataFormatTag.Album, ffprobe.format.tags.album); result.Add(MetadataFormatTag.Album, ffprobe.format.tags.album);
} }
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.albumArtist))
{
result.Add(MetadataFormatTag.AlbumArtist, ffprobe.format.tags.albumArtist);
}
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.artist)) if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.artist))
{ {
result.Add(MetadataFormatTag.Artist, ffprobe.format.tags.artist); result.Add(MetadataFormatTag.Artist, ffprobe.format.tags.artist);
// if no album artist is present, use the track artist
if (!result.ContainsKey(MetadataFormatTag.AlbumArtist))
{
result.Add(MetadataFormatTag.AlbumArtist, ffprobe.format.tags.artist);
}
} }
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.date)) if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.date))
@@ -366,6 +377,8 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
public record FFprobeFormatTags( public record FFprobeFormatTags(
string title, string title,
string artist, string artist,
[property: JsonProperty(PropertyName = "album artist")]
string albumArtist,
string album, string album,
string track, string track,
string genre, string genre,
@@ -4,6 +4,7 @@ public static class MetadataFormatTag
{ {
public static readonly string Album = "album"; public static readonly string Album = "album";
public static readonly string Artist = "artist"; public static readonly string Artist = "artist";
public static readonly string AlbumArtist = "albumartist";
public static readonly string Date = "date"; public static readonly string Date = "date";
public static readonly string Genre = "genre"; public static readonly string Genre = "genre";
public static readonly string Title = "title"; public static readonly string Title = "title";
@@ -312,7 +312,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
var allArtists = items.OfType<Song>() var allArtists = items.OfType<Song>()
.SelectMany(s => s.SongMetadata) .SelectMany(s => s.SongMetadata)
.Map(sm => sm.Artist) .Map(sm => sm.AlbumArtist)
.Distinct() .Distinct()
.ToList(); .ToList();
@@ -324,7 +324,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
var songArtistCollections = new Dictionary<int, List<MediaItem>>(); var songArtistCollections = new Dictionary<int, List<MediaItem>>();
foreach (Song song in items.OfType<Song>()) foreach (Song song in items.OfType<Song>())
{ {
int key = allArtists.IndexOf(song.SongMetadata.HeadOrNone().Match(sm => sm.Artist, string.Empty)); int key = allArtists.IndexOf(song.SongMetadata.HeadOrNone().Match(sm => sm.AlbumArtist, string.Empty));
List<MediaItem> list = songArtistCollections.ContainsKey(key) List<MediaItem> list = songArtistCollections.ContainsKey(key)
? songArtistCollections[key] ? songArtistCollections[key]
@@ -24,7 +24,7 @@ public class MetadataRepository : IMetadataRepository
public async Task<bool> Update(Metadata metadata) public async Task<bool> Update(Metadata metadata)
{ {
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
dbContext.Entry(metadata).State = EntityState.Modified; dbContext.Entry(metadata).State = EntityState.Modified;
return await dbContext.SaveChangesAsync() > 0; return await dbContext.SaveChangesAsync() > 0;
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_SongMetadataAlbumArtist : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "AlbumArtist",
table: "SongMetadata",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AlbumArtist",
table: "SongMetadata");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Reset_SongMetadataAlbumArtist : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00' WHERE Id IN
(SELECT LP.Id FROM LibraryPath LP INNER JOIN Library L on L.Id = LP.LibraryId WHERE MediaKind = 5)");
migrationBuilder.Sql(
@"UPDATE Library SET LastScan = '0001-01-01 00:00:00' WHERE MediaKind = 5");
migrationBuilder.Sql(
@"UPDATE SongMetadata SET DateUpdated = '0001-01-01 00:00:00'");
migrationBuilder.Sql(
@"UPDATE LibraryFolder SET Etag = NULL WHERE Id IN
(SELECT LF.Id FROM LibraryFolder LF INNER JOIN LibraryPath LP on LF.LibraryPathId = LP.Id INNER JOIN Library L on LP.LibraryId = L.Id WHERE MediaKind = 5)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
@@ -1672,6 +1672,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("Album") b.Property<string>("Album")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("AlbumArtist")
.HasColumnType("TEXT");
b.Property<string>("Artist") b.Property<string>("Artist")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -52,6 +52,7 @@ public sealed class SearchIndex : ISearchIndex
private const string MinutesField = "minutes"; private const string MinutesField = "minutes";
private const string ArtistField = "artist"; private const string ArtistField = "artist";
private const string StateField = "state"; private const string StateField = "state";
private const string AlbumArtistField = "album_artist";
public const string MovieType = "movie"; public const string MovieType = "movie";
public const string ShowType = "show"; public const string ShowType = "show";
@@ -894,6 +895,11 @@ public sealed class SearchIndex : ISearchIndex
doc.Add(new TextField(ArtistField, metadata.Artist, Field.Store.NO)); doc.Add(new TextField(ArtistField, metadata.Artist, Field.Store.NO));
} }
if (!string.IsNullOrWhiteSpace(metadata.AlbumArtist))
{
doc.Add(new TextField(AlbumArtistField, metadata.AlbumArtist, Field.Store.NO));
}
foreach (Tag tag in metadata.Tags) foreach (Tag tag in metadata.Tags)
{ {
doc.Add(new TextField(TagField, tag.Name, Field.Store.NO)); doc.Add(new TextField(TagField, tag.Name, Field.Store.NO));
+2 -1
View File
@@ -103,7 +103,8 @@ The following fields are available for searching songs:
- `title`: The song title, or the filename of the song (without extension) - `title`: The song title, or the filename of the song (without extension)
- `album`: The song album - `album`: The song album
- `artist`: The song artist - `artist`: The song/track artist
- `album_artist`: The album artist
- `genre`: The song genre - `genre`: The song genre
- `tag`: All of the song's parent folders - `tag`: All of the song's parent folders
- `minutes`: the rounded-up whole number duration of the song in minutes - `minutes`: the rounded-up whole number duration of the song in minutes