Merge branch 'master' into tonemap-overlay

pull/6327/head
Nyanmisaka 3 years ago committed by GitHub
commit a84dc794c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,7 +7,7 @@ parameters:
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
default: 5.0.103
default: 5.0.302
jobs:
- job: CompatibilityCheck

@ -1,7 +1,7 @@
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
DotNetSdkVersion: 5.0.103
DotNetSdkVersion: 5.0.302
jobs:
- job: Build

@ -10,7 +10,7 @@ parameters:
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
default: 5.0.103
default: 5.0.302
jobs:
- job: Test

@ -6,7 +6,7 @@ variables:
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion
value: 5.0.103
value: 5.0.302
pr:
autoCancel: true

@ -212,3 +212,4 @@
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
- [gnuyent](https://github.com/gnuyent)

@ -0,0 +1,14 @@
<Project>
<!-- Sets defaults for all projects in the repo -->
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
</PropertyGroup>
</Project>

@ -8,15 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
FROM debian:buster-slim
FROM debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@ -25,9 +17,6 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=20.3.2
ARG IGC_VERSION=1.0.5435
@ -73,6 +62,19 @@ ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
FROM app
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \

@ -13,19 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM arm32v7/debian:buster-slim
FROM arm32v7/debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@ -61,14 +50,25 @@ RUN apt-get update \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
FROM app
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \

@ -13,18 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:buster-slim
FROM arm64v8/debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@ -50,14 +40,25 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
FROM app
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \

@ -13,7 +13,8 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
namespace Emby.Dlna.Configuration
@ -74,7 +72,7 @@ namespace Emby.Dlna.Configuration
/// <summary>
/// Gets or sets the default user account that the dlna server uses.
/// </summary>
public string DefaultUserId { get; set; }
public string? DefaultUserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether playTo device profiles should be created.

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -140,7 +138,7 @@ namespace Emby.Dlna.ContentDirectory
/// </summary>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <returns>The <see cref="User"/>.</returns>
private User GetUser(DeviceProfile profile)
private User? GetUser(DeviceProfile profile)
{
if (!string.IsNullOrEmpty(profile.UserId))
{

@ -288,21 +288,14 @@ namespace Emby.Dlna.ContentDirectory
/// <returns>The xml feature list.</returns>
private static string WriteFeatureListXml()
{
// TODO: clean this up
var builder = new StringBuilder();
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">");
builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
builder.Append("</Feature>");
builder.Append("</Features>");
return builder.ToString();
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">"
+ "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">"
+ "<container id=\"I\" type=\"object.item.imageItem\"/>"
+ "<container id=\"A\" type=\"object.item.audioItem\"/>"
+ "<container id=\"V\" type=\"object.item.videoItem\"/>"
+ "</Feature>"
+ "</Features>";
}
/// <summary>

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
@ -8,9 +6,11 @@ namespace Emby.Dlna
{
public class ControlResponse
{
public ControlResponse()
public ControlResponse(string xml, bool isSuccessful)
{
Headers = new Dictionary<string, string>();
Xml = xml;
IsSuccessful = isSuccessful;
}
public IDictionary<string, string> Headers { get; }

@ -20,8 +20,7 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
</PropertyGroup>
<!-- Code Analyzers-->
@ -31,10 +30,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Images\logo120.jpg" />
<EmbeddedResource Include="Images\logo120.png" />

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
@ -8,8 +6,10 @@ namespace Emby.Dlna
{
public class EventSubscriptionResponse
{
public EventSubscriptionResponse()
public EventSubscriptionResponse(string content, string contentType)
{
Content = content;
ContentType = contentType;
Headers = new Dictionary<string, string>();
}

@ -51,11 +51,7 @@ namespace Emby.Dlna.Eventing
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
return new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
return new EventSubscriptionResponse(string.Empty, "text/plain");
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@ -103,20 +99,12 @@ namespace Emby.Dlna.Eventing
_subscriptions.TryRemove(subscriptionId, out _);
return new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
return new EventSubscriptionResponse(string.Empty, "text/plain");
}
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
{
var response = new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
var response = new EventSubscriptionResponse(string.Empty, "text/plain");
response.Headers["SID"] = subscriptionId;
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;

@ -1260,10 +1260,7 @@ namespace Emby.Dlna.PlayTo
return;
}
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
{
MediaInfo = mediaInfo
});
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs(mediaInfo));
}
private void OnPlaybackProgress(UBaseObject mediaInfo)
@ -1273,27 +1270,17 @@ namespace Emby.Dlna.PlayTo
return;
}
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
{
MediaInfo = mediaInfo
});
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs(mediaInfo));
}
private void OnPlaybackStop(UBaseObject mediaInfo)
{
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
{
MediaInfo = mediaInfo
});
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs(mediaInfo));
}
private void OnMediaChanged(UBaseObject old, UBaseObject newMedia)
{
MediaChanged?.Invoke(this, new MediaChangedEventArgs
{
OldMediaInfo = old,
NewMediaInfo = newMedia
});
MediaChanged?.Invoke(this, new MediaChangedEventArgs(old, newMedia));
}
/// <inheritdoc />

@ -1,6 +1,4 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CS1591
using System;
@ -8,6 +6,12 @@ namespace Emby.Dlna.PlayTo
{
public class MediaChangedEventArgs : EventArgs
{
public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo)
{
OldMediaInfo = oldMediaInfo;
NewMediaInfo = newMediaInfo;
}
public UBaseObject OldMediaInfo { get; set; }
public UBaseObject NewMediaInfo { get; set; }

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -8,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackProgressEventArgs : EventArgs
{
public PlaybackProgressEventArgs(UBaseObject mediaInfo)
{
MediaInfo = mediaInfo;
}
public UBaseObject MediaInfo { get; set; }
}
}

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -8,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackStartEventArgs : EventArgs
{
public PlaybackStartEventArgs(UBaseObject mediaInfo)
{
MediaInfo = mediaInfo;
}
public UBaseObject MediaInfo { get; set; }
}
}

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -8,6 +6,11 @@ namespace Emby.Dlna.PlayTo
{
public class PlaybackStoppedEventArgs : EventArgs
{
public PlaybackStoppedEventArgs(UBaseObject mediaInfo)
{
MediaInfo = mediaInfo;
}
public UBaseObject MediaInfo { get; set; }
}
}

@ -95,11 +95,7 @@ namespace Emby.Dlna.Service
var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
var controlResponse = new ControlResponse
{
Xml = xml,
IsSuccessful = true
};
var controlResponse = new ControlResponse(xml, true);
controlResponse.Headers.Add("EXT", string.Empty);

@ -46,11 +46,7 @@ namespace Emby.Dlna.Service
writer.WriteEndDocument();
}
return new ControlResponse
{
Xml = builder.ToString(),
IsSuccessful = false
};
return new ControlResponse(builder.ToString(), false);
}
}
}

@ -9,8 +9,7 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
</PropertyGroup>
<ItemGroup>
@ -30,8 +29,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
/// <param name="files">List of files composing the actual audiobook.</param>
/// <param name="extras">List of extra files.</param>
/// <param name="alternateVersions">Alternative version of files.</param>
public AudioBookInfo(string name, int? year, List<AudioBookFileInfo> files, List<AudioBookFileInfo> extras, List<AudioBookFileInfo> alternateVersions)
public AudioBookInfo(string name, int? year, IReadOnlyList<AudioBookFileInfo> files, IReadOnlyList<AudioBookFileInfo> extras, IReadOnlyList<AudioBookFileInfo> alternateVersions)
{
Name = name;
Year = year;
@ -39,18 +39,18 @@ namespace Emby.Naming.AudioBook
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<AudioBookFileInfo> Files { get; set; }
public IReadOnlyList<AudioBookFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<AudioBookFileInfo> Extras { get; set; }
public IReadOnlyList<AudioBookFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; }
public IReadOnlyList<AudioBookFileInfo> AlternateVersions { get; set; }
}
}

