Interface improvements (#34)

* fix plex library synchronization

* add basic plex movie synchronization

* proxy plex movie thumbnails (posters)

* add plex path replacements

* use transcoded plex artwork

* remove unsynchronized plex movies on save; queue plex library scan on save

* log plex path replacements

* prefer buttons instead of menus

* lock plex libraries before sync

* add movie to collection from paged view

* fix plex import memory use; quick add seasons/shows

* quick add episode to collection

* add favicon

* add search page

* disable plex for now
This commit is contained in:
Jason Dove
2021-03-02 02:54:23 +00:00
committed by GitHub
parent aef486103e
commit f281d9fca5
89 changed files with 4727 additions and 472 deletions
@@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain
{
public class PlexMediaFile : MediaFile
{
public int PlexId { get; set; }
public string Key { get; set; }
}
}
@@ -1,12 +0,0 @@
namespace ErsatzTV.Core.Domain
{
public class PlexMediaItemPart
{
public int Id { get; set; }
public int PlexId { get; set; }
public string Key { get; set; }
public int Duration { get; set; }
public string File { get; set; }
public int Size { get; set; }
}
}
@@ -3,6 +3,5 @@
public class PlexMovie : Movie
{
public string Key { get; set; }
public PlexMediaItemPart Part { get; set; }
}
}
@@ -10,5 +10,6 @@ namespace ErsatzTV.Core.Domain
// public bool IsOwned { get; set; }
public List<PlexConnection> Connections { get; set; }
public List<PlexPathReplacement> PathReplacements { get; set; }
}
}
@@ -0,0 +1,11 @@
namespace ErsatzTV.Core.Domain
{
public class PlexPathReplacement
{
public int Id { get; set; }
public string PlexPath { get; set; }
public string LocalPath { get; set; }
public int PlexMediaSourceId { get; set; }
public PlexMediaSource PlexMediaSource { get; set; }
}
}
+6 -13
View File
@@ -15,32 +15,25 @@ namespace ErsatzTV.Core.FFmpeg
public Process ForPlayoutItem(
string ffmpegPath,
Channel channel,
PlayoutItem item,
MediaVersion version,
string path,
DateTimeOffset start,
DateTimeOffset now)
{
MediaVersion version = item.MediaItem switch
{
Movie m => m.MediaVersions.Head(),
Episode e => e.MediaVersions.Head(),
_ => throw new ArgumentOutOfRangeException(nameof(item))
};
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateSettings(
channel.StreamingMode,
channel.FFmpegProfile,
version,
item.StartOffset,
start,
now);
MediaFile file = version.MediaFiles.Head();
FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath)
.WithThreads(playbackSettings.ThreadCount)
.WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags)
.WithRealtimeOutput(playbackSettings.RealtimeOutput)
.WithSeek(playbackSettings.StreamSeek)
.WithInput(file.Path);
.WithInput(path);
playbackSettings.ScaledSize.Match(
scaledSize =>
@@ -86,7 +79,7 @@ namespace ErsatzTV.Core.FFmpeg
return builder.WithPlaybackArgs(playbackSettings)
.WithMetadata(channel)
.WithFormat("mpegts")
.WithDuration(item.Start + version.Duration - now)
.WithDuration(start + version.Duration - now)
.WithPipe()
.Build();
}
@@ -1,9 +1,13 @@
using ErsatzTV.Core.Domain;
using System;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Interfaces.Metadata
{
public interface IFallbackMetadataProvider
{
ShowMetadata GetFallbackMetadataForShow(string showFolder);
Tuple<EpisodeMetadata, int> GetFallbackMetadata(Episode episode);
MovieMetadata GetFallbackMetadata(Movie movie);
string GetSortTitle(string title);
}
}
@@ -10,7 +10,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<LibraryPath> Add(LibraryPath libraryPath);
Task<Option<Library>> Get(int libraryId);
Task<Option<LocalLibrary>> GetLocal(int libraryId);
Task<List<LocalLibrary>> GetAllLocal();
Task<List<Library>> GetAll();
Task<Unit> UpdateLastScan(Library library);
Task<List<LibraryPath>> GetLocalPaths(int libraryId);
Task<Option<LibraryPath>> GetPath(int libraryPathId);
@@ -12,15 +12,18 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<List<MediaSource>> GetAll();
Task<List<PlexMediaSource>> GetAllPlex();
Task<List<PlexLibrary>> GetPlexLibraries(int plexMediaSourceId);
Task<List<PlexPathReplacement>> GetPlexPathReplacements(int plexMediaSourceId);
Task<Option<PlexLibrary>> GetPlexLibrary(int plexLibraryId);
Task<Option<MediaSource>> Get(int id);
Task<Option<PlexMediaSource>> GetPlex(int id);
Task<Option<PlexMediaSource>> GetPlexByLibraryId(int plexLibraryId);
Task<List<PlexPathReplacement>> GetPlexPathReplacementsByLibraryId(int plexLibraryPathId);
Task<int> CountMediaItems(int id);
Task Update(LocalMediaSource localMediaSource);
Task Update(PlexMediaSource plexMediaSource);
Task Update(PlexLibrary plexMediaSourceLibrary);
Task Delete(int id);
Task DisablePlexLibrarySync(IEnumerable<int> libraryIds);
Task DisablePlexLibrarySync(List<int> libraryIds);
Task EnablePlexLibrarySync(IEnumerable<int> libraryIds);
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Interfaces.Repositories
{
public interface ISearchRepository
{
public Task<List<MediaItem>> SearchMediaItems(string query);
}
}
+1 -1
View File
@@ -108,7 +108,7 @@ namespace ErsatzTV.Core.Iptv
xml.WriteAttributeString("system", "xmltv_ns");
xml.WriteString($"{s - 1}.{e - 1}.0/1");
xml.WriteEndElement(); // episode-num
xml.WriteStartElement("episode-num");
xml.WriteAttributeString("system", "onscreen");
xml.WriteString($"S{s:00}E{e:00}");
@@ -17,7 +17,7 @@ namespace ErsatzTV.Core.Metadata
return GetTelevisionShowMetadata(fileName, metadata);
}
public static Tuple<EpisodeMetadata, int> GetFallbackMetadata(Episode episode)
public Tuple<EpisodeMetadata, int> GetFallbackMetadata(Episode episode)
{
string path = episode.MediaVersions.Head().MediaFiles.Head().Path;
string fileName = Path.GetFileName(path);
@@ -26,7 +26,7 @@ namespace ErsatzTV.Core.Metadata
return fileName != null ? GetEpisodeMetadata(fileName, metadata) : Tuple(metadata, 0);
}
public static MovieMetadata GetFallbackMetadata(Movie movie)
public MovieMetadata GetFallbackMetadata(Movie movie)
{
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
string fileName = Path.GetFileName(path);
@@ -34,8 +34,28 @@ namespace ErsatzTV.Core.Metadata
return fileName != null ? GetMovieMetadata(fileName, metadata) : metadata;
}
public string GetSortTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
{
return title;
}
private static Tuple<EpisodeMetadata, int> GetEpisodeMetadata(string fileName, EpisodeMetadata metadata)
if (title.StartsWith("the ", StringComparison.OrdinalIgnoreCase))
{
return title.Substring(4);
}
if (title.StartsWith("Æ"))
{
return title.Replace("Æ", "E");
}
return title;
}
private Tuple<EpisodeMetadata, int> GetEpisodeMetadata(string fileName, EpisodeMetadata metadata)
{
try
{
@@ -55,7 +75,7 @@ namespace ErsatzTV.Core.Metadata
return Tuple(metadata, 0);
}
private static MovieMetadata GetMovieMetadata(string fileName, MovieMetadata metadata)
private MovieMetadata GetMovieMetadata(string fileName, MovieMetadata metadata)
{
try
{
@@ -76,9 +96,7 @@ namespace ErsatzTV.Core.Metadata
return metadata;
}
private static ShowMetadata GetTelevisionShowMetadata(
string fileName,
ShowMetadata metadata)
private ShowMetadata GetTelevisionShowMetadata(string fileName, ShowMetadata metadata)
{
try
{
+11 -31
View File
@@ -51,7 +51,7 @@ namespace ErsatzTV.Core.Metadata
() =>
{
ShowMetadata metadata = _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder);
metadata.SortTitle = GetSortTitle(metadata.Title);
metadata.SortTitle = _fallbackMetadataProvider.GetSortTitle(metadata.Title);
return metadata;
});
}
@@ -73,9 +73,9 @@ namespace ErsatzTV.Core.Metadata
public Task<Unit> RefreshFallbackMetadata(MediaItem mediaItem) =>
mediaItem switch
{
Episode e => ApplyMetadataUpdate(e, FallbackMetadataProvider.GetFallbackMetadata(e))
Episode e => ApplyMetadataUpdate(e, _fallbackMetadataProvider.GetFallbackMetadata(e))
.ToUnit(),
Movie m => ApplyMetadataUpdate(m, FallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(),
Movie m => ApplyMetadataUpdate(m, _fallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(),
_ => Task.FromResult(Unit.Default)
};
@@ -100,11 +100,11 @@ namespace ErsatzTV.Core.Metadata
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.Year = metadata.Year;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
existing.SortTitle = metadata.SortTitle ?? _fallbackMetadataProvider.GetSortTitle(metadata.Title);
},
() =>
{
metadata.SortTitle ??= GetSortTitle(metadata.Title);
metadata.SortTitle ??= _fallbackMetadataProvider.GetSortTitle(metadata.Title);
episode.EpisodeMetadata = new List<EpisodeMetadata> { metadata };
});
@@ -126,11 +126,11 @@ namespace ErsatzTV.Core.Metadata
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.Year = metadata.Year;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
existing.SortTitle = metadata.SortTitle ?? _fallbackMetadataProvider.GetSortTitle(metadata.Title);
},
() =>
{
metadata.SortTitle ??= GetSortTitle(metadata.Title);
metadata.SortTitle ??= _fallbackMetadataProvider.GetSortTitle(metadata.Title);
movie.MovieMetadata = new List<MovieMetadata> { metadata };
});
@@ -152,11 +152,11 @@ namespace ErsatzTV.Core.Metadata
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.Year = metadata.Year;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
existing.SortTitle = metadata.SortTitle ?? _fallbackMetadataProvider.GetSortTitle(metadata.Title);
},
() =>
{
metadata.SortTitle ??= GetSortTitle(metadata.Title);
metadata.SortTitle ??= _fallbackMetadataProvider.GetSortTitle(metadata.Title);
show.ShowMetadata = new List<ShowMetadata> { metadata };
});
@@ -247,7 +247,7 @@ namespace ErsatzTV.Core.Metadata
catch (Exception ex)
{
_logger.LogInformation(ex, "Failed to read TV episode nfo metadata from {Path}", nfoFileName);
return FallbackMetadataProvider.GetFallbackMetadata(episode);
return _fallbackMetadataProvider.GetFallbackMetadata(episode);
}
}
@@ -274,7 +274,7 @@ namespace ErsatzTV.Core.Metadata
catch (Exception ex)
{
_logger.LogInformation(ex, "Failed to read Movie nfo metadata from {Path}", nfoFileName);
return FallbackMetadataProvider.GetFallbackMetadata(mediaItem);
return _fallbackMetadataProvider.GetFallbackMetadata(mediaItem);
}
}
@@ -293,26 +293,6 @@ namespace ErsatzTV.Core.Metadata
return null;
}
private static string GetSortTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
{
return title;
}
if (title.StartsWith("the ", StringComparison.OrdinalIgnoreCase))
{
return title.Substring(4);
}
if (title.StartsWith("Æ"))
{
return title.Replace("Æ", "E");
}
return title;
}
[XmlRoot("movie")]
public class MovieNfo
{
-20
View File
@@ -1,20 +0,0 @@
using System.Collections.Generic;
namespace ErsatzTV.Core.Plex
{
public class PlexMediaEntry
{
public int Id { get; set; }
public int Duration { get; set; }
public int Bitrate { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public double AspectRatio { get; set; }
public int AudioChannels { get; set; }
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
public string Container { get; set; }
public string VideoFrameRate { get; set; }
public List<PlexPartEntry> Part { get; set; }
}
}
-19
View File
@@ -1,19 +0,0 @@
using System.Collections.Generic;
namespace ErsatzTV.Core.Plex
{
public class PlexMetadataEntry
{
public string Key { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public int Year { get; set; }
public string Tagline { get; set; }
public string Thumb { get; set; }
public string Art { get; set; }
public string OriginallyAvailableAt { get; set; }
public int AddedAt { get; set; }
public int UpdatedAt { get; set; }
public List<PlexMediaEntry> Media { get; set; }
}
}
-11
View File
@@ -1,11 +0,0 @@
namespace ErsatzTV.Core.Plex
{
public class PlexPartEntry
{
public int Id { get; set; }
public string Key { get; set; }
public int Duration { get; set; }
public string File { get; set; }
public int Size { get; set; }
}
}