mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-16 08:08:16 +00:00
Compare commits
92 Commits
v10.3.0-rc
...
v10.3.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
855911333a | ||
|
|
127bfc7d3b | ||
|
|
7919dd81da | ||
|
|
e1da046960 | ||
|
|
a756026962 | ||
|
|
75260a960b | ||
|
|
69ee49bee6 | ||
|
|
1bf3a26a61 | ||
|
|
c5760b3a40 | ||
|
|
4de8bf3295 | ||
|
|
87c8f19f19 | ||
|
|
0ef52c739e | ||
|
|
0a9a6b949c | ||
|
|
c22068d6b1 | ||
|
|
bbc1a86b57 | ||
|
|
cd83d80f2b | ||
|
|
12721eb7dd | ||
|
|
b8a09339cd | ||
|
|
3634d367c1 | ||
|
|
c1daea0ec7 | ||
|
|
e8196fed7c | ||
|
|
477702fbb9 | ||
|
|
2216a271bb | ||
|
|
91cd7d2f6b | ||
|
|
4e681d25d9 | ||
|
|
06cc2891de | ||
|
|
682432f55a | ||
|
|
7c4cb5ec58 | ||
|
|
1df73fdeba | ||
|
|
21ba8a0593 | ||
|
|
08ed52eb72 | ||
|
|
99700e1b95 | ||
|
|
4e0be95368 | ||
|
|
c8a59c8343 | ||
|
|
2b2a2ed708 | ||
|
|
a827a2fbcc | ||
|
|
f97f6b8061 | ||
|
|
844ea9d77e | ||
|
|
71479286e9 | ||
|
|
28c2ac528d | ||
|
|
696a36b4a5 | ||
|
|
5fb4922c6f | ||
|
|
3738f95871 | ||
|
|
6797bdec04 | ||
|
|
764c6d5461 | ||
|
|
6973182ade | ||
|
|
f62af07381 | ||
|
|
46c37c0ae8 | ||
|
|
4ad71766fc | ||
|
|
9d60cc8c66 | ||
|
|
4a6243096a | ||
|
|
10cbdc8e8e | ||
|
|
4886fc467c | ||
|
|
8e2827cc39 | ||
|
|
395d2e4917 | ||
|
|
d31f5229da | ||
|
|
d6622818dc | ||
|
|
ba684d6d3a | ||
|
|
90c04a4640 | ||
|
|
06a1e1f166 | ||
|
|
31ad366aa9 | ||
|
|
10f33b0273 | ||
|
|
eaa1ac8013 | ||
|
|
ca3bb308b3 | ||
|
|
e790f024c2 | ||
|
|
250e0c75df | ||
|
|
cde7375049 | ||
|
|
c7fedfbca3 | ||
|
|
f520831025 | ||
|
|
2f0719a883 | ||
|
|
34ab99caf1 | ||
|
|
b2f94c0e40 | ||
|
|
65bff1181a | ||
|
|
efb14f0b58 | ||
|
|
75a4f04cce | ||
|
|
007fe34363 | ||
|
|
a7e31ef31f | ||
|
|
eae0c28e6d | ||
|
|
21950382b9 | ||
|
|
1af9c047fb | ||
|
|
2d19bfa7fb | ||
|
|
f5f7de64de | ||
|
|
754e76a61b | ||
|
|
09505e0988 | ||
|
|
67e206fa0f | ||
|
|
608fd873de | ||
|
|
05a4161fd3 | ||
|
|
05040351dc | ||
|
|
d75324afc9 | ||
|
|
38fcd31917 | ||
|
|
816d8a0216 | ||
|
|
e37ccd6ec0 |
@@ -24,6 +24,7 @@
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [fasheng](https://github.com/fasheng)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ARG DOTNET_VERSION=2
|
||||
ARG DOTNET_VERSION=2.2
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
@@ -8,7 +8,7 @@ RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin"
|
||||
|
||||
FROM jellyfin/ffmpeg as ffmpeg
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
|
||||
# libfontconfig1 is required for Skia
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
@@ -21,7 +21,7 @@ RUN apt-get update \
|
||||
COPY --from=ffmpeg / /
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.2.2
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.4
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
|
||||
@@ -8,7 +8,7 @@ FROM alpine as qemu_extract
|
||||
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
|
||||
RUN tar -xzvf qemu-arm-static.tar.gz
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
@@ -21,7 +21,7 @@ RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
|
||||
COPY --from=qemu_extract qemu-arm-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
@@ -30,7 +30,7 @@ RUN apt-get update \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.2.2
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.4
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
|
||||
@@ -9,7 +9,7 @@ COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
|
||||
RUN tar -xzvf qemu-aarch64-static.tar.gz
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
@@ -22,7 +22,7 @@ RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
|
||||
COPY --from=qemu_extract qemu-aarch64-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
@@ -31,7 +31,7 @@ RUN apt-get update \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.2.2
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.4
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
|
||||
@@ -920,8 +920,6 @@ namespace Emby.Dlna.Didl
|
||||
}
|
||||
}
|
||||
|
||||
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
||||
|
||||
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
||||
@@ -930,6 +928,9 @@ namespace Emby.Dlna.Didl
|
||||
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
|
||||
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
|
||||
}
|
||||
|
||||
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
||||
|
||||
}
|
||||
|
||||
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1046,8 +1046,8 @@ namespace Emby.Server.Implementations
|
||||
|
||||
private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
|
||||
{
|
||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(args.Argument.targetFilename));
|
||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly)
|
||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
||||
.Select(x => Assembly.LoadFrom(x))
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
||||
@@ -1167,7 +1167,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading plugin {pluginName}", plugin.GetType().FullName);
|
||||
Logger.LogError(ex, "Error loading plugin {PluginName}", plugin.GetType().FullName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1181,10 +1181,32 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
Logger.LogInformation("Loading assemblies");
|
||||
|
||||
AllConcreteTypes = GetComposablePartAssemblies()
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
|
||||
.ToArray();
|
||||
AllConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
foreach (var ass in assemblies)
|
||||
{
|
||||
Type[] exportedTypes;
|
||||
try
|
||||
{
|
||||
exportedTypes = ass.GetExportedTypes();
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Type type in exportedTypes)
|
||||
{
|
||||
if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CertificateInfo CertificateInfo { get; set; }
|
||||
@@ -1346,10 +1368,21 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
if (Directory.Exists(ApplicationPaths.PluginsPath))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
Logger.LogInformation("Loading assembly {Path}", file);
|
||||
yield return Assembly.LoadFrom(file);
|
||||
Assembly plugAss;
|
||||
try
|
||||
{
|
||||
plugAss = Assembly.LoadFrom(file);
|
||||
}
|
||||
catch (FileLoadException ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to load assembly {Path}", file);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file);
|
||||
yield return plugAss;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1425,7 +1458,6 @@ namespace Emby.Server.Implementations
|
||||
HasPendingRestart = HasPendingRestart,
|
||||
IsShuttingDown = IsShuttingDown,
|
||||
Version = ApplicationVersion,
|
||||
ProductName = ApplicationProductName,
|
||||
WebSocketPortNumber = HttpPort,
|
||||
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
||||
Id = SystemId,
|
||||
@@ -1482,6 +1514,7 @@ namespace Emby.Server.Implementations
|
||||
return new PublicSystemInfo
|
||||
{
|
||||
Version = ApplicationVersion,
|
||||
ProductName = ApplicationProductName,
|
||||
Id = SystemId,
|
||||
OperatingSystem = OperatingSystem.Id.ToString(),
|
||||
WanAddress = wanAddress,
|
||||
|
||||
@@ -74,23 +74,14 @@ namespace Emby.Server.Implementations.Configuration
|
||||
/// </summary>
|
||||
private void UpdateMetadataPath()
|
||||
{
|
||||
string metadataPath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
|
||||
{
|
||||
metadataPath = GetInternalMetadataPath();
|
||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
|
||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
|
||||
}
|
||||
|
||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
||||
}
|
||||
|
||||
private string GetInternalMetadataPath()
|
||||
{
|
||||
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
// If the user password is the sha1 hash of the empty string, remove it
|
||||
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
||||
|| !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
||||
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rangeHeader))
|
||||
{
|
||||
Headers[HeaderNames.ContentLength] = TotalContentLength.ToString(CultureInfo.InvariantCulture);
|
||||
StatusCode = HttpStatusCode.OK;
|
||||
}
|
||||
else
|
||||
@@ -99,10 +100,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
RangeStart = requestedRange.Key;
|
||||
RangeLength = 1 + RangeEnd - RangeStart;
|
||||
|
||||
// Content-Length is the length of what we're serving, not the original content
|
||||
var lengthString = RangeLength.ToString(CultureInfo.InvariantCulture);
|
||||
Headers[HeaderNames.ContentLength] = lengthString;
|
||||
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
||||
Headers[HeaderNames.ContentRange] = rangeString;
|
||||
|
||||
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString);
|
||||
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
case DirectoryNotFoundException _:
|
||||
case FileNotFoundException _:
|
||||
case ResourceNotFoundException _: return 404;
|
||||
case MethodNotAllowedException _: return 405;
|
||||
case RemoteServiceUnavailableException _: return 502;
|
||||
default: return 500;
|
||||
}
|
||||
@@ -637,6 +638,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private static Task Write(IResponse response, string text)
|
||||
{
|
||||
var bOutput = Encoding.UTF8.GetBytes(text);
|
||||
response.OriginalResponse.ContentLength = bOutput.Length;
|
||||
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
content = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(content, contentType);
|
||||
result = new StreamWriter(content, contentType, contentLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
bytes = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(bytes, contentType);
|
||||
result = new StreamWriter(bytes, contentType, contentLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -335,13 +335,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType);
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new StreamWriter(content, contentType);
|
||||
var result = new StreamWriter(content, contentType, contentLength);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
@@ -581,6 +581,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
else
|
||||
{
|
||||
if (totalContentLength.HasValue)
|
||||
{
|
||||
responseHeaders["Content-Length"] = totalContentLength.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
using (stream)
|
||||
@@ -624,7 +629,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (lastModifiedDate.HasValue)
|
||||
{
|
||||
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString();
|
||||
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
RangeStart = requestedRange.Key;
|
||||
RangeLength = 1 + RangeEnd - RangeStart;
|
||||
|
||||
Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture);
|
||||
Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
||||
|
||||
if (RangeStart > 0 && SourceStream.CanSeek)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
public void FilterResponse(IRequest req, IResponse res, object dto)
|
||||
{
|
||||
// Try to prevent compatibility view
|
||||
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||
res.AddHeader("Access-Control-Allow-Origin", "*");
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
res.OriginalResponse.ContentLength = length;
|
||||
res.SendChunked = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -49,6 +50,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
SourceStream = source;
|
||||
|
||||
Headers["Content-Type"] = contentType;
|
||||
|
||||
if (source.CanSeek)
|
||||
{
|
||||
Headers[HeaderNames.ContentLength] = source.Length.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
}
|
||||
|
||||
@@ -57,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
public StreamWriter(byte[] source, string contentType)
|
||||
public StreamWriter(byte[] source, string contentType, int contentLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
@@ -66,6 +74,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
SourceBytes = source;
|
||||
|
||||
Headers[HeaderNames.ContentLength] = contentLength.ToString(CultureInfo.InvariantCulture);
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public string Name => "Default";
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
|
||||
// This is dumb and an artifact of the backwards way auth providers were designed.
|
||||
// This version of authenticate was never meant to be called, but needs to be here for interface compat
|
||||
// Only the providers that don't provide local user support use this
|
||||
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
// This is the verson that we need to use for local users. Because reasons.
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||
{
|
||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
|
||||
string hash = user.Password;
|
||||
user.Password = string.Format("$SHA1${0}", hash);
|
||||
}
|
||||
|
||||
|
||||
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
|
||||
{
|
||||
string hash = user.EasyPassword;
|
||||
@@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library
|
||||
return user.Password;
|
||||
}
|
||||
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
ConvertPasswordFormat(user);
|
||||
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordHash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordHash));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordHash;
|
||||
}
|
||||
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
// This should be removed in the future. This was added to let user login after
|
||||
// Jellyfin 10.3.3 failed to save a well formatted PIN.
|
||||
ConvertPasswordFormat(user);
|
||||
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: (new PasswordHash(user.EasyPassword)).Hash;
|
||||
}
|
||||
|
||||
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
|
||||
{
|
||||
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
|
||||
|
||||
@@ -277,24 +277,35 @@ namespace Emby.Server.Implementations.Library
|
||||
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var success = false;
|
||||
string updatedUsername = null;
|
||||
IAuthenticationProvider authenticationProvider = null;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
success = authResult.Item2;
|
||||
updatedUsername = authResult.Item2;
|
||||
success = authResult.Item3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// user is null
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
success = authResult.Item2;
|
||||
updatedUsername = authResult.Item2;
|
||||
success = authResult.Item3;
|
||||
|
||||
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
|
||||
{
|
||||
user = await CreateUser(username).ConfigureAwait(false);
|
||||
// We should trust the user that the authprovider says, not what was typed
|
||||
if (updatedUsername != username)
|
||||
{
|
||||
username = updatedUsername;
|
||||
}
|
||||
|
||||
// Search the database for the user again; the authprovider might have created it
|
||||
user = Users
|
||||
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy;
|
||||
if (hasNewUserPolicy != null)
|
||||
@@ -414,32 +425,40 @@ namespace Emby.Server.Implementations.Library
|
||||
return providers;
|
||||
}
|
||||
|
||||
private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
|
||||
private async Task<Tuple<string, bool>> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requiresResolvedUser = provider as IRequiresResolvedUser;
|
||||
ProviderAuthenticationResult authenticationResult = null;
|
||||
if (requiresResolvedUser != null)
|
||||
{
|
||||
await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
|
||||
authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await provider.Authenticate(username, password).ConfigureAwait(false);
|
||||
authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
if(authenticationResult.Username != username)
|
||||
{
|
||||
_logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
|
||||
username = authenticationResult.Username;
|
||||
}
|
||||
|
||||
return new Tuple<string, bool>(username, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
|
||||
|
||||
return false;
|
||||
return new Tuple<string, bool>(username, false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Tuple<IAuthenticationProvider, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
|
||||
private async Task<Tuple<IAuthenticationProvider, string, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
|
||||
{
|
||||
string updatedUsername = null;
|
||||
bool success = false;
|
||||
IAuthenticationProvider authenticationProvider = null;
|
||||
|
||||
@@ -452,17 +471,20 @@ namespace Emby.Server.Implementations.Library
|
||||
if (password == null)
|
||||
{
|
||||
// legacy
|
||||
success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var provider in GetAuthenticationProviders(user))
|
||||
{
|
||||
success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
|
||||
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
|
||||
updatedUsername = providerAuthResult.Item1;
|
||||
success = providerAuthResult.Item2;
|
||||
|
||||
if (success)
|
||||
{
|
||||
authenticationProvider = provider;
|
||||
username = updatedUsername;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -475,16 +497,16 @@ namespace Emby.Server.Implementations.Library
|
||||
if (password == null)
|
||||
{
|
||||
// legacy
|
||||
success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
|
||||
success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<IAuthenticationProvider, bool>(authenticationProvider, success);
|
||||
return new Tuple<IAuthenticationProvider, string, bool>(authenticationProvider, username, success);
|
||||
}
|
||||
|
||||
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
|
||||
@@ -524,13 +546,6 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLocalPasswordHash(User user)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: user.EasyPassword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the users from the repository
|
||||
/// </summary>
|
||||
@@ -574,7 +589,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
|
||||
bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user));
|
||||
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
|
||||
|
||||
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
|
||||
hasConfiguredEasyPassword :
|
||||
@@ -862,17 +877,7 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordHash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordHash));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordHash;
|
||||
GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
|
||||
|
||||
UpdateUser(user);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Umělci",
|
||||
"AuthenticationSucceededWithUserName": "{0} úspěšně ověřen",
|
||||
"Books": "Knihy",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie",
|
||||
"Channels": "Kanály",
|
||||
"ChapterNameValue": "Kapitola {0}",
|
||||
"Collections": "Kolekce",
|
||||
@@ -16,14 +16,14 @@
|
||||
"Folders": "Složky",
|
||||
"Genres": "Žánry",
|
||||
"HeaderAlbumArtists": "Umělci alba",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderCameraUploads": "Nahrané fotografie",
|
||||
"HeaderContinueWatching": "Pokračovat ve sledování",
|
||||
"HeaderFavoriteAlbums": "Oblíbená alba",
|
||||
"HeaderFavoriteArtists": "Oblíbení umělci",
|
||||
"HeaderFavoriteArtists": "Oblíbení interpreti",
|
||||
"HeaderFavoriteEpisodes": "Oblíbené epizody",
|
||||
"HeaderFavoriteShows": "Oblíbené seriály",
|
||||
"HeaderFavoriteSongs": "Oblíbené písně",
|
||||
"HeaderLiveTV": "Živá TV",
|
||||
"HeaderFavoriteSongs": "Oblíbená hudba",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Nadcházející",
|
||||
"HeaderRecordingGroups": "Skupiny nahrávek",
|
||||
"HomeVideos": "Domáci videa",
|
||||
@@ -34,17 +34,17 @@
|
||||
"LabelRunningTimeValue": "Délka média: {0}",
|
||||
"Latest": "Nejnovější",
|
||||
"MessageApplicationUpdated": "Jellyfin Server byl aktualizován",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin server byl aktualizován na verzi {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurace sekce {0} na serveru byla aktualizována",
|
||||
"MessageServerConfigurationUpdated": "Konfigurace serveru aktualizována",
|
||||
"MixedContent": "Smíšený obsah",
|
||||
"Movies": "Filmy",
|
||||
"Music": "Hudba",
|
||||
"MusicVideos": "Hudební klipy",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "Instalace {0} selhala",
|
||||
"NameSeasonNumber": "Sezóna {0}",
|
||||
"NameSeasonUnknown": "Neznámá sezóna",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NewVersionIsAvailable": "Nová verze Jellyfin serveru je k dispozici ke stažení.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Dostupná aktualizace aplikace",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Aktualizace aplikace instalována",
|
||||
"NotificationOptionAudioPlayback": "Přehrávání audia zahájeno",
|
||||
@@ -70,12 +70,12 @@
|
||||
"ProviderValue": "Poskytl: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} selhalo",
|
||||
"ScheduledTaskStartedWithName": "{0} zahájeno",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "{0} vyžaduje restart",
|
||||
"Shows": "Seriály",
|
||||
"Songs": "Skladby",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.",
|
||||
"SubtitleDownloadFailureForItem": "Stahování titulků selhalo pro {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo",
|
||||
"SubtitlesDownloadedForItem": "Staženy titulky pro {0}",
|
||||
"Sync": "Synchronizace",
|
||||
"System": "Systém",
|
||||
@@ -88,10 +88,10 @@
|
||||
"UserOfflineFromDevice": "{0} se odpojil od {1}",
|
||||
"UserOnlineFromDevice": "{0} se připojil z {1}",
|
||||
"UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány",
|
||||
"UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií",
|
||||
"ValueSpecialEpisodeName": "Speciál - {0}",
|
||||
"VersionNumber": "Verze {0}"
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
"NotificationOptionUserLockedOut": "Bruger låst ude",
|
||||
"NotificationOptionVideoPlayback": "Videoafspilning påbegyndt",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videoafspilning stoppet",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Spillelister",
|
||||
"Photos": "Fotoer",
|
||||
"Playlists": "Afspilningslister",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} blev installeret",
|
||||
"PluginUninstalledWithName": "{0} blev afinstalleret",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Folders": "Φάκελοι",
|
||||
"Genres": "Είδη",
|
||||
"HeaderAlbumArtists": "Άλμπουμ Καλλιτεχνών",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderCameraUploads": "Μεταφορτώσεις Κάμερας",
|
||||
"HeaderContinueWatching": "Συνεχίστε να παρακολουθείτε",
|
||||
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
|
||||
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
|
||||
@@ -34,7 +34,7 @@
|
||||
"LabelRunningTimeValue": "Διάρκεια: {0}",
|
||||
"Latest": "Πρόσφατα",
|
||||
"MessageApplicationUpdated": "Ο Jellyfin Server έχει ενημερωθεί",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Ο server Jellyfin αναβαθμίστηκε σε έκδοση {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του server έχει ενημερωθεί",
|
||||
"MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του server έχει ενημερωθεί",
|
||||
"MixedContent": "Ανάμεικτο Περιεχόμενο",
|
||||
@@ -49,7 +49,7 @@
|
||||
"NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε",
|
||||
"NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε",
|
||||
"NotificationOptionAudioPlaybackStopped": "Η αναπαραγωγή ήχου σταμάτησε",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionCameraImageUploaded": "Μεταφορτώθηκε φωτογραφία απο κάμερα",
|
||||
"NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης",
|
||||
"NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο",
|
||||
"NotificationOptionPluginError": "Αποτυχία του plugin",
|
||||
@@ -75,7 +75,7 @@
|
||||
"Songs": "Τραγούδια",
|
||||
"StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.",
|
||||
"SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
|
||||
"SubtitlesDownloadedForItem": "Οι υπότιτλοι κατέβηκαν για {0}",
|
||||
"Sync": "Συγχρονισμός",
|
||||
"System": "Σύστημα",
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Albums": "Albom",
|
||||
"AppDeviceValues": "App: {0}, Grät: {1}",
|
||||
"Application": "Aawändig",
|
||||
"Artists": "Könstler",
|
||||
"AuthenticationSucceededWithUserName": "{0} het sech aagmäudet",
|
||||
"Books": "Büecher",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"CameraImageUploadedFrom": "Es nöis Foti esch ufeglade worde vo {0}",
|
||||
"Channels": "Kanäu",
|
||||
"ChapterNameValue": "Kapitu {0}",
|
||||
"Collections": "Sammlige",
|
||||
"DeviceOfflineWithName": "{0} esch offline gange",
|
||||
"DeviceOnlineWithName": "{0} esch online cho",
|
||||
"FailedLoginAttemptWithUserName": "Fäugschlagne Aamäudeversuech vo {0}",
|
||||
"Favorites": "Favorite",
|
||||
"Folders": "Ordner",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Albuminterprete",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderAlbumArtists": "Albom-Könstler",
|
||||
"HeaderCameraUploads": "Kamera-Uploads",
|
||||
"HeaderContinueWatching": "Wiiterluege",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Besti Interpret",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favorite Shows",
|
||||
"HeaderFavoriteSongs": "Besti Lieder",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderFavoriteAlbums": "Lieblingsalbe",
|
||||
"HeaderFavoriteArtists": "Lieblings-Interprete",
|
||||
"HeaderFavoriteEpisodes": "Lieblingsepisode",
|
||||
"HeaderFavoriteShows": "Lieblingsserie",
|
||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||
"HeaderLiveTV": "Live-Färnseh",
|
||||
"HeaderNextUp": "Als nächts",
|
||||
"HeaderRecordingGroups": "Ufnahmegruppe",
|
||||
"HomeVideos": "Heimfilmli",
|
||||
"Inherit": "Hinzuefüege",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Letschte",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Gmischte Inhalt",
|
||||
"Movies": "Movies",
|
||||
"ItemAddedWithName": "{0} esch de Bibliothek dezuegfüegt worde",
|
||||
"ItemRemovedWithName": "{0} esch vo de Bibliothek entfärnt worde",
|
||||
"LabelIpAddressValue": "IP-Adrässe: {0}",
|
||||
"LabelRunningTimeValue": "Loufziit: {0}",
|
||||
"Latest": "Nöischti",
|
||||
"MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server esch of Version {0} aktualisiert worde",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde",
|
||||
"MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde",
|
||||
"MixedContent": "Gmeschti Inhäut",
|
||||
"Movies": "Film",
|
||||
"Music": "Musig",
|
||||
"MusicVideos": "Musigfilm",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"MusicVideos": "Musigvideos",
|
||||
"NameInstallFailed": "Installation vo {0} fäugschlage",
|
||||
"NameSeasonNumber": "Staffle {0}",
|
||||
"NameSeasonUnknown": "Staffle unbekannt",
|
||||
"NewVersionIsAvailable": "E nöi Version vo Jellyfin Server esch zom Download parat.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Aawändigsupdate verfüegbar",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Aawändigsupdate installiert",
|
||||
"NotificationOptionAudioPlayback": "Audiowedergab gstartet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Foti ueglade",
|
||||
"NotificationOptionInstallationFailed": "Installationsfäuer",
|
||||
"NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt",
|
||||
"NotificationOptionPluginError": "Plugin-Fäuer",
|
||||
"NotificationOptionPluginInstalled": "Plugin installiert",
|
||||
"NotificationOptionPluginUninstalled": "Plugin deinstalliert",
|
||||
"NotificationOptionPluginUpdateInstalled": "Pluginupdate installiert",
|
||||
"NotificationOptionServerRestartRequired": "Serverneustart notwändig",
|
||||
"NotificationOptionTaskFailed": "Planti Uufgab fäugschlage",
|
||||
"NotificationOptionUserLockedOut": "Benotzer usgschlosse",
|
||||
"NotificationOptionVideoPlayback": "Videowedergab gstartet",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videowedergab gstoppt",
|
||||
"Photos": "Fotis",
|
||||
"Playlists": "Abspielliste",
|
||||
"Playlists": "Wedergabeliste",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Shows",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"PluginInstalledWithName": "{0} esch installiert worde",
|
||||
"PluginUninstalledWithName": "{0} esch deinstalliert worde",
|
||||
"PluginUpdatedWithName": "{0} esch updated worde",
|
||||
"ProviderValue": "Aabieter: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} esch fäugschlage",
|
||||
"ScheduledTaskStartedWithName": "{0} het gstartet",
|
||||
"ServerNameNeedsToBeRestarted": "{0} mues nöi gstartet wärde",
|
||||
"Shows": "Serie",
|
||||
"Songs": "Lieder",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server ladt. Bitte grad noeinisch probiere.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "Sync",
|
||||
"SubtitleDownloadFailureFromForItem": "Ondertetle vo {0} för {1} hend ned chönne abeglade wärde",
|
||||
"SubtitlesDownloadedForItem": "Ondertetle abeglade för {0}",
|
||||
"Sync": "Synchronisation",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Spezial - {0}",
|
||||
"TvShows": "Färnsehserie",
|
||||
"User": "Benotzer",
|
||||
"UserCreatedWithName": "Benotzer {0} esch erstöut worde",
|
||||
"UserDeletedWithName": "Benotzer {0} esch glösche worde",
|
||||
"UserDownloadingItemWithValues": "{0} ladt {1} abe",
|
||||
"UserLockedOutWithName": "Benotzer {0} esch usgschlosse worde",
|
||||
"UserOfflineFromDevice": "{0} esch vo {1} trennt worde",
|
||||
"UserOnlineFromDevice": "{0} esch online vo {1}",
|
||||
"UserPasswordChangedWithName": "S'Passwort för Benotzer {0} esch gänderet worde",
|
||||
"UserPolicyUpdatedWithName": "Benotzerrechtlinie för {0} esch aktualisiert worde",
|
||||
"UserStartedPlayingItemWithValues": "{0} hed d'Wedergab vo {1} of {2} gstartet",
|
||||
"UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt",
|
||||
"ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde",
|
||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Oryndaýshylar",
|
||||
"AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy",
|
||||
"Books": "Kitaptar",
|
||||
"CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep alyndy",
|
||||
"CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep salyndy",
|
||||
"Channels": "Arnalar",
|
||||
"ChapterNameValue": "{0}-sahna",
|
||||
"Collections": "Jıyntyqtar",
|
||||
@@ -35,8 +35,8 @@
|
||||
"Latest": "Eń keıingi",
|
||||
"MessageApplicationUpdated": "Jellyfin Serveri jańartyldy",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy",
|
||||
"MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfıgýrasýasynyń {0} bólimi jańartyldy",
|
||||
"MessageServerConfigurationUpdated": "Server konfıgýrasıasy jańartyldy",
|
||||
"MixedContent": "Aralas mazmun",
|
||||
"Movies": "Fılmder",
|
||||
"Music": "Mýzyka",
|
||||
@@ -49,7 +49,7 @@
|
||||
"NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy",
|
||||
"NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy",
|
||||
"NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy",
|
||||
"NotificationOptionCameraImageUploaded": "Kameradan fotosýret keri qotarylǵan",
|
||||
"NotificationOptionCameraImageUploaded": "Kameradan fotosýret júktep salynǵan",
|
||||
"NotificationOptionInstallationFailed": "Ornatý sátsizdigi",
|
||||
"NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen",
|
||||
"NotificationOptionPluginError": "Plagın sátsizdigi",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Channels": "Kanali",
|
||||
"ChapterNameValue": "Poglavje {0}",
|
||||
"Collections": "Zbirke",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOfflineWithName": "{0} je prekinil povezavo",
|
||||
"DeviceOnlineWithName": "{0} je povezan",
|
||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
||||
"Favorites": "Priljubljeni",
|
||||
@@ -33,9 +33,9 @@
|
||||
"LabelIpAddressValue": "IP naslov: {0}",
|
||||
"LabelRunningTimeValue": "Čas trajanja: {0}",
|
||||
"Latest": "Najnovejše",
|
||||
"MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageApplicationUpdated": "Jellyfin Server je bil posodobljen",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server je bil posodobljen na {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitve strežnika {0} je bil posodobljen",
|
||||
"MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene",
|
||||
"MixedContent": "Razne vsebine",
|
||||
"Movies": "Filmi",
|
||||
@@ -57,41 +57,41 @@
|
||||
"NotificationOptionPluginUninstalled": "Dodatek odstranjen",
|
||||
"NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena",
|
||||
"NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"Photos": "Photos",
|
||||
"Playlists": "Playlists",
|
||||
"NotificationOptionTaskFailed": "Razporejena naloga neuspešna",
|
||||
"NotificationOptionUserLockedOut": "Uporabnik zaklenjen",
|
||||
"NotificationOptionVideoPlayback": "Predvajanje videa se je začelo",
|
||||
"NotificationOptionVideoPlaybackStopped": "Predvajanje videa se je ustavilo",
|
||||
"Photos": "Fotografije",
|
||||
"Playlists": "Seznami predvajanja",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"PluginInstalledWithName": "{0} je bil nameščen",
|
||||
"PluginUninstalledWithName": "{0} je bil odstranjen",
|
||||
"PluginUpdatedWithName": "{0} je bil posodobljen",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ScheduledTaskFailedWithName": "{0} ni uspelo",
|
||||
"ScheduledTaskStartedWithName": "{0} začeto",
|
||||
"ServerNameNeedsToBeRestarted": "{0} mora biti ponovno zagnan",
|
||||
"Shows": "Serije",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"Songs": "Pesmi",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "Sync",
|
||||
"SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}",
|
||||
"SubtitlesDownloadedForItem": "Podnapisi preneseni za {0}",
|
||||
"Sync": "Sinhroniziraj",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"TvShows": "TV serije",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserCreatedWithName": "Uporabnik {0} je bil ustvarjen",
|
||||
"UserDeletedWithName": "Uporabnik {0} je bil izbrisan",
|
||||
"UserDownloadingItemWithValues": "{0} prenaša {1}",
|
||||
"UserLockedOutWithName": "Uporabnik {0} je bil zaklenjen",
|
||||
"UserOfflineFromDevice": "{0} je prekinil povezavo z {1}",
|
||||
"UserOnlineFromDevice": "{0} je aktiven iz {1}",
|
||||
"UserPasswordChangedWithName": "Geslo za uporabnika {0} je bilo spremenjeno",
|
||||
"UserPolicyUpdatedWithName": "Pravilnik uporabe je bil posodobljen za uporabnika {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
93
Emby.Server.Implementations/Localization/Core/zh-TW.json
Normal file
93
Emby.Server.Implementations/Localization/Core/zh-TW.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"Albums": "專輯",
|
||||
"AppDeviceValues": "應用: {0}, 裝置: {1}",
|
||||
"Application": "應用程式",
|
||||
"Artists": "演出者",
|
||||
"AuthenticationSucceededWithUserName": "{0} 成功授權",
|
||||
"Books": "圖書",
|
||||
"CameraImageUploadedFrom": "{0} 已經成功上傳一張相片",
|
||||
"Channels": "頻道",
|
||||
"ChapterNameValue": "章節 {0}",
|
||||
"Collections": "合輯",
|
||||
"DeviceOfflineWithName": "{0} 已經斷線",
|
||||
"DeviceOnlineWithName": "{0} 已經連線",
|
||||
"FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試",
|
||||
"Favorites": "我的最愛",
|
||||
"Folders": "資料夾",
|
||||
"Genres": "風格",
|
||||
"HeaderAlbumArtists": "專輯演出者",
|
||||
"HeaderCameraUploads": "相機上傳",
|
||||
"HeaderContinueWatching": "繼續觀賞",
|
||||
"HeaderFavoriteAlbums": "最愛專輯",
|
||||
"HeaderFavoriteArtists": "最愛演出者",
|
||||
"HeaderFavoriteEpisodes": "最愛級數",
|
||||
"HeaderFavoriteShows": "最愛節目",
|
||||
"HeaderFavoriteSongs": "最愛歌曲",
|
||||
"HeaderLiveTV": "電視直播",
|
||||
"HeaderNextUp": "接下來",
|
||||
"HomeVideos": "自製影片",
|
||||
"ItemAddedWithName": "{0} 已新增至媒體庫",
|
||||
"ItemRemovedWithName": "{0} 已從媒體庫移除",
|
||||
"LabelIpAddressValue": "IP 位置: {0}",
|
||||
"LabelRunningTimeValue": "運行時間: {0}",
|
||||
"Latest": "最新",
|
||||
"MessageApplicationUpdated": "Jellyfin Server 已經更新",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新",
|
||||
"MessageServerConfigurationUpdated": "伺服器設定已經更新",
|
||||
"MixedContent": "混合內容",
|
||||
"Movies": "電影",
|
||||
"Music": "音樂",
|
||||
"MusicVideos": "音樂MV",
|
||||
"NameInstallFailed": "{0} 安裝失敗",
|
||||
"NameSeasonNumber": "第 {0} 季",
|
||||
"NameSeasonUnknown": "未知季數",
|
||||
"NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。",
|
||||
"NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新",
|
||||
"NotificationOptionApplicationUpdateInstalled": "應用程式已更新",
|
||||
"NotificationOptionAudioPlayback": "音樂開始播放",
|
||||
"NotificationOptionAudioPlaybackStopped": "音樂停止播放",
|
||||
"NotificationOptionCameraImageUploaded": "相機相片已上傳",
|
||||
"NotificationOptionInstallationFailed": "安裝失敗",
|
||||
"NotificationOptionNewLibraryContent": "已新增新內容",
|
||||
"NotificationOptionPluginError": "外掛失敗",
|
||||
"NotificationOptionPluginInstalled": "外掛已安裝",
|
||||
"NotificationOptionPluginUninstalled": "外掛已移除",
|
||||
"NotificationOptionPluginUpdateInstalled": "已更新外掛",
|
||||
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
|
||||
"NotificationOptionTaskFailed": "排程任務失敗",
|
||||
"NotificationOptionUserLockedOut": "使用者已鎖定",
|
||||
"NotificationOptionVideoPlayback": "影片開始播放",
|
||||
"NotificationOptionVideoPlaybackStopped": "影片停止播放",
|
||||
"Photos": "相片",
|
||||
"Playlists": "播放清單",
|
||||
"Plugin": "外掛",
|
||||
"PluginInstalledWithName": "{0} 已安裝",
|
||||
"PluginUninstalledWithName": "{0} 已移除",
|
||||
"PluginUpdatedWithName": "{0} 已更新",
|
||||
"ProviderValue": "提供商: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} 已失敗",
|
||||
"ScheduledTaskStartedWithName": "{0} 已開始",
|
||||
"ServerNameNeedsToBeRestarted": "{0} 需要重新啟動",
|
||||
"Shows": "節目",
|
||||
"Songs": "歌曲",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。",
|
||||
"SubtitlesDownloadedForItem": "已為 {0} 下載字幕",
|
||||
"Sync": "同步",
|
||||
"System": "系統",
|
||||
"TvShows": "電視節目",
|
||||
"User": "使用者",
|
||||
"UserCreatedWithName": "使用者 {0} 已建立",
|
||||
"UserDeletedWithName": "使用者 {0} 已移除",
|
||||
"UserDownloadingItemWithValues": "{0} 正在下載 {1}",
|
||||
"UserLockedOutWithName": "使用者 {0} 已鎖定",
|
||||
"UserOfflineFromDevice": "{0} 已從 {1} 斷線",
|
||||
"UserOnlineFromDevice": "{0} 已連線,來自 {1}",
|
||||
"UserPasswordChangedWithName": "使用者 {0} 的密碼已變更",
|
||||
"UserPolicyUpdatedWithName": "使用者條約已更新為 {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫",
|
||||
"ValueSpecialEpisodeName": "特典 - {0}",
|
||||
"VersionNumber": "版本 {0}"
|
||||
}
|
||||
@@ -43,6 +43,11 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
var contentLength = bytesResponse.Length;
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
response.OriginalResponse.ContentLength = contentLength;
|
||||
}
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
@@ -20,6 +21,8 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
}
|
||||
|
||||
response.OriginalResponse.ContentLength = 0;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -39,11 +42,6 @@ namespace Emby.Server.Implementations.Services
|
||||
|
||||
response.StatusCode = httpResult.Status;
|
||||
response.StatusDescription = httpResult.StatusCode.ToString();
|
||||
//if (string.IsNullOrEmpty(httpResult.ContentType))
|
||||
//{
|
||||
// httpResult.ContentType = defaultContentType;
|
||||
//}
|
||||
//response.ContentType = httpResult.ContentType;
|
||||
}
|
||||
|
||||
var responseOptions = result as IHasHeaders;
|
||||
@@ -53,6 +51,7 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
response.OriginalResponse.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,52 +71,37 @@ namespace Emby.Server.Implementations.Services
|
||||
response.ContentType += "; charset=utf-8";
|
||||
}
|
||||
|
||||
var asyncStreamWriter = result as IAsyncStreamWriter;
|
||||
if (asyncStreamWriter != null)
|
||||
switch (result)
|
||||
{
|
||||
return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken);
|
||||
}
|
||||
case IAsyncStreamWriter asyncStreamWriter:
|
||||
return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken);
|
||||
case IStreamWriter streamWriter:
|
||||
streamWriter.WriteTo(response.OutputStream);
|
||||
return Task.CompletedTask;
|
||||
case FileWriter fileWriter:
|
||||
return fileWriter.WriteToAsync(response, cancellationToken);
|
||||
case Stream stream:
|
||||
return CopyStream(stream, response.OutputStream);
|
||||
case byte[] bytes:
|
||||
response.ContentType = "application/octet-stream";
|
||||
response.OriginalResponse.ContentLength = bytes.Length;
|
||||
|
||||
var streamWriter = result as IStreamWriter;
|
||||
if (streamWriter != null)
|
||||
{
|
||||
streamWriter.WriteTo(response.OutputStream);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
}
|
||||
|
||||
var fileWriter = result as FileWriter;
|
||||
if (fileWriter != null)
|
||||
{
|
||||
return fileWriter.WriteToAsync(response, cancellationToken);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
case string responseText:
|
||||
var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText);
|
||||
response.OriginalResponse.ContentLength = responseTextAsBytes.Length;
|
||||
|
||||
var stream = result as Stream;
|
||||
if (stream != null)
|
||||
{
|
||||
return CopyStream(stream, response.OutputStream);
|
||||
}
|
||||
if (responseTextAsBytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken);
|
||||
}
|
||||
|
||||
var bytes = result as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
response.ContentType = "application/octet-stream";
|
||||
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var responseText = result as string;
|
||||
if (responseText != null)
|
||||
{
|
||||
bytes = Encoding.UTF8.GetBytes(responseText);
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return WriteObject(request, result, response);
|
||||
@@ -143,14 +127,13 @@ namespace Emby.Server.Implementations.Services
|
||||
ms.Position = 0;
|
||||
|
||||
var contentLength = ms.Length;
|
||||
response.OriginalResponse.ContentLength = contentLength;
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//serializer(result, outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.Services
|
||||
string propertyName = pair.Key;
|
||||
string propertyTextValue = pair.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(propertyTextValue)
|
||||
if (propertyTextValue == null
|
||||
|| !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)
|
||||
|| propertySerializerEntry.PropertySetFn == null)
|
||||
{
|
||||
|
||||
@@ -41,8 +41,6 @@ namespace Emby.Server.Implementations.Udp
|
||||
_socketFactory = socketFactory;
|
||||
|
||||
AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message);
|
||||
AddMessageResponder("who is EmbyServer?", true, RespondToV2Message);
|
||||
AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message);
|
||||
}
|
||||
|
||||
private void AddMessageResponder(string message, bool isSubstring, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task> responder)
|
||||
|
||||
@@ -509,6 +509,8 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
private async Task PerformPackageInstallation(IProgress<double> progress, string target, PackageVersionInfo package, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Remove the `string target` argument as it is not used any longer
|
||||
|
||||
var extension = Path.GetExtension(package.targetFilename);
|
||||
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -518,12 +520,12 @@ namespace Emby.Server.Implementations.Updates
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
target = Path.Combine(_appPaths.PluginsPath, Path.GetFileNameWithoutExtension(package.targetFilename));
|
||||
}
|
||||
// Always override the passed-in target (which is a file) and figure it out again
|
||||
target = Path.Combine(_appPaths.PluginsPath, package.name);
|
||||
_logger.LogDebug("Installing plugin to {Filename}.", target);
|
||||
|
||||
// Download to temporary file so that, if interrupted, it won't destroy the existing installation
|
||||
_logger.LogDebug("Downloading ZIP.");
|
||||
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||
{
|
||||
Url = package.sourceUrl,
|
||||
@@ -536,9 +538,17 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
// TODO: Validate with a checksum, *properly*
|
||||
|
||||
// Check if the target directory already exists, and remove it if so
|
||||
if (Directory.Exists(target))
|
||||
{
|
||||
_logger.LogDebug("Deleting existing plugin at {Filename}.", target);
|
||||
Directory.Delete(target, true);
|
||||
}
|
||||
|
||||
// Success - move it to the real target
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Extracting ZIP {TempFile} to {Filename}.", tempFile, target);
|
||||
using (var stream = File.OpenRead(tempFile))
|
||||
{
|
||||
_zipClient.ExtractAllFromZip(stream, target, true);
|
||||
@@ -552,6 +562,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Deleting temporary file {Filename}.", tempFile);
|
||||
_fileSystem.DeleteFile(tempFile);
|
||||
}
|
||||
catch (IOException ex)
|
||||
@@ -574,7 +585,13 @@ namespace Emby.Server.Implementations.Updates
|
||||
_applicationHost.RemovePlugin(plugin);
|
||||
|
||||
var path = plugin.AssemblyFilePath;
|
||||
_logger.LogInformation("Deleting plugin file {0}", path);
|
||||
bool isDirectory = false;
|
||||
// Check if we have a plugin directory we should remove too
|
||||
if (Path.GetDirectoryName(plugin.AssemblyFilePath) != _appPaths.PluginsPath)
|
||||
{
|
||||
path = Path.GetDirectoryName(plugin.AssemblyFilePath);
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
// Make this case-insensitive to account for possible incorrect assembly naming
|
||||
var file = _fileSystem.GetFilePaths(Path.GetDirectoryName(path))
|
||||
@@ -585,7 +602,16 @@ namespace Emby.Server.Implementations.Updates
|
||||
path = file;
|
||||
}
|
||||
|
||||
_fileSystem.DeleteFile(path);
|
||||
if (isDirectory)
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin directory {0}", path);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Deleting plugin file {0}", path);
|
||||
_fileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
var list = _config.Configuration.UninstalledPlugins.ToList();
|
||||
var filename = Path.GetFileName(path);
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Api.Movies;
|
||||
@@ -828,7 +830,16 @@ namespace MediaBrowser.Api.Library
|
||||
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
|
||||
// Kestrel doesn't support non-ASCII characters in headers
|
||||
if (Regex.IsMatch(filename, "[^[:ascii:]]"))
|
||||
{
|
||||
// Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
|
||||
headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace MediaBrowser.Api.Playback.Progressive
|
||||
@@ -279,10 +281,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
/// <returns>Task{System.Object}.</returns>
|
||||
private async Task<object> GetStaticRemoteStreamResult(StreamState state, Dictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
string useragent = null;
|
||||
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
|
||||
|
||||
var trySupportSeek = false;
|
||||
state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent);
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
@@ -292,29 +291,14 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
CancellationToken = cancellationTokenSource.Token
|
||||
};
|
||||
|
||||
if (trySupportSeek)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range]))
|
||||
{
|
||||
options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range];
|
||||
}
|
||||
}
|
||||
var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
|
||||
|
||||
if (trySupportSeek)
|
||||
responseHeaders[HeaderNames.AcceptRanges] = "none";
|
||||
|
||||
// Seeing cases of -1 here
|
||||
if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
|
||||
{
|
||||
foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges })
|
||||
{
|
||||
var val = response.Headers[name];
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
responseHeaders[name] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
responseHeaders[HeaderNames.AcceptRanges] = "none";
|
||||
responseHeaders[HeaderNames.ContentLength] = response.ContentLength.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (isHeadRequest)
|
||||
@@ -356,10 +340,31 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
var contentType = state.GetMimeType(outputPath);
|
||||
|
||||
// TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response
|
||||
var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null;
|
||||
|
||||
if (contentLength.HasValue)
|
||||
{
|
||||
responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Headers only
|
||||
if (isHeadRequest)
|
||||
{
|
||||
return ResultFactory.GetResult(null, Array.Empty<byte>(), contentType, responseHeaders);
|
||||
var streamResult = ResultFactory.GetResult(null, Array.Empty<byte>(), contentType, responseHeaders);
|
||||
|
||||
if (streamResult is IHasHeaders hasHeaders)
|
||||
{
|
||||
if (contentLength.HasValue)
|
||||
{
|
||||
hasHeaders.Headers[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasHeaders.Headers.Remove(HeaderNames.ContentLength);
|
||||
}
|
||||
}
|
||||
|
||||
return streamResult;
|
||||
}
|
||||
|
||||
var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath);
|
||||
@@ -397,5 +402,22 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
transcodingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the estimated content.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
private long? GetEstimatedContentLength(StreamState state)
|
||||
{
|
||||
var totalBitrate = state.TotalOutputBitrate ?? 0;
|
||||
|
||||
if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
|
||||
{
|
||||
return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
request.IncludeItemTypes = "Playlist";
|
||||
}
|
||||
|
||||
if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id))
|
||||
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id))
|
||||
{
|
||||
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
|
||||
return new QueryResult<BaseItem>
|
||||
|
||||
@@ -379,10 +379,15 @@ namespace MediaBrowser.Api
|
||||
throw new ResourceNotFoundException("User not found");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Password) && string.IsNullOrEmpty(request.Pw))
|
||||
{
|
||||
throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API.");
|
||||
}
|
||||
|
||||
return Post(new AuthenticateUserByName
|
||||
{
|
||||
Username = user.Name,
|
||||
Password = request.Password,
|
||||
Password = null, // This should always be null
|
||||
Pw = request.Pw
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,6 +26,30 @@ namespace MediaBrowser.Common.Extensions
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class MethodNotAllowedException
|
||||
/// </summary>
|
||||
public class MethodNotAllowedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
|
||||
/// </summary>
|
||||
public MethodNotAllowedException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
public MethodNotAllowedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class RemoteServiceUnavailableException : Exception
|
||||
{
|
||||
public RemoteServiceUnavailableException()
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace MediaBrowser.Controller.Authentication
|
||||
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
|
||||
Task<bool> HasPassword(User user);
|
||||
Task ChangePassword(User user, string newPassword);
|
||||
void ChangeEasyPassword(User user, string newPassword, string newPasswordHash);
|
||||
string GetPasswordHash(User user);
|
||||
string GetEasyPasswordHash(User user);
|
||||
}
|
||||
|
||||
public interface IRequiresResolvedUser
|
||||
|
||||
@@ -78,10 +78,25 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <summary>
|
||||
/// The trailer folder name
|
||||
/// </summary>
|
||||
public static string TrailerFolderName = "trailers";
|
||||
public static string ThemeSongsFolderName = "theme-music";
|
||||
public static string ThemeSongFilename = "theme";
|
||||
public static string ThemeVideosFolderName = "backdrops";
|
||||
public const string TrailerFolderName = "trailers";
|
||||
public const string ThemeSongsFolderName = "theme-music";
|
||||
public const string ThemeSongFilename = "theme";
|
||||
public const string ThemeVideosFolderName = "backdrops";
|
||||
public const string ExtrasFolderName = "extras";
|
||||
public const string BehindTheScenesFolderName = "behind the scenes";
|
||||
public const string DeletedScenesFolderName = "deleted scenes";
|
||||
public const string InterviewFolderName = "interviews";
|
||||
public const string SceneFolderName = "scenes";
|
||||
public const string SampleFolderName = "samples";
|
||||
|
||||
public static readonly string[] AllExtrasTypesFolderNames = {
|
||||
ExtrasFolderName,
|
||||
BehindTheScenesFolderName,
|
||||
DeletedScenesFolderName,
|
||||
InterviewFolderName,
|
||||
SceneFolderName,
|
||||
SampleFolderName
|
||||
};
|
||||
|
||||
[IgnoreDataMember]
|
||||
public Guid[] ThemeSongIds { get; set; }
|
||||
@@ -1276,16 +1291,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
.Select(item =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = LibraryManager.GetItemById(item.Id) as Video;
|
||||
|
||||
if (dbItem != null)
|
||||
if (LibraryManager.GetItemById(item.Id) is Video dbItem)
|
||||
{
|
||||
item = dbItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
// item is new
|
||||
item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo;
|
||||
item.ExtraType = Model.Entities.ExtraType.ThemeVideo;
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -1296,33 +1310,38 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var files = fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.SelectMany(i => FileSystem.GetFiles(i.FullName));
|
||||
var extras = new List<Video>();
|
||||
|
||||
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
|
||||
.OfType<Video>()
|
||||
.Select(item =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = LibraryManager.GetItemById(item.Id) as Video;
|
||||
var folders = fileSystemChildren.Where(i => i.IsDirectory).ToArray();
|
||||
foreach (var extraFolderName in AllExtrasTypesFolderNames)
|
||||
{
|
||||
var files = folders
|
||||
.Where(i => string.Equals(i.Name, extraFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => FileSystem.GetFiles(i.FullName));
|
||||
|
||||
if (dbItem != null)
|
||||
extras.AddRange(LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
|
||||
.OfType<Video>()
|
||||
.Select(item =>
|
||||
{
|
||||
item = dbItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
// item is new
|
||||
item.ExtraType = MediaBrowser.Model.Entities.ExtraType.Clip;
|
||||
}
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (LibraryManager.GetItemById(item.Id) is Video dbItem)
|
||||
{
|
||||
item = dbItem;
|
||||
}
|
||||
|
||||
return item;
|
||||
// Use some hackery to get the extra type based on foldername
|
||||
Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
|
||||
item.ExtraType = extraType;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path).ToArray();
|
||||
return item;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path));
|
||||
}
|
||||
|
||||
return extras.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public Task RefreshMetadata(CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
|
||||
@@ -1481,7 +1500,13 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService).Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)).Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService));
|
||||
var extras = LoadExtras(fileSystemChildren, options.DirectoryService);
|
||||
var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
|
||||
var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
|
||||
var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length];
|
||||
extras.CopyTo(newExtras, 0);
|
||||
themeVideos.CopyTo(newExtras, extras.Length);
|
||||
themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length);
|
||||
|
||||
var newExtraIds = newExtras.Select(i => i.Id).ToArray();
|
||||
|
||||
@@ -1493,7 +1518,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
var tasks = newExtras.Select(i =>
|
||||
{
|
||||
return RefreshMetadataForOwnedItem(i, true, new MetadataRefreshOptions(options), cancellationToken);
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
|
||||
{
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
subOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private readonly int DefaultImageExtractionTimeoutMs;
|
||||
private readonly string StartupOptionFFmpegPath;
|
||||
|
||||
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
|
||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
@@ -230,6 +230,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <returns></returns>
|
||||
private string ExistsOnSystemPath(string filename)
|
||||
{
|
||||
string inJellyfinPath = GetEncoderPathFromDirectory(System.AppContext.BaseDirectory, filename);
|
||||
if (!string.IsNullOrEmpty(inJellyfinPath))
|
||||
{
|
||||
return inJellyfinPath;
|
||||
}
|
||||
var values = Environment.GetEnvironmentVariable("PATH");
|
||||
|
||||
foreach (var path in values.Split(Path.PathSeparator))
|
||||
@@ -577,19 +582,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
bool ranToCompletion;
|
||||
|
||||
StartProcess(processWrapper);
|
||||
|
||||
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
|
||||
if (timeoutMs <= 0)
|
||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
timeoutMs = DefaultImageExtractionTimeoutMs;
|
||||
StartProcess(processWrapper);
|
||||
|
||||
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
|
||||
if (timeoutMs <= 0)
|
||||
{
|
||||
timeoutMs = DefaultImageExtractionTimeoutMs;
|
||||
}
|
||||
|
||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
StopProcess(processWrapper, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
finally
|
||||
{
|
||||
StopProcess(processWrapper, 1000);
|
||||
_thumbnailResourcePool.Release();
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
||||
@@ -620,7 +633,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
|
||||
}
|
||||
|
||||
public async Task ExtractVideoImagesOnInterval(string[] inputFiles,
|
||||
public async Task ExtractVideoImagesOnInterval(
|
||||
string[] inputFiles,
|
||||
string container,
|
||||
MediaStream videoStream,
|
||||
MediaProtocol protocol,
|
||||
@@ -631,8 +645,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
int? maxWidth,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var resourcePool = _thumbnailResourcePool;
|
||||
|
||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||
|
||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
|
||||
@@ -696,7 +708,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
||||
|
||||
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
bool ranToCompletion = false;
|
||||
|
||||
@@ -737,7 +749,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourcePool.Release();
|
||||
_thumbnailResourcePool.Release();
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
||||
|
||||
@@ -25,6 +25,11 @@ namespace MediaBrowser.Model.System
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The product name. This is the AssemblyProduct name.
|
||||
/// </summary>
|
||||
public string ProductName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operating system.
|
||||
|
||||
@@ -32,10 +32,6 @@ namespace MediaBrowser.Model.System
|
||||
/// <value>The display name of the operating system.</value>
|
||||
public string OperatingSystemDisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The product name. This is the AssemblyProduct name.
|
||||
/// </summary>
|
||||
public string ProductName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the package name.
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return ParseReleaseList(subReader);
|
||||
return ParseReleaseList(subReader).ToList();
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return ParseArtistList(subReader);
|
||||
return ParseArtistList(subReader).ToList();
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
|
||||
get
|
||||
{
|
||||
// Refresh if necessary
|
||||
if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20)))
|
||||
if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20)))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
|
||||
/// <summary>
|
||||
/// Class RemoteEpisodeProvider
|
||||
/// </summary>
|
||||
class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
|
||||
public class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
Submodule MediaBrowser.WebDashboard/jellyfin-web updated: 9677981344...37636dae5c
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("10.3.0")]
|
||||
[assembly: AssemblyFileVersion("10.3.0")]
|
||||
[assembly: AssemblyVersion("10.3.4")]
|
||||
[assembly: AssemblyFileVersion("10.3.4")]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
---
|
||||
# We just wrap `build` so this is really it
|
||||
name: "jellyfin"
|
||||
version: "10.3.0-rc1"
|
||||
version: "10.3.4"
|
||||
packages:
|
||||
- debian-package-x64
|
||||
- debian-package-armhf
|
||||
- debian-package-arm64
|
||||
- ubuntu-package-x64
|
||||
- ubuntu-package-armhf
|
||||
- ubuntu-package-arm64
|
||||
- fedora-package-x64
|
||||
- centos-package-x64
|
||||
- linux-x64
|
||||
|
||||
17
bump_version
17
bump_version
@@ -54,6 +54,7 @@ old_version="$(
|
||||
grep "AssemblyVersion" ${shared_version_file} \
|
||||
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/'
|
||||
)"
|
||||
echo $old_version
|
||||
|
||||
# Set the shared version to the specified new_version
|
||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||
@@ -62,9 +63,11 @@ sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
|
||||
|
||||
old_version="$(
|
||||
grep "version:" ${build_file} \
|
||||
| sed -E 's/version: "([0-9\.]+)"/\1/'
|
||||
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
|
||||
)"
|
||||
echo $old_version
|
||||
|
||||
# Set the build.yaml version to the specified new_version
|
||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
|
||||
|
||||
@@ -74,6 +77,16 @@ else
|
||||
new_version_deb="${new_version}-1"
|
||||
fi
|
||||
|
||||
# Set the Dockerfile web version to the specified new_version
|
||||
old_version="$(
|
||||
grep "JELLYFIN_WEB_VERSION=" Dockerfile \
|
||||
| sed -E 's/ARG JELLYFIN_WEB_VERSION=([0-9\.]+[-a-z0-9]*)/\1/'
|
||||
)"
|
||||
echo $old_version
|
||||
|
||||
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
|
||||
sed -i "s/${old_version_sed}/${new_version}/g" Dockerfile*
|
||||
|
||||
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
|
||||
debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog"
|
||||
debian_changelog_temp="$( mktemp )"
|
||||
@@ -124,5 +137,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file}
|
||||
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
|
||||
|
||||
# Stage the changed files for commit
|
||||
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
|
||||
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile*
|
||||
git status
|
||||
|
||||
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/rpm
|
||||
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -72,9 +72,6 @@ fi
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
|
||||
# Build the RPMs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the RPMs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
|
||||
# Move the RPMs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/rpm/* "${output_dir}"
|
||||
|
||||
43
deployment/debian-package-arm64/Dockerfile.amd64
Normal file
43
deployment/debian-package-arm64/Dockerfile.amd64
Normal file
@@ -0,0 +1,43 @@
|
||||
FROM debian:9
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=2.2
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=amd64
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Prepare the cross-toolchain
|
||||
RUN dpkg --add-architecture arm64 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y cross-gcc-dev \
|
||||
&& TARGET_LIST="arm64" cross-gcc-gensource 6 \
|
||||
&& cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \
|
||||
&& apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
|
||||
|
||||
# Link to Debian source dir; mkdir needed or it fails, can't force dest
|
||||
RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
COPY . ${SOURCE_DIR}/
|
||||
|
||||
ENTRYPOINT ["/docker-build.sh"]
|
||||
#ENTRYPOINT ["/bin/bash"]
|
||||
34
deployment/debian-package-arm64/Dockerfile.arm64
Normal file
34
deployment/debian-package-arm64/Dockerfile.arm64
Normal file
@@ -0,0 +1,34 @@
|
||||
FROM debian:9
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=2.2
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=arm64
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
|
||||
|
||||
# Link to Debian source dir; mkdir needed or it fails, can't force dest
|
||||
RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
COPY . ${SOURCE_DIR}/
|
||||
|
||||
ENTRYPOINT ["/docker-build.sh"]
|
||||
29
deployment/debian-package-arm64/clean.sh
Executable file
29
deployment/debian-package-arm64/clean.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ../common.build.sh
|
||||
|
||||
keep_artifacts="${1}"
|
||||
|
||||
WORKDIR="$( pwd )"
|
||||
|
||||
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
|
||||
output_dir="${WORKDIR}/pkg-dist"
|
||||
current_user="$( whoami )"
|
||||
image_name="jellyfin-debian_arm64-build"
|
||||
|
||||
rm -rf "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
|
||||
|
||||
rm -rf "${output_dir}" &>/dev/null \
|
||||
|| sudo rm -rf "${output_dir}" &>/dev/null
|
||||
|
||||
if [[ ${keep_artifacts} == 'n' ]]; then
|
||||
docker_sudo=""
|
||||
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
|
||||
&& [[ ! ${EUID:-1000} -eq 0 ]] \
|
||||
&& [[ ! ${USER} == "root" ]] \
|
||||
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
|
||||
docker_sudo=sudo
|
||||
fi
|
||||
${docker_sudo} docker image rm ${image_name} --force
|
||||
fi
|
||||
1
deployment/debian-package-arm64/dependencies.txt
Normal file
1
deployment/debian-package-arm64/dependencies.txt
Normal file
@@ -0,0 +1 @@
|
||||
docker
|
||||
21
deployment/debian-package-arm64/docker-build.sh
Executable file
21
deployment/debian-package-arm64/docker-build.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Builds the DEB inside the Docker container
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
# Move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image
|
||||
sed -i '/dotnet-sdk-2.2,/d' debian/control
|
||||
|
||||
# Build DEB
|
||||
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
|
||||
dpkg-buildpackage -us -uc -aarm64
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
41
deployment/debian-package-arm64/package.sh
Executable file
41
deployment/debian-package-arm64/package.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ../common.build.sh
|
||||
|
||||
ARCH="$( arch )"
|
||||
WORKDIR="$( pwd )"
|
||||
|
||||
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
|
||||
output_dir="${WORKDIR}/pkg-dist"
|
||||
current_user="$( whoami )"
|
||||
image_name="jellyfin-debian_arm64-build"
|
||||
|
||||
# Determine if sudo should be used for Docker
|
||||
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
|
||||
&& [[ ! ${EUID:-1000} -eq 0 ]] \
|
||||
&& [[ ! ${USER} == "root" ]] \
|
||||
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
|
||||
docker_sudo="sudo"
|
||||
else
|
||||
docker_sudo=""
|
||||
fi
|
||||
|
||||
# Determine which Dockerfile to use
|
||||
case $ARCH in
|
||||
'x86_64')
|
||||
DOCKERFILE="Dockerfile.amd64"
|
||||
;;
|
||||
'armv7l')
|
||||
DOCKERFILE="Dockerfile.arm64"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
1
deployment/debian-package-arm64/pkg-src
Symbolic link
1
deployment/debian-package-arm64/pkg-src
Symbolic link
@@ -0,0 +1 @@
|
||||
../debian-package-x64/pkg-src
|
||||
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -30,13 +30,12 @@ case $ARCH in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the DEBs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
|
||||
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -19,13 +19,12 @@ else
|
||||
docker_sudo=""
|
||||
fi
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the DEBs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
jellyfin (10.3.0~rc1) unstable; urgency=medium
|
||||
jellyfin (10.3.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1
|
||||
* New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 30 Mar 2019 15:47:24 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 06 Jun 2019 22:45:31 -0400
|
||||
|
||||
jellyfin (10.3.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 17 May 2019 23:12:08 -0400
|
||||
|
||||
jellyfin (10.3.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 30 Apr 2019 20:18:44 -0400
|
||||
|
||||
jellyfin (10.3.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 20 Apr 2019 14:24:07 -0400
|
||||
|
||||
jellyfin (10.3.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 19 Apr 2019 14:24:29 -0400
|
||||
|
||||
jellyfin (10.2.2-1) unstable; urgency=medium
|
||||
|
||||
|
||||
@@ -23,6 +23,6 @@ Depends: at,
|
||||
jellyfin-ffmpeg,
|
||||
libfontconfig1,
|
||||
libfreetype6,
|
||||
libssl1.0.0 | libssl1.0.2
|
||||
libssl1.0.0 | libssl1.0.2 | libssl1.1
|
||||
Description: Jellyfin is a home media server.
|
||||
It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development.
|
||||
|
||||
@@ -6,18 +6,25 @@ SHELL := /bin/bash
|
||||
HOST_ARCH := $(shell arch)
|
||||
BUILD_ARCH := ${DEB_HOST_MULTIARCH}
|
||||
ifeq ($(HOST_ARCH),x86_64)
|
||||
# Building AMD64
|
||||
DOTNETRUNTIME := debian-x64
|
||||
ifeq ($(BUILD_ARCH),arm-linux-gnueabihf)
|
||||
# Cross-building ARM on AMD64
|
||||
DOTNETRUNTIME := debian-arm
|
||||
else
|
||||
# Building AMD64
|
||||
DOTNETRUNTIME := debian-x64
|
||||
endif
|
||||
ifeq ($(BUILD_ARCH),aarch64-linux-gnu)
|
||||
# Cross-building ARM on AMD64
|
||||
DOTNETRUNTIME := debian-arm64
|
||||
endif
|
||||
endif
|
||||
ifeq ($(HOST_ARCH),armv7l)
|
||||
# Building ARM
|
||||
DOTNETRUNTIME := debian-arm
|
||||
endif
|
||||
ifeq ($(HOST_ARCH),arm64)
|
||||
# Building ARM
|
||||
DOTNETRUNTIME := debian-arm64
|
||||
endif
|
||||
|
||||
export DH_VERBOSE=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
|
||||
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/rpm
|
||||
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -23,13 +23,12 @@ fi
|
||||
|
||||
./create_tarball.sh
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
|
||||
# Build the RPMs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the RPMs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
|
||||
# Move the RPMs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/rpm/* "${output_dir}"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%endif
|
||||
|
||||
Name: jellyfin
|
||||
Version: 10.3.0
|
||||
Version: 10.3.4
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media Browser
|
||||
License: GPLv2
|
||||
@@ -140,8 +140,16 @@ fi
|
||||
%systemd_postun_with_restart jellyfin.service
|
||||
|
||||
%changelog
|
||||
* Sat Mar 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.0-rc1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0-rc1
|
||||
* Thu Jun 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
|
||||
* Fri May 17 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
|
||||
* Tue Apr 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
|
||||
* Sat Apr 20 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.1
|
||||
* Fri Apr 19 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.3.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.0
|
||||
* Thu Feb 28 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- jellyfin:
|
||||
- PR968 Release 10.2.z copr autobuild
|
||||
|
||||
53
deployment/ubuntu-package-arm64/Dockerfile.amd64
Normal file
53
deployment/ubuntu-package-arm64/Dockerfile.amd64
Normal file
@@ -0,0 +1,53 @@
|
||||
FROM ubuntu:bionic
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=2.2
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=amd64
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Prepare the cross-toolchain
|
||||
RUN rm /etc/apt/sources.list \
|
||||
&& export CODENAME="$( lsb_release -c -s )" \
|
||||
&& echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
|
||||
&& echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
|
||||
&& echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
|
||||
&& echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
|
||||
&& echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
|
||||
&& echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
|
||||
&& echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
|
||||
&& echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
|
||||
&& dpkg --add-architecture arm64 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y cross-gcc-dev \
|
||||
&& TARGET_LIST="arm64" cross-gcc-gensource 6 \
|
||||
&& cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \
|
||||
&& ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
|
||||
&& apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
|
||||
|
||||
# Link to Debian source dir; mkdir needed or it fails, can't force dest
|
||||
RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
COPY . ${SOURCE_DIR}/
|
||||
|
||||
ENTRYPOINT ["/docker-build.sh"]
|
||||
34
deployment/ubuntu-package-arm64/Dockerfile.arm64
Normal file
34
deployment/ubuntu-package-arm64/Dockerfile.arm64
Normal file
@@ -0,0 +1,34 @@
|
||||
FROM ubuntu:bionic
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64
|
||||
ARG ARTIFACT_DIR=/dist
|
||||
ARG SDK_VERSION=2.2
|
||||
# Docker run environment
|
||||
ENV SOURCE_DIR=/jellyfin
|
||||
ENV ARTIFACT_DIR=/dist
|
||||
ENV DEB_BUILD_OPTIONS=noddebs
|
||||
ENV ARCH=arm64
|
||||
|
||||
# Prepare Debian build environment
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0
|
||||
|
||||
# Install dotnet repository
|
||||
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
|
||||
RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \
|
||||
&& mkdir -p dotnet-sdk \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
# Link to docker-build script
|
||||
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
|
||||
|
||||
# Link to Debian source dir; mkdir needed or it fails, can't force dest
|
||||
RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
|
||||
|
||||
VOLUME ${ARTIFACT_DIR}/
|
||||
|
||||
COPY . ${SOURCE_DIR}/
|
||||
|
||||
ENTRYPOINT ["/docker-build.sh"]
|
||||
29
deployment/ubuntu-package-arm64/clean.sh
Executable file
29
deployment/ubuntu-package-arm64/clean.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ../common.build.sh
|
||||
|
||||
keep_artifacts="${1}"
|
||||
|
||||
WORKDIR="$( pwd )"
|
||||
|
||||
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
|
||||
output_dir="${WORKDIR}/pkg-dist"
|
||||
current_user="$( whoami )"
|
||||
image_name="jellyfin-ubuntu-build"
|
||||
|
||||
rm -rf "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
|
||||
|
||||
rm -rf "${output_dir}" &>/dev/null \
|
||||
|| sudo rm -rf "${output_dir}" &>/dev/null
|
||||
|
||||
if [[ ${keep_artifacts} == 'n' ]]; then
|
||||
docker_sudo=""
|
||||
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
|
||||
&& [[ ! ${EUID:-1000} -eq 0 ]] \
|
||||
&& [[ ! ${USER} == "root" ]] \
|
||||
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
|
||||
docker_sudo=sudo
|
||||
fi
|
||||
${docker_sudo} docker image rm ${image_name} --force
|
||||
fi
|
||||
1
deployment/ubuntu-package-arm64/dependencies.txt
Normal file
1
deployment/ubuntu-package-arm64/dependencies.txt
Normal file
@@ -0,0 +1 @@
|
||||
docker
|
||||
21
deployment/ubuntu-package-arm64/docker-build.sh
Executable file
21
deployment/ubuntu-package-arm64/docker-build.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Builds the DEB inside the Docker container
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
# Move to source directory
|
||||
pushd ${SOURCE_DIR}
|
||||
|
||||
# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image
|
||||
sed -i '/dotnet-sdk-2.2,/d' debian/control
|
||||
|
||||
# Build DEB
|
||||
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
|
||||
dpkg-buildpackage -us -uc -aarm64
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
41
deployment/ubuntu-package-arm64/package.sh
Executable file
41
deployment/ubuntu-package-arm64/package.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ../common.build.sh
|
||||
|
||||
ARCH="$( arch )"
|
||||
WORKDIR="$( pwd )"
|
||||
|
||||
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
|
||||
output_dir="${WORKDIR}/pkg-dist"
|
||||
current_user="$( whoami )"
|
||||
image_name="jellyfin-ubuntu_arm64-build"
|
||||
|
||||
# Determine if sudo should be used for Docker
|
||||
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
|
||||
&& [[ ! ${EUID:-1000} -eq 0 ]] \
|
||||
&& [[ ! ${USER} == "root" ]] \
|
||||
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
|
||||
docker_sudo="sudo"
|
||||
else
|
||||
docker_sudo=""
|
||||
fi
|
||||
|
||||
# Determine which Dockerfile to use
|
||||
case $ARCH in
|
||||
'x86_64')
|
||||
DOCKERFILE="Dockerfile.amd64"
|
||||
;;
|
||||
'armv7l')
|
||||
DOCKERFILE="Dockerfile.arm64"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
1
deployment/ubuntu-package-arm64/pkg-src
Symbolic link
1
deployment/ubuntu-package-arm64/pkg-src
Symbolic link
@@ -0,0 +1 @@
|
||||
../debian-package-x64/pkg-src
|
||||
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -30,13 +30,12 @@ case $ARCH in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the DEBs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
|
||||
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/deb
|
||||
mv /jellyfin_* ${ARTIFACT_DIR}/deb/
|
||||
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
|
||||
|
||||
@@ -19,13 +19,12 @@ else
|
||||
docker_sudo=""
|
||||
fi
|
||||
|
||||
# Prepare temporary package dir
|
||||
mkdir -p "${package_temporary_dir}"
|
||||
# Set up the build environment Docker image
|
||||
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
|
||||
# Build the DEBs and copy out to ${package_temporary_dir}
|
||||
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
|
||||
# Correct ownership on the DEBs (as current user, then as root if that fails)
|
||||
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|
||||
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
|
||||
# Move the DEBs to the output directory
|
||||
mkdir -p "${output_dir}"
|
||||
mv "${package_temporary_dir}"/deb/* "${output_dir}"
|
||||
|
||||
@@ -26,7 +26,10 @@ function Build-JellyFin {
|
||||
Write-Error "arm only supported with Windows 8 or higher"
|
||||
exit
|
||||
}
|
||||
dotnet publish -c $BuildType -r "$windowsversion-$Architecture" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
|
||||
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
|
||||
Write-Verbose "InstallLocation: $InstallLocation"
|
||||
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
|
||||
dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
|
||||
}
|
||||
|
||||
function Install-FFMPEG {
|
||||
@@ -73,6 +76,7 @@ function Install-NSSM {
|
||||
Write-Warning "NSSM will not be installed"
|
||||
}else{
|
||||
Write-Verbose "Downloading NSSM"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ function Elevate-Window {
|
||||
|
||||
if($Quiet.IsPresent -or $Quiet -eq $true){
|
||||
if([string]::IsNullOrEmpty($JellyfinLibraryLocation)){
|
||||
$Script:JellyfinDataDir = "$env:AppData\jellyfin\"
|
||||
$Script:JellyfinDataDir = "$env:LOCALAPPDATA\jellyfin\"
|
||||
}else{
|
||||
$Script:JellyfinDataDir = $JellyfinLibraryLocation
|
||||
}
|
||||
@@ -82,7 +82,7 @@ if($Quiet.IsPresent -or $Quiet -eq $true){
|
||||
}else{
|
||||
$Script:InstallServiceAsUser = $true
|
||||
$Script:UserCredentials = $ServiceUser
|
||||
$Script:JellyfinDataDir = "C:\Users\$($Script:UserCredentials.UserName)\Appdata\Roaming\jellyfin\"}
|
||||
$Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Local\jellyfin\"}
|
||||
if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false}
|
||||
if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false}
|
||||
if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false}
|
||||
@@ -131,7 +131,7 @@ if($Quiet.IsPresent -or $Quiet -eq $true){
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$Script:JellyFinDataDir = "$env:AppData\jellyfin\"
|
||||
$Script:JellyFinDataDir = "$env:LOCALAPPDATA\jellyfin\"
|
||||
$Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\"
|
||||
$Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\"
|
||||
$Script:InstallAsService = $False
|
||||
@@ -392,7 +392,7 @@ $ServiceUserBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDow
|
||||
$GUIElementsCollection += $ServiceUserBox
|
||||
|
||||
$MigrateLibraryCheck = New-Object system.Windows.Forms.CheckBox
|
||||
$MigrateLibraryCheck.text = "Import Emby Library"
|
||||
$MigrateLibraryCheck.text = "Import Emby/Old JF Library"
|
||||
$MigrateLibraryCheck.AutoSize = $false
|
||||
$MigrateLibraryCheck.width = 160
|
||||
$MigrateLibraryCheck.height = 20
|
||||
@@ -401,7 +401,7 @@ $MigrateLibraryCheck.Font = 'Microsoft Sans Serif,10'
|
||||
$GUIElementsCollection += $MigrateLibraryCheck
|
||||
|
||||
$LibraryMigrationLabel = New-Object system.Windows.Forms.Label
|
||||
$LibraryMigrationLabel.text = "Emby Library Path"
|
||||
$LibraryMigrationLabel.text = "Emby/Old JF Library Path"
|
||||
$LibraryMigrationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
|
||||
$LibraryMigrationLabel.AutoSize = $false
|
||||
$LibraryMigrationLabel.width = 120
|
||||
|
||||
Reference in New Issue
Block a user