@ -87,7 +87,7 @@ namespace Emby.Naming.AudioBook
foreach (var audioFile in group)
{
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
if (name.Equals("audiobook") ||
if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
{

@ -284,7 +284,7 @@ namespace Emby.Naming.Common
// Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?<seriesname>[-\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
{
IsNamed = true
},

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
@ -9,12 +9,11 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
@ -50,8 +49,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;

@ -21,7 +21,7 @@ namespace Emby.Naming.Video
/// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
{
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))

@ -9,10 +9,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>

@ -22,10 +22,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- Code Analyzers-->

@ -82,12 +82,10 @@ namespace Emby.Server.Implementations.Collections
internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded)
{
var existingFolders = FindFolders(path)
.ToList();
if (existingFolders.Count > 0)
var existingFolder = FindFolders(path).FirstOrDefault();
if (existingFolder != null)
{
return existingFolders[0];
return existingFolder;
}
if (!createIfNeeded)

@ -44,12 +44,13 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
<NoWarn>AD0001</NoWarn>
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->

@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
}
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
var allowTokenInfoUpdate = authInfo.Client == null || !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(authInfo.Device))
{

@ -118,5 +118,7 @@
"TaskCleanActivityLog": "حذف سجل الأنشطة",
"Default": "الإعدادات الافتراضية",
"Undefined": "غير معرف",
"Forced": "ملحقة"
"Forced": "ملحقة",
"TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تشير ضمنًا إلى أن تعديلات قاعدة البيانات قد تؤدي إلى تحسين الأداء.",
"TaskOptimizeDatabase": "تحسين قاعدة البيانات"
}

@ -8,7 +8,7 @@
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
"Channels": "Канали",
"ChapterNameValue": "Глава {0}",
"Collections": "Поредици",
"Collections": "Колекции",
"DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан",
"FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}",
@ -29,13 +29,13 @@
"Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
"LabelIpAddressValue": "ИП адрес: {0}",
"LabelRunningTimeValue": "Стартирано от: {0}",
"LabelIpAddressValue": "IP адрес: {0}",
"LabelRunningTimeValue": "Продължителност: {0}",
"Latest": "Последни",
"MessageApplicationUpdated": "Сървърът е обновен",
"MessageApplicationUpdatedTo": "Сървърът е обновен до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Секцията {0} от сървърната конфигурация се актуализира",
"MessageServerConfigurationUpdated": "Конфигурацията на сървъра се актуализира",
"MessageApplicationUpdated": "Сървърът беше обновен",
"MessageApplicationUpdatedTo": "Сървърът беше обновен до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Секцията {0} от сървърната конфигурация беше актуализирана",
"MessageServerConfigurationUpdated": "Конфигурацията на сървъра беше актуализирана",
"MixedContent": "Смесено съдържание",
"Movies": "Филми",
"Music": "Музика",
@ -118,5 +118,7 @@
"Forced": "Принудително",
"Default": "По подразбиране",
"TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.",
"TaskCleanActivityLog": "Изчисти дневника с активност"
"TaskCleanActivityLog": "Изчисти дневника с активност",
"TaskOptimizeDatabaseDescription": "Прави базата данни по-компактна и освобождава място. Пускането на тази задача след сканиране на библиотеката или правене на други промени, свързани с модификации на базата данни, може да подобри производителността.",
"TaskOptimizeDatabase": "Оптимизирай базата данни"
}

@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Ryd Aktivitetslog",
"Undefined": "Udefineret",
"Forced": "Tvunget",
"Default": "Standard"
"Default": "Standard",
"TaskOptimizeDatabaseDescription": "Kompakter database og forkorter fri plads. Ved at køre denne proces efter at scanne biblioteket eller efter at ændre noget som kunne have indflydelse på databasen, kan forbedre ydeevne.",
"TaskOptimizeDatabase": "Optimér database"
}

@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciada",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series de Televisión",
"Shows": "Series",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",

@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} fallito",
"ScheduledTaskStartedWithName": "{0} avviati",
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
"Shows": "Programmi",
"Shows": "Serie TV",
"Songs": "Canzoni",
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",

@ -117,5 +117,7 @@
"TaskCleanActivityLog": "アクティビティの履歴を消去",
"Undefined": "未定義",
"Forced": "強制",
"Default": "デフォルト"
"Default": "デフォルト",
"TaskOptimizeDatabaseDescription": "データベースをコンパクトにして、空き領域を切り詰めます。メディアライブラリのスキャン後でこのタスクを実行するとパフォーマンスが向上する可能性があります。",
"TaskOptimizeDatabase": "データベースの最適化"
}

@ -117,5 +117,7 @@
"TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.",
"TaskCleanActivityLog": "Notīrīt Darbību Žurnālu",
"Undefined": "Nenoteikts",
"Default": "Noklusējums"
"Default": "Noklusējums",
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
"TaskOptimizeDatabase": "Optimizēt datubāzi"
}

@ -118,5 +118,6 @@
"Undefined": "Udefinert",
"Forced": "Tvunget",
"Default": "Standard",
"TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen."
"TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen.",
"TaskOptimizeDatabase": "Optimiser database"
}

@ -16,7 +16,7 @@
"Folders": "Mape",
"Genres": "Zvrsti",
"HeaderAlbumArtists": "Izvajalci albuma",
"HeaderContinueWatching": "Nadaljuj z ogledom",
"HeaderContinueWatching": "Nadaljuj ogled",
"HeaderFavoriteAlbums": "Priljubljeni albumi",
"HeaderFavoriteArtists": "Priljubljeni izvajalci",
"HeaderFavoriteEpisodes": "Priljubljene epizode",
@ -90,7 +90,7 @@
"UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}",
"UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}",
"ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici",
"ValueSpecialEpisodeName": "Posebna - {0}",
"ValueSpecialEpisodeName": "Bonus - {0}",
"VersionNumber": "Različica {0}",
"TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise",
"TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.",

@ -43,7 +43,7 @@
"NameInstallFailed": "{0} kurulumu başarısız",
"NameSeasonNumber": "Sezon {0}",
"NameSeasonUnknown": "Bilinmeyen Sezon",
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.",
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
"NotificationOptionAudioPlayback": "Ses çalma başladı",
@ -75,7 +75,7 @@
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
"Sync": "Eşitle",
"Sync": "Eşzamanlama",
"System": "Sistem",
"TvShows": "Diziler",
"User": "Kullanıcı",
@ -89,34 +89,36 @@
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
"ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
"VersionNumber": "Versiyon {0}",
"VersionNumber": "Sürüm {0}",
"TaskCleanCache": "Geçici dosya klasörünü temizle",
"TasksChannelsCategory": "İnternet kanalları",
"TasksApplicationCategory": "Uygulama",
"TasksLibraryCategory": "Kütüphane",
"TasksMaintenanceCategory": "Onarım",
"TasksMaintenanceCategory": "Bakım",
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
"TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
"TaskRefreshChannels": "Kanalları Yenile",
"TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
"TaskCleanTranscodeDescription": "Bir günden daha eski dönüştürme dosyalarını siler.",
"TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
"TaskUpdatePlugins": "Eklentileri Güncelle",
"TaskRefreshPeople": "Kullanıcıları Yenile",
"TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
"TaskCleanLogs": "Log Dizinini Temizle",
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
"TaskCleanLogsDescription": "{0} günden eski günlük dosyalarını siler.",
"TaskCleanLogs": "Günlük Dizinini Temizle",
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.",
"TaskRefreshLibrary": "Medya Kütüphanesini Tara",
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
"TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
"TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle",
"TaskCleanActivityLogDescription": "Yapılandırılan tarihten daha eski olan etkinlik günlüğü girişlerini siler.",
"Undefined": "Bilinmeyen",
"Default": "Varsayılan",
"Forced": "Zorla"
"Forced": "Zorla",
"TaskOptimizeDatabaseDescription": "Veritabanını sıkıştırır ve boş alanı keser. Kitaplığı taradıktan sonra veya veritabanında değişiklik anlamına gelen diğer işlemleri yaptıktan sonra bu görevi çalıştırmak performansı artırabilir.",
"TaskOptimizeDatabase": "Veritabanını optimize et"
}

