diff options
27 files changed, 279 insertions, 185 deletions
diff --git a/Dockerfile b/Dockerfile index e2d424df7..91a4f5a2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,12 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin + +ARG JELLYFIN_WEB_VERSION=10.2.2 +RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && rm -rf /jellyfin/jellyfin-web \ + && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web + EXPOSE 8096 VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ diff --git a/Dockerfile.arm b/Dockerfile.arm index d220763a2..42f0354a3 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -28,6 +28,12 @@ RUN apt-get update \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin + +ARG JELLYFIN_WEB_VERSION=10.2.2 +RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && rm -rf /jellyfin/jellyfin-web \ + && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web + EXPOSE 8096 VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 243b841e9..d3103d389 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -29,6 +29,12 @@ RUN apt-get update \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin + +ARG JELLYFIN_WEB_VERSION=10.2.2 +RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ + && rm -rf /jellyfin/jellyfin-web \ + && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web + EXPOSE 8096 VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index f784be83e..90125fa3e 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -26,17 +26,17 @@ namespace DvdLib.Ifo if (vmgPath == null) { - var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)); - - foreach (var ifo in allIfos) + foreach (var ifo in allFiles) { - var num = ifo.Name.Split('_').ElementAtOrDefault(1); - var numbersRead = new List<ushort>(); + if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase)) + { + continue; + } - if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber)) + var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); + if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) { ReadVTS(ifoNumber, ifo.FullName); - numbersRead.Add(ifoNumber); } } } @@ -76,7 +76,7 @@ namespace DvdLib.Ifo } } - private void ReadVTS(ushort vtsNum, List<FileSystemMetadata> allFiles) + private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles) { var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum); diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 1150afdba..84f38ff76 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -260,7 +260,7 @@ namespace Emby.Dlna.ContentDirectory if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue) { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id); } @@ -273,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory } else { - var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount)); + var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); totalCount = childrenResult.TotalRecordCount; provided = childrenResult.Items.Length; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 605f4f37b..1268f3d5c 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -818,10 +818,9 @@ namespace Emby.Dlna.Didl { AddCommonFields(item, itemStubType, context, writer, filter); - var hasArtists = item as IHasArtist; var hasAlbumArtists = item as IHasAlbumArtist; - if (hasArtists != null) + if (item is IHasArtist hasArtists) { foreach (var artist in hasArtists.Artists) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d27451..d6ee5d13a 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -15,7 +16,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -29,7 +29,7 @@ namespace Emby.Dlna private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; - private readonly IAssemblyInfo _assemblyInfo; + private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal); @@ -39,8 +39,7 @@ namespace Emby.Dlna IApplicationPaths appPaths, ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - IServerApplicationHost appHost, - IAssemblyInfo assemblyInfo) + IServerApplicationHost appHost) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; @@ -48,7 +47,6 @@ namespace Emby.Dlna _logger = loggerFactory.CreateLogger("Dlna"); _jsonSerializer = jsonSerializer; _appHost = appHost; - _assemblyInfo = assemblyInfo; } public async Task InitProfilesAsync() @@ -368,15 +366,18 @@ namespace Emby.Dlna var systemProfilesPath = SystemProfilesPath; - foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType()) - .Where(i => i.StartsWith(namespaceName)) - .ToList()) + foreach (var name in _assembly.GetManifestResourceNames()) { + if (!name.StartsWith(namespaceName)) + { + continue; + } + var filename = Path.GetFileName(name).Substring(namespaceName.Length); var path = Path.Combine(systemProfilesPath, filename); - using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name)) + using (var stream = _assembly.GetManifestResourceStream(name)) { var fileInfo = _fileSystem.GetFileInfo(path); @@ -514,7 +515,7 @@ namespace Emby.Dlna return new ImageStream { Format = format, - Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource) + Stream = _assembly.GetManifestResourceStream(resource) }; } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5a7c9b617..57ed0097a 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -246,7 +246,7 @@ namespace Emby.Dlna.Main private async Task RegisterServerEndpoints() { - var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList(); + var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false); var udn = CreateUuid(_appHost.SystemId); diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index b96fa43e5..4f9e398e9 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -107,12 +107,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, null); + } } return string.Format(CommandBase, action.Name, xmlNamespace, stateString); @@ -125,11 +131,18 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Direction == "out") + { continue; + } + if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else + { stateString += BuildArgumentXml(arg, value.ToString(), commandParameter); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); @@ -142,11 +155,17 @@ namespace Emby.Dlna.PlayTo foreach (var arg in action.ArgumentList) { if (arg.Name == "InstanceID") + { stateString += BuildArgumentXml(arg, "0"); + } else if (dictionary.ContainsKey(arg.Name)) + { stateString += BuildArgumentXml(arg, dictionary[arg.Name]); + } else + { stateString += BuildArgumentXml(arg, value.ToString()); + } } return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs index 9485d697b..a8f81a3b8 100644 --- a/Emby.Naming/TV/EpisodePathParser.cs +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; using Emby.Naming.Common; namespace Emby.Naming.TV @@ -22,7 +21,9 @@ namespace Emby.Naming.TV // There were no failed tests without this block, but to be safe, we can keep it until // the regex which require file extensions are modified so that they don't need them. if (IsDirectory) + { path += ".mp4"; + } EpisodePathParserResult result = null; @@ -35,6 +36,7 @@ namespace Emby.Naming.TV continue; } } + if (isNamed.HasValue) { if (expression.IsNamed != isNamed.Value) @@ -42,6 +44,7 @@ namespace Emby.Naming.TV continue; } } + if (isOptimistic.HasValue) { if (expression.IsOptimistic != isOptimistic.Value) @@ -191,13 +194,20 @@ namespace Emby.Naming.TV private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions) { - var results = expressions - .Where(i => i.IsNamed) - .Select(i => Parse(path, i)) - .Where(i => i.Success); - - foreach (var result in results) + foreach (var i in expressions) { + if (!i.IsNamed) + { + continue; + } + + var result = Parse(path, i); + + if (!result.Success) + { + continue; + } + if (string.IsNullOrEmpty(info.SeriesName)) { info.SeriesName = result.SeriesName; @@ -208,12 +218,10 @@ namespace Emby.Naming.TV info.EndingEpsiodeNumber = result.EndingEpsiodeNumber; } - if (!string.IsNullOrEmpty(info.SeriesName)) + if (!string.IsNullOrEmpty(info.SeriesName) + && (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)) { - if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue) - { - break; - } + break; } } } diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 6febcc2f7..0c513ea12 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -39,8 +39,13 @@ namespace Emby.Server.Implementations.Activity { var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); - foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty))) + foreach (var item in result.Items) { + if (item.UserId == Guid.Empty) + { + continue; + } + var user = _userManager.GetUserById(item.UserId); if (user != null) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b5a64cbdd..b3bb4f740 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -744,10 +744,8 @@ namespace Emby.Server.Implementations TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); serviceCollection.AddSingleton(TVSeriesManager); - var encryptionManager = new EncryptionManager(); - serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager); - DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); + serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); @@ -769,7 +767,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton<IDlnaManager>( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 949b89226..7e50650d7 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -243,8 +243,7 @@ namespace Emby.Server.Implementations.Channels { foreach (var item in returnItems) { - var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None); - Task.WaitAll(task); + RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult(); } } @@ -303,9 +302,7 @@ namespace Emby.Server.Implementations.Channels } numComplete++; - double percent = numComplete; - percent /= allChannelsList.Count; - + double percent = (double)numComplete / allChannelsList.Count; progress.Report(100 * percent); } @@ -658,9 +655,7 @@ namespace Emby.Server.Implementations.Channels foreach (var item in result.Items) { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await GetChannelItemsInternal(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 70e5fa640..06f6563a3 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2279,11 +2279,10 @@ namespace Emby.Server.Implementations.Data private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - "Audio", - "MusicAlbum", - "MusicVideo", + "Book", "AudioBook", - "AudioPodcast" + "Episode", + "Season" }; private bool HasSeriesFields(InternalItemsQuery query) diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7a9b72244..4109b7ad1 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data { list.Add(row[0].ReadGuidFromBlob()); } - catch + catch (Exception ex) { - + Logger.LogError(ex, "Error while getting user"); } } } diff --git a/Emby.Server.Implementations/Security/EncryptionManager.cs b/Emby.Server.Implementations/Security/EncryptionManager.cs deleted file mode 100644 index fa8872ccc..000000000 --- a/Emby.Server.Implementations/Security/EncryptionManager.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Controller.Security; - -namespace Emby.Server.Implementations.Security -{ - public class EncryptionManager : IEncryptionManager - { - /// <summary> - /// Encrypts the string. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentNullException">value</exception> - public string EncryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return EncryptStringUniversal(value); - } - - /// <summary> - /// Decrypts the string. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentNullException">value</exception> - public string DecryptString(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - return DecryptStringUniversal(value); - } - - private static string EncryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - private static string DecryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - } -} diff --git a/Emby.XmlTv/Emby.XmlTv/Classes/XmlTvReader.cs b/Emby.XmlTv/Emby.XmlTv/Classes/XmlTvReader.cs index 52ec7a135..46bf6cc21 100644 --- a/Emby.XmlTv/Emby.XmlTv/Classes/XmlTvReader.cs +++ b/Emby.XmlTv/Emby.XmlTv/Classes/XmlTvReader.cs @@ -495,9 +495,7 @@ namespace Emby.XmlTv.Classes ParseMovieDbSystem(reader, result); break; case "SxxExx": - // TODO - // <episode-num system="SxxExx">S03E12</episode-num> - reader.Skip(); + ParseSxxExxSystem(reader, result); break; default: // Handles empty string and nulls reader.Skip(); @@ -505,6 +503,29 @@ namespace Emby.XmlTv.Classes } } + public void ParseSxxExxSystem(XmlReader reader, XmlTvProgram result) + { + // <episode-num system="SxxExx">S012E32</episode-num> + + var value = reader.ReadElementContentAsString(); + var res = Regex.Match(value, "s([0-9]+)e([0-9]+)", RegexOptions.IgnoreCase); + + if (res.Success) + { + int parsedInt; + + if (int.TryParse(res.Groups[1].Value, out parsedInt)) + { + result.Episode.Series = parsedInt; + } + + if (int.TryParse(res.Groups[2].Value, out parsedInt)) + { + result.Episode.Episode = parsedInt; + } + } + } + public void ParseMovieDbSystem(XmlReader reader, XmlTvProgram result) { // <episode-num system="thetvdb.com">series/248841</episode-num> diff --git a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs index 0d5a1d3c0..c72f295fd 100644 --- a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs +++ b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Drawing.Skia foregroundWidth *= percent; foregroundWidth /= 100; - paint.Color = SKColor.Parse("#FF52B54B"); + paint.Color = SKColor.Parse("#FF00A4DC"); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint); } } diff --git a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs index 62497da27..7f3c18bb2 100644 --- a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Drawing.Skia using (var paint = new SKPaint()) { - paint.Color = SKColor.Parse("#CC52B54B"); + paint.Color = SKColor.Parse("#CC00A4DC"); paint.Style = SKPaintStyle.Fill; canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); } diff --git a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs index ba712bff7..dbf935f4e 100644 --- a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs +++ b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Drawing.Skia using (var paint = new SKPaint()) { - paint.Color = SKColor.Parse("#CC52B54B"); + paint.Color = SKColor.Parse("#CC00A4DC"); paint.Style = SKPaintStyle.Fill; canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); } diff --git a/MediaBrowser.Controller/Security/IEncryptionManager.cs b/MediaBrowser.Controller/Security/IEncryptionManager.cs deleted file mode 100644 index 68680fdf3..000000000 --- a/MediaBrowser.Controller/Security/IEncryptionManager.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MediaBrowser.Controller.Security -{ - public interface IEncryptionManager - { - /// <summary> - /// Encrypts the string. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.String.</returns> - string EncryptString(string value); - - /// <summary> - /// Decrypts the string. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.String.</returns> - string DecryptString(string value); - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 262772959..f725d2c01 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Globalization; +using System.Collections.ObjectModel; using System.Linq; using System.Text.RegularExpressions; using MediaBrowser.Model.Diagnostics; @@ -58,18 +58,107 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - output = " " + output + " "; + // The min and max FFmpeg versions required to run jellyfin successfully + var minRequired = new Version(4, 0); + var maxRequired = new Version(4, 0); - for (var i = 2013; i <= 2015; i++) + // Work out what the version under test is + var underTest = GetFFmpegVersion(output); + + if (logOutput) { - var yearString = i.ToString(CultureInfo.InvariantCulture); - if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1) + _logger.LogInformation("FFmpeg validation: Found ffmpeg version {0}", underTest != null ? underTest.ToString() : "unknown"); + + if (underTest == null) // Version is unknown + { + if (minRequired.Equals(maxRequired)) + { + _logger.LogWarning("FFmpeg validation: We recommend ffmpeg version {0}", minRequired.ToString()); + } + else + { + _logger.LogWarning("FFmpeg validation: We recommend a minimum of {0} and maximum of {1}", minRequired.ToString(), maxRequired.ToString()); + } + } + else if (underTest.CompareTo(minRequired) < 0) // Version is below what we recommend { - return false; + _logger.LogWarning("FFmpeg validation: The minimum recommended ffmpeg version is {0}", minRequired.ToString()); } + else if (underTest.CompareTo(maxRequired) > 0) // Version is above what we recommend + { + _logger.LogWarning("FFmpeg validation: The maximum recommended ffmpeg version is {0}", maxRequired.ToString()); + } + else // Version is ok + { + _logger.LogInformation("FFmpeg validation: Found suitable ffmpeg version"); + } + } + + // underTest shall be null if versions is unknown + return (underTest == null) ? false : (underTest.CompareTo(minRequired) >= 0 && underTest.CompareTo(maxRequired) <= 0); + } + + /// <summary> + /// Using the output from "ffmpeg -version" work out the FFmpeg version. + /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy + /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions. + /// If that fails then we use one of the main libraries to determine if it's new/older than the latest + /// we have stored. + /// </summary> + /// <param name="output"></param> + /// <returns></returns> + static private Version GetFFmpegVersion(string output) + { + // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output + var match = Regex.Match(output, @"ffmpeg version (\d+\.\d+)"); + + if (match.Success) + { + return new Version(match.Groups[1].Value); + } + else + { + // Try and use the individual library versions to determine a FFmpeg version + // This lookup table is to be maintained with the following command line: + // $ ./ffmpeg.exe -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/' + var lut = new ReadOnlyDictionary<Version, string> + (new Dictionary<Version, string> + { + { new Version("4.1"), "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3," }, + { new Version("4.0"), "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1," }, + { new Version("3.4"), "libavutil=55.78,libavcodec=57.107,libavformat=57.83,libavdevice=57.10,libavfilter=6.107,libswscale=4.8,libswresample=2.9,libpostproc=54.7," }, + { new Version("3.3"), "libavutil=55.58,libavcodec=57.89,libavformat=57.71,libavdevice=57.6,libavfilter=6.82,libswscale=4.6,libswresample=2.7,libpostproc=54.5," }, + { new Version("3.2"), "libavutil=55.34,libavcodec=57.64,libavformat=57.56,libavdevice=57.1,libavfilter=6.65,libswscale=4.2,libswresample=2.3,libpostproc=54.1," }, + { new Version("2.8"), "libavutil=54.31,libavcodec=56.60,libavformat=56.40,libavdevice=56.4,libavfilter=5.40,libswscale=3.1,libswresample=1.2,libpostproc=53.3," } + }); + + // Create a reduced version string and lookup key from dictionary + var reducedVersion = GetVersionString(output); + + // Try to lookup the string and return Key, otherwise if not found returns null + return lut.FirstOrDefault(x => x.Value == reducedVersion).Key; + } + } + + /// <summary> + /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output + /// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc." + /// </summary> + /// <param name="output"></param> + /// <returns></returns> + static private string GetVersionString(string output) + { + string pattern = @"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))"; + RegexOptions options = RegexOptions.Multiline; + + string rc = null; + + foreach (Match m in Regex.Matches(output, pattern, options)) + { + rc += string.Concat(m.Groups["name"], '=', m.Groups["major"], '.', m.Groups["minor"], ','); } - return true; + return rc; } private static readonly string[] requiredDecoders = new[] diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs index 6a5162b8d..a7e3f6197 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -29,17 +30,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; - public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) + public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem) { _logger = loggerFactory.CreateLogger(GetType().Name); _httpClient = httpClient; _config = config; - _encryption = encryption; _json = json; _fileSystem = fileSystem; @@ -63,16 +62,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); + options.OpenSubtitlesPasswordHash = EncodePassword(options.OpenSubtitlesPasswordHash); } } - private string EncryptPassword(string password) + private static string EncodePassword(string password) { - return PasswordHashPrefix + _encryption.EncryptString(password); + var bytes = Encoding.UTF8.GetBytes(password); + return PasswordHashPrefix + Convert.ToBase64String(bytes); } - private string DecryptPassword(string password) + private static string DecodePassword(string password) { if (password == null || !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) @@ -80,7 +80,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles return string.Empty; } - return _encryption.DecryptString(password.Substring(2)); + var bytes = Convert.FromBase64String(password.Substring(2)); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); } public string Name => "Open Subtitles"; @@ -186,7 +187,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var options = GetOptions(); var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); + var password = DecodePassword(options.OpenSubtitlesPasswordHash); var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); @@ -26,7 +26,7 @@ usage() { echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch <web_branch>] <platform> <action>" echo -e "" echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds." - echo -e "The web_branch defaults to the same branch name as the current main branch." + echo -e "The web_branch defaults to the same branch name as the current main branch or can be 'local' to not touch the submodule branching." echo -e "To build all platforms, use 'all'." echo -e "To perform all build actions, use 'all'." echo -e "Build output files are collected at '../jellyfin-build/<platform>'." @@ -164,37 +164,39 @@ for target_platform in ${platform[@]}; do fi done -# Initialize submodules -git submodule update --init --recursive +if [[ ${web_branch} != 'local' ]]; then + # Initialize submodules + git submodule update --init --recursive -# configure branch -pushd MediaBrowser.WebDashboard/jellyfin-web + # configure branch + pushd MediaBrowser.WebDashboard/jellyfin-web -if ! git diff-index --quiet HEAD --; then - popd - echo - echo "ERROR: Your 'jellyfin-web' submodule working directory is not clean!" - echo "This script will overwrite your unstaged and unpushed changes." - echo "Please do development on 'jellyfin-web' outside of the submodule." - exit 1 -fi - -git fetch --all -# If this is an official branch name, fetch it from origin -official_branches_regex="^master$|^dev$|^release-.*$|^hotfix-.*$" -if [[ ${web_branch} =~ ${official_branches_regex} ]]; then - git checkout origin/${web_branch} || { - echo "ERROR: 'jellyfin-web' branch 'origin/${web_branch}' is invalid." + if ! git diff-index --quiet HEAD --; then + popd + echo + echo "ERROR: Your 'jellyfin-web' submodule working directory is not clean!" + echo "This script will overwrite your unstaged and unpushed changes." + echo "Please do development on 'jellyfin-web' outside of the submodule." exit 1 - } -# Otherwise, just check out the local branch (for testing, etc.) -else - git checkout ${web_branch} || { - echo "ERROR: 'jellyfin-web' branch '${web_branch}' is invalid." - exit 1 - } + fi + + git fetch --all + # If this is an official branch name, fetch it from origin + official_branches_regex="^master$|^dev$|^release-.*$|^hotfix-.*$" + if [[ ${web_branch} =~ ${official_branches_regex} ]]; then + git checkout origin/${web_branch} || { + echo "ERROR: 'jellyfin-web' branch 'origin/${web_branch}' is invalid." + exit 1 + } + # Otherwise, just check out the local branch (for testing, etc.) + else + git checkout ${web_branch} || { + echo "ERROR: 'jellyfin-web' branch '${web_branch}' is invalid." + exit 1 + } + fi + popd fi -popd # Execute each platform and action in order, if said action is enabled pushd deployment/ @@ -217,7 +219,7 @@ for target_platform in ${platform[@]}; do done if [[ -d pkg-dist/ ]]; then echo -e ">> Collecting build artifacts" - target_dir="../../../jellyfin-build/${target_platform}" + target_dir="../../../bin/${target_platform}" mkdir -p ${target_dir} mv pkg-dist/* ${target_dir}/ fi diff --git a/build.yaml b/build.yaml new file mode 100644 index 000000000..b0d2502d5 --- /dev/null +++ b/build.yaml @@ -0,0 +1,15 @@ +--- +# We just wrap `build` so this is really it +name: "jellyfin" +version: "10.2.2" +packages: + - debian-package-x64 + - debian-package-armhf + - ubuntu-package-x64 + - fedora-package-x64 + - centos-package-x64 + - linux-x64 + - macos + - portable + - win-x64 + - win-x86 diff --git a/deployment/win-x64/package.sh b/deployment/win-x64/package.sh index d21e3b532..b438c28e4 100755 --- a/deployment/win-x64/package.sh +++ b/deployment/win-x64/package.sh @@ -21,8 +21,8 @@ package_win64() ( cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${OUTPUT_DIR}/ffmpeg.exe cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffprobe.exe ${OUTPUT_DIR}/ffprobe.exe rm -r ${TEMP_DIR} - cp ${ROOT}/deployment/win-generic/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1 - cp ${ROOT}/deployment/win-generic/install.bat ${OUTPUT_DIR}/install.bat + cp ${ROOT}/deployment/windows/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1 + cp ${ROOT}/deployment/windows/install.bat ${OUTPUT_DIR}/install.bat mkdir -p ${PKG_DIR} pushd ${OUTPUT_DIR} ${ARCHIVE_CMD} ${ROOT}/${PKG_DIR}/`basename "${OUTPUT_DIR}"`.zip . diff --git a/deployment/win-x86/package.sh b/deployment/win-x86/package.sh index 3cc4eb623..8752d92a8 100755 --- a/deployment/win-x86/package.sh +++ b/deployment/win-x86/package.sh @@ -20,8 +20,8 @@ package_win32() ( cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${OUTPUT_DIR}/ffmpeg.exe cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffprobe.exe ${OUTPUT_DIR}/ffprobe.exe rm -r ${TEMP_DIR} - cp ${ROOT}/deployment/win-generic/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1 - cp ${ROOT}/deployment/win-generic/install.bat ${OUTPUT_DIR}/install.bat + cp ${ROOT}/deployment/windows/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1 + cp ${ROOT}/deployment/windows/install.bat ${OUTPUT_DIR}/install.bat mkdir -p ${PKG_DIR} pushd ${OUTPUT_DIR} ${ARCHIVE_CMD} ${ROOT}/${PKG_DIR}/`basename "${OUTPUT_DIR}"`.zip . |
