Merge remote-tracking branch 'upstream/master' into video-resolver
This commit is contained in:
commit
9056908bc7
|
@ -62,6 +62,7 @@ jobs:
|
|||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Reference Assembly Build Artifact'
|
||||
enabled: false
|
||||
inputs:
|
||||
source: "specific"
|
||||
artifact: "$(NugetPackageName)"
|
||||
|
@ -73,6 +74,7 @@ jobs:
|
|||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy Reference Assembly Build Artifact'
|
||||
enabled: false
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
||||
contents: '**/*.dll'
|
||||
|
@ -83,6 +85,7 @@ jobs:
|
|||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Execute ABI Compatibility Check Tool'
|
||||
enabled: false
|
||||
inputs:
|
||||
command: custom
|
||||
custom: compat
|
||||
|
|
|
@ -28,6 +28,12 @@ jobs:
|
|||
inputs:
|
||||
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
|
||||
|
||||
## Authenticate with npm registry
|
||||
- task: npmAuthenticate@0
|
||||
inputs:
|
||||
workingFile: ./.npmrc
|
||||
customEndpoint: 'jellyfin-bot for NPM'
|
||||
|
||||
## Generate npm api client
|
||||
# Unstable
|
||||
- task: CmdLine@2
|
||||
|
|
|
@ -63,6 +63,7 @@ jobs:
|
|||
sshEndpoint: repository
|
||||
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
|
||||
contents: '**'
|
||||
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
||||
|
||||
- job: OpenAPISpec
|
||||
dependsOn: Test
|
||||
|
@ -166,7 +167,7 @@ jobs:
|
|||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
|
||||
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Update Stable Repository'
|
||||
|
@ -175,7 +176,7 @@ jobs:
|
|||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) &
|
||||
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) &
|
||||
|
||||
- job: PublishNuget
|
||||
displayName: 'Publish NuGet packages'
|
||||
|
|
3
.npmrc
Normal file
3
.npmrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
registry=https://registry.npmjs.org/
|
||||
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
|
||||
always-auth=true
|
|
@ -487,7 +487,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
User = user,
|
||||
Recursive = true,
|
||||
IsMissing = false,
|
||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
||||
ExcludeItemTypes = new[] { nameof(Book) },
|
||||
IsFolder = isFolder,
|
||||
MediaTypes = mediaTypes,
|
||||
DtoOptions = GetDtoOptions()
|
||||
|
@ -556,7 +556,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
IsVirtualItem = false,
|
||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
||||
ExcludeItemTypes = new[] { nameof(Book) },
|
||||
IsPlaceHolder = false,
|
||||
DtoOptions = GetDtoOptions()
|
||||
};
|
||||
|
@ -575,7 +575,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
StartIndex = startIndex,
|
||||
Limit = limit,
|
||||
};
|
||||
query.IncludeItemTypes = new[] { typeof(LiveTvChannel).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(LiveTvChannel) };
|
||||
|
||||
SetSorting(query, sort, false);
|
||||
|
||||
|
@ -910,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -923,7 +923,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -936,7 +936,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
// query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -949,7 +949,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -962,7 +962,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Audio).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Audio) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -975,7 +975,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Audio).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Audio) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -988,7 +988,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -1001,7 +1001,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -1014,7 +1014,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -1027,7 +1027,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
|
@ -1181,7 +1181,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
{
|
||||
UserId = user.Id,
|
||||
Limit = 50,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
ParentId = parent == null ? Guid.Empty : parent.Id,
|
||||
GroupItems = false
|
||||
},
|
||||
|
@ -1215,7 +1215,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
Recursive = true,
|
||||
ParentId = parentId,
|
||||
ArtistIds = new[] { item.Id },
|
||||
IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
DtoOptions = GetDtoOptions()
|
||||
|
@ -1259,7 +1259,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
Recursive = true,
|
||||
ParentId = parentId,
|
||||
GenreIds = new[] { item.Id },
|
||||
IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
DtoOptions = GetDtoOptions()
|
||||
|
@ -1346,8 +1346,8 @@ namespace Emby.Dlna.ContentDirectory
|
|||
{
|
||||
if (id.StartsWith(name + "_", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stubType = (StubType)Enum.Parse(typeof(StubType), name, true);
|
||||
id = id.Split(new[] { '_' }, 2)[1];
|
||||
stubType = Enum.Parse<StubType>(name, true);
|
||||
id = id.Split('_', 2)[1];
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
foreach (var att in profile.XmlRootAttributes)
|
||||
{
|
||||
var parts = att.Name.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = att.Name.Split(':', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
writer.WriteAttributeString(parts[0], parts[1], null, att.Value);
|
||||
|
|
|
@ -383,9 +383,9 @@ namespace Emby.Dlna
|
|||
continue;
|
||||
}
|
||||
|
||||
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
||||
|
||||
var path = Path.Combine(systemProfilesPath, filename);
|
||||
var path = Path.Join(
|
||||
systemProfilesPath,
|
||||
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||
{
|
||||
|
|
|
@ -168,7 +168,7 @@ namespace Emby.Dlna.Eventing
|
|||
|
||||
builder.Append("</e:propertyset>");
|
||||
|
||||
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
|
||||
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
|
||||
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
|
||||
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
|
||||
|
|
|
@ -257,9 +257,10 @@ namespace Emby.Dlna.Main
|
|||
|
||||
private async Task RegisterServerEndpoints()
|
||||
{
|
||||
var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
|
||||
var addresses = await _appHost.GetLocalIpAddresses().ConfigureAwait(false);
|
||||
|
||||
var udn = CreateUuid(_appHost.SystemId);
|
||||
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
||||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
|
@ -279,7 +280,6 @@ namespace Emby.Dlna.Main
|
|||
|
||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
||||
|
||||
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
||||
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
|
||||
|
||||
var device = new SsdpRootDevice
|
||||
|
|
|
@ -326,7 +326,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
|
||||
_logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand);
|
||||
|
||||
var user = command.ControllingUserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(command.ControllingUserId);
|
||||
|
||||
|
@ -339,7 +339,7 @@ namespace Emby.Dlna.PlayTo
|
|||
var startIndex = command.StartIndex ?? 0;
|
||||
if (startIndex > 0)
|
||||
{
|
||||
items = items.Skip(startIndex).ToList();
|
||||
items = items.GetRange(startIndex, items.Count - startIndex);
|
||||
}
|
||||
|
||||
var playlist = new List<PlaylistItem>();
|
||||
|
|
|
@ -209,7 +209,10 @@ namespace Emby.Notifications
|
|||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
items = items.Take(10).ToList();
|
||||
if (items.Count > 10)
|
||||
{
|
||||
items = items.GetRange(0, 10);
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||
public string VirtualDataPath => "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache path.
|
||||
|
|
|
@ -339,7 +339,7 @@ namespace Emby.Server.Implementations
|
|||
/// Gets the email address for use within a comment section of a user agent field.
|
||||
/// Presently used to provide contact information to MusicBrainz service.
|
||||
/// </summary>
|
||||
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
|
||||
public string ApplicationUserAgentAddress => "team@jellyfin.org";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application name.
|
||||
|
@ -403,7 +403,7 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>() => ServiceProvider.GetService<T>();
|
||||
|
||||
|
|
|
@ -250,21 +250,16 @@ namespace Emby.Server.Implementations.Channels
|
|||
var all = channels;
|
||||
var totalCount = all.Count;
|
||||
|
||||
if (query.StartIndex.HasValue)
|
||||
if (query.StartIndex.HasValue || query.Limit.HasValue)
|
||||
{
|
||||
all = all.Skip(query.StartIndex.Value).ToList();
|
||||
int startIndex = query.StartIndex ?? 0;
|
||||
int count = query.Limit == null ? totalCount - startIndex : Math.Min(query.Limit.Value, totalCount - startIndex);
|
||||
all = all.GetRange(startIndex, count);
|
||||
}
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
all = all.Take(query.Limit.Value).ToList();
|
||||
}
|
||||
|
||||
var returnItems = all.ToArray();
|
||||
|
||||
if (query.RefreshLatestChannelItems)
|
||||
{
|
||||
foreach (var item in returnItems)
|
||||
foreach (var item in all)
|
||||
{
|
||||
RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
@ -272,7 +267,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
|
||||
return new QueryResult<Channel>
|
||||
{
|
||||
Items = returnItems,
|
||||
Items = all,
|
||||
TotalRecordCount = totalCount
|
||||
};
|
||||
}
|
||||
|
@ -543,7 +538,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
return _libraryManager.GetItemIds(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||
IncludeItemTypes = new[] { nameof(Channel) },
|
||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
|
||||
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||
IncludeItemTypes = new[] { nameof(Channel) },
|
||||
ExcludeItemIds = installedChannelIds.ToArray()
|
||||
});
|
||||
|
||||
|
|
|
@ -3914,7 +3914,7 @@ namespace Emby.Server.Implementations.Data
|
|||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
// We should probably figure this out for all folders, but for right now, this is the only place where we need it
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(Series).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (query.IsPlayed.Value)
|
||||
{
|
||||
|
@ -4755,29 +4755,29 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
var list = new List<string>();
|
||||
|
||||
if (IsTypeInQuery(typeof(Person).Name, query))
|
||||
if (IsTypeInQuery(nameof(Person), query))
|
||||
{
|
||||
list.Add(typeof(Person).Name);
|
||||
list.Add(nameof(Person));
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Genre).Name, query))
|
||||
if (IsTypeInQuery(nameof(Genre), query))
|
||||
{
|
||||
list.Add(typeof(Genre).Name);
|
||||
list.Add(nameof(Genre));
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
||||
if (IsTypeInQuery(nameof(MusicGenre), query))
|
||||
{
|
||||
list.Add(typeof(MusicGenre).Name);
|
||||
list.Add(nameof(MusicGenre));
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
||||
if (IsTypeInQuery(nameof(MusicArtist), query))
|
||||
{
|
||||
list.Add(typeof(MusicArtist).Name);
|
||||
list.Add(nameof(MusicArtist));
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Studio).Name, query))
|
||||
if (IsTypeInQuery(nameof(Studio), query))
|
||||
{
|
||||
list.Add(typeof(Studio).Name);
|
||||
list.Add(nameof(Studio));
|
||||
}
|
||||
|
||||
return list;
|
||||
|
@ -4832,12 +4832,12 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
var types = new[]
|
||||
{
|
||||
typeof(Episode).Name,
|
||||
typeof(Video).Name,
|
||||
typeof(Movie).Name,
|
||||
typeof(MusicVideo).Name,
|
||||
typeof(Series).Name,
|
||||
typeof(Season).Name
|
||||
nameof(Episode),
|
||||
nameof(Video),
|
||||
nameof(Movie),
|
||||
nameof(MusicVideo),
|
||||
nameof(Series),
|
||||
nameof(Season)
|
||||
};
|
||||
|
||||
if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)))
|
||||
|
|
|
@ -465,7 +465,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
{
|
||||
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
Name = item.Album,
|
||||
Limit = 1
|
||||
});
|
||||
|
|
|
@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
if (param.Length == 2)
|
||||
{
|
||||
var value = NormalizeValue(param[1].Trim(new[] { '"' }));
|
||||
result.Add(param[0], value);
|
||||
result[param[0]] = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Images
|
|||
// return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
// {
|
||||
// ArtistIds = new[] { item.Id },
|
||||
// IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
// IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
// OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
// Limit = 4,
|
||||
// Recursive = true,
|
||||
|
|
|
@ -42,7 +42,12 @@ namespace Emby.Server.Implementations.Images
|
|||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Genres = new[] { item.Name },
|
||||
IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name },
|
||||
IncludeItemTypes = new[]
|
||||
{
|
||||
nameof(MusicAlbum),
|
||||
nameof(MusicVideo),
|
||||
nameof(Audio)
|
||||
},
|
||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
Limit = 4,
|
||||
Recursive = true,
|
||||
|
@ -77,7 +82,7 @@ namespace Emby.Server.Implementations.Images
|
|||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Genres = new[] { item.Name },
|
||||
IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name },
|
||||
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
|
||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
Limit = 4,
|
||||
Recursive = true,
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Library
|
|||
var genres = item
|
||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
DtoOptions = dtoOptions
|
||||
})
|
||||
.Cast<Audio>()
|
||||
|
@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
|
||||
GenreIds = genreIds.ToArray(),
|
||||
|
||||
|
|
|
@ -87,61 +87,61 @@ namespace Emby.Server.Implementations.Library
|
|||
var excludeItemTypes = query.ExcludeItemTypes.ToList();
|
||||
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList();
|
||||
|
||||
excludeItemTypes.Add(typeof(Year).Name);
|
||||
excludeItemTypes.Add(typeof(Folder).Name);
|
||||
excludeItemTypes.Add(nameof(Year));
|
||||
excludeItemTypes.Add(nameof(Folder));
|
||||
|
||||
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, typeof(Genre).Name);
|
||||
AddIfMissing(includeItemTypes, typeof(MusicGenre).Name);
|
||||
AddIfMissing(includeItemTypes, nameof(Genre));
|
||||
AddIfMissing(includeItemTypes, nameof(MusicGenre));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, typeof(Genre).Name);
|
||||
AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
|
||||
AddIfMissing(excludeItemTypes, nameof(Genre));
|
||||
AddIfMissing(excludeItemTypes, nameof(MusicGenre));
|
||||
}
|
||||
|
||||
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, typeof(Person).Name);
|
||||
AddIfMissing(includeItemTypes, nameof(Person));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, typeof(Person).Name);
|
||||
AddIfMissing(excludeItemTypes, nameof(Person));
|
||||
}
|
||||
|
||||
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, typeof(Studio).Name);
|
||||
AddIfMissing(includeItemTypes, nameof(Studio));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, typeof(Studio).Name);
|
||||
AddIfMissing(excludeItemTypes, nameof(Studio));
|
||||
}
|
||||
|
||||
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, typeof(MusicArtist).Name);
|
||||
AddIfMissing(includeItemTypes, nameof(MusicArtist));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, typeof(MusicArtist).Name);
|
||||
AddIfMissing(excludeItemTypes, nameof(MusicArtist));
|
||||
}
|
||||
|
||||
AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name);
|
||||
AddIfMissing(excludeItemTypes, typeof(Folder).Name);
|
||||
AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
|
||||
AddIfMissing(excludeItemTypes, nameof(Folder));
|
||||
var mediaTypes = query.MediaTypes.ToList();
|
||||
|
||||
if (includeItemTypes.Count > 0)
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(MusicArtist).Name },
|
||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
||||
IsDeadArtist = true,
|
||||
IsLocked = false
|
||||
}).Cast<MusicArtist>().ToList();
|
||||
|
|
|
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Person).Name },
|
||||
IncludeItemTypes = new[] { nameof(Person) },
|
||||
IsDeadPerson = true,
|
||||
IsLocked = false
|
||||
});
|
||||
|
|
|
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Studio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Studio) },
|
||||
IsDeadStudio = true,
|
||||
IsLocked = false
|
||||
});
|
||||
|
|
|
@ -1790,7 +1790,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
Limit = 1,
|
||||
ExternalId = timer.ProgramId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
@ -2151,7 +2151,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
|
@ -2370,7 +2370,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
ExternalSeriesId = seriesTimer.SeriesId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
|
@ -2405,7 +2405,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
channel = _libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
||||
ItemIds = new[] { parent.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
}).FirstOrDefault() as LiveTvChannel;
|
||||
|
@ -2464,7 +2464,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
channel = _libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
||||
ItemIds = new[] { programInfo.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
}).FirstOrDefault() as LiveTvChannel;
|
||||
|
@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
var seriesIds = _libraryManager.GetItemIds(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new[] { nameof(Series) },
|
||||
Name = program.Name
|
||||
}).ToArray();
|
||||
|
||||
|
@ -2542,7 +2542,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
var result = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
ParentIndexNumber = program.SeasonNumber.Value,
|
||||
IndexNumber = program.EpisodeNumber.Value,
|
||||
AncestorIds = seriesIds,
|
||||
|
|
|
@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
|
@ -253,7 +253,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
|
@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
|
@ -307,7 +307,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
ExternalSeriesId = programSeriesId,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
|
|
|
@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
IsKids = query.IsKids,
|
||||
IsSports = query.IsSports,
|
||||
IsSeries = query.IsSeries,
|
||||
IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
|
||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
||||
TopParentIds = new[] { topFolder.Id },
|
||||
IsFavorite = query.IsFavorite,
|
||||
IsLiked = query.IsLiked,
|
||||
|
@ -808,7 +808,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
MinEndDate = query.MinEndDate,
|
||||
MinStartDate = query.MinStartDate,
|
||||
MaxEndDate = query.MaxEndDate,
|
||||
|
@ -872,7 +872,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IsAiring = query.IsAiring,
|
||||
HasAired = query.HasAired,
|
||||
IsNews = query.IsNews,
|
||||
|
@ -1089,8 +1089,8 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (cleanDatabase)
|
||||
{
|
||||
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken);
|
||||
}
|
||||
|
||||
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
|
||||
|
@ -1181,7 +1181,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
ChannelIds = new Guid[] { currentChannel.Id },
|
||||
DtoOptions = new DtoOptions(true)
|
||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||
|
@ -1346,11 +1346,11 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
if (query.IsMovie.Value)
|
||||
{
|
||||
includeItemTypes.Add(typeof(Movie).Name);
|
||||
includeItemTypes.Add(nameof(Movie));
|
||||
}
|
||||
else
|
||||
{
|
||||
excludeItemTypes.Add(typeof(Movie).Name);
|
||||
excludeItemTypes.Add(nameof(Movie));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1358,11 +1358,11 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
if (query.IsSeries.Value)
|
||||
{
|
||||
includeItemTypes.Add(typeof(Episode).Name);
|
||||
includeItemTypes.Add(nameof(Episode));
|
||||
}
|
||||
else
|
||||
{
|
||||
excludeItemTypes.Add(typeof(Episode).Name);
|
||||
excludeItemTypes.Add(nameof(Episode));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1883,7 +1883,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
ChannelIds = channelIds,
|
||||
MaxStartDate = now,
|
||||
MinEndDate = now,
|
||||
|
|
|
@ -131,6 +131,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string GetFilePath()
|
||||
{
|
||||
return TempFilePath;
|
||||
}
|
||||
|
||||
private Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
|
|
|
@ -65,7 +65,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
||||
|
||||
return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||
return await new M3uParser(Logger, _httpClientFactory, _appHost)
|
||||
.Parse(info, channelIdPrefix, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
|
@ -126,7 +128,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public async Task Validate(TunerHostInfo info)
|
||||
{
|
||||
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
||||
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ using MediaBrowser.Common.Extensions;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
@ -30,12 +31,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public async Task<List<ChannelInfo>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
|
||||
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
|
||||
{
|
||||
// Read the file and display it line by line.
|
||||
using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
|
||||
using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
|
||||
{
|
||||
return GetChannels(reader, channelIdPrefix, tunerHostId);
|
||||
return GetChannels(reader, channelIdPrefix, info.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,15 +49,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
}
|
||||
}
|
||||
|
||||
public Task<Stream> GetListingsStream(string url, CancellationToken cancellationToken)
|
||||
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.GetStreamAsync(url);
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
|
||||
if (!string.IsNullOrEmpty(info.UserAgent))
|
||||
{
|
||||
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
|
||||
}
|
||||
|
||||
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(requestMessage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Task.FromResult((Stream)File.OpenRead(url));
|
||||
return File.OpenRead(info.Url);
|
||||
}
|
||||
|
||||
private const string ExtInfPrefix = "#EXTINF:";
|
||||
|
|
|
@ -55,7 +55,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
var typeName = GetType().Name;
|
||||
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
|
||||
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
// Response stream is disposed manually.
|
||||
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
@ -121,6 +122,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
}
|
||||
}
|
||||
|
||||
public string GetFilePath()
|
||||
{
|
||||
return TempFilePath;
|
||||
}
|
||||
|
||||
private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
|
|
|
@ -113,5 +113,7 @@
|
|||
"TasksChannelsCategory": "Internetové kanály",
|
||||
"TasksApplicationCategory": "Aplikace",
|
||||
"TasksLibraryCategory": "Knihovna",
|
||||
"TasksMaintenanceCategory": "Údržba"
|
||||
"TasksMaintenanceCategory": "Údržba",
|
||||
"TaskCleanActivityLogDescription": "Smazat záznamy o aktivitě, které jsou starší než zadaná doba.",
|
||||
"TaskCleanActivityLog": "Smazat záznam aktivity"
|
||||
}
|
||||
|
|
|
@ -95,6 +95,8 @@
|
|||
"TasksLibraryCategory": "Library",
|
||||
"TasksApplicationCategory": "Application",
|
||||
"TasksChannelsCategory": "Internet Channels",
|
||||
"TaskCleanActivityLog": "Clean Activity Log",
|
||||
"TaskCleanActivityLogDescription": "Deletes activity log entries older than the configured age.",
|
||||
"TaskCleanCache": "Clean Cache Directory",
|
||||
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
|
||||
"TaskRefreshChapterImages": "Extract Chapter Images",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"HeaderLiveTV": "Live-TV",
|
||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||
"NameSeasonUnknown": "Tuntematon kausi",
|
||||
"NameSeasonNumber": "Kausi {0}",
|
||||
"NameInstallFailed": "{0} asennus epäonnistui",
|
||||
"MusicVideos": "Musiikkivideot",
|
||||
|
@ -19,23 +19,23 @@
|
|||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||
"Inherit": "Periytyä",
|
||||
"HomeVideos": "Kotivideot",
|
||||
"HeaderRecordingGroups": "Nauhoiteryhmät",
|
||||
"HeaderRecordingGroups": "Tallennusryhmät",
|
||||
"HeaderNextUp": "Seuraavaksi",
|
||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||
"HeaderFavoriteShows": "Lempisarjat",
|
||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||
"HeaderFavoriteArtists": "Lempiartistit",
|
||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||
"HeaderFavoriteSongs": "Suosikkikappaleet",
|
||||
"HeaderFavoriteShows": "Suosikkisarjat",
|
||||
"HeaderFavoriteEpisodes": "Suosikkijaksot",
|
||||
"HeaderFavoriteArtists": "Suosikkiartistit",
|
||||
"HeaderFavoriteAlbums": "Suosikkialbumit",
|
||||
"HeaderContinueWatching": "Jatka katsomista",
|
||||
"HeaderAlbumArtists": "Albumin esittäjä",
|
||||
"HeaderAlbumArtists": "Albumin artistit",
|
||||
"Genres": "Tyylilajit",
|
||||
"Folders": "Kansiot",
|
||||
"Favorites": "Suosikit",
|
||||
"FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
|
||||
"DeviceOnlineWithName": "{0} on yhdistetty",
|
||||
"DeviceOfflineWithName": "{0} on katkaissut yhteytensä",
|
||||
"DeviceOfflineWithName": "{0} yhteys on katkaistu",
|
||||
"Collections": "Kokoelmat",
|
||||
"ChapterNameValue": "Luku: {0}",
|
||||
"ChapterNameValue": "Jakso: {0}",
|
||||
"Channels": "Kanavat",
|
||||
"CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
|
||||
"Books": "Kirjat",
|
||||
|
@ -61,25 +61,25 @@
|
|||
"UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}",
|
||||
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
||||
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
||||
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
||||
"UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}",
|
||||
"UserLockedOutWithName": "Käyttäjä {0} lukittu",
|
||||
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||
"TvShows": "TV-sarjat",
|
||||
"TvShows": "TV-ohjelmat",
|
||||
"Sync": "Synkronoi",
|
||||
"SubtitleDownloadFailureFromForItem": "Tekstitysten lataus ({0} -> {1}) epäonnistui //this string would have to be generated for each provider and movie because of finnish cases, sorry",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.",
|
||||
"SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.",
|
||||
"Songs": "Kappaleet",
|
||||
"Shows": "Sarjat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} täytyy käynnistää uudelleen",
|
||||
"Shows": "Ohjelmat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen",
|
||||
"ProviderValue": "Tarjoaja: {0}",
|
||||
"Plugin": "Liitännäinen",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videota toistetaan",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
||||
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||
"NotificationOptionServerRestartRequired": "Palvelin pitää käynnistää uudelleen",
|
||||
"NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen",
|
||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||
|
@ -104,10 +104,10 @@
|
|||
"TaskRefreshPeople": "Päivitä henkilöt",
|
||||
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
|
||||
"TaskCleanLogs": "Puhdista lokihakemisto",
|
||||
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
|
||||
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.",
|
||||
"TaskRefreshLibrary": "Skannaa mediakirjasto",
|
||||
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
|
||||
"TaskRefreshChapterImages": "Eristä lukujen kuvat",
|
||||
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.",
|
||||
"TaskRefreshChapterImages": "Pura jakson kuvat",
|
||||
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
|
||||
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
|
||||
"TasksChannelsCategory": "Internet kanavat",
|
||||
|
|
|
@ -113,5 +113,7 @@
|
|||
"TaskCleanCache": "Vider le répertoire cache",
|
||||
"TasksApplicationCategory": "Application",
|
||||
"TasksLibraryCategory": "Bibliothèque",
|
||||
"TasksMaintenanceCategory": "Maintenance"
|
||||
"TasksMaintenanceCategory": "Maintenance",
|
||||
"TaskCleanActivityLogDescription": "Supprime les entrées du journal d'activité antérieures à l'âge configuré.",
|
||||
"TaskCleanActivityLog": "Nettoyer le journal d'activité"
|
||||
}
|
||||
|
|
3
Emby.Server.Implementations/Localization/Core/hi.json
Normal file
3
Emby.Server.Implementations/Localization/Core/hi.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"Albums": "आल्बुम्"
|
||||
}
|
|
@ -96,7 +96,7 @@
|
|||
"TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。",
|
||||
"TaskRefreshLibrary": "メディアライブラリのスキャン",
|
||||
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
|
||||
"TaskCleanCache": "キャッシュの掃除",
|
||||
"TaskCleanCache": "キャッシュを消去",
|
||||
"TasksChannelsCategory": "ネットチャンネル",
|
||||
"TasksApplicationCategory": "アプリケーション",
|
||||
"TasksLibraryCategory": "ライブラリ",
|
||||
|
@ -112,5 +112,7 @@
|
|||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||
"TaskRefreshChapterImages": "チャプター画像を抽出する",
|
||||
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする"
|
||||
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする",
|
||||
"TaskCleanActivityLogDescription": "設定された期間よりも古いアクティビティの履歴を削除します。",
|
||||
"TaskCleanActivityLog": "アクティビティの履歴を消去"
|
||||
}
|
||||
|
|
|
@ -113,5 +113,7 @@
|
|||
"TaskCleanCacheDescription": "시스템에서 더 이상 필요하지 않은 캐시 파일을 삭제합니다.",
|
||||
"TaskCleanCache": "캐시 폴더 청소",
|
||||
"TasksChannelsCategory": "인터넷 채널",
|
||||
"TasksLibraryCategory": "라이브러리"
|
||||
"TasksLibraryCategory": "라이브러리",
|
||||
"TaskCleanActivityLogDescription": "구성된 기간보다 오래된 활동내역 삭제",
|
||||
"TaskCleanActivityLog": "활동내역청소"
|
||||
}
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
"AppDeviceValues": "Aplikacija: {0}, Naprava: {1}",
|
||||
"Application": "Aplikacija",
|
||||
"Artists": "Izvajalci",
|
||||
"AuthenticationSucceededWithUserName": "{0} preverjanje pristnosti uspešno",
|
||||
"AuthenticationSucceededWithUserName": "{0} se je uspešno prijavil",
|
||||
"Books": "Knjige",
|
||||
"CameraImageUploadedFrom": "Nova fotografija je bila naložena z {0}",
|
||||
"CameraImageUploadedFrom": "Nova fotografija je bila naložena iz {0}",
|
||||
"Channels": "Kanali",
|
||||
"ChapterNameValue": "Poglavje {0}",
|
||||
"Collections": "Zbirke",
|
||||
"DeviceOfflineWithName": "{0} je prekinil povezavo",
|
||||
"DeviceOnlineWithName": "{0} je povezan",
|
||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave iz {0}",
|
||||
"Favorites": "Priljubljeno",
|
||||
"Folders": "Mape",
|
||||
"Genres": "Zvrsti",
|
||||
"HeaderAlbumArtists": "Izvajalci albuma",
|
||||
"HeaderContinueWatching": "Nadaljuj gledanje",
|
||||
"HeaderContinueWatching": "Nadaljuj z ogledom",
|
||||
"HeaderFavoriteAlbums": "Priljubljeni albumi",
|
||||
"HeaderFavoriteArtists": "Priljubljeni izvajalci",
|
||||
"HeaderFavoriteEpisodes": "Priljubljene epizode",
|
||||
|
@ -32,23 +32,23 @@
|
|||
"LabelIpAddressValue": "IP naslov: {0}",
|
||||
"LabelRunningTimeValue": "Čas trajanja: {0}",
|
||||
"Latest": "Najnovejše",
|
||||
"MessageApplicationUpdated": "Jellyfin Server je bil posodobljen",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server je bil posodobljen na {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitve strežnika {0} je bil posodobljen",
|
||||
"MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Oddelek nastavitev {0} je bil posodobljen",
|
||||
"MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene",
|
||||
"MixedContent": "Razne vsebine",
|
||||
"MixedContent": "Mešane vsebine",
|
||||
"Movies": "Filmi",
|
||||
"Music": "Glasba",
|
||||
"MusicVideos": "Glasbeni videi",
|
||||
"NameInstallFailed": "{0} namestitev neuspešna",
|
||||
"NameSeasonNumber": "Sezona {0}",
|
||||
"NameSeasonUnknown": "Season neznana",
|
||||
"NameSeasonUnknown": "Neznana sezona",
|
||||
"NewVersionIsAvailable": "Nova različica Jellyfin strežnika je na voljo za prenos.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena",
|
||||
"NotificationOptionAudioPlayback": "Predvajanje zvoka začeto",
|
||||
"NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka zaustavljeno",
|
||||
"NotificationOptionCameraImageUploaded": "Posnetek kamere naložen",
|
||||
"NotificationOptionAudioPlayback": "Predvajanje zvoka se je začelo",
|
||||
"NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka se je ustavilo",
|
||||
"NotificationOptionCameraImageUploaded": "Fotografija naložena",
|
||||
"NotificationOptionInstallationFailed": "Namestitev neuspešna",
|
||||
"NotificationOptionNewLibraryContent": "Nove vsebine dodane",
|
||||
"NotificationOptionPluginError": "Napaka dodatka",
|
||||
|
@ -56,41 +56,41 @@
|
|||
"NotificationOptionPluginUninstalled": "Dodatek odstranjen",
|
||||
"NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena",
|
||||
"NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika",
|
||||
"NotificationOptionTaskFailed": "Razporejena naloga neuspešna",
|
||||
"NotificationOptionTaskFailed": "Načrtovano opravilo neuspešno",
|
||||
"NotificationOptionUserLockedOut": "Uporabnik zaklenjen",
|
||||
"NotificationOptionVideoPlayback": "Predvajanje videa se je začelo",
|
||||
"NotificationOptionVideoPlaybackStopped": "Predvajanje videa se je ustavilo",
|
||||
"Photos": "Fotografije",
|
||||
"Playlists": "Seznami predvajanja",
|
||||
"Plugin": "Plugin",
|
||||
"Plugin": "Dodatek",
|
||||
"PluginInstalledWithName": "{0} je bil nameščen",
|
||||
"PluginUninstalledWithName": "{0} je bil odstranjen",
|
||||
"PluginUpdatedWithName": "{0} je bil posodobljen",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ProviderValue": "Ponudnik: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} ni uspelo",
|
||||
"ScheduledTaskStartedWithName": "{0} začeto",
|
||||
"ServerNameNeedsToBeRestarted": "{0} mora biti ponovno zagnan",
|
||||
"Shows": "Serije",
|
||||
"Songs": "Pesmi",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin strežnik se zaganja. Poskusite ponovno kasneje.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}",
|
||||
"Sync": "Sinhroniziraj",
|
||||
"System": "System",
|
||||
"System": "Sistem",
|
||||
"TvShows": "TV serije",
|
||||
"User": "User",
|
||||
"User": "Uporabnik",
|
||||
"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}",
|
||||
"UserOnlineFromDevice": "{0} je aktiven na {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": "Poseben - {0}",
|
||||
"ValueSpecialEpisodeName": "Posebna - {0}",
|
||||
"VersionNumber": "Različica {0}",
|
||||
"TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise",
|
||||
"TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.",
|
||||
|
@ -102,7 +102,7 @@
|
|||
"TaskRefreshPeopleDescription": "Osveži metapodatke za igralce in režiserje v vaši knjižnici.",
|
||||
"TaskRefreshPeople": "Osveži osebe",
|
||||
"TaskCleanLogsDescription": "Izbriše dnevniške datoteke starejše od {0} dni.",
|
||||
"TaskCleanLogs": "Počisti mapo dnevnika",
|
||||
"TaskCleanLogs": "Počisti mapo dnevnikov",
|
||||
"TaskRefreshLibraryDescription": "Preišče vašo knjižnico za nove datoteke in osveži metapodatke.",
|
||||
"TaskRefreshLibrary": "Preišči knjižnico predstavnosti",
|
||||
"TaskRefreshChapterImagesDescription": "Ustvari sličice za poglavja videoposnetkov.",
|
||||
|
|
|
@ -112,5 +112,5 @@
|
|||
"Artists": "Artistë",
|
||||
"Application": "Aplikacioni",
|
||||
"AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}",
|
||||
"Albums": "Albumet"
|
||||
"Albums": "Albume"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"Channels": "Kanaler",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Samlingar",
|
||||
"DeviceOfflineWithName": "{0} har kopplat från",
|
||||
"DeviceOfflineWithName": "{0} har kopplat ner",
|
||||
"DeviceOnlineWithName": "{0} är ansluten",
|
||||
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
|
||||
"Favorites": "Favoriter",
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
|
||||
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
|
||||
"TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
|
||||
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
|
||||
"TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.",
|
||||
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
|
||||
"TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
|
||||
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
|
||||
|
|
|
@ -703,7 +703,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
MaxRuntimeTicks = info.MaxRuntimeTicks
|
||||
};
|
||||
|
||||
if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (info.Type.Equals(nameof(DailyTrigger), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!info.TimeOfDayTicks.HasValue)
|
||||
{
|
||||
|
@ -717,7 +717,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
};
|
||||
}
|
||||
|
||||
if (info.Type.Equals(typeof(WeeklyTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (info.Type.Equals(nameof(WeeklyTrigger), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!info.TimeOfDayTicks.HasValue)
|
||||
{
|
||||
|
@ -737,7 +737,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
};
|
||||
}
|
||||
|
||||
if (info.Type.Equals(typeof(IntervalTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (info.Type.Equals(nameof(IntervalTrigger), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!info.IntervalTicks.HasValue)
|
||||
{
|
||||
|
@ -751,7 +751,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
};
|
||||
}
|
||||
|
||||
if (info.Type.Equals(typeof(StartupTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (info.Type.Equals(nameof(StartupTrigger), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new StartupTrigger();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes old activity log entries.
|
||||
/// </summary>
|
||||
public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CleanActivityLogTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
|
||||
/// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public CleanActivityLogTask(
|
||||
ILocalizationManager localization,
|
||||
IActivityManager activityManager,
|
||||
IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => _localization.GetLocalizedString("TaskCleanActivityLog");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "CleanActivityLog";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
|
||||
if (!retentionDays.HasValue || retentionDays <= 0)
|
||||
{
|
||||
throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
|
||||
}
|
||||
|
||||
var startDate = DateTime.UtcNow.AddDays(retentionDays.Value * -1);
|
||||
return _activityManager.CleanAsync(startDate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return Enumerable.Empty<TaskTriggerInfo>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -104,6 +104,6 @@ namespace Emby.Server.Implementations
|
|||
public string InternalMetadataPath { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
||||
public string VirtualInternalMetadataPath => "%MetadataPath%";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.TV
|
|||
.GetItemList(
|
||||
new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
|
||||
SeriesPresentationUniqueKey = presentationUniqueKey,
|
||||
Limit = limit,
|
||||
|
@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.TV
|
|||
{
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
|
||||
Limit = 1,
|
||||
IsPlayed = false,
|
||||
|
|
|
@ -10,6 +10,7 @@ using System.Runtime.Serialization;
|
|||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -17,6 +18,8 @@ using MediaBrowser.Common.Plugins;
|
|||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Common.System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Events.Updates;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
@ -36,6 +39,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
/// </summary>
|
||||
private readonly ILogger<InstallationManager> _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IEventManager _eventManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
@ -65,23 +69,20 @@ namespace Emby.Server.Implementations.Updates
|
|||
ILogger<InstallationManager> logger,
|
||||
IApplicationHost appHost,
|
||||
IApplicationPaths appPaths,
|
||||
IEventManager eventManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>();
|
||||
_completedInstallationsInternal = new ConcurrentBag<InstallationInfo>();
|
||||
|
||||
_logger = logger;
|
||||
_applicationHost = appHost;
|
||||
_appPaths = appPaths;
|
||||
_eventManager = eventManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_config = config;
|
||||
|
@ -89,27 +90,6 @@ namespace Emby.Server.Implementations.Updates
|
|||
_zipClient = zipClient;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationInfo> PackageInstalling;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationInfo> PackageInstallationCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationInfo> PackageInstallationCancelled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<IPlugin> PluginUninstalled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationInfo> PluginUpdated;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<InstallationInfo> PluginInstalled;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||
|
||||
|
@ -268,11 +248,11 @@ namespace Emby.Server.Implementations.Updates
|
|||
|
||||
var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token;
|
||||
|
||||
PackageInstalling?.Invoke(this, package);
|
||||
await _eventManager.PublishAsync(new PluginInstallingEventArgs(package)).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await InstallPackageInternal(package, linkedToken).ConfigureAwait(false);
|
||||
var isUpdate = await InstallPackageInternal(package, linkedToken).ConfigureAwait(false);
|
||||
|
||||
lock (_currentInstallationsLock)
|
||||
{
|
||||
|
@ -280,8 +260,11 @@ namespace Emby.Server.Implementations.Updates
|
|||
}
|
||||
|
||||
_completedInstallationsInternal.Add(package);
|
||||
await _eventManager.PublishAsync(isUpdate
|
||||
? (GenericEventArgs<InstallationInfo>)new PluginUpdatedEventArgs(package)
|
||||
: new PluginInstalledEventArgs(package)).ConfigureAwait(false);
|
||||
|
||||
PackageInstallationCompleted?.Invoke(this, package);
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
@ -292,7 +275,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
|
||||
_logger.LogInformation("Package installation cancelled: {0} {1}", package.Name, package.Version);
|
||||
|
||||
PackageInstallationCancelled?.Invoke(this, package);
|
||||
await _eventManager.PublishAsync(new PluginInstallationCancelledEventArgs(package)).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
@ -305,11 +288,11 @@ namespace Emby.Server.Implementations.Updates
|
|||
_currentInstallations.Remove(tuple);
|
||||
}
|
||||
|
||||
PackageInstallationFailed?.Invoke(this, new InstallationFailedEventArgs
|
||||
await _eventManager.PublishAsync(new InstallationFailedEventArgs
|
||||
{
|
||||
InstallationInfo = package,
|
||||
Exception = ex
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
@ -326,7 +309,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
/// <param name="package">The package.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns><see cref="Task" />.</returns>
|
||||
private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
|
||||
private async Task<bool> InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
|
||||
{
|
||||
// Set last update time if we were installed before
|
||||
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => p.Id == package.Guid)
|
||||
|
@ -336,20 +319,9 @@ namespace Emby.Server.Implementations.Updates
|
|||
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Do plugin-specific processing
|
||||
if (plugin == null)
|
||||
{
|
||||
_logger.LogInformation("New plugin installed: {0} {1}", package.Name, package.Version);
|
||||
_logger.LogInformation(plugin == null ? "New plugin installed: {0} {1}" : "Plugin updated: {0} {1}", package.Name, package.Version);
|
||||
|
||||
PluginInstalled?.Invoke(this, package);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Plugin updated: {0} {1}", package.Name, package.Version);
|
||||
|
||||
PluginUpdated?.Invoke(this, package);
|
||||
}
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
return plugin != null;
|
||||
}
|
||||
|
||||
private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken)
|
||||
|
@ -467,7 +439,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
PluginUninstalled?.Invoke(this, plugin);
|
||||
_eventManager.Publish(new PluginUninstalledEventArgs(plugin));
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -89,7 +91,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -99,7 +101,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
@ -145,9 +147,9 @@ namespace Jellyfin.Api.Controllers
|
|||
NameLessThan = nameLessThan,
|
||||
NameStartsWith = nameStartsWith,
|
||||
NameStartsWithOrGreater = nameStartsWithOrGreater,
|
||||
Tags = RequestHelpers.Split(tags, ',', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
|
||||
Genres = RequestHelpers.Split(genres, ',', true),
|
||||
Tags = RequestHelpers.Split(tags, '|', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
|
||||
Genres = RequestHelpers.Split(genres, '|', true),
|
||||
GenreIds = RequestHelpers.GetGuids(genreIds),
|
||||
StudioIds = RequestHelpers.GetGuids(studioIds),
|
||||
Person = person,
|
||||
|
@ -298,7 +300,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -308,7 +310,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
@ -354,9 +356,9 @@ namespace Jellyfin.Api.Controllers
|
|||
NameLessThan = nameLessThan,
|
||||
NameStartsWith = nameStartsWith,
|
||||
NameStartsWithOrGreater = nameStartsWithOrGreater,
|
||||
Tags = RequestHelpers.Split(tags, ',', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
|
||||
Genres = RequestHelpers.Split(genres, ',', true),
|
||||
Tags = RequestHelpers.Split(tags, '|', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
|
||||
Genres = RequestHelpers.Split(genres, '|', true),
|
||||
GenreIds = RequestHelpers.GetGuids(genreIds),
|
||||
StudioIds = RequestHelpers.GetGuids(studioIds),
|
||||
Person = person,
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -121,7 +122,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? sortOrder,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] string? sortBy,
|
||||
[FromQuery] string? fields)
|
||||
{
|
||||
|
@ -196,7 +197,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] string? channelIds)
|
||||
{
|
||||
|
|
|
@ -83,14 +83,14 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Adds items to a collection.
|
||||
/// </summary>
|
||||
/// <param name="collectionId">The collection id.</param>
|
||||
/// <param name="itemIds">Item ids, comma delimited.</param>
|
||||
/// <param name="ids">Item ids, comma delimited.</param>
|
||||
/// <response code="204">Items added to collection.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
|
||||
[HttpPost("{collectionId}/Items")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string itemIds)
|
||||
public async Task<ActionResult> AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string ids)
|
||||
{
|
||||
await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
|
||||
await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(ids)).ConfigureAwait(true);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
@ -98,14 +98,14 @@ namespace Jellyfin.Api.Controllers
|
|||
/// Removes items from a collection.
|
||||
/// </summary>
|
||||
/// <param name="collectionId">The collection id.</param>
|
||||
/// <param name="itemIds">Item ids, comma delimited.</param>
|
||||
/// <param name="ids">Item ids, comma delimited.</param>
|
||||
/// <response code="204">Items removed from collection.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
|
||||
[HttpDelete("{collectionId}/Items")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string itemIds)
|
||||
public async Task<ActionResult> RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string ids)
|
||||
{
|
||||
await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
|
||||
await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(ids)).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <summary>
|
||||
/// Devices Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DevicesController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
@ -46,7 +46,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <response code="200">Devices retrieved.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
|
||||
{
|
||||
|
@ -62,7 +61,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <response code="404">Device not found.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||
[HttpGet("Info")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id)
|
||||
|
@ -84,7 +82,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <response code="404">Device not found.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||
[HttpGet("Options")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id)
|
||||
|
@ -107,7 +104,6 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <response code="404">Device not found.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||
[HttpPost("Options")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult UpdateDeviceOptions(
|
||||
|
|
|
@ -5,11 +5,13 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -90,7 +92,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -100,7 +102,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace Jellyfin.Api.Controllers
|
|||
var userDataPath = Path.Combine(_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
|
||||
if (user.ProfileImage != null)
|
||||
{
|
||||
_userManager.ClearProfileImage(user);
|
||||
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
|
||||
|
@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult DeleteUserImage(
|
||||
public async Task<ActionResult> DeleteUserImage(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromRoute] int? index = null)
|
||||
|
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
|
|||
_logger.LogError(e, "Error deleting user profile image:");
|
||||
}
|
||||
|
||||
_userManager.ClearProfileImage(user);
|
||||
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Entities;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -71,7 +72,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -108,7 +109,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var album = _libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var playlist = (Playlist)_libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -182,7 +183,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
? _userManager.GetUserById(userId.Value)
|
||||
|
@ -218,7 +219,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -255,7 +256,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -292,7 +293,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -315,9 +316,9 @@ namespace Jellyfin.Api.Controllers
|
|||
TotalRecordCount = list.Count
|
||||
};
|
||||
|
||||
if (limit.HasValue)
|
||||
if (limit.HasValue && limit < list.Count)
|
||||
{
|
||||
list = list.Take(limit.Value).ToList();
|
||||
list = list.GetRange(0, limit.Value);
|
||||
}
|
||||
|
||||
var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -159,7 +160,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? isHd,
|
||||
[FromQuery] bool? is4K,
|
||||
[FromQuery] string? locationTypes,
|
||||
[FromQuery] LocationType[] excludeLocationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] bool? isUnaired,
|
||||
[FromQuery] double? minCommunityRating,
|
||||
|
@ -182,10 +183,10 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? imageTypes,
|
||||
[FromQuery] ImageType[] imageTypes,
|
||||
[FromQuery] string? sortBy,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -194,7 +195,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
@ -342,7 +343,7 @@ namespace Jellyfin.Api.Controllers
|
|||
PersonIds = RequestHelpers.GetGuids(personIds),
|
||||
PersonTypes = RequestHelpers.Split(personTypes, ',', true),
|
||||
Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
|
||||
ImageTypes = RequestHelpers.Split(imageTypes, ',', true).Select(v => Enum.Parse<ImageType>(v, true)).ToArray(),
|
||||
ImageTypes = imageTypes,
|
||||
VideoTypes = RequestHelpers.Split(videoTypes, ',', true).Select(v => Enum.Parse<VideoType>(v, true)).ToArray(),
|
||||
AdjacentTo = adjacentTo,
|
||||
ItemIds = RequestHelpers.GetGuids(ids),
|
||||
|
@ -536,7 +537,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LibraryStructureDto;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
|
@ -75,7 +76,7 @@ namespace Jellyfin.Api.Controllers
|
|||
public async Task<ActionResult> AddVirtualFolder(
|
||||
[FromQuery] string? name,
|
||||
[FromQuery] string? collectionType,
|
||||
[FromQuery] string[] paths,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
|
||||
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
|
||||
[FromQuery] bool refreshLibrary = false)
|
||||
{
|
||||
|
|
|
@ -26,6 +26,7 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? isDisliked,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] string? sortBy,
|
||||
|
@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool? isMovie,
|
||||
|
@ -349,7 +350,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
|
@ -560,7 +561,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? genreIds,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] Guid? librarySeriesId,
|
||||
|
@ -591,7 +592,7 @@ namespace Jellyfin.Api.Controllers
|
|||
IsKids = isKids,
|
||||
IsSports = isSports,
|
||||
SeriesTimerId = seriesTimerId,
|
||||
Genres = RequestHelpers.Split(genres, ',', true),
|
||||
Genres = RequestHelpers.Split(genres, '|', true),
|
||||
GenreIds = RequestHelpers.GetGuids(genreIds)
|
||||
};
|
||||
|
||||
|
@ -647,7 +648,7 @@ namespace Jellyfin.Api.Controllers
|
|||
IsKids = body.IsKids,
|
||||
IsSports = body.IsSports,
|
||||
SeriesTimerId = body.SeriesTimerId,
|
||||
Genres = RequestHelpers.Split(body.Genres, ',', true),
|
||||
Genres = RequestHelpers.Split(body.Genres, '|', true),
|
||||
GenreIds = RequestHelpers.GetGuids(body.GenreIds)
|
||||
};
|
||||
|
||||
|
@ -704,7 +705,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? isSports,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? genreIds,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
|
@ -1219,11 +1220,8 @@ namespace Jellyfin.Api.Controllers
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await new ProgressiveFileCopier(liveStreamInfo, null, _transcodingJobHelper, CancellationToken.None)
|
||||
.WriteToAsync(memoryStream, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
return File(memoryStream, MimeTypes.GetMimeType("file." + container));
|
||||
var liveStream = new ProgressiveFileStream(liveStreamInfo.GetFilePath(), null, _transcodingJobHelper);
|
||||
return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
|
||||
}
|
||||
|
||||
private void AssertUserCanManageLiveTv()
|
||||
|
|
|
@ -85,8 +85,8 @@ namespace Jellyfin.Api.Controllers
|
|||
IncludeItemTypes = new[]
|
||||
{
|
||||
nameof(Movie),
|
||||
// typeof(Trailer).Name,
|
||||
// typeof(LiveTvProgram).Name
|
||||
// nameof(Trailer),
|
||||
// nameof(LiveTvProgram)
|
||||
},
|
||||
// IsMovie = true
|
||||
OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
|
||||
|
|
|
@ -5,12 +5,14 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -89,7 +91,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -99,7 +101,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
|
|
@ -5,11 +5,13 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -89,7 +91,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -99,7 +101,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
|
|
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Playlists;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -151,7 +152,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes)
|
||||
[FromQuery] ImageType[] enableImageTypes)
|
||||
{
|
||||
var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
|
||||
if (playlist == null)
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <returns>The list of scheduled tasks.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public IEnumerable<IScheduledTaskWorker> GetTasks(
|
||||
public IEnumerable<TaskInfo> GetTasks(
|
||||
[FromQuery] bool? isHidden,
|
||||
[FromQuery] bool? isEnabled)
|
||||
{
|
||||
|
@ -57,7 +57,7 @@ namespace Jellyfin.Api.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
yield return task;
|
||||
yield return ScheduledTaskHelpers.GetTaskInfo(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers
|
|||
public ActionResult PostCapabilities(
|
||||
[FromQuery] string? id,
|
||||
[FromQuery] string? playableMediaTypes,
|
||||
[FromQuery] GeneralCommandType[] supportedCommands,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
|
||||
[FromQuery] bool supportsMediaControl = false,
|
||||
[FromQuery] bool supportsSync = false,
|
||||
[FromQuery] bool supportsPersistentIdentifier = true)
|
||||
|
|
|
@ -4,11 +4,13 @@ using System.Linq;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -88,7 +90,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] string? includeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -98,7 +100,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
@ -144,9 +146,9 @@ namespace Jellyfin.Api.Controllers
|
|||
NameLessThan = nameLessThan,
|
||||
NameStartsWith = nameStartsWith,
|
||||
NameStartsWithOrGreater = nameStartsWithOrGreater,
|
||||
Tags = RequestHelpers.Split(tags, ',', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
|
||||
Genres = RequestHelpers.Split(genres, ',', true),
|
||||
Tags = RequestHelpers.Split(tags, '|', true),
|
||||
OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
|
||||
Genres = RequestHelpers.Split(genres, '|', true),
|
||||
GenreIds = RequestHelpers.GetGuids(genreIds),
|
||||
StudioIds = RequestHelpers.GetGuids(studioIds),
|
||||
Person = person,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
@ -18,6 +20,7 @@ namespace Jellyfin.Api.Controllers
|
|||
/// The suggestions controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class SuggestionsController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDtoService _dtoService;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
@ -125,7 +126,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? isHd,
|
||||
[FromQuery] bool? is4K,
|
||||
[FromQuery] string? locationTypes,
|
||||
[FromQuery] LocationType[] excludeLocationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] bool? isUnaired,
|
||||
[FromQuery] double? minCommunityRating,
|
||||
|
@ -147,10 +148,10 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? parentId,
|
||||
[FromQuery] string? fields,
|
||||
[FromQuery] string? excludeItemTypes,
|
||||
[FromQuery] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] string? mediaTypes,
|
||||
[FromQuery] string? imageTypes,
|
||||
[FromQuery] ImageType[] imageTypes,
|
||||
[FromQuery] string? sortBy,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery] string? genres,
|
||||
|
@ -159,7 +160,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery] string? personIds,
|
||||
[FromQuery] string? personTypes,
|
||||
|
|
|
@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities.TV;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? parentId,
|
||||
[FromQuery] bool? enableImges,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
|
@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? parentId,
|
||||
[FromQuery] bool? enableImges,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
@ -206,7 +207,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] string? sortBy)
|
||||
{
|
||||
|
@ -325,7 +326,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? adjacentTo,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||
|
|
|
@ -88,16 +88,14 @@ namespace Jellyfin.Api.Controllers
|
|||
/// <response code="302">Redirected to remote audio stream.</response>
|
||||
/// <returns>A <see cref="Task"/> containing the audio file.</returns>
|
||||
[HttpGet("Audio/{itemId}/universal")]
|
||||
[HttpGet("Audio/{itemId}/universal.{container}", Name = "GetUniversalAudioStream_2")]
|
||||
[HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
|
||||
[HttpHead("Audio/{itemId}/universal.{container}", Name = "HeadUniversalAudioStream_2")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetUniversalAudioStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute] string? container,
|
||||
[FromQuery] string? container,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
|
@ -270,20 +268,24 @@ namespace Jellyfin.Api.Controllers
|
|||
{
|
||||
var deviceProfile = new DeviceProfile();
|
||||
|
||||
var directPlayProfiles = new List<DirectPlayProfile>();
|
||||
|
||||
var containers = RequestHelpers.Split(container, ',', true);
|
||||
|
||||
foreach (var cont in containers)
|
||||
int len = containers.Length;
|
||||
var directPlayProfiles = new DirectPlayProfile[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var parts = RequestHelpers.Split(cont, ',', true);
|
||||
var parts = RequestHelpers.Split(containers[i], '|', true);
|
||||
|
||||
var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray());
|
||||
var audioCodecs = parts.Length == 1 ? null : string.Join(',', parts.Skip(1));
|
||||
|
||||
directPlayProfiles.Add(new DirectPlayProfile { Type = DlnaProfileType.Audio, Container = parts[0], AudioCodec = audioCodecs });
|
||||
directPlayProfiles[i] = new DirectPlayProfile
|
||||
{
|
||||
Type = DlnaProfileType.Audio,
|
||||
Container = parts[0],
|
||||
AudioCodec = audioCodecs
|
||||
};
|
||||
}
|
||||
|
||||
deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray();
|
||||
deviceProfile.DirectPlayProfiles = directPlayProfiles;
|
||||
|
||||
deviceProfile.TranscodingProfiles = new[]
|
||||
{
|
||||
|
|
|
@ -381,17 +381,13 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
if (string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
|
||||
{
|
||||
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
|
||||
_userManager.UpdateConfiguration(user.Id, updateUser.Configuration);
|
||||
}
|
||||
else
|
||||
if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
|
||||
{
|
||||
await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false);
|
||||
_userManager.UpdateConfiguration(updateUser.Id, updateUser.Configuration);
|
||||
}
|
||||
|
||||
await _userManager.UpdateConfigurationAsync(user.Id, updateUser.Configuration).ConfigureAwait(false);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
@ -409,7 +405,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult UpdateUserPolicy(
|
||||
public async Task<ActionResult> UpdateUserPolicy(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody] UserPolicy newPolicy)
|
||||
{
|
||||
|
@ -447,7 +443,7 @@ namespace Jellyfin.Api.Controllers
|
|||
_sessionManager.RevokeUserTokens(user.Id, currentToken);
|
||||
}
|
||||
|
||||
_userManager.UpdatePolicy(userId, newPolicy);
|
||||
await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
@ -464,7 +460,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult UpdateUserConfiguration(
|
||||
public async Task<ActionResult> UpdateUserConfiguration(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody] UserConfiguration userConfig)
|
||||
{
|
||||
|
@ -473,7 +469,7 @@ namespace Jellyfin.Api.Controllers
|
|||
return Forbid("User configuration update not allowed");
|
||||
}
|
||||
|
||||
_userManager.UpdateConfiguration(userId, userConfig);
|
||||
await _userManager.UpdateConfigurationAsync(userId, userConfig).ConfigureAwait(false);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
@ -534,6 +530,33 @@ namespace Jellyfin.Api.Controllers
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user based on auth token.
|
||||
/// </summary>
|
||||
/// <response code="200">User returned.</response>
|
||||
/// <response code="400">Token is not owned by a user.</response>
|
||||
/// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
|
||||
[HttpGet("Me")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<UserDto> GetCurrentUser()
|
||||
{
|
||||
var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
|
||||
if (userId == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return _userManager.GetUserDto(user);
|
||||
}
|
||||
|
||||
private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
|
||||
{
|
||||
var users = _userManager.Users;
|
||||
|
|
|
@ -272,7 +272,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int limit = 20,
|
||||
[FromQuery] bool groupItems = true)
|
||||
|
|
|
@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var baseUrlParam = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"\"hls{0}\"",
|
||||
"\"hls/{0}/\"",
|
||||
Path.GetFileNameWithoutExtension(outputPath));
|
||||
|
||||
return string.Format(
|
||||
|
|
|
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? sortBy,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery] string? enableImageTypes,
|
||||
[FromQuery] ImageType[] enableImageTypes,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool recursive = true,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
|
|
|
@ -126,7 +126,7 @@ namespace Jellyfin.Api.Extensions
|
|||
bool? enableImages,
|
||||
bool? enableUserData,
|
||||
int? imageTypeLimit,
|
||||
string? enableImageTypes)
|
||||
ImageType[] enableImageTypes)
|
||||
{
|
||||
dtoOptions.EnableImages = enableImages ?? true;
|
||||
|
||||
|
@ -140,11 +140,9 @@ namespace Jellyfin.Api.Extensions
|
|||
dtoOptions.EnableUserData = enableUserData.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(enableImageTypes))
|
||||
if (enableImageTypes.Length != 0)
|
||||
{
|
||||
dtoOptions.ImageTypes = enableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
|
||||
.ToArray();
|
||||
dtoOptions.ImageTypes = enableImageTypes;
|
||||
}
|
||||
|
||||
return dtoOptions;
|
||||
|
|
|
@ -155,7 +155,7 @@ namespace Jellyfin.Api.Helpers
|
|||
return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
|
||||
}
|
||||
|
||||
var totalBitrate = state.OutputAudioBitrate ?? 0 + state.OutputVideoBitrate ?? 0;
|
||||
var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
|
|
|
@ -123,9 +123,8 @@ namespace Jellyfin.Api.Helpers
|
|||
state.Dispose();
|
||||
}
|
||||
|
||||
await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None)
|
||||
.WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false);
|
||||
return new FileStreamResult(httpContext.Response.Body, contentType);
|
||||
var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper);
|
||||
return new FileStreamResult(stream, contentType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
166
Jellyfin.Api/Helpers/ProgressiveFileStream.cs
Normal file
166
Jellyfin.Api/Helpers/ProgressiveFileStream.cs
Normal file
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Models.PlaybackDtos;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Jellyfin.Api.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A progressive file stream for transferring transcoded files as they are written to.
|
||||
/// </summary>
|
||||
public class ProgressiveFileStream : Stream
|
||||
{
|
||||
private readonly FileStream _fileStream;
|
||||
private readonly TranscodingJobDto? _job;
|
||||
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||
private readonly bool _allowAsyncFileRead;
|
||||
private int _bytesWritten;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The path to the transcoded file.</param>
|
||||
/// <param name="job">The transcoding job information.</param>
|
||||
/// <param name="transcodingJobHelper">The transcoding job helper.</param>
|
||||
public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper)
|
||||
{
|
||||
_job = job;
|
||||
_transcodingJobHelper = transcodingJobHelper;
|
||||
_bytesWritten = 0;
|
||||
|
||||
var fileOptions = FileOptions.SequentialScan;
|
||||
_allowAsyncFileRead = false;
|
||||
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
fileOptions |= FileOptions.Asynchronous;
|
||||
_allowAsyncFileRead = true;
|
||||
}
|
||||
|
||||
_fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => _fileStream.CanRead;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
{
|
||||
_fileStream.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _fileStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int remainingBytesToRead = count;
|
||||
|
||||
int newOffset = offset;
|
||||
while (remainingBytesToRead > 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
int bytesRead;
|
||||
if (_allowAsyncFileRead)
|
||||
{
|
||||
bytesRead = await _fileStream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesRead = _fileStream.Read(buffer, newOffset, remainingBytesToRead);
|
||||
}
|
||||
|
||||
remainingBytesToRead -= bytesRead;
|
||||
newOffset += bytesRead;
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
_bytesWritten += bytesRead;
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
if (_job != null)
|
||||
{
|
||||
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the job is null it's a live stream and will require user action to close
|
||||
if (_job?.HasExited ?? false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_fileStream.Dispose();
|
||||
|
||||
if (_job != null)
|
||||
{
|
||||
_transcodingJobHelper.OnTranscodeEndRequest(_job);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_disposed = true;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using Jellyfin.Data.Enums;
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
@ -161,5 +162,32 @@ namespace Jellyfin.Api.Helpers
|
|||
.Select(i => i!.Value)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item fields.
|
||||
/// </summary>
|
||||
/// <param name="imageTypes">The image types string.</param>
|
||||
/// <returns>IEnumerable{ItemFields}.</returns>
|
||||
internal static ImageType[] GetImageTypes(string? imageTypes)
|
||||
{
|
||||
if (string.IsNullOrEmpty(imageTypes))
|
||||
{
|
||||
return Array.Empty<ImageType>();
|
||||
}
|
||||
|
||||
return Split(imageTypes, ',', true)
|
||||
.Select(v =>
|
||||
{
|
||||
if (Enum.TryParse(v, true, out ImageType value))
|
||||
{
|
||||
return (ImageType?)value;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(i => i.HasValue)
|
||||
.Select(i => i!.Value)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,9 +50,9 @@ namespace Jellyfin.Api.Helpers
|
|||
|
||||
var returnItems = items;
|
||||
|
||||
if (limit.HasValue)
|
||||
if (limit.HasValue && limit < returnItems.Count)
|
||||
{
|
||||
returnItems = returnItems.Take(limit.Value).ToList();
|
||||
returnItems = returnItems.GetRange(0, limit.Value);
|
||||
}
|
||||
|
||||
var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Jellyfin.Api.ModelBinders
|
|||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
var elementType = bindingContext.ModelType.GetElementType();
|
||||
var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0];
|
||||
var converter = TypeDescriptor.GetConverter(elementType);
|
||||
|
||||
if (valueProviderResult.Length > 1)
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Jellyfin.Api.ModelBinders
|
||||
{
|
||||
/// <summary>
|
||||
/// Comma delimited array model binder provider.
|
||||
/// </summary>
|
||||
public class CommaDelimitedArrayModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
private readonly IModelBinder _binder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinderProvider"/> class.
|
||||
/// </summary>
|
||||
public CommaDelimitedArrayModelBinderProvider()
|
||||
{
|
||||
_binder = new CommaDelimitedArrayModelBinder();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModelBinder? GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
return context.Metadata.ModelType.IsArray ? _binder : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using MediaBrowser.Common.Json.Converters;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Api.Models.LiveTvDtos
|
||||
{
|
||||
|
@ -137,7 +141,9 @@ namespace Jellyfin.Api.Models.LiveTvDtos
|
|||
/// Gets or sets the image types to include in the output.
|
||||
/// Optional.
|
||||
/// </summary>
|
||||
public string? EnableImageTypes { get; set; }
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "EnableImageTypes", Justification = "Imported from ServiceStack")]
|
||||
public ImageType[] EnableImageTypes { get; set; } = Array.Empty<ImageType>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets include user data.
|
||||
|
|
|
@ -72,6 +72,18 @@ namespace Jellyfin.Server.Implementations.Activity
|
|||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CleanAsync(DateTime startDate)
|
||||
{
|
||||
await using var dbContext = _provider.CreateContext();
|
||||
var entries = dbContext.ActivityLogs
|
||||
.AsQueryable()
|
||||
.Where(entry => entry.DateCreated <= startDate);
|
||||
|
||||
dbContext.RemoveRange(entries);
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static ActivityLogEntry ConvertToOldModel(ActivityLog entry)
|
||||
{
|
||||
return new ActivityLogEntry
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#pragma warning disable CA1307
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -48,6 +49,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
|
||||
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
||||
|
||||
private readonly IDictionary<Guid, User> _users;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserManager"/> class.
|
||||
/// </summary>
|
||||
|
@ -81,38 +84,28 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
||||
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
||||
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||
|
||||
_users = new ConcurrentDictionary<Guid, User>();
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
foreach (var user in dbContext.Users
|
||||
.Include(user => user.Permissions)
|
||||
.Include(user => user.Preferences)
|
||||
.Include(user => user.AccessSchedules)
|
||||
.Include(user => user.ProfileImage)
|
||||
.AsEnumerable())
|
||||
{
|
||||
_users.Add(user.Id, user);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<User> Users
|
||||
{
|
||||
get
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users
|
||||
.Include(user => user.Permissions)
|
||||
.Include(user => user.Preferences)
|
||||
.Include(user => user.AccessSchedules)
|
||||
.Include(user => user.ProfileImage)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
public IEnumerable<User> Users => _users.Values;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Guid> UsersIds
|
||||
{
|
||||
get
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users
|
||||
.AsQueryable()
|
||||
.Select(user => user.Id)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
public IEnumerable<Guid> UsersIds => _users.Keys;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public User? GetUserById(Guid id)
|
||||
|
@ -122,13 +115,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users
|
||||
.Include(user => user.Permissions)
|
||||
.Include(user => user.Preferences)
|
||||
.Include(user => user.AccessSchedules)
|
||||
.Include(user => user.ProfileImage)
|
||||
.FirstOrDefault(user => user.Id == id);
|
||||
_users.TryGetValue(id, out var user);
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -139,14 +127,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
throw new ArgumentException("Invalid username", nameof(name));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users
|
||||
.Include(user => user.Permissions)
|
||||
.Include(user => user.Preferences)
|
||||
.Include(user => user.AccessSchedules)
|
||||
.Include(user => user.ProfileImage)
|
||||
.AsEnumerable()
|
||||
.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
|
||||
return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -205,13 +186,17 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
|
||||
: 0;
|
||||
|
||||
return new User(
|
||||
var user = new User(
|
||||
name,
|
||||
_defaultAuthenticationProvider.GetType().FullName,
|
||||
_defaultPasswordResetProvider.GetType().FullName)
|
||||
{
|
||||
InternalId = max + 1
|
||||
};
|
||||
|
||||
_users.Add(user.Id, user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -237,28 +222,12 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
/// <inheritdoc/>
|
||||
public void DeleteUser(Guid userId)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var user = dbContext.Users
|
||||
.Include(u => u.Permissions)
|
||||
.Include(u => u.Preferences)
|
||||
.Include(u => u.AccessSchedules)
|
||||
.Include(u => u.ProfileImage)
|
||||
.FirstOrDefault(u => u.Id == userId);
|
||||
if (user == null)
|
||||
if (!_users.TryGetValue(userId, out var user))
|
||||
{
|
||||
throw new ResourceNotFoundException(nameof(userId));
|
||||
}
|
||||
|
||||
if (dbContext.Users.Find(user.Id) == null)
|
||||
{
|
||||
throw new ArgumentException(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
|
||||
user.Username,
|
||||
user.Id));
|
||||
}
|
||||
|
||||
if (dbContext.Users.Count() == 1)
|
||||
if (_users.Count == 1)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
|
@ -277,6 +246,8 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
nameof(userId));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
// Clear all entities related to the user from the database.
|
||||
if (user.ProfileImage != null)
|
||||
{
|
||||
|
@ -288,6 +259,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
dbContext.RemoveRange(user.AccessSchedules);
|
||||
dbContext.Users.Remove(user);
|
||||
dbContext.SaveChanges();
|
||||
_users.Remove(userId);
|
||||
|
||||
_eventManager.Publish(new UserDeletedEventArgs(user));
|
||||
}
|
||||
|
@ -460,11 +432,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
// the authentication provider might have created it
|
||||
user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
|
||||
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null)
|
||||
{
|
||||
UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy());
|
||||
|
||||
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||
await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -589,9 +559,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
public async Task InitializeAsync()
|
||||
{
|
||||
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
if (await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false))
|
||||
if (_users.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -604,6 +572,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
|
||||
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
|
||||
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
|
||||
newUser.SetPermission(PermissionKind.IsAdministrator, true);
|
||||
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
|
||||
|
@ -644,9 +613,9 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateConfiguration(Guid userId, UserConfiguration config)
|
||||
public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
var user = dbContext.Users
|
||||
.Include(u => u.Permissions)
|
||||
.Include(u => u.Preferences)
|
||||
|
@ -673,13 +642,13 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||
|
||||
dbContext.Update(user);
|
||||
dbContext.SaveChanges();
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdatePolicy(Guid userId, UserPolicy policy)
|
||||
public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
var user = dbContext.Users
|
||||
.Include(u => u.Permissions)
|
||||
.Include(u => u.Preferences)
|
||||
|
@ -744,15 +713,16 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
|
||||
dbContext.Update(user);
|
||||
dbContext.SaveChanges();
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearProfileImage(User user)
|
||||
public async Task ClearProfileImageAsync(User user)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
dbContext.Remove(user.ProfileImage);
|
||||
dbContext.SaveChanges();
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
user.ProfileImage = null;
|
||||
}
|
||||
|
||||
private static bool IsValidUsername(string name)
|
||||
|
|
|
@ -16,7 +16,6 @@ using Jellyfin.Api.Auth.LocalAccessPolicy;
|
|||
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Controllers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using Jellyfin.Server.Formatters;
|
||||
|
@ -167,8 +166,6 @@ namespace Jellyfin.Server.Extensions
|
|||
|
||||
opts.OutputFormatters.Add(new CssOutputFormatter());
|
||||
opts.OutputFormatters.Add(new XmlOutputFormatter());
|
||||
|
||||
opts.ModelBinderProviders.Insert(0, new CommaDelimitedArrayModelBinderProvider());
|
||||
})
|
||||
|
||||
// Clear app parts to avoid other assemblies being picked up
|
||||
|
|
|
@ -42,8 +42,8 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.9" />
|
||||
<PackageReference Include="prometheus-net" Version="3.6.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
|
||||
<PackageReference Include="prometheus-net" Version="4.0.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
|
||||
|
|
|
@ -290,23 +290,19 @@ namespace Jellyfin.Server
|
|||
{
|
||||
_logger.LogInformation("Kestrel listening on {IpAddress}", address);
|
||||
options.Listen(address, appHost.HttpPort);
|
||||
|
||||
if (appHost.ListenWithHttps)
|
||||
{
|
||||
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(appHost.Certificate);
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
options.Listen(
|
||||
address,
|
||||
appHost.HttpsPort,
|
||||
listenOptions => listenOptions.UseHttps(appHost.Certificate));
|
||||
}
|
||||
else if (builderContext.HostingEnvironment.IsDevelopment())
|
||||
{
|
||||
try
|
||||
{
|
||||
options.Listen(address, appHost.HttpsPort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps();
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
options.Listen(address, appHost.HttpsPort, listenOptions => listenOptions.UseHttps());
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
|
@ -322,21 +318,15 @@ namespace Jellyfin.Server
|
|||
|
||||
if (appHost.ListenWithHttps)
|
||||
{
|
||||
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(appHost.Certificate);
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
options.ListenAnyIP(
|
||||
appHost.HttpsPort,
|
||||
listenOptions => listenOptions.UseHttps(appHost.Certificate));
|
||||
}
|
||||
else if (builderContext.HostingEnvironment.IsDevelopment())
|
||||
{
|
||||
try
|
||||
{
|
||||
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps();
|
||||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||
});
|
||||
options.ListenAnyIP(appHost.HttpsPort, listenOptions => listenOptions.UseHttps());
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MediaBrowser.Common.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert comma delimited string to array of type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert to.</typeparam>
|
||||
public class JsonCommaDelimitedArrayConverter<T> : JsonConverter<T[]>
|
||||
{
|
||||
private readonly TypeConverter _typeConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class.
|
||||
/// </summary>
|
||||
public JsonCommaDelimitedArrayConverter()
|
||||
{
|
||||
_typeConverter = TypeDescriptor.GetConverter(typeof(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var stringEntries = reader.GetString()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (stringEntries == null || stringEntries.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
var entries = new T[stringEntries.Length];
|
||||
for (var i = 0; i < stringEntries.Length; i++)
|
||||
{
|
||||
entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i].Trim());
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T[]>(ref reader, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MediaBrowser.Common.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Json comma delimited array converter factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
|
||||
/// </remarks>
|
||||
public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0];
|
||||
return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,29 +11,6 @@ namespace MediaBrowser.Common.Updates
|
|||
{
|
||||
public interface IInstallationManager : IDisposable
|
||||
{
|
||||
event EventHandler<InstallationInfo> PackageInstalling;
|
||||
|
||||
event EventHandler<InstallationInfo> PackageInstallationCompleted;
|
||||
|
||||
event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
|
||||
|
||||
event EventHandler<InstallationInfo> PackageInstallationCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin is uninstalled.
|
||||
/// </summary>
|
||||
event EventHandler<IPlugin> PluginUninstalled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin is updated.
|
||||
/// </summary>
|
||||
event EventHandler<InstallationInfo> PluginUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin is installed.
|
||||
/// </summary>
|
||||
event EventHandler<InstallationInfo> PluginInstalled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the completed installations.
|
||||
/// </summary>
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
{
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Audio), nameof(MusicVideo), nameof(MusicAlbum) };
|
||||
query.ArtistIds = new[] { Id };
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(MusicVideo), nameof(Audio), nameof(MusicAlbum), nameof(MusicArtist) };
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
|
|
@ -723,7 +723,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
private bool RequiresPostFiltering2(InternalItemsQuery query)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(BoxSet).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to BoxSet query");
|
||||
return true;
|
||||
|
@ -813,7 +813,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(nameof(Series)))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to IsPlayed");
|
||||
return true;
|
||||
|
|
|
@ -59,7 +59,13 @@ namespace MediaBrowser.Controller.Entities
|
|||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.ExcludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.ExcludeItemTypes = new[]
|
||||
{
|
||||
nameof(MusicVideo),
|
||||
nameof(Entities.Audio.Audio),
|
||||
nameof(MusicAlbum),
|
||||
nameof(MusicArtist)
|
||||
};
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
|
@ -207,7 +207,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
|
||||
query.AncestorWithPresentationUniqueKey = null;
|
||||
query.SeriesPresentationUniqueKey = seriesKey;
|
||||
query.IncludeItemTypes = new[] { typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Season) };
|
||||
query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray();
|
||||
|
||||
if (user != null && !user.DisplayMissingEpisodes)
|
||||
|
@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
|
@ -253,7 +253,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
{
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode), nameof(Season) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
|
@ -364,7 +364,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
{
|
||||
AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
|
||||
SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
|
|
|
@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
}
|
||||
|
||||
return parent.QueryRecursive(query);
|
||||
|
@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, InternalItemsQuery query)
|
||||
{
|
||||
query.Parent = null;
|
||||
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
||||
query.SetUser(user);
|
||||
query.Recursive = true;
|
||||
|
||||
|
@ -223,7 +223,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name },
|
||||
IncludeItemTypes = new[] { nameof(Movie) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
}).Items
|
||||
|
@ -286,7 +286,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
query.IsVirtualItem = false;
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
|
@ -362,7 +362,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new[] { nameof(Series) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
}).Items
|
||||
|
@ -413,7 +413,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
|
|
@ -56,10 +56,11 @@ namespace MediaBrowser.Controller
|
|||
/// <summary>
|
||||
/// Gets the system info.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>SystemInfo.</returns>
|
||||
Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken);
|
||||
Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken);
|
||||
Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request
|
||||
|
@ -67,7 +68,7 @@ namespace MediaBrowser.Controller
|
|||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>A list containing all the local IP addresses of the server.</returns>
|
||||
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken);
|
||||
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
|
||||
|
@ -75,7 +76,7 @@ namespace MediaBrowser.Controller
|
|||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>The server URL.</returns>
|
||||
Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
|
||||
Task<string> GetLocalApiUrl(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1)
|
||||
|
|
|
@ -115,5 +115,7 @@ namespace MediaBrowser.Controller.Library
|
|||
public interface IDirectStreamProvider
|
||||
{
|
||||
Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
|
||||
|
||||
string GetFilePath();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,7 +158,8 @@ namespace MediaBrowser.Controller.Library
|
|||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="config">The request containing the new user configuration.</param>
|
||||
void UpdateConfiguration(Guid userId, UserConfiguration config);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdateConfigurationAsync(Guid userId, UserConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// This method updates the user's policy.
|
||||
|
@ -167,12 +168,14 @@ namespace MediaBrowser.Controller.Library
|
|||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="policy">The request containing the new user policy.</param>
|
||||
void UpdatePolicy(Guid userId, UserPolicy policy);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdatePolicyAsync(Guid userId, UserPolicy policy);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the user's profile image.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
void ClearProfileImage(User user);
|
||||
/// <returns>A task representing the clearing of the profile image.</returns>
|
||||
Task ClearProfileImageAsync(User user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1380,24 +1380,40 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
|
||||
{
|
||||
if (audioStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.AudioBitRate.HasValue)
|
||||
{
|
||||
// Don't encode any higher than this
|
||||
return Math.Min(384000, request.AudioBitRate.Value);
|
||||
}
|
||||
|
||||
return null;
|
||||
// Empty bitrate area is not allow on iOS
|
||||
// Default audio bitrate to 128K if it is not being requested
|
||||
// https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
|
||||
return 128000;
|
||||
}
|
||||
|
||||
public int? GetAudioBitrateParam(int? audioBitRate, MediaStream audioStream)
|
||||
{
|
||||
if (audioStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (audioBitRate.HasValue)
|
||||
{
|
||||
// Don't encode any higher than this
|
||||
return Math.Min(384000, audioBitRate.Value);
|
||||
}
|
||||
|
||||
return null;
|
||||
// Empty bitrate area is not allow on iOS
|
||||
// Default audio bitrate to 128K if it is not being requested
|
||||
// https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
|
||||
return 128000;
|
||||
}
|
||||
|
||||
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls)
|
||||
|
@ -2659,9 +2675,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
state.MediaSource = mediaSource;
|
||||
|
||||
var request = state.BaseRequest;
|
||||
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
||||
var supportedAudioCodecs = state.SupportedAudioCodecs;
|
||||
if (request != null && supportedAudioCodecs != null && supportedAudioCodecs.Length > 0)
|
||||
{
|
||||
var supportedAudioCodecsList = request.AudioCodec.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
var supportedAudioCodecsList = supportedAudioCodecs.ToList();
|
||||
|
||||
ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
|
||||
|
||||
|
@ -3068,7 +3085,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
}
|
||||
|
||||
var whichCodec = videoStream.Codec.ToLowerInvariant();
|
||||
var whichCodec = videoStream.Codec?.ToLowerInvariant();
|
||||
switch (whichCodec)
|
||||
{
|
||||
case "avc":
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user