@ -23,6 +23,9 @@ namespace Emby.Server.Implementations.Localization
public class LocalizationManager : ILocalizationManager
{
private const string DefaultCulture = "en-US";
private const string RatingsPath = "Emby.Server.Implementations.Localization.Ratings.";
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
@ -58,43 +61,39 @@ namespace Emby.Server.Implementations.Localization
/// <returns><see cref="Task" />.</returns>
public async Task LoadAll()
{
const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
// Extract from the assembly
foreach (var resource in _assembly.GetManifestResourceNames())
{
if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal))
if (!resource.StartsWith(RatingsPath, StringComparison.Ordinal))
{
continue;
}
string countryCode = resource.Substring(RatingsResource.Length, 2);
string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
using (var str = _assembly.GetManifestResourceStream(resource))
using (var reader = new StreamReader(str))
await using var str = _assembly.GetManifestResourceStream(resource);
using var reader = new StreamReader(str);
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
if (string.IsNullOrWhiteSpace(line))
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
string[] parts = line.Split(',');
if (parts.Length == 2
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
var name = parts[0];
dict.Add(name, new ParentalRating(name, value));
}
continue;
}
string[] parts = line.Split(',');
if (parts.Length == 2
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
var name = parts[0];
dict.Add(name, new ParentalRating(name, value));
}
#if DEBUG
else
{
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
}
#endif
else
{
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
}
#endif
}
_allParentalRatings[countryCode] = dict;
@ -114,52 +113,48 @@ namespace Emby.Server.Implementations.Localization
{
List<CultureDto> list = new List<CultureDto>();
const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt";
using (var stream = _assembly.GetManifestResourceStream(ResourcePath))
using (var reader = new StreamReader(stream))
await using var stream = _assembly.GetManifestResourceStream(CulturesPath);
using var reader = new StreamReader(stream);
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
if (string.IsNullOrWhiteSpace(line))
{
if (string.IsNullOrWhiteSpace(line))
continue;
}
var parts = line.Split('|');
if (parts.Length == 5)
{
string name = parts[3];
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
var parts = line.Split('|');
string twoCharName = parts[2];
if (string.IsNullOrWhiteSpace(twoCharName))
{
continue;
}
if (parts.Length == 5)
string[] threeletterNames;
if (string.IsNullOrWhiteSpace(parts[1]))
{
threeletterNames = new[] { parts[0] };
}
else
{
string name = parts[3];
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
string twoCharName = parts[2];
if (string.IsNullOrWhiteSpace(twoCharName))
{
continue;
}
string[] threeletterNames;
if (string.IsNullOrWhiteSpace(parts[1]))
{
threeletterNames = new[] { parts[0] };
}
else
{
threeletterNames = new[] { parts[0], parts[1] };
}
list.Add(new CultureDto
{
DisplayName = name,
Name = name,
ThreeLetterISOLanguageNames = threeletterNames,
TwoLetterISOLanguageName = twoCharName
});
threeletterNames = new[] { parts[0], parts[1] };
}
list.Add(new CultureDto
{
DisplayName = name,
Name = name,
ThreeLetterISOLanguageNames = threeletterNames,
TwoLetterISOLanguageName = twoCharName
});
}
}
@ -188,7 +183,7 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries()
{
using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath));
return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
}
@ -350,23 +345,21 @@ namespace Emby.Server.Implementations.Localization
private async Task CopyInto(IDictionary<string, string> dictionary, string resourcePath)
{
using (var stream = _assembly.GetManifestResourceStream(resourcePath))
await using var stream = _assembly.GetManifestResourceStream(resourcePath);
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
if (stream != null)
{
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
if (stream != null)
{
var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
foreach (var key in dict.Keys)
{
dictionary[key] = dict[key];
}
}
else
foreach (var key in dict.Keys)
{
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
dictionary[key] = dict[key];
}
}
else
{
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
}
}
private static string GetResourceFilename(string culture)

@ -55,9 +55,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
_localization = localization;
}
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
/// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
/// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshChapterImages";
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
@ -162,26 +172,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
}
}
/// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
/// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshChapterImages";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
}
}

@ -380,7 +380,7 @@ namespace Jellyfin.Api.Helpers
private void DeleteHlsPartialStreamFiles(string outputFilePath)
{
var directory = Path.GetDirectoryName(outputFilePath)
?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
var name = Path.GetFileNameWithoutExtension(outputFilePath);
@ -444,6 +444,10 @@ namespace Jellyfin.Api.Helpers
{
var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec;
var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType;
HardwareEncodingType? hardwareAccelerationType = string.IsNullOrEmpty(hardwareAccelerationTypeString)
? null
: (HardwareEncodingType)Enum.Parse(typeof(HardwareEncodingType), hardwareAccelerationTypeString, true);
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{
@ -458,6 +462,7 @@ namespace Jellyfin.Api.Helpers
AudioChannels = state.OutputAudioChannels,
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
HardwareAccelerationType = hardwareAccelerationType,
TranscodeReasons = state.TranscodeReasons
});
}
@ -759,8 +764,8 @@ namespace Jellyfin.Api.Helpers
if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
{
var liveStreamResponse = await _mediaSourceManager.OpenLiveStream(
new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },
cancellationTokenSource.Token)
new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },
cancellationTokenSource.Token)
.ConfigureAwait(false);
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();

@ -8,17 +8,16 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
<NoWarn>AD0001</NoWarn>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />
</ItemGroup>
<ItemGroup>
@ -33,10 +32,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Jellyfin.Api.Tests</_Parameter1>

@ -4,10 +4,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<Nullable>enable</Nullable>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>

@ -9,10 +9,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>

@ -3,10 +3,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>

