fix ogg song metadata (#1030)
This commit is contained in:
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Fix removing Jellyfin and Emby libraries that have been deleted from the source media server
|
- Fix removing Jellyfin and Emby libraries that have been deleted from the source media server
|
||||||
- Fix `Work-Ahead HLS Segmenter Limit` setting to properly limit number of channels that can work-ahead at once
|
- Fix `Work-Ahead HLS Segmenter Limit` setting to properly limit number of channels that can work-ahead at once
|
||||||
- Include base path value in generated channel playlist (M3U) and channel guide (XMLTV) links
|
- Include base path value in generated channel playlist (M3U) and channel guide (XMLTV) links
|
||||||
|
- Fix parsing song metadata from OGG audio files
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add audio stream selector scripts for episodes and movies
|
- Add audio stream selector scripts for episodes and movies
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ public interface ILocalStatisticsProvider
|
|||||||
MediaItem mediaItem,
|
MediaItem mediaItem,
|
||||||
string mediaItemPath);
|
string mediaItemPath);
|
||||||
|
|
||||||
Task<Either<BaseError, Dictionary<string, string>>> GetFormatTags(string ffprobePath, MediaItem mediaItem);
|
Task<Either<BaseError, Dictionary<string, string>>> GetSongTags(string ffprobePath, MediaItem mediaItem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Either<BaseError, Dictionary<string, string>> maybeTags =
|
Either<BaseError, Dictionary<string, string>> maybeTags =
|
||||||
await _localStatisticsProvider.GetFormatTags(ffprobePath, song);
|
await _localStatisticsProvider.GetSongTags(ffprobePath, song);
|
||||||
|
|
||||||
foreach (Dictionary<string, string> tags in maybeTags.RightToSeq())
|
foreach (Dictionary<string, string> tags in maybeTags.RightToSeq())
|
||||||
{
|
{
|
||||||
@@ -297,37 +297,37 @@ public class LocalMetadataProvider : ILocalMetadataProvider
|
|||||||
Tags = new List<Tag>()
|
Tags = new List<Tag>()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Album, out string album))
|
if (tags.TryGetValue(MetadataSongTag.Album, out string album))
|
||||||
{
|
{
|
||||||
result.Album = album;
|
result.Album = album;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Artist, out string artist))
|
if (tags.TryGetValue(MetadataSongTag.Artist, out string artist))
|
||||||
{
|
{
|
||||||
result.Artist = artist;
|
result.Artist = artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.AlbumArtist, out string albumArtist))
|
if (tags.TryGetValue(MetadataSongTag.AlbumArtist, out string albumArtist))
|
||||||
{
|
{
|
||||||
result.AlbumArtist = albumArtist;
|
result.AlbumArtist = albumArtist;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Date, out string date))
|
if (tags.TryGetValue(MetadataSongTag.Date, out string date))
|
||||||
{
|
{
|
||||||
result.Date = date;
|
result.Date = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Genre, out string genre))
|
if (tags.TryGetValue(MetadataSongTag.Genre, out string genre))
|
||||||
{
|
{
|
||||||
result.Genres.AddRange(SplitGenres(genre).Map(n => new Genre { Name = n }));
|
result.Genres.AddRange(SplitGenres(genre).Map(n => new Genre { Name = n }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Title, out string title))
|
if (tags.TryGetValue(MetadataSongTag.Title, out string title))
|
||||||
{
|
{
|
||||||
result.Title = title;
|
result.Title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.TryGetValue(MetadataFormatTag.Track, out string track))
|
if (tags.TryGetValue(MetadataSongTag.Track, out string track))
|
||||||
{
|
{
|
||||||
result.Track = track;
|
result.Track = track;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Either<BaseError, Dictionary<string, string>>> GetFormatTags(
|
public async Task<Either<BaseError, Dictionary<string, string>>> GetSongTags(
|
||||||
string ffprobePath,
|
string ffprobePath,
|
||||||
MediaItem mediaItem)
|
MediaItem mediaItem)
|
||||||
{
|
{
|
||||||
@@ -90,55 +90,109 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
|
|||||||
{
|
{
|
||||||
string mediaItemPath = mediaItem.GetHeadVersion().MediaFiles.Head().Path;
|
string mediaItemPath = mediaItem.GetHeadVersion().MediaFiles.Head().Path;
|
||||||
Either<BaseError, FFprobe> maybeProbe = await GetProbeOutput(ffprobePath, mediaItemPath);
|
Either<BaseError, FFprobe> maybeProbe = await GetProbeOutput(ffprobePath, mediaItemPath);
|
||||||
return maybeProbe.Match(
|
foreach (BaseError error in maybeProbe.LeftToSeq())
|
||||||
ffprobe =>
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<FFprobeTags> maybeFormatTags = maybeProbe.RightToSeq()
|
||||||
|
.Map(p => p?.format?.tags ?? FFprobeTags.Empty)
|
||||||
|
.HeadOrNone();
|
||||||
|
|
||||||
|
Option<FFprobeTags> maybeAudioTags = maybeProbe.RightToSeq()
|
||||||
|
.Bind(p => p.streams.Filter(s => s.codec_type == "audio").HeadOrNone())
|
||||||
|
.Map(s => s.tags ?? FFprobeTags.Empty)
|
||||||
|
.HeadOrNone();
|
||||||
|
|
||||||
|
foreach (FFprobeTags formatTags in maybeFormatTags)
|
||||||
|
foreach (FFprobeTags audioTags in maybeAudioTags)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
// album
|
||||||
|
if (!string.IsNullOrWhiteSpace(formatTags.album))
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>();
|
result.Add(MetadataSongTag.Album, formatTags.album);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.album))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Album, audioTags.album);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.album))
|
// album artist
|
||||||
|
if (!string.IsNullOrWhiteSpace(formatTags.albumArtist))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.AlbumArtist, formatTags.albumArtist);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.albumArtist))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.AlbumArtist, audioTags.albumArtist);
|
||||||
|
}
|
||||||
|
|
||||||
|
// artist
|
||||||
|
if (!string.IsNullOrWhiteSpace(formatTags.artist))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Artist, formatTags.artist);
|
||||||
|
|
||||||
|
// if no album artist is present, use the track artist
|
||||||
|
if (!result.ContainsKey(MetadataSongTag.AlbumArtist))
|
||||||
{
|
{
|
||||||
result.Add(MetadataFormatTag.Album, ffprobe.format.tags.album);
|
result.Add(MetadataSongTag.AlbumArtist, formatTags.artist);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.artist))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Artist, audioTags.artist);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.albumArtist))
|
// if no album artist is present, use the track artist
|
||||||
|
if (!result.ContainsKey(MetadataSongTag.AlbumArtist))
|
||||||
{
|
{
|
||||||
result.Add(MetadataFormatTag.AlbumArtist, ffprobe.format.tags.albumArtist);
|
result.Add(MetadataSongTag.AlbumArtist, audioTags.artist);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.artist))
|
// date
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(formatTags.date))
|
||||||
result.Add(MetadataFormatTag.Artist, ffprobe.format.tags.artist);
|
{
|
||||||
|
result.Add(MetadataSongTag.Date, formatTags.date);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.date))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Date, audioTags.date);
|
||||||
|
}
|
||||||
|
|
||||||
// if no album artist is present, use the track artist
|
// genre
|
||||||
if (!result.ContainsKey(MetadataFormatTag.AlbumArtist))
|
if (!string.IsNullOrWhiteSpace(formatTags.genre))
|
||||||
{
|
{
|
||||||
result.Add(MetadataFormatTag.AlbumArtist, ffprobe.format.tags.artist);
|
result.Add(MetadataSongTag.Genre, formatTags.genre);
|
||||||
}
|
}
|
||||||
}
|
else if (!string.IsNullOrWhiteSpace(audioTags.genre))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Genre, audioTags.genre);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.date))
|
// title
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(formatTags.title))
|
||||||
result.Add(MetadataFormatTag.Date, ffprobe.format.tags.date);
|
{
|
||||||
}
|
result.Add(MetadataSongTag.Title, formatTags.title);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.title))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Title, audioTags.title);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.genre))
|
// track
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(formatTags.track))
|
||||||
result.Add(MetadataFormatTag.Genre, ffprobe.format.tags.genre);
|
{
|
||||||
}
|
result.Add(MetadataSongTag.Track, formatTags.track);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(audioTags.track))
|
||||||
|
{
|
||||||
|
result.Add(MetadataSongTag.Track, audioTags.track);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.title))
|
return result;
|
||||||
{
|
}
|
||||||
result.Add(MetadataFormatTag.Title, ffprobe.format.tags.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffprobe?.format?.tags?.track))
|
|
||||||
{
|
|
||||||
result.Add(MetadataFormatTag.Track, ffprobe.format.tags.track);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Right<BaseError, Dictionary<string, string>>(result);
|
|
||||||
},
|
|
||||||
Left<BaseError, Dictionary<string, string>>);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -146,6 +200,8 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
|
|||||||
_client.Notify(ex);
|
_client.Notify(ex);
|
||||||
return BaseError.New(ex.Message);
|
return BaseError.New(ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return BaseError.New("BUG - this should never happen");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ApplyVersionUpdate(MediaItem mediaItem, MediaVersion version, string filePath)
|
private async Task<bool> ApplyVersionUpdate(MediaItem mediaItem, MediaVersion version, string filePath)
|
||||||
@@ -467,22 +523,10 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
public record FFprobe(FFprobeFormat format, List<FFprobeStream> streams, List<FFprobeChapter> chapters);
|
public record FFprobe(FFprobeFormat format, List<FFprobeStream> streams, List<FFprobeChapter> chapters);
|
||||||
|
|
||||||
public record FFprobeFormat(string duration, FFprobeFormatTags tags);
|
public record FFprobeFormat(string duration, FFprobeTags tags);
|
||||||
|
|
||||||
public record FFprobeDisposition(int @default, int forced, int attached_pic);
|
public record FFprobeDisposition(int @default, int forced, int attached_pic);
|
||||||
|
|
||||||
public record FFprobeTags(string language, string title, string filename, string mimetype);
|
|
||||||
|
|
||||||
public record FFprobeFormatTags(
|
|
||||||
string title,
|
|
||||||
string artist,
|
|
||||||
[property: JsonProperty(PropertyName = "album_artist")]
|
|
||||||
string albumArtist,
|
|
||||||
string album,
|
|
||||||
string track,
|
|
||||||
string genre,
|
|
||||||
string date);
|
|
||||||
|
|
||||||
public record FFprobeStream(
|
public record FFprobeStream(
|
||||||
int index,
|
int index,
|
||||||
string codec_name,
|
string codec_name,
|
||||||
@@ -505,5 +549,21 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
|
|||||||
string start_time,
|
string start_time,
|
||||||
string end_time,
|
string end_time,
|
||||||
FFprobeTags tags);
|
FFprobeTags tags);
|
||||||
|
|
||||||
|
public record FFprobeTags(
|
||||||
|
string language,
|
||||||
|
string title,
|
||||||
|
string filename,
|
||||||
|
string mimetype,
|
||||||
|
string artist,
|
||||||
|
[property: JsonProperty(PropertyName = "album_artist")]
|
||||||
|
string albumArtist,
|
||||||
|
string album,
|
||||||
|
string track,
|
||||||
|
string genre,
|
||||||
|
string date)
|
||||||
|
{
|
||||||
|
public static readonly FFprobeTags Empty = new(null, null, null, null, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
// ReSharper restore InconsistentNaming
|
// ReSharper restore InconsistentNaming
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
namespace ErsatzTV.Core.Metadata;
|
namespace ErsatzTV.Core.Metadata;
|
||||||
|
|
||||||
public static class MetadataFormatTag
|
public static class MetadataSongTag
|
||||||
{
|
{
|
||||||
public static readonly string Album = "album";
|
public static readonly string Album = "album";
|
||||||
public static readonly string Artist = "artist";
|
public static readonly string Artist = "artist";
|
||||||
Reference in New Issue
Block a user