add song album_artist metadata (#673)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+3886
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+3886
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));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user