@ -86,15 +86,12 @@ namespace Jellyfin.Server.Implementations.Activity
private static ActivityLogEntry ConvertToOldModel(ActivityLog entry)
{
return new ActivityLogEntry
return new ActivityLogEntry(entry.Name, entry.Type, entry.UserId)
{
Id = entry.Id,
Name = entry.Name,
Overview = entry.Overview,
ShortOverview = entry.ShortOverview,
Type = entry.Type,
ItemId = entry.ItemId,
UserId = entry.UserId,
Date = entry.DateCreated,
Severity = entry.LogSeverity
};

@ -4,14 +4,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- Code analysers-->

@ -303,7 +303,7 @@ namespace Jellyfin.Server.Extensions
{
description.TryGetMethodInfo(out MethodInfo methodInfo);
// Attribute name, method name, none.
return description?.ActionDescriptor?.AttributeRouteInfo?.Name
return description?.ActionDescriptor.AttributeRouteInfo?.Name
?? methodInfo?.Name
?? null;
});
@ -341,7 +341,7 @@ namespace Jellyfin.Server.Extensions
{
foreach (var address in host.GetAddresses())
{
AddIpAddress(config, options, addr.Address, addr.PrefixLength);
AddIpAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
}
}
}
@ -397,7 +397,7 @@ namespace Jellyfin.Server.Extensions
Type = "object",
Properties = typeof(ImageType).GetEnumNames().ToDictionary(
name => name,
name => new OpenApiSchema
_ => new OpenApiSchema
{
Type = "object",
AdditionalProperties = new OpenApiSchema

@ -12,11 +12,6 @@
<ServerGarbageCollection>false</ServerGarbageCollection>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<!-- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> -->
</PropertyGroup>
<ItemGroup>

@ -58,9 +58,12 @@ namespace Jellyfin.Server.Middleware
return;
}
if (!startsWithBaseUrl)
if (!startsWithBaseUrl
|| localPath.Length == baseUrlPrefix.Length
// Local path is /baseUrl/
|| (localPath.Length == baseUrlPrefix.Length + 1 && localPath[^1] == '/'))
{
// Always redirect back to the default path if the base prefix is invalid or missing
// Always redirect back to the default path if the base prefix is invalid, missing, or is the full path.
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
return;

@ -137,11 +137,6 @@ namespace Jellyfin.Server.Middleware
private string NormalizeExceptionMessage(string msg)
{
if (msg == null)
{
return string.Empty;
}
// Strip any information we don't want to reveal
return msg.Replace(
_configuration.ApplicationPaths.ProgramSystemPath,

@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations
.Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m))
.OfType<IMigrationRoutine>()
.ToArray();
var migrationOptions = ((IConfigurationManager)host.ConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
var migrationOptions = host.ConfigurationManager.GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
{

@ -92,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
{
// This is not a valid Guid, see if it is an internal ID from an old Emby schema
_logger.LogWarning("Invalid Guid in UserId column: ", entry[6].ToString());
_logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
statement.TryBind("@Id", entry[6].ToString());

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;

@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -121,11 +120,11 @@ namespace Jellyfin.Server
// Log uncaught exceptions to the logging instead of std error
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
AppDomain.CurrentDomain.UnhandledException += (sender, e)
AppDomain.CurrentDomain.UnhandledException += (_, e)
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
// Intercept Ctrl+C and Ctrl+Break
Console.CancelKeyPress += (sender, e) =>
Console.CancelKeyPress += (_, e) =>
{
if (_tokenSource.IsCancellationRequested)
{
@ -139,7 +138,7 @@ namespace Jellyfin.Server
};
// Register a SIGTERM handler
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{
if (_tokenSource.IsCancellationRequested)
{
@ -180,7 +179,7 @@ namespace Jellyfin.Server
"The server is expected to host the web client, but the provided content directory is either " +
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
"server, you may set the '--nowebclient' command line flag, or set" +
$"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
$"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
}
}
@ -543,7 +542,7 @@ namespace Jellyfin.Server
// Get a stream of the resource contents
// NOTE: The .csproj name is used instead of the assembly name in the resource path
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
await using Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
// Copy the resource contents to the expected file path for the config file

@ -40,7 +40,7 @@ namespace MediaBrowser.Common.Extensions
// Add an event handler for the process exit event
var tcs = new TaskCompletionSource<bool>();
process.Exited += (sender, args) => tcs.TrySetResult(true);
process.Exited += (_, _) => tcs.TrySetResult(true);
// Return immediately if the process has already exited
if (process.HasExitedSafe())

@ -32,10 +32,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>

@ -18,7 +18,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text to parse.</param>
/// <param name="imdbId">The parsed IMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId)
public static bool TryFindImdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> imdbId)
{
// imdb id is at least 9 chars (tt + 7 numbers)
while (text.Length >= 2 + ImdbMinNumbers)
@ -62,7 +62,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param>
/// <param name="tmdbId">The parsed TMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
=> TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
/// <summary>
@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param>
/// <param name="tmdbId">The parsed TMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
=> TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
/// <summary>
@ -80,7 +80,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param>
/// <param name="tvdbId">The parsed TVDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId)
public static bool TryFindTvdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tvdbId)
=> TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId)

@ -17,6 +17,12 @@ namespace MediaBrowser.Controller.Channels
{
public class Channel : Folder
{
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
[JsonIgnore]
public override SourceType SourceType => SourceType.Channel;
public override bool IsVisible(User user)
{
var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels);
@ -39,12 +45,6 @@ namespace MediaBrowser.Controller.Channels
return base.IsVisible(user);
}
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
[JsonIgnore]
public override SourceType SourceType => SourceType.Channel;
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{
try

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System;
using System.Collections.Generic;

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System.Collections.Generic;

@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Channels
{

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System.Collections.Generic;
using MediaBrowser.Model.Channels;

@ -12,6 +12,8 @@ namespace MediaBrowser.Controller.Chapters
/// <summary>
/// Saves the chapters.
/// </summary>
/// <param name="itemId">The item.</param>
/// <param name="chapters">The set of chapters.</param>
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
}
}

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA2227, CS1591
using System;
using System.Collections.Generic;

@ -32,6 +32,7 @@ namespace MediaBrowser.Controller.Collections
/// Creates the collection.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>BoxSet wrapped in an awaitable task.</returns>
Task<BoxSet> CreateCollectionAsync(CollectionCreationOptions options);
/// <summary>

@ -57,6 +57,15 @@ namespace MediaBrowser.Controller.Drawing
/// <summary>
/// Encode an image.
/// </summary>
/// <param name="inputPath">Input path of image.</param>
/// <param name="dateModified">Date modified.</param>
/// <param name="outputPath">Output path of image.</param>
/// <param name="autoOrient">Auto-orient image.</param>
/// <param name="orientation">Desired orientation of image.</param>
/// <param name="quality">Quality of encoded image.</param>
/// <param name="options">Image processing options.</param>
/// <param name="outputFormat">Image format of output.</param>
/// <returns>Path of encoded image.</returns>
string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
/// <summary>

@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CA1711, CS1591
using System;
using System.IO;

@ -1,4 +1,5 @@
#nullable disable
#pragma warning disable CA1002
using System.Collections.Generic;
using Jellyfin.Data.Entities;

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
@ -31,6 +31,29 @@ namespace MediaBrowser.Controller.Library
/// </summary>
public interface ILibraryManager
{
/// <summary>
/// Occurs when [item added].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemAdded;
/// <summary>
/// Occurs when [item updated].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemUpdated;
/// <summary>
/// Occurs when [item removed].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemRemoved;
/// <summary>
/// Gets the root folder.
/// </summary>
/// <value>The root folder.</value>
AggregateFolder RootFolder { get; }
bool IsScanRunning { get; }
/// <summary>
/// Resolves the path.
/// </summary>
@ -57,16 +80,10 @@ namespace MediaBrowser.Controller.Library
LibraryOptions libraryOptions,
string collectionType = null);
/// <summary>
/// Gets the root folder.
/// </summary>
/// <value>The root folder.</value>
AggregateFolder RootFolder { get; }
/// <summary>
/// Gets a Person.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="name">The name of the person.</param>
/// <returns>Task{Person}.</returns>
Person GetPerson(string name);
@ -81,7 +98,7 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the artist.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="name">The name of the artist.</param>
/// <returns>Task{Artist}.</returns>
MusicArtist GetArtist(string name);
@ -90,21 +107,21 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets a Studio.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="name">The name of the studio.</param>
/// <returns>Task{Studio}.</returns>
Studio GetStudio(string name);
/// <summary>
/// Gets a Genre.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="name">The name of the genre.</param>
/// <returns>Task{Genre}.</returns>
Genre GetGenre(string name);
/// <summary>
/// Gets the genre.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="name">The name of the music genre.</param>
/// <returns>Task{MusicGenre}.</returns>
MusicGenre GetMusicGenre(string name);
@ -113,7 +130,7 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Task{Year}.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="ArgumentOutOfRangeException">Throws if year is invalid.</exception>
Year GetYear(int value);
/// <summary>
@ -205,16 +222,26 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Creates the item.
/// </summary>
/// <param name="item">Item to create.</param>
/// <param name="parent">Parent of new item.</param>
void CreateItem(BaseItem item, BaseItem parent);
/// <summary>
/// Creates the items.
/// </summary>
/// <param name="items">Items to create.</param>
/// <param name="parent">Parent of new items.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
void CreateItems(IReadOnlyList<BaseItem> items, BaseItem parent, CancellationToken cancellationToken);
/// <summary>
/// Updates the item.
/// </summary>
/// <param name="items">Items to update.</param>
/// <param name="parent">Parent of updated items.</param>
/// <param name="updateReason">Reason for update.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Returns a Task that can be awaited.</returns>
Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
/// <summary>
@ -224,6 +251,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="parent">The parent item.</param>
/// <param name="updateReason">The update reason.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Returns a Task that can be awaited.</returns>
Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
/// <summary>
@ -233,23 +261,6 @@ namespace MediaBrowser.Controller.Library
/// <returns>BaseItem.</returns>
BaseItem RetrieveItem(Guid id);
bool IsScanRunning { get; }
/// <summary>
/// Occurs when [item added].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemAdded;
/// <summary>
/// Occurs when [item updated].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemUpdated;
/// <summary>
/// Occurs when [item removed].
/// </summary>
event EventHandler<ItemChangeEventArgs> ItemRemoved;
/// <summary>
/// Finds the type of the collection.
/// </summary>
@ -294,16 +305,25 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Deletes the item.
/// </summary>
/// <param name="item">Item to delete.</param>
/// <param name="options">Options to use for deletion.</param>
void DeleteItem(BaseItem item, DeleteOptions options);
/// <summary>
/// Deletes the item.
/// </summary>
/// <param name="item">Item to delete.</param>
/// <param name="options">Options to use for deletion.</param>
/// <param name="notifyParentItem">Notify parent of deletion.</param>
void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem);
/// <summary>
/// Deletes the item.
/// </summary>
/// <param name="item">Item to delete.</param>
/// <param name="options">Options to use for deletion.</param>
/// <param name="parent">Parent of item.</param>
/// <param name="notifyParentItem">Notify parent of deletion.</param>
void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem);
/// <summary>
@ -314,6 +334,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="parentId">The parent identifier.</param>
/// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param>
/// <returns>The named view.</returns>
UserView GetNamedView(
User user,
string name,
@ -328,6 +349,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="name">The name.</param>
/// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param>
/// <returns>The named view.</returns>
UserView GetNamedView(
User user,
string name,
@ -340,6 +362,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="name">The name.</param>
/// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param>
/// <returns>The named view.</returns>
UserView GetNamedView(
string name,
string viewType,
@ -397,6 +420,9 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Fills the missing episode numbers from path.
/// </summary>
/// <param name="episode">Episode to use.</param>
/// <param name="forceRefresh">Option to force refresh of episode numbers.</param>
/// <returns>True if successful.</returns>
bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh);
/// <summary>
@ -539,6 +565,9 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the items.
/// </summary>
/// <param name="query">The query to use.</param>
/// <param name="parents">Items to use for query.</param>
/// <returns>List of items.</returns>
List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents);
/// <summary>

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1711, CS1591
using System.Threading;
using System.Threading.Tasks;

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
@ -62,16 +62,32 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the playack media sources.
/// </summary>
/// <param name="item">Item to use.</param>
/// <param name="user">User to use for operation.</param>
/// <param name="allowMediaProbe">Option to allow media probe.</param>
/// <param name="enablePathSubstitution">Option to enable path substitution.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>List of media sources wrapped in an awaitable task.</returns>
Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Gets the static media sources.
/// </summary>
/// <param name="item">Item to use.</param>
/// <param name="enablePathSubstitution">Option to enable path substitution.</param>
/// <param name="user">User to use for operation.</param>
/// <returns>List of media sources.</returns>
List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null);
/// <summary>
/// Gets the static media source.
/// </summary>
/// <param name="item">Item to use.</param>
/// <param name="mediaSourceId">Media source to get.</param>
/// <param name="liveStreamId">Live stream to use.</param>
/// <param name="enablePathSubstitution">Option to enable path substitution.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>The static media source wrapped in an awaitable task.</returns>
Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>

@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CA1002, CS1591
using System.Collections.Generic;
using System.Threading;
@ -21,6 +21,10 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Opens the media source.
/// </summary>
/// <param name="openToken">Token to use.</param>
/// <param name="currentLiveStreams">List of live streams.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>The media source wrapped as an awaitable task.</returns>
Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
}
}

@ -29,7 +29,6 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
void Save(BaseItem item, CancellationToken cancellationToken);
}
}

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CS1591
using System.Collections.Generic;
using Jellyfin.Data.Entities;
@ -15,16 +15,28 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the instant mix from song.
/// </summary>
/// <param name="item">The item to use.</param>
/// <param name="user">The user to use.</param>
/// <param name="dtoOptions">The options to use.</param>
/// <returns>List of items.</returns>
List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions);
/// <summary>
/// Gets the instant mix from artist.
/// </summary>
/// <param name="artist">The artist to use.</param>
/// <param name="user">The user to use.</param>
/// <param name="dtoOptions">The options to use.</param>
/// <returns>List of items.</returns>
List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions);
/// <summary>
/// Gets the instant mix from genre.
/// </summary>
/// <param name="genres">The genres to use.</param>
/// <param name="user">The user to use.</param>
/// <param name="dtoOptions">The options to use.</param>
/// <returns>List of items.</returns>
List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions);
}
}

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA1707, CS1591
using System;
using System.Collections.Generic;
@ -42,6 +42,9 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the user data dto.
/// </summary>
/// <param name="item">Item to use.</param>
/// <param name="user">User to use.</param>
/// <returns>User data dto.</returns>
UserItemDataDto GetUserDataDto(BaseItem item, User user);
UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options);
@ -64,6 +67,10 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Updates playstate for an item and returns true or false indicating if it was played to completion.
/// </summary>
/// <param name="item">Item to update.</param>
/// <param name="data">Data to update.</param>
/// <param name="positionTicks">New playstate.</param>
/// <returns>True if playstate was updated.</returns>
bool UpdatePlayState(BaseItem item, UserItemData data, long? positionTicks);
}
}

@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Initializes the user manager and ensures that a user exists.
/// </summary>
/// <returns>Awaitable task.</returns>
Task InitializeAsync();
/// <summary>
@ -109,17 +110,22 @@ namespace MediaBrowser.Controller.Library
/// Resets the easy password.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
void ResetEasyPassword(User user);
/// <summary>
/// Changes the password.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="newPassword">New password to use.</param>
/// <returns>Awaitable task.</returns>
Task ChangePassword(User user, string newPassword);
/// <summary>
/// Changes the easy password.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="newPassword">New password to use.</param>
/// <param name="newPasswordSha1">Hash of new password.</param>
void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1);
/// <summary>
@ -133,6 +139,12 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Authenticates the user.
/// </summary>
/// <param name="username">The user.</param>
/// <param name="password">The password to use.</param>
/// <param name="passwordSha1">Hash of password.</param>
/// <param name="remoteEndPoint">Remove endpoint to use.</param>
/// <param name="isUserSession">Specifies if a user session.</param>
/// <returns>User wrapped in awaitable task.</returns>
Task<User> AuthenticateUser(string username, string password, string passwordSha1, string remoteEndPoint, bool isUserSession);
/// <summary>

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
@ -13,10 +13,29 @@ namespace MediaBrowser.Controller.Library
{
public interface IUserViewManager
{
/// <summary>
/// Gets user views.
/// </summary>
/// <param name="query">Query to use.</param>
/// <returns>Set of folders.</returns>
Folder[] GetUserViews(UserViewQuery query);
/// <summary>
/// Gets user sub views.
/// </summary>
/// <param name="parentId">Parent to use.</param>
/// <param name="type">Type to use.</param>
/// <param name="localizationKey">Localization key to use.</param>
/// <param name="sortName">Sort to use.</param>
/// <returns>User view.</returns>
UserView GetUserSubView(Guid parentId, string type, string localizationKey, string sortName);
/// <summary>
/// Gets latest items.
/// </summary>
/// <param name="request">Query to use.</param>
/// <param name="options">Options to use.</param>
/// <returns>Set of items.</returns>
List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options);
}
}

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1711, CS1591
using MediaBrowser.Controller.Entities;

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1721, CA1819, CS1591
using System;
using System.Collections.Generic;
@ -109,6 +109,21 @@ namespace MediaBrowser.Controller.Library
/// <value>The additional locations.</value>
private List<string> AdditionalLocations { get; set; }
/// <summary>
/// Gets the physical locations.
/// </summary>
/// <value>The physical locations.</value>
public string[] PhysicalLocations
{
get
{
var paths = string.IsNullOrEmpty(Path) ? Array.Empty<string>() : new[] { Path };
return AdditionalLocations == null ? paths : paths.Concat(AdditionalLocations).ToArray();
}
}
public string CollectionType { get; set; }
public bool HasParent<T>()
where T : Folder
{
@ -138,6 +153,16 @@ namespace MediaBrowser.Controller.Library
return false;
}
/// <summary>
/// Determines whether the specified <see cref="object" /> is equal to this instance.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
return Equals(obj as ItemResolveArgs);
}
/// <summary>
/// Adds the additional location.
/// </summary>
@ -156,19 +181,6 @@ namespace MediaBrowser.Controller.Library
// REVIEW: @bond
/// <summary>
/// Gets the physical locations.
/// </summary>
/// <value>The physical locations.</value>
public string[] PhysicalLocations
{
get
{
var paths = string.IsNullOrEmpty(Path) ? Array.Empty<string>() : new[] { Path };
return AdditionalLocations == null ? paths : paths.Concat(AdditionalLocations).ToArray();
}
}
/// <summary>
/// Gets the name of the file system entry by.
/// </summary>
@ -190,7 +202,7 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="path">The path.</param>
/// <returns>FileSystemInfo.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentNullException">Throws if path is invalid.</exception>
public FileSystemMetadata GetFileSystemEntryByPath(string path)
{
if (string.IsNullOrEmpty(path))
@ -224,18 +236,6 @@ namespace MediaBrowser.Controller.Library
return CollectionType;
}
public string CollectionType { get; set; }
/// <summary>
/// Determines whether the specified <see cref="object" /> is equal to this instance.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
return Equals(obj as ItemResolveArgs);
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System;
using System.Collections.Generic;

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System;
using System.Collections.Generic;

@ -22,12 +22,22 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
public interface ILiveTvManager
{
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
/// <summary>
/// Gets the services.
/// </summary>
/// <value>The services.</value>
IReadOnlyList<ILiveTvService> Services { get; }
IListingsProvider[] ListingProviders { get; }
/// <summary>
/// Gets the new timer defaults asynchronous.
/// </summary>
@ -86,6 +96,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <param name="query">The query.</param>
/// <param name="options">The options.</param>
/// <returns>A recording.</returns>
QueryResult<BaseItemDto> GetRecordings(RecordingQuery query, DtoOptions options);
/// <summary>
@ -176,11 +187,16 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="query">The query.</param>
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Recommended programs.</returns>
QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken);
/// <summary>
/// Gets the recommended programs internal.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Recommended programs.</returns>
QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken);
/// <summary>
@ -202,6 +218,7 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets the live tv folder.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Live TV folder.</returns>
Folder GetInternalLiveTvFolder(CancellationToken cancellationToken);
/// <summary>
@ -213,11 +230,18 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the internal channels.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="dtoOptions">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Internal channels.</returns>
QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel media sources.
/// </summary>
/// <param name="item">Item to search for.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Channel media sources wrapped in a task.</returns>
Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken);
/// <summary>
@ -232,6 +256,9 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Saves the tuner host.
/// </summary>
/// <param name="info">Turner host to save.</param>
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
/// <returns>Tuner host information wrapped in a task.</returns>
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
/// <summary>
@ -271,20 +298,10 @@ namespace MediaBrowser.Controller.LiveTv
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
IListingsProvider[] ListingProviders { get; }
List<NameIdPair> GetTunerHostTypes();
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
string GetEmbyTvActiveRecordingPath(string id);
ActiveRecordingInfo GetActiveRecordingInfo(string path);

@ -30,6 +30,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the channels.
/// </summary>
/// <param name="enableCache">Option to enable using cache.</param>
/// <param name="cancellationToken">The CancellationToken for this operation.</param>
/// <returns>Task&lt;IEnumerable&lt;ChannelInfo&gt;&gt;.</returns>
Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
@ -47,6 +49,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="streamId">The stream identifier.</param>
/// <param name="currentLiveStreams">The current live streams.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>Live stream wrapped in a task.</returns>
Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
/// <summary>

@ -18,23 +18,6 @@ namespace MediaBrowser.Controller.LiveTv
{
public class LiveTvChannel : BaseItem, IHasMediaSources, IHasProgramAttributes
{
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (!ConfigurationManager.Configuration.DisableLiveTvChannelUserDataName)
{
list.Insert(0, GetClientTypeName() + "-" + Name);
}
return list;
}
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.LiveTvChannel;
}
[JsonIgnore]
public override bool SupportsPositionTicksResume => false;
@ -59,6 +42,67 @@ namespace MediaBrowser.Controller.LiveTv
[JsonIgnore]
public override LocationType LocationType => LocationType.Remote;
[JsonIgnore]
public override string MediaType => ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
[JsonIgnore]
public bool IsMovie { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is sports.
/// </summary>
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsSports { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is series.
/// </summary>
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is news.
/// </summary>
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsNews { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is kids.
/// </summary>
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase);
[JsonIgnore]
public bool IsRepeat { get; set; }
/// <summary>
/// Gets or sets the episode title.
/// </summary>
/// <value>The episode title.</value>
[JsonIgnore]
public string EpisodeTitle { get; set; }
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (!ConfigurationManager.Configuration.DisableLiveTvChannelUserDataName)
{
list.Insert(0, GetClientTypeName() + "-" + Name);
}
return list;
}
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.LiveTvChannel;
}
protected override string CreateSortName()
{
if (!string.IsNullOrEmpty(Number))
@ -74,9 +118,6 @@ namespace MediaBrowser.Controller.LiveTv
return (Number ?? string.Empty) + "-" + (Name ?? string.Empty);
}
[JsonIgnore]
public override string MediaType => ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
public override string GetClientTypeName()
{
return "TvChannel";
@ -122,46 +163,5 @@ namespace MediaBrowser.Controller.LiveTv
{
return false;
}
[JsonIgnore]
public bool IsMovie { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is sports.
/// </summary>
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsSports { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is series.
/// </summary>
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is news.
/// </summary>
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsNews { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is kids.
/// </summary>
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase);
[JsonIgnore]
public bool IsRepeat { get; set; }
/// <summary>
/// Gets or sets the episode title.
/// </summary>
/// <value>The episode title.</value>
[JsonIgnore]
public string EpisodeTitle { get; set; }
}
}

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CS1591, SA1306
using System;
using System.Collections.Generic;
@ -19,54 +19,14 @@ namespace MediaBrowser.Controller.LiveTv
{
public class LiveTvProgram : BaseItem, IHasLookupInfo<ItemLookupInfo>, IHasStartDate, IHasProgramAttributes
{
private static string EmbyServiceName = "Emby";
public LiveTvProgram()
{
IsVirtualItem = true;
}
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (!IsSeries)
{
var key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
}
else if (!string.IsNullOrEmpty(EpisodeTitle))
{
var name = GetClientTypeName();
list.Insert(0, name + "-" + Name + (EpisodeTitle ?? string.Empty));
}
return list;
}
private static string EmbyServiceName = "Emby";
public override double GetDefaultPrimaryImageAspectRatio()
{
var serviceName = ServiceName;
if (string.Equals(serviceName, EmbyServiceName, StringComparison.OrdinalIgnoreCase) || string.Equals(serviceName, "Next Pvr", StringComparison.OrdinalIgnoreCase))
{
return 2.0 / 3;
}
else
{
return 16.0 / 9;
}
}
public string SeriesName { get; set; }
[JsonIgnore]
public override SourceType SourceType => SourceType.LiveTV;
@ -182,6 +142,66 @@ namespace MediaBrowser.Controller.LiveTv
}
}
[JsonIgnore]
public override bool SupportsPeople
{
get
{
// Optimization
if (IsNews || IsSports)
{
return false;
}
return base.SupportsPeople;
}
}
[JsonIgnore]
public override bool SupportsAncestors => false;
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (!IsSeries)
{
var key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
}
else if (!string.IsNullOrEmpty(EpisodeTitle))
{
var name = GetClientTypeName();
list.Insert(0, name + "-" + Name + (EpisodeTitle ?? string.Empty));
}
return list;
}
public override double GetDefaultPrimaryImageAspectRatio()
{
var serviceName = ServiceName;
if (string.Equals(serviceName, EmbyServiceName, StringComparison.OrdinalIgnoreCase) || string.Equals(serviceName, "Next Pvr", StringComparison.OrdinalIgnoreCase))
{
return 2.0 / 3;
}
else
{
return 16.0 / 9;
}
}
public override string GetClientTypeName()
{
return "Program";
@ -202,24 +222,6 @@ namespace MediaBrowser.Controller.LiveTv
return false;
}
[JsonIgnore]
public override bool SupportsPeople
{
get
{
// Optimization
if (IsNews || IsSports)
{
return false;
}
return base.SupportsPeople;
}
}
[JsonIgnore]
public override bool SupportsAncestors => false;
private LiveTvOptions GetConfiguration()
{
return ConfigurationManager.GetConfiguration<LiveTvOptions>("livetv");
@ -273,7 +275,5 @@ namespace MediaBrowser.Controller.LiveTv
return list;
}
public string SeriesName { get; set; }
}
}

@ -35,14 +35,15 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">

@ -10,6 +10,15 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class BaseEncodingJobOptions
{
public BaseEncodingJobOptions()
{
EnableAutoStreamCopy = true;
AllowVideoStreamCopy = true;
AllowAudioStreamCopy = true;
Context = EncodingContext.Streaming;
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Gets or sets the id.
/// </summary>
@ -191,14 +200,5 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
public BaseEncodingJobOptions()
{
EnableAutoStreamCopy = true;
AllowVideoStreamCopy = true;
AllowAudioStreamCopy = true;
Context = EncodingContext.Streaming;
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}

@ -189,6 +189,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
/// <param name="state">Encording state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var codec = state.OutputVideoCodec;
@ -343,6 +346,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return container;
}
/// <summary>
/// Gets decoder from a codec.
/// </summary>
/// <param name="codec">Codec to use.</param>
/// <returns>Decoder string.</returns>
public string GetDecoderFromCodec(string codec)
{
// For these need to find out the ffmpeg names
@ -372,6 +380,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Infers the audio codec based on the url.
/// </summary>
/// <param name="container">Container to use.</param>
/// <returns>Codec string.</returns>
public string InferAudioCodec(string container)
{
var ext = "." + (container ?? string.Empty);
@ -517,6 +527,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the input argument.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <returns>Input arguments.</returns>
public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
{
var arg = new StringBuilder();
@ -1002,6 +1015,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the video bitrate to specify on the command line.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="videoEncoder">Video encoder to use.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <param name="defaultPreset">Default present to use for encoding.</param>
/// <returns>Video bitrate.</returns>
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
var param = string.Empty;
@ -2003,8 +2021,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
/// Gets the graphical subtitle param.
/// Gets the graphical subtitle parameter.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>Graphical subtitle parameter.</returns>
public string GetGraphicalSubtitleParam(
EncodingJobInfo state,
EncodingOptions options,
@ -2552,6 +2574,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
}
/// <summary>
/// Gets the output size parameter.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>The output size parameter.</returns>
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
@ -2562,8 +2591,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
/// Gets the output size parameter.
/// If we're going to put a fixed size on the command line, this will calculate it.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>The output size parameter.</returns>
public string GetOutputSizeParamInternal(
EncodingJobInfo state,
EncodingOptions options,
@ -3111,6 +3145,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the number of threads.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>Number of threads.</returns>
#nullable enable
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{
@ -3759,6 +3797,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a hw decoder name.
/// </summary>
/// <param name="options">Encoding options.</param>
/// <param name="decoder">Decoder to use.</param>
/// <param name="videoCodec">Video codec to use.</param>
/// <param name="isColorDepth10">Specifies if color depth 10.</param>
/// <returns>Hardware decoder name.</returns>
public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
{
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
@ -3777,6 +3820,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="videoCodec">Video codec to use.</param>
/// <param name="isColorDepth10">Specifies if color depth 10.</param>
/// <returns>Hardware accelerator type.</returns>
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{
var isWindows = OperatingSystem.IsWindows();

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@ -20,6 +20,44 @@ namespace MediaBrowser.Controller.MediaEncoding
// For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo
{
public int? OutputAudioBitrate;
public int? OutputAudioChannels;
private TranscodeReason[] _transcodeReasons = null;
public EncodingJobInfo(TranscodingJobType jobType)
{
TranscodingType = jobType;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
SupportedAudioCodecs = Array.Empty<string>();
SupportedVideoCodecs = Array.Empty<string>();
SupportedSubtitleCodecs = Array.Empty<string>();
}
public TranscodeReason[] TranscodeReasons
{
get
{
if (_transcodeReasons == null)
{
if (BaseRequest.TranscodeReasons == null)
{
return Array.Empty<TranscodeReason>();
}
_transcodeReasons = BaseRequest.TranscodeReasons
.Split(',')
.Where(i => !string.IsNullOrEmpty(i))
.Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
.ToArray();
}
return _transcodeReasons;
}
}
public IProgress<double> Progress { get; set; }
public MediaStream VideoStream { get; set; }
public VideoType VideoType { get; set; }
@ -58,40 +96,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string MimeType { get; set; }
public string GetMimeType(string outputPath, bool enableStreamDefault = true)
{
if (!string.IsNullOrEmpty(MimeType))
{
return MimeType;
}
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
}
private TranscodeReason[] _transcodeReasons = null;
public TranscodeReason[] TranscodeReasons
{
get
{
if (_transcodeReasons == null)
{
if (BaseRequest.TranscodeReasons == null)
{
return Array.Empty<TranscodeReason>();
}
_transcodeReasons = BaseRequest.TranscodeReasons
.Split(',')
.Where(i => !string.IsNullOrEmpty(i))
.Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
.ToArray();
}
return _transcodeReasons;
}
}
public bool IgnoreInputDts => MediaSource.IgnoreDts;
public bool IgnoreInputIndex => MediaSource.IgnoreIndex;
@ -144,196 +148,17 @@ namespace MediaBrowser.Controller.MediaEncoding
public BaseEncodingJobOptions BaseRequest { get; set; }
public long? StartTimeTicks => BaseRequest.StartTimeTicks;
public bool CopyTimestamps => BaseRequest.CopyTimestamps;
public int? OutputAudioBitrate;
public int? OutputAudioChannels;
public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
{
var videoStream = VideoStream;
var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
if (!isInputInterlaced)
{
return false;
}
// Support general param
if (BaseRequest.DeInterlace)
{
return true;
}
if (!string.IsNullOrEmpty(videoCodec))
{
if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
}
public string[] GetRequestedProfiles(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Profile))
{
return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
if (!string.IsNullOrEmpty(codec))
{
var profile = BaseRequest.GetOption(codec, "profile");
if (!string.IsNullOrEmpty(profile))
{
return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
}
return Array.Empty<string>();
}
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))
{
return BaseRequest.Level;
}
if (!string.IsNullOrEmpty(codec))
{
return BaseRequest.GetOption(codec, "level");
}
return null;
}
public int? GetRequestedMaxRefFrames(string codec)
{
if (BaseRequest.MaxRefFrames.HasValue)
{
return BaseRequest.MaxRefFrames;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "maxrefframes");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedVideoBitDepth(string codec)
{
if (BaseRequest.MaxVideoBitDepth.HasValue)
{
return BaseRequest.MaxVideoBitDepth;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "videobitdepth");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedAudioBitDepth(string codec)
{
if (BaseRequest.MaxAudioBitDepth.HasValue)
{
return BaseRequest.MaxAudioBitDepth;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiobitdepth");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedAudioChannels(string codec)
{
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiochannels");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
if (BaseRequest.MaxAudioChannels.HasValue)
{
return BaseRequest.MaxAudioChannels;
}
if (BaseRequest.AudioChannels.HasValue)
{
return BaseRequest.AudioChannels;
}
if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
{
return BaseRequest.TranscodingMaxAudioChannels;
}
return null;
}
public bool IsVideoRequest { get; set; }
public TranscodingJobType TranscodingType { get; set; }
public EncodingJobInfo(TranscodingJobType jobType)
{
TranscodingType = jobType;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
SupportedAudioCodecs = Array.Empty<string>();
SupportedVideoCodecs = Array.Empty<string>();
SupportedSubtitleCodecs = Array.Empty<string>();
}
public long? StartTimeTicks => BaseRequest.StartTimeTicks;
public bool CopyTimestamps => BaseRequest.CopyTimestamps;
public bool IsSegmentedLiveStream
=> TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue;
public bool EnableBreakOnNonKeyFrames(string videoCodec)
{
if (TranscodingType != TranscodingJobType.Progressive)
{
if (IsSegmentedLiveStream)
{
return false;
}
return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
}
return false;
}
public int? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
public int? OutputWidth
@ -682,6 +507,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0;
public bool EnableBreakOnNonKeyFrames(string videoCodec)
{
if (TranscodingType != TranscodingJobType.Progressive)
{
if (IsSegmentedLiveStream)
{
return false;
}
return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
}
return false;
}
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
@ -694,7 +534,167 @@ namespace MediaBrowser.Controller.MediaEncoding
return count;
}
public IProgress<double> Progress { get; set; }
public string GetMimeType(string outputPath, bool enableStreamDefault = true)
{
if (!string.IsNullOrEmpty(MimeType))
{
return MimeType;
}
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
}
public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
{
var videoStream = VideoStream;
var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
if (!isInputInterlaced)
{
return false;
}
// Support general param
if (BaseRequest.DeInterlace)
{
return true;
}
if (!string.IsNullOrEmpty(videoCodec))
{
if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
}
public string[] GetRequestedProfiles(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Profile))
{
return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
if (!string.IsNullOrEmpty(codec))
{
var profile = BaseRequest.GetOption(codec, "profile");
if (!string.IsNullOrEmpty(profile))
{
return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
}
return Array.Empty<string>();
}
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))
{
return BaseRequest.Level;
}
if (!string.IsNullOrEmpty(codec))
{
return BaseRequest.GetOption(codec, "level");
}
return null;
}
public int? GetRequestedMaxRefFrames(string codec)
{
if (BaseRequest.MaxRefFrames.HasValue)
{
return BaseRequest.MaxRefFrames;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "maxrefframes");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedVideoBitDepth(string codec)
{
if (BaseRequest.MaxVideoBitDepth.HasValue)
{
return BaseRequest.MaxVideoBitDepth;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "videobitdepth");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedAudioBitDepth(string codec)
{
if (BaseRequest.MaxAudioBitDepth.HasValue)
{
return BaseRequest.MaxAudioBitDepth;
}
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiobitdepth");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null;
}
public int? GetRequestedAudioChannels(string codec)
{
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiochannels");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
if (BaseRequest.MaxAudioChannels.HasValue)
{
return BaseRequest.MaxAudioChannels;
}
if (BaseRequest.AudioChannels.HasValue)
{
return BaseRequest.AudioChannels;
}
if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
{
return BaseRequest.TranscodingMaxAudioChannels;
}
return null;
}
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{

@ -16,6 +16,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Refreshes the chapter images.
/// </summary>
/// <param name="video">Video to use.</param>
/// <param name="directoryService">Directory service to use.</param>
/// <param name="chapters">Set of chapters to refresh.</param>
/// <param name="extractImages">Option to extract images.</param>
/// <param name="saveChapters">Option to save chapters.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns><c>true</c> if successful, <c>false</c> if not.</returns>
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}

@ -83,13 +83,42 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Extracts the video image.
/// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="videoStream">Media stream information.</param>
/// <param name="threedFormat">Video 3D format.</param>
/// <param name="offset">Time offset.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video image.
/// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="imageStream">Media stream information.</param>
/// <param name="imageStreamIndex">Index of the stream to extract from.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.
/// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="videoStream">Media stream information.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="threedFormat">Video 3D format.</param>
/// <param name="interval">Time interval.</param>
/// <param name="targetDirectory">Directory to write images.</param>
/// <param name="filenamePrefix">Filename prefix to use.</param>
/// <param name="maxWidth">Maximum width of image.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>A task.</returns>
Task ExtractVideoImagesOnInterval(
string inputFile,
string container,
@ -134,10 +163,24 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns>
string EscapeSubtitleFilterPath(string path);
/// <summary>
/// Sets the path to find FFmpeg.
/// </summary>
void SetFFmpegPath();
/// <summary>
/// Updates the encoder path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="pathType">The type of path.</param>
void UpdateEncoderPath(string path, string pathType);
/// <summary>
/// Gets the primary playlist of .vob files.
/// </summary>
/// <param name="path">The to the .vob files.</param>
/// <param name="titleNumber">The title number to start with.</param>
/// <returns>A playlist.</returns>
IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save