Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52c399d4d1 |
@@ -1,3 +1,6 @@
|
|||||||
|
[submodule "ThirdParty/taglib-sharp"]
|
||||||
|
path = ThirdParty/taglib-sharp
|
||||||
|
url = https://github.com/mono/taglib-sharp
|
||||||
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
|
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
|
||||||
path = MediaBrowser.WebDashboard/jellyfin-web
|
path = MediaBrowser.WebDashboard/jellyfin-web
|
||||||
url = https://github.com/jellyfin/jellyfin-web.git
|
url = https://github.com/jellyfin/jellyfin-web.git
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
- [cvium](https://github.com/cvium)
|
- [cvium](https://github.com/cvium)
|
||||||
- [wtayl0r](https://github.com/wtayl0r)
|
- [wtayl0r](https://github.com/wtayl0r)
|
||||||
- [TtheCreator](https://github.com/Tthecreator)
|
- [TtheCreator](https://github.com/Tthecreator)
|
||||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -932,7 +932,13 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
||||||
{
|
{
|
||||||
ImageDownloadInfo imageInfo = GetImageInfo(item);;
|
ImageDownloadInfo imageInfo = null;
|
||||||
|
|
||||||
|
// Finally, just use the image from the item
|
||||||
|
if (imageInfo == null)
|
||||||
|
{
|
||||||
|
imageInfo = GetImageInfo(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (imageInfo == null)
|
if (imageInfo == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -270,10 +270,17 @@ namespace Emby.Drawing
|
|||||||
// create the bitmap
|
// create the bitmap
|
||||||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||||
|
|
||||||
// decode
|
if (bitmap != null)
|
||||||
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
{
|
||||||
|
// decode
|
||||||
|
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||||
|
|
||||||
origin = codec.EncodedOrigin;
|
origin = codec.EncodedOrigin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
origin = GetSKEncodedOrigin(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,8 @@ namespace Emby.Naming.AudioBook
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
if (IsDirectory)
|
||||||
if (IsDirectory) // TODO
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
var extension = Path.GetExtension(path) ?? string.Empty;
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
|
<ProjectReference Include="..\ThirdParty\taglib-sharp\src\taglib-sharp.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="TagLibSharp" Version="2.2.0-beta" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
@@ -93,18 +92,18 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
|
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
|
||||||
Type = NotificationType.CameraImageUploaded.ToString()
|
Type = NotificationType.CameraImageUploaded.ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
|
||||||
Type = NotificationType.UserLockedOut.ToString(),
|
Type = NotificationType.UserLockedOut.ToString(),
|
||||||
@@ -112,9 +111,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
|
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
|
||||||
Type = "SubtitleDownloadFailure",
|
Type = "SubtitleDownloadFailure",
|
||||||
@@ -123,7 +122,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
@@ -146,7 +145,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
var user = e.Users.First();
|
var user = e.Users.First();
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
||||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
@@ -154,7 +153,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
@@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
var user = e.Users.First();
|
var user = e.Users.First();
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
||||||
Type = GetPlaybackNotificationType(item.MediaType),
|
Type = GetPlaybackNotificationType(item.MediaType),
|
||||||
@@ -238,7 +237,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
string name;
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
@@ -255,7 +254,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
|
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = "SessionEnded",
|
Type = "SessionEnded",
|
||||||
@@ -264,11 +263,11 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||||
{
|
{
|
||||||
var user = e.Argument.User;
|
var user = e.Argument.User;
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
|
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
|
||||||
Type = "AuthenticationSucceeded",
|
Type = "AuthenticationSucceeded",
|
||||||
@@ -277,9 +276,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
|
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
|
||||||
Type = "AuthenticationFailed",
|
Type = "AuthenticationFailed",
|
||||||
@@ -288,9 +287,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
|
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
|
||||||
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
|
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
|
||||||
@@ -298,27 +297,27 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
|
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
|
||||||
Type = "NamedConfigurationUpdated"
|
Type = "NamedConfigurationUpdated"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
|
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
|
||||||
Type = "ServerConfigurationUpdated"
|
Type = "ServerConfigurationUpdated"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
|
||||||
Type = "UserPolicyUpdated",
|
Type = "UserPolicyUpdated",
|
||||||
@@ -326,18 +325,18 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
|
||||||
Type = "UserDeleted"
|
Type = "UserDeleted"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
|
||||||
Type = "UserPasswordChanged",
|
Type = "UserPasswordChanged",
|
||||||
@@ -345,9 +344,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
|
||||||
Type = "UserCreated",
|
Type = "UserCreated",
|
||||||
@@ -355,9 +354,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
|
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
|
||||||
Type = "SubtitlesDownloaded",
|
Type = "SubtitlesDownloaded",
|
||||||
@@ -366,7 +365,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
string name;
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
@@ -383,7 +382,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
|
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = "SessionStarted",
|
Type = "SessionStarted",
|
||||||
@@ -392,9 +391,9 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
|
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
|
||||||
Type = NotificationType.PluginUpdateInstalled.ToString(),
|
Type = NotificationType.PluginUpdateInstalled.ToString(),
|
||||||
@@ -403,18 +402,18 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
|
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
|
||||||
Type = NotificationType.PluginUninstalled.ToString()
|
Type = NotificationType.PluginUninstalled.ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||||
{
|
{
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
|
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
|
||||||
Type = NotificationType.PluginInstalled.ToString(),
|
Type = NotificationType.PluginInstalled.ToString(),
|
||||||
@@ -422,11 +421,11 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||||
{
|
{
|
||||||
var installationInfo = e.InstallationInfo;
|
var installationInfo = e.InstallationInfo;
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
|
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
|
||||||
Type = NotificationType.InstallationFailed.ToString(),
|
Type = NotificationType.InstallationFailed.ToString(),
|
||||||
@@ -435,7 +434,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||||
{
|
{
|
||||||
var result = e.Result;
|
var result = e.Result;
|
||||||
var task = e.Task;
|
var task = e.Task;
|
||||||
@@ -462,7 +461,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
vals.Add(e.Result.LongErrorMessage);
|
vals.Add(e.Result.LongErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
||||||
Type = NotificationType.TaskFailed.ToString(),
|
Type = NotificationType.TaskFailed.ToString(),
|
||||||
@@ -473,11 +472,11 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateLogEntry(ActivityLogEntry entry)
|
private void CreateLogEntry(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _activityManager.CreateAsync(entry);
|
_activityManager.Create(entry);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
@@ -27,38 +26,20 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateAsync(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
entry.Date = DateTime.UtcNow;
|
entry.Date = DateTime.UtcNow;
|
||||||
|
|
||||||
await _repo.CreateAsync(entry);
|
_repo.Create(entry);
|
||||||
|
|
||||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var result = _repo.GetActivityLogEntries();
|
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
||||||
|
|
||||||
if (minDate.HasValue)
|
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
|
||||||
{
|
|
||||||
result = result.Where(x => x.Date >= minDate.Value);
|
|
||||||
}
|
|
||||||
if (hasUserId.HasValue)
|
|
||||||
{
|
|
||||||
result = result.Where(x => x.UserId != null && x.UserId != Guid.Empty);
|
|
||||||
}
|
|
||||||
if (startIndex.HasValue)
|
|
||||||
{
|
|
||||||
result = result.Where(x => x.Id >= startIndex.Value);
|
|
||||||
}
|
|
||||||
if (limit.HasValue)
|
|
||||||
{
|
|
||||||
result = result.Take(limit.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add images for each user
|
|
||||||
foreach (var item in result)
|
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(item.UserId);
|
var user = _userManager.GetUserById(item.UserId);
|
||||||
|
|
||||||
@@ -69,7 +50,12 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.AsEnumerable();
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
||||||
|
{
|
||||||
|
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,310 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using Emby.Server.Implementations.Data;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
public class ActivityRepository : DbContext, IActivityRepository
|
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
||||||
{
|
{
|
||||||
protected string _dataDirPath;
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
protected IFileSystem FileSystem { get; private set; }
|
||||||
|
|
||||||
public DbSet<ActivityLogEntry> ActivityLogs { get; set; }
|
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
|
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
|
||||||
public ActivityRepository(string dataDirPath)
|
|
||||||
{
|
{
|
||||||
_dataDirPath = dataDirPath;
|
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
||||||
|
FileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
public void Initialize()
|
||||||
{
|
{
|
||||||
// Ensure the dir exists
|
try
|
||||||
Directory.CreateDirectory(_dataDirPath);
|
{
|
||||||
|
InitializeInternal();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
||||||
|
|
||||||
optionsBuilder.UseSqlite($"Filename={Path.Combine(_dataDirPath, "activitylog.sqlite.db")}");
|
FileSystem.DeleteFile(DbFilePath);
|
||||||
|
|
||||||
|
InitializeInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateAsync(ActivityLogEntry entry)
|
private void InitializeInternal()
|
||||||
{
|
{
|
||||||
await ActivityLogs.AddAsync(entry);
|
using (var connection = CreateConnection())
|
||||||
await SaveChangesAsync();
|
{
|
||||||
|
RunDefaultInitialization(connection);
|
||||||
|
|
||||||
|
connection.RunQueries(new[]
|
||||||
|
{
|
||||||
|
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
|
||||||
|
"drop index if exists idx_ActivityLogEntries"
|
||||||
|
});
|
||||||
|
|
||||||
|
TryMigrate(connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQueryable<ActivityLogEntry> GetActivityLogEntries()
|
private void TryMigrate(ManagedConnection connection)
|
||||||
=> ActivityLogs;
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TableExists(connection, "ActivityLogEntries"))
|
||||||
|
{
|
||||||
|
connection.RunQueries(new[]
|
||||||
|
{
|
||||||
|
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
|
||||||
|
"drop table if exists ActivityLogEntries"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error migrating activity log database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
||||||
|
|
||||||
|
public void Create(ActivityLogEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (WriteLock.Write())
|
||||||
|
using (var connection = CreateConnection())
|
||||||
|
{
|
||||||
|
connection.RunInTransaction(db =>
|
||||||
|
{
|
||||||
|
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
||||||
|
{
|
||||||
|
statement.TryBind("@Name", entry.Name);
|
||||||
|
|
||||||
|
statement.TryBind("@Overview", entry.Overview);
|
||||||
|
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||||
|
statement.TryBind("@Type", entry.Type);
|
||||||
|
statement.TryBind("@ItemId", entry.ItemId);
|
||||||
|
|
||||||
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
|
{
|
||||||
|
statement.TryBindNull("@UserId");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
|
|
||||||
|
statement.MoveNext();
|
||||||
|
}
|
||||||
|
}, TransactionMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ActivityLogEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (WriteLock.Write())
|
||||||
|
using (var connection = CreateConnection())
|
||||||
|
{
|
||||||
|
connection.RunInTransaction(db =>
|
||||||
|
{
|
||||||
|
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
||||||
|
{
|
||||||
|
statement.TryBind("@Id", entry.Id);
|
||||||
|
|
||||||
|
statement.TryBind("@Name", entry.Name);
|
||||||
|
statement.TryBind("@Overview", entry.Overview);
|
||||||
|
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||||
|
statement.TryBind("@Type", entry.Type);
|
||||||
|
statement.TryBind("@ItemId", entry.ItemId);
|
||||||
|
|
||||||
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
|
{
|
||||||
|
statement.TryBindNull("@UserId");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
|
|
||||||
|
statement.MoveNext();
|
||||||
|
}
|
||||||
|
}, TransactionMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||||
|
{
|
||||||
|
using (WriteLock.Read())
|
||||||
|
using (var connection = CreateConnection(true))
|
||||||
|
{
|
||||||
|
var commandText = BaseActivitySelectText;
|
||||||
|
var whereClauses = new List<string>();
|
||||||
|
|
||||||
|
if (minDate.HasValue)
|
||||||
|
{
|
||||||
|
whereClauses.Add("DateCreated>=@DateCreated");
|
||||||
|
}
|
||||||
|
if (hasUserId.HasValue)
|
||||||
|
{
|
||||||
|
if (hasUserId.Value)
|
||||||
|
{
|
||||||
|
whereClauses.Add("UserId not null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
whereClauses.Add("UserId is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||||
|
string.Empty :
|
||||||
|
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||||
|
|
||||||
|
if (startIndex.HasValue && startIndex.Value > 0)
|
||||||
|
{
|
||||||
|
var pagingWhereText = whereClauses.Count == 0 ?
|
||||||
|
string.Empty :
|
||||||
|
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||||
|
|
||||||
|
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
|
||||||
|
pagingWhereText,
|
||||||
|
startIndex.Value.ToString(_usCulture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var whereText = whereClauses.Count == 0 ?
|
||||||
|
string.Empty :
|
||||||
|
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||||
|
|
||||||
|
commandText += whereText;
|
||||||
|
|
||||||
|
commandText += " ORDER BY DateCreated DESC";
|
||||||
|
|
||||||
|
if (limit.HasValue)
|
||||||
|
{
|
||||||
|
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var statementTexts = new List<string>();
|
||||||
|
statementTexts.Add(commandText);
|
||||||
|
statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging);
|
||||||
|
|
||||||
|
return connection.RunInTransaction(db =>
|
||||||
|
{
|
||||||
|
var list = new List<ActivityLogEntry>();
|
||||||
|
var result = new QueryResult<ActivityLogEntry>();
|
||||||
|
|
||||||
|
var statements = PrepareAllSafe(db, statementTexts).ToList();
|
||||||
|
|
||||||
|
using (var statement = statements[0])
|
||||||
|
{
|
||||||
|
if (minDate.HasValue)
|
||||||
|
{
|
||||||
|
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var row in statement.ExecuteQuery())
|
||||||
|
{
|
||||||
|
list.Add(GetEntry(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var statement = statements[1])
|
||||||
|
{
|
||||||
|
if (minDate.HasValue)
|
||||||
|
{
|
||||||
|
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Items = list.ToArray();
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}, ReadTransactionMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
var info = new ActivityLogEntry
|
||||||
|
{
|
||||||
|
Id = reader[index].ToInt64()
|
||||||
|
};
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.Name = reader[index].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.Overview = reader[index].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.ShortOverview = reader[index].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.Type = reader[index].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.ItemId = reader[index].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.UserId = new Guid(reader[index].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
info.Date = reader[index].ReadDateTime();
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
|
{
|
||||||
|
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitAsync()
|
public void Init()
|
||||||
{
|
{
|
||||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||||
@@ -739,7 +739,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
SetHttpLimit();
|
SetHttpLimit();
|
||||||
|
|
||||||
await RegisterResourcesAsync();
|
RegisterResources();
|
||||||
|
|
||||||
FindParts();
|
FindParts();
|
||||||
}
|
}
|
||||||
@@ -754,7 +754,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers resources that classes will depend on
|
/// Registers resources that classes will depend on
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected async Task RegisterResourcesAsync()
|
protected void RegisterResources()
|
||||||
{
|
{
|
||||||
RegisterSingleInstance(ConfigurationManager);
|
RegisterSingleInstance(ConfigurationManager);
|
||||||
RegisterSingleInstance<IApplicationHost>(this);
|
RegisterSingleInstance<IApplicationHost>(this);
|
||||||
@@ -931,7 +931,7 @@ namespace Emby.Server.Implementations
|
|||||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||||
RegisterSingleInstance(EncodingManager);
|
RegisterSingleInstance(EncodingManager);
|
||||||
|
|
||||||
var activityLogRepo = await GetActivityLogRepositoryAsync();
|
var activityLogRepo = GetActivityLogRepository();
|
||||||
RegisterSingleInstance(activityLogRepo);
|
RegisterSingleInstance(activityLogRepo);
|
||||||
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||||
|
|
||||||
@@ -1146,11 +1146,11 @@ namespace Emby.Server.Implementations
|
|||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActivityRepository> GetActivityLogRepositoryAsync()
|
private IActivityRepository GetActivityLogRepository()
|
||||||
{
|
{
|
||||||
var repo = new ActivityRepository(ServerConfigurationManager.ApplicationPaths.DataPath);
|
var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
|
||||||
|
|
||||||
await repo.Database.EnsureCreatedAsync();
|
repo.Initialize();
|
||||||
|
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -681,17 +681,22 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
// Find the corresponding channel provider plugin
|
// Find the corresponding channel provider plugin
|
||||||
var channelProvider = GetChannelProvider(channel);
|
var channelProvider = GetChannelProvider(channel);
|
||||||
|
|
||||||
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
var user = query.User;
|
||||||
|
|
||||||
|
ChannelItemSortField? sortField = null;
|
||||||
|
var sortDescending = false;
|
||||||
|
|
||||||
|
var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel;
|
||||||
|
|
||||||
var itemsResult = await GetChannelItems(channelProvider,
|
var itemsResult = await GetChannelItems(channelProvider,
|
||||||
query.User,
|
user,
|
||||||
parentItem is Channel ? null : parentItem.ExternalId,
|
parentItem is Channel ? null : parentItem.ExternalId,
|
||||||
null,
|
sortField,
|
||||||
false,
|
sortDescending,
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (query.ParentId == Guid.Empty)
|
if (query.ParentId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
query.Parent = channel;
|
query.Parent = channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4712,21 +4712,9 @@ namespace Emby.Server.Implementations.Data
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
|
|
||||||
// buut this is not implemented
|
|
||||||
//hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
|
|
||||||
|
|
||||||
// TODO this is a really BAD way to do it since the pair:
|
|
||||||
// Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567
|
|
||||||
// and maybe even NotTmdb=1234.
|
|
||||||
|
|
||||||
// this is a placeholder for this specific pair to correlate it in the bigger query
|
|
||||||
var paramName = "@HasAnyProviderId" + index;
|
var paramName = "@HasAnyProviderId" + index;
|
||||||
|
//hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
|
||||||
// this is a search for the placeholder
|
|
||||||
hasProviderIds.Add("ProviderIds like " + paramName + "");
|
hasProviderIds.Add("ProviderIds like " + paramName + "");
|
||||||
|
|
||||||
// this replaces the placeholder with a value, here: %key=val%
|
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
||||||
|
|||||||
@@ -105,22 +105,26 @@ namespace Emby.Server.Implementations.Diagnostics
|
|||||||
{
|
{
|
||||||
return _process.WaitForExit(timeMs);
|
return _process.WaitForExit(timeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> WaitForExitAsync(int timeMs)
|
public Task<bool> WaitForExitAsync(int timeMs)
|
||||||
{
|
{
|
||||||
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
//if (_process.WaitForExit(100))
|
||||||
|
//{
|
||||||
if (HasExited)
|
// return Task.FromResult(true);
|
||||||
{
|
//}
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//timeMs -= 100;
|
||||||
timeMs = Math.Max(0, timeMs);
|
timeMs = Math.Max(0, timeMs);
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
||||||
|
|
||||||
|
if (HasExited)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||||
|
|
||||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" />
|
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
||||||
<PackageReference Include="SimpleInjector" Version="4.4.2" />
|
<PackageReference Include="SimpleInjector" Version="4.4.2" />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
private long RangeStart { get; set; }
|
private long RangeStart { get; set; }
|
||||||
private long RangeEnd { get; set; }
|
private long RangeEnd { get; set; }
|
||||||
private long RangeLength { get; set; }
|
private long RangeLength { get; set; }
|
||||||
public long TotalContentLength { get; set; }
|
private long TotalContentLength { get; set; }
|
||||||
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
public Action OnError { get; set; }
|
public Action OnError { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -424,11 +423,13 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
||||||
{
|
{
|
||||||
|
responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
|
||||||
|
|
||||||
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
|
||||||
if (!noCache)
|
if (!noCache)
|
||||||
{
|
{
|
||||||
if (IsNotModified(requestContext, cacheKey))
|
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
|
||||||
{
|
{
|
||||||
AddAgeHeader(responseHeaders, lastDateModified);
|
AddAgeHeader(responseHeaders, lastDateModified);
|
||||||
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
|
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
|
||||||
@@ -441,7 +442,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
|
AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -531,11 +532,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
|
public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
|
||||||
{
|
{
|
||||||
|
var cacheKey = options.CacheKey;
|
||||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var contentType = options.ContentType;
|
var contentType = options.ContentType;
|
||||||
var etag = requestContext.Headers.Get("If-None-Match");
|
|
||||||
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
|
|
||||||
if (!cacheKey.Equals(Guid.Empty))
|
if (!cacheKey.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
var key = cacheKey.ToString("N");
|
var key = cacheKey.ToString("N");
|
||||||
@@ -554,6 +554,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
var factoryFn = options.ContentFactory;
|
var factoryFn = options.ContentFactory;
|
||||||
var responseHeaders = options.ResponseHeaders;
|
var responseHeaders = options.ResponseHeaders;
|
||||||
|
|
||||||
|
//var requestedCompressionType = GetCompressionType(requestContext);
|
||||||
|
|
||||||
var rangeHeader = requestContext.Headers.Get("Range");
|
var rangeHeader = requestContext.Headers.Get("Range");
|
||||||
|
|
||||||
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
|
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
|
||||||
@@ -566,21 +568,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||||
// Generate an ETag based on identifying information - TODO read contents from filesystem instead?
|
|
||||||
var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}";
|
|
||||||
var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId));
|
|
||||||
hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N");
|
|
||||||
|
|
||||||
return hasHeaders;
|
return hasHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = await factoryFn().ConfigureAwait(false);
|
var stream = await factoryFn().ConfigureAwait(false);
|
||||||
// Generate an etag based on stream content
|
|
||||||
var streamHash = MD5.Create().ComputeHash(stream);
|
|
||||||
var newEtag = new Guid(streamHash).ToString("N");
|
|
||||||
|
|
||||||
// reset position so the response can re-use it -- TODO is this ok?
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
var totalContentLength = options.ContentLength;
|
var totalContentLength = options.ContentLength;
|
||||||
if (!totalContentLength.HasValue)
|
if (!totalContentLength.HasValue)
|
||||||
@@ -603,7 +594,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||||
hasHeaders.Headers["ETag"] = newEtag;
|
|
||||||
return hasHeaders;
|
return hasHeaders;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -628,7 +618,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||||
hasHeaders.Headers["ETag"] = newEtag;
|
|
||||||
return hasHeaders;
|
return hasHeaders;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -641,8 +630,16 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the caching responseHeaders.
|
/// Adds the caching responseHeaders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
|
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
|
||||||
{
|
{
|
||||||
|
// Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
|
||||||
|
// https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
|
||||||
|
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
|
||||||
|
{
|
||||||
|
AddAgeHeader(responseHeaders, lastDateModified);
|
||||||
|
responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r");
|
||||||
|
}
|
||||||
|
|
||||||
if (cacheDuration.HasValue)
|
if (cacheDuration.HasValue)
|
||||||
{
|
{
|
||||||
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
|
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
|
||||||
@@ -695,15 +692,28 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <param name="lastDateModified">The last date modified.</param>
|
/// <param name="lastDateModified">The last date modified.</param>
|
||||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||||
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
||||||
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
|
private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
|
||||||
{
|
{
|
||||||
|
//var isNotModified = true;
|
||||||
|
|
||||||
|
var ifModifiedSinceHeader = requestContext.Headers.Get("If-Modified-Since");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(ifModifiedSinceHeader)
|
||||||
|
&& DateTime.TryParse(ifModifiedSinceHeader, out DateTime ifModifiedSince)
|
||||||
|
&& IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
|
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
|
||||||
|
|
||||||
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
|
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
|
||||||
|
|
||||||
// Validate If-None-Match
|
// Validate If-None-Match
|
||||||
if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))
|
if ((hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader)))
|
||||||
{
|
{
|
||||||
|
ifNoneMatchHeader = (ifNoneMatchHeader ?? string.Empty).Trim('\"');
|
||||||
|
|
||||||
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
|
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
|
||||||
&& cacheKey.Equals(ifNoneMatch))
|
&& cacheKey.Equals(ifNoneMatch))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Don't hardcode this
|
// TODO: Don't hardcode this
|
||||||
const bool isAudio = false;
|
var isAudio = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -480,11 +480,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// hack - these two values were taken from LiveTVMediaSourceProvider
|
// hack - these two values were taken from LiveTVMediaSourceProvider
|
||||||
string cacheKey = request.OpenToken;
|
var cacheKey = request.OpenToken;
|
||||||
|
|
||||||
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
|
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false);
|
||||||
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -493,7 +491,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
AddMediaInfo(mediaSource, isAudio);
|
AddMediaInfo(mediaSource, isAudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: @bond Fix
|
|
||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
var json = _jsonSerializer.SerializeToString(mediaSource);
|
||||||
_logger.LogInformation("Live stream opened: " + json);
|
_logger.LogInformation("Live stream opened: " + json);
|
||||||
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
||||||
|
|||||||
@@ -86,7 +86,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
|
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
if (isBooksCollectionType)
|
||||||
|
{
|
||||||
|
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
|
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
|
||||||
@@ -140,19 +145,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
||||||
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
|
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
|
||||||
{
|
{
|
||||||
|
var multiDiscFolders = new List<FileSystemMetadata>();
|
||||||
|
|
||||||
|
var libraryOptions = args.GetLibraryOptions();
|
||||||
|
var filesFromOtherItems = new List<FileSystemMetadata>();
|
||||||
|
|
||||||
// TODO: Allow GetMultiDiscMovie in here
|
// TODO: Allow GetMultiDiscMovie in here
|
||||||
const bool supportsMultiVersion = false;
|
var supportsMultiVersion = false;
|
||||||
|
|
||||||
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
||||||
new MultiItemResolverResult();
|
new MultiItemResolverResult();
|
||||||
|
|
||||||
if (result.Items.Count == 1)
|
if (result.Items.Count == 1)
|
||||||
{
|
{
|
||||||
|
var videoPath = result.Items[0].Path;
|
||||||
|
|
||||||
// If we were supporting this we'd be checking filesFromOtherItems
|
// If we were supporting this we'd be checking filesFromOtherItems
|
||||||
var item = (T)result.Items[0];
|
var hasOtherItems = false;
|
||||||
item.IsInMixedFolder = false;
|
|
||||||
item.Name = Path.GetFileName(item.ContainingFolderPath);
|
if (!hasOtherItems)
|
||||||
return item;
|
{
|
||||||
|
var item = (T)result.Items[0];
|
||||||
|
item.IsInMixedFolder = false;
|
||||||
|
item.Name = Path.GetFileName(item.ContainingFolderPath);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
|
||||||
|
{
|
||||||
|
//return GetMultiDiscAudio<T>(multiDiscFolders, directoryService);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -172,7 +194,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
{
|
{
|
||||||
leftOver.Add(child);
|
leftOver.Add(child);
|
||||||
}
|
}
|
||||||
else if (!IsIgnored(child.Name))
|
else if (IsIgnored(child.Name))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
files.Add(child);
|
files.Add(child);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Allow GetMultiDiscMovie in here
|
// TODO: Allow GetMultiDiscMovie in here
|
||||||
const bool supportsMultiVersion = true;
|
var supportsMultiVersion = true;
|
||||||
|
|
||||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
||||||
new MultiItemResolverResult();
|
new MultiItemResolverResult();
|
||||||
|
|||||||
@@ -153,8 +153,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
|
||||||
|
|
||||||
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
|
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
|
||||||
|
bool? isNamed = null;
|
||||||
|
bool? isOptimistic = null;
|
||||||
|
|
||||||
var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false);
|
if (!isTvContentType)
|
||||||
|
{
|
||||||
|
isNamed = true;
|
||||||
|
isOptimistic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var episodeInfo = episodeResolver.Resolve(fullName, false, isNamed, isOptimistic, fillExtendedInfo: false);
|
||||||
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
|
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -322,14 +322,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
|
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
if (user != null)
|
||||||
{
|
{
|
||||||
throw new SecurityException("Forbidden.");
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||||
}
|
{
|
||||||
|
throw new SecurityException("Forbidden.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.IsParentalScheduleAllowed())
|
if (!user.IsParentalScheduleAllowed())
|
||||||
{
|
{
|
||||||
throw new SecurityException("User is not allowed access at this time.");
|
throw new SecurityException("User is not allowed access at this time.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update LastActivityDate and LastLoginDate, then save
|
// Update LastActivityDate and LastLoginDate, then save
|
||||||
@@ -460,26 +463,26 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
user.Policy.InvalidLoginAttemptCount = newValue;
|
user.Policy.InvalidLoginAttemptCount = newValue;
|
||||||
|
|
||||||
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
|
var maxCount = user.Policy.IsAdministrator ?
|
||||||
|
3 :
|
||||||
|
5;
|
||||||
|
|
||||||
// TODO: Fix
|
|
||||||
/*
|
|
||||||
var fireLockout = false;
|
var fireLockout = false;
|
||||||
|
|
||||||
if (newValue >= maxCount)
|
if (newValue >= maxCount)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
|
//_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
|
||||||
user.Policy.IsDisabled = true;
|
//user.Policy.IsDisabled = true;
|
||||||
|
|
||||||
fireLockout = true;
|
//fireLockout = true;
|
||||||
}*/
|
}
|
||||||
|
|
||||||
UpdateUserPolicy(user, user.Policy, false);
|
UpdateUserPolicy(user, user.Policy, false);
|
||||||
|
|
||||||
/* if (fireLockout)
|
if (fireLockout)
|
||||||
{
|
{
|
||||||
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
|
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1133,8 +1133,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
IgnoreIndex = true
|
IgnoreIndex = true
|
||||||
};
|
};
|
||||||
|
|
||||||
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths)
|
var isAudio = false;
|
||||||
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
|
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new List<MediaSourceInfo>
|
return new List<MediaSourceInfo>
|
||||||
{
|
{
|
||||||
@@ -1149,12 +1149,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
|
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ResetTuner(string id, CancellationToken cancellationToken)
|
public Task ResetTuner(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
|
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
|
||||||
|
|||||||
@@ -175,6 +175,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
|
|
||||||
var videoStream = mediaSource.VideoStream;
|
var videoStream = mediaSource.VideoStream;
|
||||||
|
string videoDecoder = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(videoDecoder))
|
||||||
|
{
|
||||||
|
inputModifier += " " + videoDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaSource.ReadAtNativeFramerate)
|
if (mediaSource.ReadAtNativeFramerate)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -354,8 +354,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
int? height = null;
|
int? height = null;
|
||||||
bool isInterlaced = true;
|
bool isInterlaced = true;
|
||||||
string videoCodec = null;
|
string videoCodec = null;
|
||||||
|
string audioCodec = null;
|
||||||
|
|
||||||
int? videoBitrate = null;
|
int? videoBitrate = null;
|
||||||
|
int? audioBitrate = null;
|
||||||
|
|
||||||
var isHd = channelInfo.IsHD ?? true;
|
var isHd = channelInfo.IsHD ?? true;
|
||||||
|
|
||||||
@@ -425,17 +427,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(videoCodec))
|
if (channelInfo != null)
|
||||||
{
|
{
|
||||||
videoCodec = channelInfo.VideoCodec;
|
if (string.IsNullOrWhiteSpace(videoCodec))
|
||||||
}
|
{
|
||||||
string audioCodec = channelInfo.AudioCodec;
|
videoCodec = channelInfo.VideoCodec;
|
||||||
|
}
|
||||||
|
audioCodec = channelInfo.AudioCodec;
|
||||||
|
|
||||||
if (!videoBitrate.HasValue)
|
if (!videoBitrate.HasValue)
|
||||||
{
|
{
|
||||||
videoBitrate = isHd ? 15000000 : 2000000;
|
videoBitrate = isHd ? 15000000 : 2000000;
|
||||||
|
}
|
||||||
|
audioBitrate = isHd ? 448000 : 192000;
|
||||||
}
|
}
|
||||||
int? audioBitrate = isHd ? 448000 : 192000;
|
|
||||||
|
|
||||||
// normalize
|
// normalize
|
||||||
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|||||||
@@ -27,10 +27,6 @@
|
|||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
|
||||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Jellyfin.Server
|
|||||||
new SystemEvents(),
|
new SystemEvents(),
|
||||||
new NetworkManager(_loggerFactory, environmentInfo)))
|
new NetworkManager(_loggerFactory, environmentInfo)))
|
||||||
{
|
{
|
||||||
await appHost.InitAsync();
|
appHost.Init();
|
||||||
|
|
||||||
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
|
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
|
||||||
|
|
||||||
@@ -108,6 +108,7 @@ namespace Jellyfin.Server
|
|||||||
await appHost.RunStartupTasks();
|
await appHost.RunStartupTasks();
|
||||||
|
|
||||||
// TODO: read input for a stop command
|
// TODO: read input for a stop command
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Block main thread until shutdown
|
// Block main thread until shutdown
|
||||||
@@ -166,6 +167,7 @@ namespace Jellyfin.Server
|
|||||||
{
|
{
|
||||||
Directory.CreateDirectory(programDataPath);
|
Directory.CreateDirectory(programDataPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
||||||
if (string.IsNullOrEmpty(configDir))
|
if (string.IsNullOrEmpty(configDir))
|
||||||
{
|
{
|
||||||
@@ -222,7 +224,7 @@ namespace Jellyfin.Server
|
|||||||
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
|
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
|
||||||
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
|
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
|
||||||
{
|
{
|
||||||
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
|
await rscstr.CopyToAsync(fstr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var configuration = new ConfigurationBuilder()
|
var configuration = new ConfigurationBuilder()
|
||||||
@@ -332,9 +334,11 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
commandLineArgsString = string.Join(
|
commandLineArgsString = string.Join(" ",
|
||||||
" ",
|
Environment.GetCommandLineArgs()
|
||||||
Environment.GetCommandLineArgs().Skip(1).Select(NormalizeCommandLineArgument));
|
.Skip(1)
|
||||||
|
.Select(NormalizeCommandLineArgument)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Executable: {0}", module);
|
_logger.LogInformation("Executable: {0}", module);
|
||||||
|
|||||||
@@ -15,27 +15,19 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
int ap = header.IndexOf(attr);
|
int ap = header.IndexOf(attr);
|
||||||
if (ap == -1)
|
if (ap == -1)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
ap += attr.Length;
|
ap += attr.Length;
|
||||||
if (ap >= header.Length)
|
if (ap >= header.Length)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
char ending = header[ap];
|
char ending = header[ap];
|
||||||
if (ending != '"')
|
if (ending != '"')
|
||||||
{
|
|
||||||
ending = ' ';
|
ending = ' ';
|
||||||
}
|
|
||||||
|
|
||||||
int end = header.IndexOf(ending, ap + 1);
|
int end = header.IndexOf(ending, ap + 1);
|
||||||
if (end == -1)
|
if (end == -1)
|
||||||
{
|
|
||||||
return ending == '"' ? null : header.Substring(ap);
|
return ending == '"' ? null : header.Substring(ap);
|
||||||
}
|
|
||||||
|
|
||||||
return header.Substring(ap + 1, end - ap - 1);
|
return header.Substring(ap + 1, end - ap - 1);
|
||||||
}
|
}
|
||||||
@@ -44,9 +36,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
string boundary = GetParameter(ContentType, "; boundary=");
|
string boundary = GetParameter(ContentType, "; boundary=");
|
||||||
if (boundary == null)
|
if (boundary == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using (var requestStream = InputStream)
|
using (var requestStream = InputStream)
|
||||||
{
|
{
|
||||||
@@ -134,9 +124,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
string v = "\"" + value + "\"";
|
string v = "\"" + value + "\"";
|
||||||
if (v.Length > 20)
|
if (v.Length > 20)
|
||||||
{
|
|
||||||
v = v.Substring(0, 16) + "...\"";
|
v = v.Substring(0, 16) + "...\"";
|
||||||
}
|
|
||||||
|
|
||||||
string msg = string.Format("A potentially dangerous Request.{0} value was " +
|
string msg = string.Format("A potentially dangerous Request.{0} value was " +
|
||||||
"detected from the client ({1}={2}).", name, key, v);
|
"detected from the client ({1}={2}).", name, key, v);
|
||||||
@@ -147,23 +135,21 @@ namespace Jellyfin.SocketSharp
|
|||||||
static void ValidateNameValueCollection(string name, QueryParamCollection coll)
|
static void ValidateNameValueCollection(string name, QueryParamCollection coll)
|
||||||
{
|
{
|
||||||
if (coll == null)
|
if (coll == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pair in coll)
|
foreach (var pair in coll)
|
||||||
{
|
{
|
||||||
var key = pair.Name;
|
var key = pair.Name;
|
||||||
var val = pair.Value;
|
var val = pair.Value;
|
||||||
if (val != null && val.Length > 0 && IsInvalidString(val))
|
if (val != null && val.Length > 0 && IsInvalidString(val))
|
||||||
{
|
|
||||||
ThrowValidationException(name, key, val);
|
ThrowValidationException(name, key, val);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsInvalidString(string val)
|
internal static bool IsInvalidString(string val)
|
||||||
=> IsInvalidString(val, out var validationFailureIndex);
|
{
|
||||||
|
return IsInvalidString(val, out var validationFailureIndex);
|
||||||
|
}
|
||||||
|
|
||||||
internal static bool IsInvalidString(string val, out int validationFailureIndex)
|
internal static bool IsInvalidString(string val, out int validationFailureIndex)
|
||||||
{
|
{
|
||||||
@@ -171,9 +157,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
|
|
||||||
int len = val.Length;
|
int len = val.Length;
|
||||||
if (len < 2)
|
if (len < 2)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
char current = val[0];
|
char current = val[0];
|
||||||
for (int idx = 1; idx < len; idx++)
|
for (int idx = 1; idx < len; idx++)
|
||||||
@@ -211,15 +195,10 @@ namespace Jellyfin.SocketSharp
|
|||||||
|
|
||||||
bool IsContentType(string ct, bool starts_with)
|
bool IsContentType(string ct, bool starts_with)
|
||||||
{
|
{
|
||||||
if (ct == null || ContentType == null)
|
if (ct == null || ContentType == null) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (starts_with)
|
if (starts_with)
|
||||||
{
|
|
||||||
return StrUtils.StartsWith(ContentType, ct, true);
|
return StrUtils.StartsWith(ContentType, ct, true);
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
@@ -252,9 +231,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
value.Append((char)c);
|
value.Append((char)c);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
{
|
{
|
||||||
@@ -263,26 +240,22 @@ namespace Jellyfin.SocketSharp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (c == '&')
|
else if (c == '&')
|
||||||
{
|
|
||||||
AddRawKeyValue(form, key, value);
|
AddRawKeyValue(form, key, value);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
key.Append((char)c);
|
key.Append((char)c);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
{
|
|
||||||
AddRawKeyValue(form, key, value);
|
AddRawKeyValue(form, key, value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
|
void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
|
||||||
{
|
{
|
||||||
form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
|
string decodedKey = WebUtility.UrlDecode(key.ToString());
|
||||||
|
form.Add(decodedKey,
|
||||||
|
WebUtility.UrlDecode(value.ToString()));
|
||||||
|
|
||||||
key.Length = 0;
|
key.Length = 0;
|
||||||
value.Length = 0;
|
value.Length = 0;
|
||||||
@@ -298,9 +271,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
foreach (var pair in this)
|
foreach (var pair in this)
|
||||||
{
|
{
|
||||||
if (result.Length > 0)
|
if (result.Length > 0)
|
||||||
{
|
|
||||||
result.Append('&');
|
result.Append('&');
|
||||||
}
|
|
||||||
|
|
||||||
var key = pair.Name;
|
var key = pair.Name;
|
||||||
if (key != null && key.Length > 0)
|
if (key != null && key.Length > 0)
|
||||||
@@ -343,52 +314,33 @@ namespace Jellyfin.SocketSharp
|
|||||||
public override int Read(byte[] buffer, int dest_offset, int count)
|
public override int Read(byte[] buffer, int dest_offset, int count)
|
||||||
{
|
{
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(buffer));
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
}
|
|
||||||
|
|
||||||
if (dest_offset < 0)
|
if (dest_offset < 0)
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
|
throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 0)
|
if (count < 0)
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
|
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
|
||||||
}
|
|
||||||
|
|
||||||
int len = buffer.Length;
|
int len = buffer.Length;
|
||||||
if (dest_offset > len)
|
if (dest_offset > len)
|
||||||
{
|
|
||||||
throw new ArgumentException("destination offset is beyond array size");
|
throw new ArgumentException("destination offset is beyond array size");
|
||||||
}
|
|
||||||
|
|
||||||
// reordered to avoid possible integer overflow
|
// reordered to avoid possible integer overflow
|
||||||
if (dest_offset > len - count)
|
if (dest_offset > len - count)
|
||||||
{
|
|
||||||
throw new ArgumentException("Reading would overrun buffer");
|
throw new ArgumentException("Reading would overrun buffer");
|
||||||
}
|
|
||||||
|
|
||||||
if (count > end - position)
|
if (count > end - position)
|
||||||
{
|
|
||||||
count = (int)(end - position);
|
count = (int)(end - position);
|
||||||
}
|
|
||||||
|
|
||||||
if (count <= 0)
|
if (count <= 0)
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
s.Position = position;
|
s.Position = position;
|
||||||
int result = s.Read(buffer, dest_offset, count);
|
int result = s.Read(buffer, dest_offset, count);
|
||||||
if (result > 0)
|
if (result > 0)
|
||||||
{
|
|
||||||
position += result;
|
position += result;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
position = end;
|
position = end;
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -396,20 +348,14 @@ namespace Jellyfin.SocketSharp
|
|||||||
public override int ReadByte()
|
public override int ReadByte()
|
||||||
{
|
{
|
||||||
if (position >= end)
|
if (position >= end)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
s.Position = position;
|
s.Position = position;
|
||||||
int result = s.ReadByte();
|
int result = s.ReadByte();
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
|
||||||
position = end;
|
position = end;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
position++;
|
position++;
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -434,9 +380,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
|
|
||||||
long virt = real - offset;
|
long virt = real - offset;
|
||||||
if (virt < 0 || virt > Length)
|
if (virt < 0 || virt > Length)
|
||||||
{
|
|
||||||
throw new ArgumentException();
|
throw new ArgumentException();
|
||||||
}
|
|
||||||
|
|
||||||
position = s.Seek(real, SeekOrigin.Begin);
|
position = s.Seek(real, SeekOrigin.Begin);
|
||||||
return position;
|
return position;
|
||||||
@@ -466,9 +410,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value > Length)
|
if (value > Length)
|
||||||
{
|
throw new ArgumentOutOfRangeException();
|
||||||
throw new ArgumentOutOfRangeException(nameof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
position = Seek(value, SeekOrigin.Begin);
|
position = Seek(value, SeekOrigin.Begin);
|
||||||
}
|
}
|
||||||
@@ -496,7 +438,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
|
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class StrUtils
|
internal sealed class StrUtils
|
||||||
{
|
{
|
||||||
public static bool StartsWith(string str1, string str2, bool ignore_case)
|
public static bool StartsWith(string str1, string str2, bool ignore_case)
|
||||||
{
|
{
|
||||||
@@ -513,15 +455,11 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
int l2 = str2.Length;
|
int l2 = str2.Length;
|
||||||
if (l2 == 0)
|
if (l2 == 0)
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
int l1 = str1.Length;
|
int l1 = str1.Length;
|
||||||
if (l2 > l1)
|
if (l2 > l1)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
||||||
return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
|
return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
|
||||||
@@ -555,7 +493,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
Encoding encoding;
|
Encoding encoding;
|
||||||
StringBuilder sb;
|
StringBuilder sb;
|
||||||
|
|
||||||
const byte LF = (byte)'\n', CR = (byte)'\r';
|
const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
|
||||||
|
|
||||||
// See RFC 2046
|
// See RFC 2046
|
||||||
// In the case of multipart entities, in which one or more different
|
// In the case of multipart entities, in which one or more different
|
||||||
@@ -582,7 +520,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
sb = new StringBuilder();
|
sb = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ReadLine()
|
string ReadLine()
|
||||||
{
|
{
|
||||||
// CRLF or LF are ok as line endings.
|
// CRLF or LF are ok as line endings.
|
||||||
bool got_cr = false;
|
bool got_cr = false;
|
||||||
@@ -605,86 +543,58 @@ namespace Jellyfin.SocketSharp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (got_cr)
|
if (got_cr)
|
||||||
{
|
|
||||||
sb.Length--;
|
sb.Length--;
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetContentDispositionAttribute(string l, string name)
|
static string GetContentDispositionAttribute(string l, string name)
|
||||||
{
|
{
|
||||||
int idx = l.IndexOf(name + "=\"");
|
int idx = l.IndexOf(name + "=\"");
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
int begin = idx + name.Length + "=\"".Length;
|
int begin = idx + name.Length + "=\"".Length;
|
||||||
int end = l.IndexOf('"', begin);
|
int end = l.IndexOf('"', begin);
|
||||||
if (end < 0)
|
if (end < 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
if (begin == end)
|
if (begin == end)
|
||||||
{
|
return "";
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.Substring(begin, end - begin);
|
return l.Substring(begin, end - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetContentDispositionAttributeWithEncoding(string l, string name)
|
string GetContentDispositionAttributeWithEncoding(string l, string name)
|
||||||
{
|
{
|
||||||
int idx = l.IndexOf(name + "=\"");
|
int idx = l.IndexOf(name + "=\"");
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
int begin = idx + name.Length + "=\"".Length;
|
int begin = idx + name.Length + "=\"".Length;
|
||||||
int end = l.IndexOf('"', begin);
|
int end = l.IndexOf('"', begin);
|
||||||
if (end < 0)
|
if (end < 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
if (begin == end)
|
if (begin == end)
|
||||||
{
|
return "";
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string temp = l.Substring(begin, end - begin);
|
string temp = l.Substring(begin, end - begin);
|
||||||
byte[] source = new byte[temp.Length];
|
byte[] source = new byte[temp.Length];
|
||||||
for (int i = temp.Length - 1; i >= 0; i--)
|
for (int i = temp.Length - 1; i >= 0; i--)
|
||||||
{
|
|
||||||
source[i] = (byte)temp[i];
|
source[i] = (byte)temp[i];
|
||||||
}
|
|
||||||
|
|
||||||
return encoding.GetString(source, 0, source.Length);
|
return encoding.GetString(source, 0, source.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ReadBoundary()
|
bool ReadBoundary()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string line = ReadLine();
|
string line = ReadLine();
|
||||||
while (line == string.Empty)
|
while (line == "")
|
||||||
{
|
|
||||||
line = ReadLine();
|
line = ReadLine();
|
||||||
}
|
|
||||||
|
|
||||||
if (line[0] != '-' || line[1] != '-')
|
if (line[0] != '-' || line[1] != '-')
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!StrUtils.EndsWith(line, boundary, false))
|
if (!StrUtils.EndsWith(line, boundary, false))
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -693,31 +603,25 @@ namespace Jellyfin.SocketSharp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ReadHeaders()
|
string ReadHeaders()
|
||||||
{
|
{
|
||||||
string s = ReadLine();
|
string s = ReadLine();
|
||||||
if (s.Length == 0)
|
if (s == "")
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CompareBytes(byte[] orig, byte[] other)
|
bool CompareBytes(byte[] orig, byte[] other)
|
||||||
{
|
{
|
||||||
for (int i = orig.Length - 1; i >= 0; i--)
|
for (int i = orig.Length - 1; i >= 0; i--)
|
||||||
{
|
|
||||||
if (orig[i] != other[i])
|
if (orig[i] != other[i])
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long MoveToNextBoundary()
|
long MoveToNextBoundary()
|
||||||
{
|
{
|
||||||
long retval = 0;
|
long retval = 0;
|
||||||
bool got_cr = false;
|
bool got_cr = false;
|
||||||
@@ -727,18 +631,13 @@ namespace Jellyfin.SocketSharp
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (state == 0 && c == LF)
|
if (state == 0 && c == LF)
|
||||||
{
|
{
|
||||||
retval = data.Position - 1;
|
retval = data.Position - 1;
|
||||||
if (got_cr)
|
if (got_cr)
|
||||||
{
|
|
||||||
retval--;
|
retval--;
|
||||||
}
|
|
||||||
|
|
||||||
state = 1;
|
state = 1;
|
||||||
c = data.ReadByte();
|
c = data.ReadByte();
|
||||||
}
|
}
|
||||||
@@ -751,9 +650,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
c = data.ReadByte();
|
c = data.ReadByte();
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (c != '-')
|
if (c != '-')
|
||||||
{
|
{
|
||||||
@@ -765,9 +662,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
int nread = data.Read(buffer, 0, buffer.Length);
|
int nread = data.Read(buffer, 0, buffer.Length);
|
||||||
int bl = buffer.Length;
|
int bl = buffer.Length;
|
||||||
if (nread != bl)
|
if (nread != bl)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (!CompareBytes(boundary_bytes, buffer))
|
if (!CompareBytes(boundary_bytes, buffer))
|
||||||
{
|
{
|
||||||
@@ -778,7 +673,6 @@ namespace Jellyfin.SocketSharp
|
|||||||
data.Position++;
|
data.Position++;
|
||||||
got_cr = false;
|
got_cr = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = data.ReadByte();
|
c = data.ReadByte();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -796,16 +690,12 @@ namespace Jellyfin.SocketSharp
|
|||||||
data.Position++;
|
data.Position++;
|
||||||
got_cr = false;
|
got_cr = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = data.ReadByte();
|
c = data.ReadByte();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
data.Position = retval + 2;
|
data.Position = retval + 2;
|
||||||
if (got_cr)
|
if (got_cr)
|
||||||
{
|
|
||||||
data.Position++;
|
data.Position++;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -821,9 +711,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
public Element ReadNextElement()
|
public Element ReadNextElement()
|
||||||
{
|
{
|
||||||
if (at_eof || ReadBoundary())
|
if (at_eof || ReadBoundary())
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var elem = new Element();
|
var elem = new Element();
|
||||||
string header;
|
string header;
|
||||||
@@ -846,27 +734,19 @@ namespace Jellyfin.SocketSharp
|
|||||||
elem.Start = start;
|
elem.Start = start;
|
||||||
long pos = MoveToNextBoundary();
|
long pos = MoveToNextBoundary();
|
||||||
if (pos == -1)
|
if (pos == -1)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
elem.Length = pos - start;
|
elem.Length = pos - start;
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string StripPath(string path)
|
static string StripPath(string path)
|
||||||
{
|
{
|
||||||
if (path == null || path.Length == 0)
|
if (path == null || path.Length == 0)
|
||||||
{
|
|
||||||
return path;
|
return path;
|
||||||
}
|
|
||||||
|
|
||||||
if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
|
if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
|
||||||
&& !path.StartsWith("\\\\", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return path;
|
return path;
|
||||||
}
|
|
||||||
|
|
||||||
return path.Substring(path.LastIndexOf('\\') + 1);
|
return path.Substring(path.LastIndexOf('\\') + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,15 +83,15 @@ namespace Jellyfin.SocketSharp
|
|||||||
|
|
||||||
private void ProcessContext(HttpListenerContext context)
|
private void ProcessContext(HttpListenerContext context)
|
||||||
{
|
{
|
||||||
var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
|
//InitTask(context, _disposeCancellationToken);
|
||||||
|
Task.Run(() => InitTask(context, _disposeCancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
private void LogRequest(ILogger logger, HttpListenerRequest request)
|
||||||
{
|
{
|
||||||
var url = request.Url.ToString();
|
var url = request.Url.ToString();
|
||||||
|
|
||||||
logger.LogInformation("{0} {1}. UserAgent: {2}",
|
logger.LogInformation("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
|
||||||
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
|
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
|
||||||
@@ -196,7 +196,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ctx.Response.StatusCode = statusCode;
|
ctx.Response.StatusCode = 200;
|
||||||
ctx.Response.Close();
|
ctx.Response.Close();
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
|
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string Xml = "application/xml";
|
||||||
private static string GetQueryStringContentType(IRequest httpReq)
|
private static string GetQueryStringContentType(IRequest httpReq)
|
||||||
{
|
{
|
||||||
var format = httpReq.QueryString["format"];
|
var format = httpReq.QueryString["format"];
|
||||||
@@ -249,40 +250,22 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
const int formatMaxLength = 4;
|
const int formatMaxLength = 4;
|
||||||
var pi = httpReq.PathInfo;
|
var pi = httpReq.PathInfo;
|
||||||
if (pi == null || pi.Length <= formatMaxLength)
|
if (pi == null || pi.Length <= formatMaxLength) return null;
|
||||||
{
|
if (pi[0] == '/') pi = pi.Substring(1);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (pi[0] == '/')
|
|
||||||
{
|
|
||||||
pi = pi.Substring(1);
|
|
||||||
}
|
|
||||||
format = LeftPart(pi, '/');
|
format = LeftPart(pi, '/');
|
||||||
if (format.Length > formatMaxLength)
|
if (format.Length > formatMaxLength) return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
format = LeftPart(format, '.').ToLower();
|
format = LeftPart(format, '.').ToLower();
|
||||||
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
|
if (format.Contains("json")) return "application/json";
|
||||||
{
|
if (format.Contains("xml")) return Xml;
|
||||||
return "application/json";
|
|
||||||
}
|
|
||||||
if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "application/xml";
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string LeftPart(string strVal, char needle)
|
public static string LeftPart(string strVal, char needle)
|
||||||
{
|
{
|
||||||
if (strVal == null)
|
if (strVal == null) return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var pos = strVal.IndexOf(needle);
|
var pos = strVal.IndexOf(needle);
|
||||||
return pos == -1
|
return pos == -1
|
||||||
? strVal
|
? strVal
|
||||||
@@ -300,14 +283,14 @@ namespace Jellyfin.SocketSharp
|
|||||||
{
|
{
|
||||||
var mode = HandlerFactoryPath;
|
var mode = HandlerFactoryPath;
|
||||||
|
|
||||||
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal);
|
var pos = request.RawUrl.IndexOf("?");
|
||||||
if (pos != -1)
|
if (pos != -1)
|
||||||
{
|
{
|
||||||
var path = request.RawUrl.Substring(0, pos);
|
var path = request.RawUrl.Substring(0, pos);
|
||||||
this.pathInfo = GetPathInfo(
|
this.pathInfo = GetPathInfo(
|
||||||
path,
|
path,
|
||||||
mode,
|
mode,
|
||||||
mode ?? string.Empty);
|
mode ?? "");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -324,27 +307,18 @@ namespace Jellyfin.SocketSharp
|
|||||||
private static string GetPathInfo(string fullPath, string mode, string appPath)
|
private static string GetPathInfo(string fullPath, string mode, string appPath)
|
||||||
{
|
{
|
||||||
var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
|
var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
|
||||||
if (!string.IsNullOrEmpty(pathInfo))
|
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
|
||||||
{
|
|
||||||
return pathInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Wildcard mode relies on this to work out the handlerPath
|
//Wildcard mode relies on this to work out the handlerPath
|
||||||
pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
|
pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
|
||||||
if (!string.IsNullOrEmpty(pathInfo))
|
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
|
||||||
{
|
|
||||||
return pathInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
|
private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
|
||||||
{
|
{
|
||||||
if (mappedPathRoot == null)
|
if (mappedPathRoot == null) return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sbPathInfo = new StringBuilder();
|
var sbPathInfo = new StringBuilder();
|
||||||
var fullPathParts = fullPath.Split('/');
|
var fullPathParts = fullPath.Split('/');
|
||||||
@@ -371,10 +345,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!pathRootFound)
|
if (!pathRootFound) return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = sbPathInfo.ToString();
|
var path = sbPathInfo.ToString();
|
||||||
return path.Length > 1 ? path.TrimEnd('/') : "/";
|
return path.Length > 1 ? path.TrimEnd('/') : "/";
|
||||||
@@ -429,10 +400,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
public static Encoding GetEncoding(string contentTypeHeader)
|
public static Encoding GetEncoding(string contentTypeHeader)
|
||||||
{
|
{
|
||||||
var param = GetParameter(contentTypeHeader, "charset=");
|
var param = GetParameter(contentTypeHeader, "charset=");
|
||||||
if (param == null)
|
if (param == null) return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Encoding.GetEncoding(param);
|
return Encoding.GetEncoding(param);
|
||||||
@@ -455,9 +423,7 @@ namespace Jellyfin.SocketSharp
|
|||||||
if (httpFiles == null)
|
if (httpFiles == null)
|
||||||
{
|
{
|
||||||
if (files == null)
|
if (files == null)
|
||||||
{
|
return httpFiles = new IHttpFile[0];
|
||||||
return httpFiles = Array.Empty<IHttpFile>();
|
|
||||||
}
|
|
||||||
|
|
||||||
httpFiles = new IHttpFile[files.Count];
|
httpFiles = new IHttpFile[files.Count];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|||||||
@@ -839,7 +839,7 @@ namespace MediaBrowser.Api.Library
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_activityManager.CreateAsync(new ActivityLogEntry
|
_activityManager.Create(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
|
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
|
||||||
Type = "UserDownloadingContent",
|
Type = "UserDownloadingContent",
|
||||||
|
|||||||
@@ -74,19 +74,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly IAuthorizationContext _authContext;
|
private readonly IAuthorizationContext _authContext;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public MediaInfoService(
|
public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, IJsonSerializer json, IAuthorizationContext authContext)
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IDeviceManager deviceManager,
|
|
||||||
ILibraryManager libraryManager,
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
INetworkManager networkManager,
|
|
||||||
IMediaEncoder mediaEncoder,
|
|
||||||
IUserManager userManager,
|
|
||||||
IJsonSerializer json,
|
|
||||||
IAuthorizationContext authContext,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
{
|
{
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_deviceManager = deviceManager;
|
_deviceManager = deviceManager;
|
||||||
@@ -97,7 +86,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_json = json;
|
_json = json;
|
||||||
_authContext = authContext;
|
_authContext = authContext;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(MediaInfoService));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetBitrateTestBytes request)
|
public object Get(GetBitrateTestBytes request)
|
||||||
@@ -177,7 +165,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
var profile = request.DeviceProfile;
|
var profile = request.DeviceProfile;
|
||||||
|
|
||||||
//Logger.LogInformation("GetPostedPlaybackInfo profile: {profile}", _json.SerializeToString(profile));
|
//Logger.Info("GetPostedPlaybackInfo profile: {0}", _json.SerializeToString(profile));
|
||||||
|
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
{
|
{
|
||||||
@@ -274,7 +262,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
mediaSources = new List<MediaSourceInfo>();
|
mediaSources = new List<MediaSourceInfo>();
|
||||||
_logger.LogError(ex, "Could not find media sources for item id {id}", id);
|
// TODO Log exception
|
||||||
// TODO PlaybackException ??
|
// TODO PlaybackException ??
|
||||||
//result.ErrorCode = ex.ErrorCode;
|
//result.ErrorCode = ex.ErrorCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ using MediaBrowser.Model.MediaInfo;
|
|||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
@@ -76,24 +75,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
[Authenticated]
|
[Authenticated]
|
||||||
public class UniversalAudioService : BaseApiService
|
public class UniversalAudioService : BaseApiService
|
||||||
{
|
{
|
||||||
public UniversalAudioService(
|
public UniversalAudioService(IServerConfigurationManager serverConfigurationManager, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager, IEnvironmentInfo environmentInfo)
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
|
||||||
IUserManager userManager,
|
|
||||||
ILibraryManager libraryManager,
|
|
||||||
IIsoManager isoManager,
|
|
||||||
IMediaEncoder mediaEncoder,
|
|
||||||
IFileSystem fileSystem,
|
|
||||||
IDlnaManager dlnaManager,
|
|
||||||
IDeviceManager deviceManager,
|
|
||||||
ISubtitleEncoder subtitleEncoder,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IZipClient zipClient,
|
|
||||||
IJsonSerializer jsonSerializer,
|
|
||||||
IAuthorizationContext authorizationContext,
|
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
INetworkManager networkManager,
|
|
||||||
IEnvironmentInfo environmentInfo,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
{
|
{
|
||||||
ServerConfigurationManager = serverConfigurationManager;
|
ServerConfigurationManager = serverConfigurationManager;
|
||||||
UserManager = userManager;
|
UserManager = userManager;
|
||||||
@@ -111,8 +93,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
ImageProcessor = imageProcessor;
|
ImageProcessor = imageProcessor;
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
EnvironmentInfo = environmentInfo;
|
EnvironmentInfo = environmentInfo;
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
_logger = loggerFactory.CreateLogger(nameof(UniversalAudioService));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
|
protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
|
||||||
@@ -131,8 +111,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
protected IImageProcessor ImageProcessor { get; private set; }
|
protected IImageProcessor ImageProcessor { get; private set; }
|
||||||
protected INetworkManager NetworkManager { get; private set; }
|
protected INetworkManager NetworkManager { get; private set; }
|
||||||
protected IEnvironmentInfo EnvironmentInfo { get; private set; }
|
protected IEnvironmentInfo EnvironmentInfo { get; private set; }
|
||||||
private ILoggerFactory _loggerFactory;
|
|
||||||
private ILogger _logger;
|
|
||||||
|
|
||||||
public Task<object> Get(GetUniversalAudioStream request)
|
public Task<object> Get(GetUniversalAudioStream request)
|
||||||
{
|
{
|
||||||
@@ -243,7 +221,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
|
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
|
||||||
|
|
||||||
var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext, _loggerFactory)
|
var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext)
|
||||||
{
|
{
|
||||||
Request = Request
|
Request = Request
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ namespace MediaBrowser.Common
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inits this instance.
|
/// Inits this instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task InitAsync();
|
void Init();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the instance.
|
/// Creates the instance.
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The tracks.</value>
|
/// <value>The tracks.</value>
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
|
public IEnumerable<BaseItem> Tracks => GetRecursiveChildren(i => i is Audio);
|
||||||
|
|
||||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
var list = new List<Tuple<StringBuilder, bool>>();
|
var list = new List<Tuple<StringBuilder, bool>>();
|
||||||
|
|
||||||
int thisMarker = 0;
|
int thisMarker = 0, thisNumericChunk = 0;
|
||||||
|
|
||||||
while (thisMarker < s1.Length)
|
while (thisMarker < s1.Length)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -644,9 +644,12 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return PostFilterAndSort(items, query, true);
|
return PostFilterAndSort(items, query, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(this is UserRootFolder) && !(this is AggregateFolder) && query.ParentId == Guid.Empty)
|
if (!(this is UserRootFolder) && !(this is AggregateFolder))
|
||||||
{
|
{
|
||||||
query.Parent = this;
|
if (!query.ParentId.Equals(Guid.Empty))
|
||||||
|
{
|
||||||
|
query.Parent = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RequiresPostFiltering2(query))
|
if (RequiresPostFiltering2(query))
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||||
EnableRaisingEvents = true,
|
|
||||||
IsHidden = true,
|
IsHidden = true,
|
||||||
ErrorDialog = false
|
ErrorDialog = false
|
||||||
});
|
});
|
||||||
@@ -574,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
EnableRaisingEvents = true,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = processArgs,
|
Arguments = processArgs,
|
||||||
IsHidden = true,
|
IsHidden = true,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Activity
|
namespace MediaBrowser.Model.Activity
|
||||||
{
|
{
|
||||||
@@ -9,8 +8,10 @@ namespace MediaBrowser.Model.Activity
|
|||||||
{
|
{
|
||||||
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
||||||
|
|
||||||
Task CreateAsync(ActivityLogEntry entry);
|
void Create(ActivityLogEntry entry);
|
||||||
|
|
||||||
IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
|
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit);
|
||||||
|
|
||||||
|
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Activity
|
namespace MediaBrowser.Model.Activity
|
||||||
{
|
{
|
||||||
public interface IActivityRepository
|
public interface IActivityRepository
|
||||||
{
|
{
|
||||||
Task CreateAsync(ActivityLogEntry entry);
|
void Create(ActivityLogEntry entry);
|
||||||
|
|
||||||
IQueryable<ActivityLogEntry> GetActivityLogEntries();
|
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,21 +66,21 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return MaxBitrate;
|
return MaxBitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Profile == null)
|
if (Profile != null)
|
||||||
{
|
{
|
||||||
return null;
|
if (Context == EncodingContext.Static)
|
||||||
}
|
|
||||||
|
|
||||||
if (Context == EncodingContext.Static)
|
|
||||||
{
|
|
||||||
if (isAudio && Profile.MaxStaticMusicBitrate.HasValue)
|
|
||||||
{
|
{
|
||||||
return Profile.MaxStaticMusicBitrate;
|
if (isAudio && Profile.MaxStaticMusicBitrate.HasValue)
|
||||||
|
{
|
||||||
|
return Profile.MaxStaticMusicBitrate;
|
||||||
|
}
|
||||||
|
return Profile.MaxStaticBitrate;
|
||||||
}
|
}
|
||||||
return Profile.MaxStaticBitrate;
|
|
||||||
|
return Profile.MaxStreamingBitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Profile.MaxStreamingBitrate;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ using MediaBrowser.Model.MediaInfo;
|
|||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public static class ConditionProcessor
|
public class ConditionProcessor
|
||||||
{
|
{
|
||||||
public static bool IsVideoConditionSatisfied(
|
public bool IsVideoConditionSatisfied(ProfileCondition condition,
|
||||||
ProfileCondition condition,
|
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? videoBitDepth,
|
int? videoBitDepth,
|
||||||
@@ -65,7 +64,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
|
public bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
|
||||||
{
|
{
|
||||||
switch (condition.Property)
|
switch (condition.Property)
|
||||||
{
|
{
|
||||||
@@ -78,7 +77,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
|
public bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
|
||||||
{
|
{
|
||||||
switch (condition.Property)
|
switch (condition.Property)
|
||||||
{
|
{
|
||||||
@@ -95,8 +94,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsVideoAudioConditionSatisfied(
|
public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
|
||||||
ProfileCondition condition,
|
|
||||||
int? audioChannels,
|
int? audioChannels,
|
||||||
int? audioBitrate,
|
int? audioBitrate,
|
||||||
int? audioSampleRate,
|
int? audioSampleRate,
|
||||||
@@ -123,7 +121,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, int? currentValue)
|
private bool IsConditionSatisfied(ProfileCondition condition, int? currentValue)
|
||||||
{
|
{
|
||||||
if (!currentValue.HasValue)
|
if (!currentValue.HasValue)
|
||||||
{
|
{
|
||||||
@@ -152,7 +150,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, string currentValue)
|
private bool IsConditionSatisfied(ProfileCondition condition, string currentValue)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(currentValue))
|
if (string.IsNullOrEmpty(currentValue))
|
||||||
{
|
{
|
||||||
@@ -177,7 +175,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
|
private bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
|
||||||
{
|
{
|
||||||
if (!currentValue.HasValue)
|
if (!currentValue.HasValue)
|
||||||
{
|
{
|
||||||
@@ -201,7 +199,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, float currentValue)
|
private bool IsConditionSatisfied(ProfileCondition condition, float currentValue)
|
||||||
{
|
{
|
||||||
if (currentValue <= 0)
|
if (currentValue <= 0)
|
||||||
{
|
{
|
||||||
@@ -229,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, double? currentValue)
|
private bool IsConditionSatisfied(ProfileCondition condition, double? currentValue)
|
||||||
{
|
{
|
||||||
if (!currentValue.HasValue)
|
if (!currentValue.HasValue)
|
||||||
{
|
{
|
||||||
@@ -257,7 +255,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp? timestamp)
|
private bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp? timestamp)
|
||||||
{
|
{
|
||||||
if (!timestamp.HasValue)
|
if (!timestamp.HasValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -188,10 +188,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
var anyOff = false;
|
var anyOff = false;
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsAudioConditionSatisfied(GetModelProfileCondition(c), audioChannels, audioBitrate, audioSampleRate, audioBitDepth))
|
if (!conditionProcessor.IsAudioConditionSatisfied(GetModelProfileCondition(c), audioChannels, audioBitrate, audioSampleRate, audioBitDepth))
|
||||||
{
|
{
|
||||||
anyOff = true;
|
anyOff = true;
|
||||||
break;
|
break;
|
||||||
@@ -233,10 +235,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
var anyOff = false;
|
var anyOff = false;
|
||||||
foreach (var c in i.Conditions)
|
foreach (var c in i.Conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
|
if (!conditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
|
||||||
{
|
{
|
||||||
anyOff = true;
|
anyOff = true;
|
||||||
break;
|
break;
|
||||||
@@ -297,10 +301,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
var anyOff = false;
|
var anyOff = false;
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!conditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
anyOff = true;
|
anyOff = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -93,10 +93,19 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
|
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
|
private StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
|
||||||
=> SortMediaSources(streams, maxBitrate).FirstOrDefault();
|
{
|
||||||
|
var sorted = SortMediaSources(streams, maxBitrate);
|
||||||
|
|
||||||
private static IOrderedEnumerable<StreamInfo> SortMediaSources(List<StreamInfo> streams, long maxBitrate)
|
foreach (StreamInfo stream in sorted)
|
||||||
|
{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamInfo[] SortMediaSources(List<StreamInfo> streams, long maxBitrate)
|
||||||
{
|
{
|
||||||
return streams.OrderBy(i =>
|
return streams.OrderBy(i =>
|
||||||
{
|
{
|
||||||
@@ -142,17 +151,25 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}).ThenBy(streams.IndexOf);
|
}).ThenBy(streams.IndexOf).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
|
private TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
|
||||||
{
|
{
|
||||||
switch (condition.Property)
|
switch (condition.Property)
|
||||||
{
|
{
|
||||||
case ProfileConditionValue.AudioBitrate:
|
case ProfileConditionValue.AudioBitrate:
|
||||||
|
if (condition.Condition == ProfileConditionType.LessThanEqual)
|
||||||
|
{
|
||||||
|
return TranscodeReason.AudioBitrateNotSupported;
|
||||||
|
}
|
||||||
return TranscodeReason.AudioBitrateNotSupported;
|
return TranscodeReason.AudioBitrateNotSupported;
|
||||||
|
|
||||||
case ProfileConditionValue.AudioChannels:
|
case ProfileConditionValue.AudioChannels:
|
||||||
|
if (condition.Condition == ProfileConditionType.LessThanEqual)
|
||||||
|
{
|
||||||
|
return TranscodeReason.AudioChannelsNotSupported;
|
||||||
|
}
|
||||||
return TranscodeReason.AudioChannelsNotSupported;
|
return TranscodeReason.AudioChannelsNotSupported;
|
||||||
|
|
||||||
case ProfileConditionValue.AudioProfile:
|
case ProfileConditionValue.AudioProfile:
|
||||||
@@ -229,7 +246,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string _, DeviceProfile profile, DlnaProfileType type)
|
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string unused1, DeviceProfile profile, DlnaProfileType type)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(inputContainer))
|
if (string.IsNullOrEmpty(inputContainer))
|
||||||
{
|
{
|
||||||
@@ -249,10 +266,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
foreach (var directPlayProfile in profile.DirectPlayProfiles)
|
foreach (var directPlayProfile in profile.DirectPlayProfiles)
|
||||||
{
|
{
|
||||||
if (directPlayProfile.Type == type
|
if (directPlayProfile.Type == type)
|
||||||
&& directPlayProfile.SupportsContainer(format))
|
|
||||||
{
|
{
|
||||||
return format;
|
if (directPlayProfile.SupportsContainer(format))
|
||||||
|
{
|
||||||
|
return format;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +282,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||||
{
|
{
|
||||||
StreamInfo playlistItem = new StreamInfo
|
var transcodeReasons = new List<TranscodeReason>();
|
||||||
|
|
||||||
|
var playlistItem = new StreamInfo
|
||||||
{
|
{
|
||||||
ItemId = options.ItemId,
|
ItemId = options.ItemId,
|
||||||
MediaType = DlnaProfileType.Audio,
|
MediaType = DlnaProfileType.Audio,
|
||||||
@@ -292,16 +313,18 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
|
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
|
||||||
|
|
||||||
var directPlayMethods = directPlayInfo.Item1;
|
var directPlayMethods = directPlayInfo.Item1;
|
||||||
var transcodeReasons = directPlayInfo.Item2.ToList();
|
transcodeReasons.AddRange(directPlayInfo.Item2);
|
||||||
|
|
||||||
int? inputAudioChannels = audioStream?.Channels;
|
var conditionProcessor = new ConditionProcessor();
|
||||||
int? inputAudioBitrate = audioStream?.BitDepth;
|
|
||||||
int? inputAudioSampleRate = audioStream?.SampleRate;
|
|
||||||
int? inputAudioBitDepth = audioStream.BitDepth;
|
|
||||||
|
|
||||||
if (directPlayMethods.Count() > 0)
|
int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
|
||||||
|
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
||||||
|
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
|
||||||
|
if (directPlayMethods.Count > 0)
|
||||||
{
|
{
|
||||||
string audioCodec = audioStream?.Codec;
|
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||||
|
|
||||||
// Make sure audio codec profiles are satisfied
|
// Make sure audio codec profiles are satisfied
|
||||||
var conditions = new List<ProfileCondition>();
|
var conditions = new List<ProfileCondition>();
|
||||||
@@ -312,7 +335,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
||||||
{
|
{
|
||||||
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -322,7 +345,10 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (applyConditions)
|
if (applyConditions)
|
||||||
{
|
{
|
||||||
conditions.AddRange(i.Conditions);
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
conditions.Add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,7 +356,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool all = true;
|
bool all = true;
|
||||||
foreach (ProfileCondition c in conditions)
|
foreach (ProfileCondition c in conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
||||||
{
|
{
|
||||||
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
|
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
|
||||||
var transcodeReason = GetTranscodeReasonForFailedCondition(c);
|
var transcodeReason = GetTranscodeReasonForFailedCondition(c);
|
||||||
@@ -359,12 +385,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
TranscodingProfile transcodingProfile = null;
|
TranscodingProfile transcodingProfile = null;
|
||||||
foreach (var i in options.Profile.TranscodingProfiles)
|
foreach (var i in options.Profile.TranscodingProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == playlistItem.MediaType
|
if (i.Type == playlistItem.MediaType && i.Context == options.Context)
|
||||||
&& i.Context == options.Context
|
|
||||||
&& _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
|
|
||||||
{
|
{
|
||||||
transcodingProfile = i;
|
if (_transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
|
||||||
break;
|
{
|
||||||
|
transcodingProfile = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +421,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (var applyCondition in i.ApplyConditions)
|
foreach (var applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
|
||||||
{
|
{
|
||||||
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -436,7 +463,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return playlistItem;
|
return playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
|
private long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
|
||||||
{
|
{
|
||||||
if (item.Protocol == MediaProtocol.File)
|
if (item.Protocol == MediaProtocol.File)
|
||||||
{
|
{
|
||||||
@@ -446,56 +473,65 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return options.GetMaxBitrate(isAudio);
|
return options.GetMaxBitrate(isAudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (IEnumerable<PlayMethod>, IEnumerable<TranscodeReason>) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
private Tuple<List<PlayMethod>, List<TranscodeReason>> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
||||||
{
|
{
|
||||||
DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
|
var transcodeReasons = new List<TranscodeReason>();
|
||||||
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
|
|
||||||
|
|
||||||
if (directPlayProfile == null)
|
DirectPlayProfile directPlayProfile = null;
|
||||||
|
foreach (var i in options.Profile.DirectPlayProfiles)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
|
if (i.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(i, item, audioStream))
|
||||||
options.Profile.Name ?? "Unknown Profile",
|
{
|
||||||
item.Path ?? "Unknown path");
|
directPlayProfile = i;
|
||||||
|
break;
|
||||||
return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var playMethods = new List<PlayMethod>();
|
var playMethods = new List<PlayMethod>();
|
||||||
var transcodeReasons = new List<TranscodeReason>();
|
|
||||||
|
|
||||||
// While options takes the network and other factors into account. Only applies to direct stream
|
if (directPlayProfile != null)
|
||||||
if (item.SupportsDirectStream)
|
|
||||||
{
|
{
|
||||||
if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
// While options takes the network and other factors into account. Only applies to direct stream
|
||||||
|
if (item.SupportsDirectStream)
|
||||||
{
|
{
|
||||||
if (options.EnableDirectStream)
|
if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
||||||
{
|
{
|
||||||
playMethods.Add(PlayMethod.DirectStream);
|
if (options.EnableDirectStream)
|
||||||
|
{
|
||||||
|
playMethods.Add(PlayMethod.DirectStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The profile describes what the device supports
|
// The profile describes what the device supports
|
||||||
// If device requirements are satisfied then allow both direct stream and direct play
|
// If device requirements are satisfied then allow both direct stream and direct play
|
||||||
if (item.SupportsDirectPlay)
|
if (item.SupportsDirectPlay)
|
||||||
{
|
|
||||||
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
|
|
||||||
{
|
{
|
||||||
if (options.EnableDirectPlay)
|
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
|
||||||
{
|
{
|
||||||
playMethods.Add(PlayMethod.DirectPlay);
|
if (options.EnableDirectPlay)
|
||||||
|
{
|
||||||
|
playMethods.Add(PlayMethod.DirectPlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transcodeReasons.InsertRange(0, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
|
||||||
|
|
||||||
|
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
|
||||||
|
options.Profile.Name ?? "Unknown Profile",
|
||||||
|
item.Path ?? "Unknown path");
|
||||||
|
}
|
||||||
|
|
||||||
if (playMethods.Count > 0)
|
if (playMethods.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -506,25 +542,41 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
transcodeReasons = transcodeReasons.Distinct().ToList();
|
transcodeReasons = transcodeReasons.Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (playMethods, transcodeReasons);
|
return new Tuple<List<PlayMethod>, List<TranscodeReason>>(playMethods, transcodeReasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
|
private List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
|
||||||
{
|
{
|
||||||
|
var list = new List<TranscodeReason>();
|
||||||
var containerSupported = false;
|
var containerSupported = false;
|
||||||
var audioSupported = false;
|
var audioSupported = false;
|
||||||
var videoSupported = false;
|
var videoSupported = false;
|
||||||
|
|
||||||
foreach (var profile in directPlayProfiles)
|
foreach (var profile in directPlayProfiles)
|
||||||
{
|
{
|
||||||
|
audioSupported = false;
|
||||||
|
videoSupported = false;
|
||||||
|
|
||||||
// Check container type
|
// Check container type
|
||||||
if (profile.SupportsContainer(item.Container))
|
if (profile.SupportsContainer(item.Container))
|
||||||
{
|
{
|
||||||
containerSupported = true;
|
containerSupported = true;
|
||||||
|
|
||||||
videoSupported = videoStream != null && profile.SupportsVideoCodec(videoStream.Codec);
|
if (videoStream != null)
|
||||||
|
{
|
||||||
|
if (profile.SupportsVideoCodec(videoStream.Codec))
|
||||||
|
{
|
||||||
|
videoSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
audioSupported = audioStream != null && profile.SupportsAudioCodec(audioStream.Codec);
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
if (profile.SupportsAudioCodec(audioStream.Codec))
|
||||||
|
{
|
||||||
|
audioSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (videoSupported && audioSupported)
|
if (videoSupported && audioSupported)
|
||||||
{
|
{
|
||||||
@@ -533,7 +585,6 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<TranscodeReason>();
|
|
||||||
if (!containerSupported)
|
if (!containerSupported)
|
||||||
{
|
{
|
||||||
list.Add(TranscodeReason.ContainerNotSupported);
|
list.Add(TranscodeReason.ContainerNotSupported);
|
||||||
@@ -552,17 +603,18 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
|
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
|
||||||
{
|
{
|
||||||
int highestScore = -1;
|
int highestScore = -1;
|
||||||
|
|
||||||
foreach (var stream in item.MediaStreams)
|
foreach (var stream in item.MediaStreams)
|
||||||
{
|
{
|
||||||
if (stream.Type == MediaStreamType.Subtitle
|
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
|
||||||
&& stream.Score.HasValue
|
|
||||||
&& stream.Score.Value > highestScore)
|
|
||||||
{
|
{
|
||||||
highestScore = stream.Score.Value;
|
if (stream.Score.Value > highestScore)
|
||||||
|
{
|
||||||
|
highestScore = stream.Score.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,7 +646,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return item.DefaultSubtitleStreamIndex;
|
return item.DefaultSubtitleStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
|
private void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
|
if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
|
||||||
{
|
{
|
||||||
@@ -634,10 +686,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)
|
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
|
||||||
&& int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels))
|
|
||||||
{
|
{
|
||||||
playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
|
if (int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out var transcodingMaxAudioChannels))
|
||||||
|
{
|
||||||
|
playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,7 +702,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
throw new ArgumentNullException(nameof(item));
|
throw new ArgumentNullException(nameof(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamInfo playlistItem = new StreamInfo
|
var transcodeReasons = new List<TranscodeReason>();
|
||||||
|
|
||||||
|
var playlistItem = new StreamInfo
|
||||||
{
|
{
|
||||||
ItemId = options.ItemId,
|
ItemId = options.ItemId,
|
||||||
MediaType = DlnaProfileType.Video,
|
MediaType = DlnaProfileType.Video,
|
||||||
@@ -681,8 +737,6 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
isEligibleForDirectPlay,
|
isEligibleForDirectPlay,
|
||||||
isEligibleForDirectStream);
|
isEligibleForDirectStream);
|
||||||
|
|
||||||
var transcodeReasons = new List<TranscodeReason>();
|
|
||||||
|
|
||||||
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
||||||
{
|
{
|
||||||
// See if it can be direct played
|
// See if it can be direct played
|
||||||
@@ -749,6 +803,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
|
SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
|
||||||
|
|
||||||
|
var conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
var isFirstAppliedCodecProfile = true;
|
var isFirstAppliedCodecProfile = true;
|
||||||
foreach (var i in options.Profile.CodecProfiles)
|
foreach (var i in options.Profile.CodecProfiles)
|
||||||
{
|
{
|
||||||
@@ -757,26 +813,26 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
int? width = videoStream?.Width;
|
int? width = videoStream == null ? null : videoStream.Width;
|
||||||
int? height = videoStream?.Height;
|
int? height = videoStream == null ? null : videoStream.Height;
|
||||||
int? bitDepth = videoStream?.BitDepth;
|
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
|
||||||
int? videoBitrate = videoStream?.BitRate;
|
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
|
||||||
double? videoLevel = videoStream?.Level;
|
double? videoLevel = videoStream == null ? null : videoStream.Level;
|
||||||
string videoProfile = videoStream?.Profile;
|
string videoProfile = videoStream == null ? null : videoStream.Profile;
|
||||||
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
||||||
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
|
||||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
bool? isInterlaced = videoStream == null ? (bool?)null : videoStream.IsInterlaced;
|
||||||
string videoCodecTag = videoStream?.CodecTag;
|
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
|
||||||
bool? isAvc = videoStream?.IsAVC;
|
bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
|
||||||
|
|
||||||
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
|
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
|
||||||
int? packetLength = videoStream?.PacketLength;
|
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||||
int? refFrames = videoStream?.RefFrames;
|
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||||
|
|
||||||
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
||||||
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
||||||
|
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
|
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -820,7 +876,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
||||||
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
|
||||||
if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))
|
||||||
{
|
{
|
||||||
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
|
//LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -866,7 +922,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return playlistItem;
|
return playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream)
|
private int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream)
|
||||||
{
|
{
|
||||||
if ((audioStream.Channels ?? 0) >= 6)
|
if ((audioStream.Channels ?? 0) >= 6)
|
||||||
{
|
{
|
||||||
@@ -876,37 +932,33 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return 192000;
|
return 192000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item)
|
private int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item)
|
||||||
{
|
{
|
||||||
string targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
|
var targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
|
||||||
|
|
||||||
int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
|
var targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
|
||||||
|
|
||||||
|
int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? GetDefaultAudioBitrateIfUnknown(audioStream);
|
||||||
|
|
||||||
|
// Reduce the bitrate if we're downmixing
|
||||||
|
if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value)
|
||||||
|
{
|
||||||
|
defaultBitrate = targetAudioChannels.Value <= 2 ? 128000 : 192000;
|
||||||
|
}
|
||||||
|
|
||||||
int defaultBitrate;
|
|
||||||
int encoderAudioBitrateLimit = int.MaxValue;
|
int encoderAudioBitrateLimit = int.MaxValue;
|
||||||
|
|
||||||
if (audioStream == null)
|
if (audioStream != null)
|
||||||
{
|
{
|
||||||
defaultBitrate = 192000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (targetAudioChannels.HasValue && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value)
|
|
||||||
{
|
|
||||||
// Reduce the bitrate if we're downmixing
|
|
||||||
defaultBitrate = targetAudioChannels.Value < 2 ? 128000 : 192000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
defaultBitrate = audioStream.BitRate ?? GetDefaultAudioBitrateIfUnknown(audioStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seeing webm encoding failures when source has 1 audio channel and 22k bitrate.
|
// Seeing webm encoding failures when source has 1 audio channel and 22k bitrate.
|
||||||
// Any attempts to transcode over 64k will fail
|
// Any attempts to transcode over 64k will fail
|
||||||
if (audioStream.Channels == 1
|
if (audioStream.Channels.HasValue &&
|
||||||
&& (audioStream.BitRate ?? 0) < 64000)
|
audioStream.Channels.Value == 1)
|
||||||
{
|
{
|
||||||
encoderAudioBitrateLimit = 64000;
|
if ((audioStream.BitRate ?? 0) < 64000)
|
||||||
|
{
|
||||||
|
encoderAudioBitrateLimit = 64000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,17 +970,19 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
|
return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
|
private int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
|
||||||
{
|
{
|
||||||
if (totalBitrate <= 640000)
|
if (totalBitrate <= 640000)
|
||||||
{
|
{
|
||||||
return 128000;
|
return 128000;
|
||||||
}
|
}
|
||||||
else if (totalBitrate <= 2000000)
|
|
||||||
|
if (totalBitrate <= 2000000)
|
||||||
{
|
{
|
||||||
return 384000;
|
return 384000;
|
||||||
}
|
}
|
||||||
else if (totalBitrate <= 3000000)
|
|
||||||
|
if (totalBitrate <= 3000000)
|
||||||
{
|
{
|
||||||
return 448000;
|
return 448000;
|
||||||
}
|
}
|
||||||
@@ -936,25 +990,24 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return 640000;
|
return 640000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (PlayMethod?, List<TranscodeReason>) GetVideoDirectPlayProfile(
|
private Tuple<PlayMethod?, List<TranscodeReason>> GetVideoDirectPlayProfile(VideoOptions options,
|
||||||
VideoOptions options,
|
|
||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
MediaStream videoStream,
|
MediaStream videoStream,
|
||||||
MediaStream audioStream,
|
MediaStream audioStream,
|
||||||
bool isEligibleForDirectPlay,
|
bool isEligibleForDirectPlay,
|
||||||
bool isEligibleForDirectStream)
|
bool isEligibleForDirectStream)
|
||||||
{
|
{
|
||||||
|
DeviceProfile profile = options.Profile;
|
||||||
|
|
||||||
if (options.ForceDirectPlay)
|
if (options.ForceDirectPlay)
|
||||||
{
|
{
|
||||||
return (PlayMethod.DirectPlay, new List<TranscodeReason>());
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectPlay, new List<TranscodeReason>());
|
||||||
}
|
}
|
||||||
if (options.ForceDirectStream)
|
if (options.ForceDirectStream)
|
||||||
{
|
{
|
||||||
return (PlayMethod.DirectStream, new List<TranscodeReason>());
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceProfile profile = options.Profile;
|
|
||||||
|
|
||||||
// See if it can be direct played
|
// See if it can be direct played
|
||||||
DirectPlayProfile directPlay = null;
|
DirectPlayProfile directPlay = null;
|
||||||
foreach (var i in profile.DirectPlayProfiles)
|
foreach (var i in profile.DirectPlayProfiles)
|
||||||
@@ -972,7 +1025,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
profile.Name ?? "Unknown Profile",
|
profile.Name ?? "Unknown Profile",
|
||||||
mediaSource.Path ?? "Unknown path");
|
mediaSource.Path ?? "Unknown path");
|
||||||
|
|
||||||
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
string container = mediaSource.Container;
|
string container = mediaSource.Container;
|
||||||
@@ -980,8 +1033,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
var conditions = new List<ProfileCondition>();
|
var conditions = new List<ProfileCondition>();
|
||||||
foreach (var i in profile.ContainerProfiles)
|
foreach (var i in profile.ContainerProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == DlnaProfileType.Video
|
if (i.Type == DlnaProfileType.Video &&
|
||||||
&& i.ContainsContainer(container))
|
i.ContainsContainer(container))
|
||||||
{
|
{
|
||||||
foreach (var c in i.Conditions)
|
foreach (var c in i.Conditions)
|
||||||
{
|
{
|
||||||
@@ -990,27 +1043,29 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int? width = videoStream?.Width;
|
var conditionProcessor = new ConditionProcessor();
|
||||||
int? height = videoStream?.Height;
|
|
||||||
int? bitDepth = videoStream?.BitDepth;
|
|
||||||
int? videoBitrate = videoStream?.BitRate;
|
|
||||||
double? videoLevel = videoStream?.Level;
|
|
||||||
string videoProfile = videoStream?.Profile;
|
|
||||||
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
|
||||||
bool? isAnamorphic = videoStream?.IsAnamorphic;
|
|
||||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
|
||||||
string videoCodecTag = videoStream?.CodecTag;
|
|
||||||
bool? isAvc = videoStream?.IsAVC;
|
|
||||||
|
|
||||||
int? audioBitrate = audioStream?.BitRate;
|
int? width = videoStream == null ? null : videoStream.Width;
|
||||||
int? audioChannels = audioStream?.Channels;
|
int? height = videoStream == null ? null : videoStream.Height;
|
||||||
string audioProfile = audioStream?.Profile;
|
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
|
||||||
int? audioSampleRate = audioStream?.SampleRate;
|
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
|
||||||
int? audioBitDepth = audioStream?.BitDepth;
|
double? videoLevel = videoStream == null ? null : videoStream.Level;
|
||||||
|
string videoProfile = videoStream == null ? null : videoStream.Profile;
|
||||||
|
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
|
||||||
|
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
|
||||||
|
bool? isInterlaced = videoStream == null ? (bool?)null : videoStream.IsInterlaced;
|
||||||
|
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
|
||||||
|
bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
|
||||||
|
|
||||||
|
int? audioBitrate = audioStream == null ? null : audioStream.BitRate;
|
||||||
|
int? audioChannels = audioStream == null ? null : audioStream.Channels;
|
||||||
|
string audioProfile = audioStream == null ? null : audioStream.Profile;
|
||||||
|
int? audioSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
||||||
|
int? audioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
|
||||||
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
|
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
|
||||||
int? packetLength = videoStream?.PacketLength;
|
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||||
int? refFrames = videoStream?.RefFrames;
|
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||||
|
|
||||||
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
||||||
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
||||||
@@ -1018,20 +1073,20 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
// Check container conditions
|
// Check container conditions
|
||||||
foreach (ProfileCondition i in conditions)
|
foreach (ProfileCondition i in conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
|
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
|
||||||
|
|
||||||
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
||||||
var transcodeReasons = transcodeReason.HasValue
|
var transcodeReasons = transcodeReason.HasValue
|
||||||
? new List<TranscodeReason> { transcodeReason.Value }
|
? new List<TranscodeReason> { transcodeReason.Value }
|
||||||
: new List<TranscodeReason>();
|
: new List<TranscodeReason> { };
|
||||||
|
|
||||||
return (null, transcodeReasons);
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string videoCodec = videoStream?.Codec;
|
string videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||||
|
|
||||||
conditions = new List<ProfileCondition>();
|
conditions = new List<ProfileCondition>();
|
||||||
foreach (var i in profile.CodecProfiles)
|
foreach (var i in profile.CodecProfiles)
|
||||||
@@ -1041,7 +1096,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
//LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
|
//LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -1061,22 +1116,23 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
foreach (ProfileCondition i in conditions)
|
foreach (ProfileCondition i in conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
|
||||||
{
|
{
|
||||||
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
|
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
|
||||||
|
|
||||||
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
||||||
var transcodeReasons = transcodeReason.HasValue
|
var transcodeReasons = transcodeReason.HasValue
|
||||||
? new List<TranscodeReason> { transcodeReason.Value }
|
? new List<TranscodeReason> { transcodeReason.Value }
|
||||||
: new List<TranscodeReason>();
|
: new List<TranscodeReason> { };
|
||||||
|
|
||||||
return (null, transcodeReasons);
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioStream != null)
|
if (audioStream != null)
|
||||||
{
|
{
|
||||||
string audioCodec = audioStream.Codec;
|
string audioCodec = audioStream.Codec;
|
||||||
|
|
||||||
conditions = new List<ProfileCondition>();
|
conditions = new List<ProfileCondition>();
|
||||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||||
|
|
||||||
@@ -1087,7 +1143,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
|
||||||
{
|
{
|
||||||
//LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
|
//LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
@@ -1107,26 +1163,26 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
foreach (ProfileCondition i in conditions)
|
foreach (ProfileCondition i in conditions)
|
||||||
{
|
{
|
||||||
if (!ConditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
|
||||||
{
|
{
|
||||||
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
|
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
|
||||||
|
|
||||||
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
|
||||||
var transcodeReasons = transcodeReason.HasValue
|
var transcodeReasons = transcodeReason.HasValue
|
||||||
? new List<TranscodeReason> { transcodeReason.Value }
|
? new List<TranscodeReason> { transcodeReason.Value }
|
||||||
: new List<TranscodeReason>();
|
: new List<TranscodeReason> { };
|
||||||
|
|
||||||
return (null, transcodeReasons);
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
|
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
|
||||||
{
|
{
|
||||||
return (PlayMethod.DirectStream, new List<TranscodeReason>());
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return (null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
|
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
|
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
|
||||||
@@ -1141,8 +1197,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
mediaSource.Path ?? "Unknown path");
|
mediaSource.Path ?? "Unknown path");
|
||||||
}
|
}
|
||||||
|
|
||||||
private (bool directPlay, TranscodeReason? reason) IsEligibleForDirectPlay(
|
private ValueTuple<bool, TranscodeReason?> IsEligibleForDirectPlay(MediaSourceInfo item,
|
||||||
MediaSourceInfo item,
|
|
||||||
long maxBitrate,
|
long maxBitrate,
|
||||||
MediaStream subtitleStream,
|
MediaStream subtitleStream,
|
||||||
VideoOptions options,
|
VideoOptions options,
|
||||||
@@ -1155,23 +1210,21 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod);
|
_logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod);
|
||||||
return (false, TranscodeReason.SubtitleCodecNotSupported);
|
return new ValueTuple<bool, TranscodeReason?>(false, TranscodeReason.SubtitleCodecNotSupported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
|
var result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
|
||||||
|
|
||||||
return (result, result ? (TranscodeReason?)null : TranscodeReason.ContainerBitrateExceedsLimit);
|
if (result)
|
||||||
|
{
|
||||||
|
return new ValueTuple<bool, TranscodeReason?>(result, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValueTuple<bool, TranscodeReason?>(result, TranscodeReason.ContainerBitrateExceedsLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SubtitleProfile GetSubtitleProfile(
|
public static SubtitleProfile GetSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, string outputContainer, string transcodingSubProtocol)
|
||||||
MediaSourceInfo mediaSource,
|
|
||||||
MediaStream subtitleStream,
|
|
||||||
SubtitleProfile[] subtitleProfiles,
|
|
||||||
PlayMethod playMethod,
|
|
||||||
ITranscoderSupport transcoderSupport,
|
|
||||||
string outputContainer,
|
|
||||||
string transcodingSubProtocol)
|
|
||||||
{
|
{
|
||||||
if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)))
|
if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
@@ -1248,20 +1301,27 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(transcodingContainer))
|
if (!string.IsNullOrEmpty(transcodingContainer))
|
||||||
{
|
{
|
||||||
string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
|
var normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
|
||||||
|
|
||||||
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts")
|
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts"))
|
||||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "mpegts")
|
|
||||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv")
|
if (ContainerProfile.ContainsContainer(normalizedContainers, "mpegts"))
|
||||||
|| ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") ||
|
||||||
|
ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1328,22 +1388,22 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
|
var requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
|
||||||
|
|
||||||
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
|
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
|
||||||
int itemBitrate = item.Bitrate ?? 40000000;
|
var itemBitrate = item.Bitrate ??
|
||||||
|
40000000;
|
||||||
|
|
||||||
if (itemBitrate > requestedMaxBitrate)
|
if (itemBitrate > requestedMaxBitrate)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
|
_logger.LogInformation("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", itemBitrate.ToString(CultureInfo.InvariantCulture), requestedMaxBitrate.ToString(CultureInfo.InvariantCulture));
|
||||||
playMethod, itemBitrate, requestedMaxBitrate);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateInput(VideoOptions options)
|
private void ValidateInput(VideoOptions options)
|
||||||
{
|
{
|
||||||
ValidateAudioInput(options);
|
ValidateAudioInput(options);
|
||||||
|
|
||||||
@@ -1358,7 +1418,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateAudioInput(AudioOptions options)
|
private void ValidateAudioInput(AudioOptions options)
|
||||||
{
|
{
|
||||||
if (options.ItemId.Equals(Guid.Empty))
|
if (options.ItemId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
@@ -1378,6 +1438,32 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyTranscodingConditions(StreamInfo item, List<CodecProfile> codecProfiles)
|
||||||
|
{
|
||||||
|
foreach (var profile in codecProfiles)
|
||||||
|
{
|
||||||
|
ApplyTranscodingConditions(item, profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTranscodingConditions(StreamInfo item, CodecProfile codecProfile)
|
||||||
|
{
|
||||||
|
var codecs = ContainerProfile.SplitValue(codecProfile.Codec);
|
||||||
|
if (codecs.Length == 0)
|
||||||
|
{
|
||||||
|
ApplyTranscodingConditions(item, codecProfile.Conditions, null, true, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableNonQualified = true;
|
||||||
|
|
||||||
|
foreach (var codec in codecs)
|
||||||
|
{
|
||||||
|
ApplyTranscodingConditions(item, codecProfile.Conditions, codec, true, enableNonQualified);
|
||||||
|
enableNonQualified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions)
|
private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions)
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition condition in conditions)
|
foreach (ProfileCondition condition in conditions)
|
||||||
@@ -1752,7 +1838,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
|
private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
|
||||||
{
|
{
|
||||||
// Check container type
|
// Check container type
|
||||||
if (!profile.SupportsContainer(item.Container))
|
if (!profile.SupportsContainer(item.Container))
|
||||||
@@ -1761,7 +1847,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check audio codec
|
// Check audio codec
|
||||||
string audioCodec = audioStream?.Codec;
|
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||||
if (!profile.SupportsAudioCodec(audioCodec))
|
if (!profile.SupportsAudioCodec(audioCodec))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -1779,16 +1865,20 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check video codec
|
// Check video codec
|
||||||
string videoCodec = videoStream?.Codec;
|
string videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||||
if (!profile.SupportsVideoCodec(videoCodec))
|
if (!profile.SupportsVideoCodec(videoCodec))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check audio codec
|
// Check audio codec
|
||||||
if (audioStream != null && !profile.SupportsAudioCodec(audioStream.Codec))
|
if (audioStream != null)
|
||||||
{
|
{
|
||||||
return false;
|
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||||
|
if (!profile.SupportsAudioCodec(audioCodec))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "taglib-sharp", "ThirdParty\taglib-sharp\src\taglib-sharp.csproj", "{D45FC504-D06B-41A0-A220-C20B7E8F1304}"
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}"
|
||||||
@@ -142,6 +144,10 @@ Global
|
|||||||
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D45FC504-D06B-41A0-A220-C20B7E8F1304}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D45FC504-D06B-41A0-A220-C20B7E8F1304}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D45FC504-D06B-41A0-A220-C20B7E8F1304}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D45FC504-D06B-41A0-A220-C20B7E8F1304}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace Mono.Nat
|
|||||||
{
|
{
|
||||||
public event EventHandler<DeviceEventArgs> DeviceFound;
|
public event EventHandler<DeviceEventArgs> DeviceFound;
|
||||||
|
|
||||||
|
private DateTime nextSearch;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
|
||||||
@@ -97,6 +98,11 @@ namespace Mono.Nat
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DateTime NextSearch
|
||||||
|
{
|
||||||
|
get { return nextSearch; }
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDeviceFound(DeviceEventArgs args)
|
private void OnDeviceFound(DeviceEventArgs args)
|
||||||
{
|
{
|
||||||
if (DeviceFound != null)
|
if (DeviceFound != null)
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ namespace SocketHttpListener
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Fields
|
||||||
|
|
||||||
|
internal byte[] EntityBodyData;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Protected Fields
|
#region Protected Fields
|
||||||
|
|
||||||
protected const string CrLf = "\r\n";
|
protected const string CrLf = "\r\n";
|
||||||
@@ -31,6 +37,18 @@ namespace SocketHttpListener
|
|||||||
|
|
||||||
#region Public Properties
|
#region Public Properties
|
||||||
|
|
||||||
|
public string EntityBody
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = EntityBodyData;
|
||||||
|
|
||||||
|
return data != null && data.Length > 0
|
||||||
|
? getEncoding(_headers["Content-Type"]).GetString(data, 0, data.Length)
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public QueryParamCollection Headers => _headers;
|
public QueryParamCollection Headers => _headers;
|
||||||
|
|
||||||
public Version ProtocolVersion => _version;
|
public Version ProtocolVersion => _version;
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ namespace SocketHttpListener
|
|||||||
|
|
||||||
output.Append(CrLf);
|
output.Append(CrLf);
|
||||||
|
|
||||||
|
var entity = EntityBody;
|
||||||
|
if (entity.Length > 0)
|
||||||
|
output.Append(entity);
|
||||||
|
|
||||||
return output.ToString();
|
return output.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ namespace SocketHttpListener.Net
|
|||||||
int _reuses;
|
int _reuses;
|
||||||
bool _contextBound;
|
bool _contextBound;
|
||||||
bool secure;
|
bool secure;
|
||||||
|
int _timeout = 90000; // 90k ms for first request, 15k ms from then on
|
||||||
|
private Timer _timer;
|
||||||
IPEndPoint local_ep;
|
IPEndPoint local_ep;
|
||||||
HttpListener _lastListener;
|
HttpListener _lastListener;
|
||||||
X509Certificate cert;
|
X509Certificate cert;
|
||||||
@@ -89,6 +91,8 @@ namespace SocketHttpListener.Net
|
|||||||
|
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
{
|
{
|
||||||
|
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
|
||||||
|
|
||||||
if (ssl_stream != null)
|
if (ssl_stream != null)
|
||||||
{
|
{
|
||||||
var enableAsync = true;
|
var enableAsync = true;
|
||||||
@@ -158,10 +162,14 @@ namespace SocketHttpListener.Net
|
|||||||
_buffer = new byte[BufferSize];
|
_buffer = new byte[BufferSize];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_reuses == 1)
|
||||||
|
_timeout = 15000;
|
||||||
|
//_timer.Change(_timeout, Timeout.Infinite);
|
||||||
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
|
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
//_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||||
CloseSocket();
|
CloseSocket();
|
||||||
Unbind();
|
Unbind();
|
||||||
}
|
}
|
||||||
@@ -208,6 +216,7 @@ namespace SocketHttpListener.Net
|
|||||||
|
|
||||||
private void OnReadInternal(IAsyncResult ares)
|
private void OnReadInternal(IAsyncResult ares)
|
||||||
{
|
{
|
||||||
|
//_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||||
int nread = -1;
|
int nread = -1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace SocketHttpListener
|
|||||||
{
|
{
|
||||||
#region Private Fields
|
#region Private Fields
|
||||||
|
|
||||||
|
private string _base64Key;
|
||||||
private Action _closeContext;
|
private Action _closeContext;
|
||||||
private CompressionMethod _compression;
|
private CompressionMethod _compression;
|
||||||
private WebSocketContext _context;
|
private WebSocketContext _context;
|
||||||
@@ -34,12 +35,20 @@ namespace SocketHttpListener
|
|||||||
private object _forMessageEventQueue;
|
private object _forMessageEventQueue;
|
||||||
private object _forSend;
|
private object _forSend;
|
||||||
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
private Func<WebSocketContext, string>
|
||||||
|
_handshakeRequestChecker;
|
||||||
private Queue<MessageEventArgs> _messageEventQueue;
|
private Queue<MessageEventArgs> _messageEventQueue;
|
||||||
|
private uint _nonceCount;
|
||||||
|
private string _origin;
|
||||||
|
private bool _preAuth;
|
||||||
private string _protocol;
|
private string _protocol;
|
||||||
|
private string[] _protocols;
|
||||||
|
private Uri _proxyUri;
|
||||||
private volatile WebSocketState _readyState;
|
private volatile WebSocketState _readyState;
|
||||||
private AutoResetEvent _receivePong;
|
private AutoResetEvent _receivePong;
|
||||||
private bool _secure;
|
private bool _secure;
|
||||||
private Stream _stream;
|
private Stream _stream;
|
||||||
|
private Uri _uri;
|
||||||
private const string _version = "13";
|
private const string _version = "13";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
+1
Submodule ThirdParty/taglib-sharp added at 60e7588b53
@@ -23,7 +23,8 @@ get_version()
|
|||||||
(
|
(
|
||||||
local ROOT=${1-$DEFAULT_ROOT}
|
local ROOT=${1-$DEFAULT_ROOT}
|
||||||
grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \
|
grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \
|
||||||
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/'
|
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' \
|
||||||
|
| sed -E 's/.0$//'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run a build
|
# Run a build
|
||||||
|
|||||||
@@ -4,15 +4,4 @@ source ../common.build.sh
|
|||||||
|
|
||||||
VERSION=`get_version ../..`
|
VERSION=`get_version ../..`
|
||||||
|
|
||||||
package_temporary_dir="`pwd`/pkg-dist-tmp"
|
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
|
||||||
pkg_src_dir="`pwd`/pkg-src"
|
|
||||||
image_name="jellyfin-rpmbuild"
|
|
||||||
docker_sudo=""
|
|
||||||
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
|
|
||||||
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
|
|
||||||
docker_sudo=sudo
|
|
||||||
fi
|
|
||||||
|
|
||||||
$docker_sudo docker image rm $image_name --force
|
|
||||||
rm -rf "$package_temporary_dir"
|
|
||||||
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
|
|
||||||
|
|||||||
@@ -18,15 +18,10 @@ output_dir="`pwd`/pkg-dist"
|
|||||||
pkg_src_dir="`pwd`/pkg-src"
|
pkg_src_dir="`pwd`/pkg-src"
|
||||||
current_user="`whoami`"
|
current_user="`whoami`"
|
||||||
image_name="jellyfin-rpmbuild"
|
image_name="jellyfin-rpmbuild"
|
||||||
docker_sudo=""
|
|
||||||
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
|
|
||||||
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
|
|
||||||
docker_sudo=sudo
|
|
||||||
fi
|
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
set +o errexit
|
set +o errexit
|
||||||
$docker_sudo docker image rm $image_name --force
|
docker image rm $image_name --force
|
||||||
rm -rf "$package_temporary_dir"
|
rm -rf "$package_temporary_dir"
|
||||||
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
|
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
|
||||||
}
|
}
|
||||||
@@ -35,7 +30,7 @@ GNU_TAR=1
|
|||||||
mkdir -p "$package_temporary_dir"
|
mkdir -p "$package_temporary_dir"
|
||||||
echo "Bundling all sources for RPM build."
|
echo "Bundling all sources for RPM build."
|
||||||
tar \
|
tar \
|
||||||
--transform "s,^\.,jellyfin-${VERSION}," \
|
--transform "s,^\.,jellyfin-${VERSION}" \
|
||||||
--exclude='.git*' \
|
--exclude='.git*' \
|
||||||
--exclude='**/.git' \
|
--exclude='**/.git' \
|
||||||
--exclude='**/.hg' \
|
--exclude='**/.hg' \
|
||||||
@@ -47,8 +42,10 @@ tar \
|
|||||||
--exclude='**/.nuget' \
|
--exclude='**/.nuget' \
|
||||||
--exclude='*.deb' \
|
--exclude='*.deb' \
|
||||||
--exclude='*.rpm' \
|
--exclude='*.rpm' \
|
||||||
-zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \
|
-Jcvf \
|
||||||
-C "../.." ./ || GNU_TAR=0
|
"$package_temporary_dir/jellyfin-${VERSION}.tar.xz" \
|
||||||
|
-C "../.." \
|
||||||
|
./ || true && GNU_TAR=0
|
||||||
|
|
||||||
if [ $GNU_TAR -eq 0 ]; then
|
if [ $GNU_TAR -eq 0 ]; then
|
||||||
echo "The installed tar binary did not support --transform. Using workaround."
|
echo "The installed tar binary did not support --transform. Using workaround."
|
||||||
@@ -78,9 +75,9 @@ if [ $GNU_TAR -eq 0 ]; then
|
|||||||
tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}"
|
tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile
|
docker build ../.. -t "$image_name" -f ./Dockerfile
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
$docker_sudo docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;'
|
docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;'
|
||||||
chown -R "$current_user" "$package_temporary_dir" \
|
chown -R "$current_user" "$package_temporary_dir" \
|
||||||
|| sudo chown -R "$current_user" "$package_temporary_dir"
|
|| sudo chown -R "$current_user" "$package_temporary_dir"
|
||||||
mv "$package_temporary_dir"/*.rpm "$output_dir"
|
mv "$package_temporary_dir"/*.rpm "$output_dir"
|
||||||
|
|||||||
@@ -40,21 +40,21 @@ function Install-FFMPEG {
|
|||||||
Write-Warning "FFMPEG will not be installed"
|
Write-Warning "FFMPEG will not be installed"
|
||||||
}elseif($Architecture -eq 'x64'){
|
}elseif($Architecture -eq 'x64'){
|
||||||
Write-Verbose "Downloading 64 bit FFMPEG"
|
Write-Verbose "Downloading 64 bit FFMPEG"
|
||||||
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0.2-win64-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
|
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.1-win64-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
|
||||||
}else{
|
}else{
|
||||||
Write-Verbose "Downloading 32 bit FFMPEG"
|
Write-Verbose "Downloading 32 bit FFMPEG"
|
||||||
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.0.2-win32-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
|
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.1-win32-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
|
||||||
}
|
}
|
||||||
|
|
||||||
Expand-Archive "$tempdir/fmmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" | Write-Verbose
|
Expand-Archive "$tempdir/fmmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" | Write-Verbose
|
||||||
if($Architecture -eq 'x64'){
|
if($Architecture -eq 'x64'){
|
||||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||||
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.0.2-win64-static/bin" | ForEach-Object {
|
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.1-win64-static/bin" | ForEach-Object {
|
||||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||||
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.0.2-win32-static/bin" | ForEach-Object {
|
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.1-win32-static/bin" | ForEach-Object {
|
||||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +102,8 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
|
|||||||
Write-Verbose "Starting NSSM Install"
|
Write-Verbose "Starting NSSM Install"
|
||||||
Install-NSSM $InstallLocation $Architecture
|
Install-NSSM $InstallLocation $Architecture
|
||||||
}
|
}
|
||||||
Copy-Item .\deployment\win-generic\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
|
Copy-Item .\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
|
||||||
Copy-Item .\deployment\win-generic\install.bat $InstallLocation\install.bat
|
Copy-Item .\install.bat $InstallLocation\install.bat
|
||||||
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
|
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
|
||||||
Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force
|
Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
package_win64() (
|
|
||||||
local NSSM_VERSION="nssm-2.24-101-g897c7ad"
|
|
||||||
local NSSM_URL="https://nssm.cc/ci/${NSSM_VERSION}.zip"
|
|
||||||
local FFMPEG_VERSION="ffmpeg-4.0.2-win64-static"
|
|
||||||
local FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
|
|
||||||
local ROOT=${1-$DEFAULT_ROOT}
|
|
||||||
local OUTPUT_DIR=${2-$DEFAULT_OUTPUT_DIR}
|
|
||||||
local PKG_DIR=${3-$DEFAULT_PKG_DIR}
|
|
||||||
local ARCHIVE_CMD="zip -r"
|
|
||||||
# Package portable build result
|
|
||||||
if [ -d ${OUTPUT_DIR} ]; then
|
|
||||||
echo -e "${CYAN}Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}'.${NC}"
|
|
||||||
local TEMP_DIR="$(mktemp -d)"
|
|
||||||
wget ${NSSM_URL} -O ${TEMP_DIR}/nssm.zip
|
|
||||||
wget ${FFMPEG_URL} -O ${TEMP_DIR}/ffmpeg.zip
|
|
||||||
unzip ${TEMP_DIR}/nssm.zip -d $TEMP_DIR
|
|
||||||
cp ${TEMP_DIR}/${NSSM_VERSION}}/win64/nssm.exe ${OUTPUT_DIR}/nssm.exe
|
|
||||||
unzip ${TEMP_DIR}/ffmpeg.zip -d $TEMP_DIR
|
|
||||||
cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${OUTPUT_DIR}/ffmpeg.exe
|
|
||||||
cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffprobe.exe ${OUTPUT_DIR}/ffprobe.exe
|
|
||||||
rm -r ${TEMP_DIR}
|
|
||||||
cp ${ROOT}/deployment/win-generic/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1
|
|
||||||
cp ${ROOT}/deployment/win-generic/install.bat ${OUTPUT_DIR}/install.bat
|
|
||||||
mkdir -p ${PKG_DIR}
|
|
||||||
pushd ${OUTPUT_DIR}
|
|
||||||
${ARCHIVE_CMD} ${ROOT}/${PKG_DIR}/`basename "${OUTPUT_DIR}"`.zip .
|
|
||||||
popd
|
|
||||||
local EXIT_CODE=$?
|
|
||||||
if [ $EXIT_CODE -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[DONE] Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}' complete.${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[FAIL] Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}' FAILED.${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}[FAIL] Build artifacts do not exist for ${OUTPUT_DIR}. Run build.sh first.${NC}"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
source ../common.build.sh
|
source ../common.build.sh
|
||||||
|
|
||||||
VERSION=`get_version ../..`
|
VERSION=`get_version ../..`
|
||||||
|
|
||||||
package_win64 ../.. `pwd`/dist/jellyfin_${VERSION}
|
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
|
||||||
|
|
||||||
#TODO setup and maybe change above code to produce the Windows native zip format.
|
#TODO setup and maybe change above code to produce the Windows native zip format.
|
||||||
|
|||||||
@@ -1,45 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
package_win32() (
|
|
||||||
local NSSM_VERSION="nssm-2.24-101-g897c7ad"
|
|
||||||
local NSSM_URL="https://nssm.cc/ci/${NSSM_VERSION}.zip"
|
|
||||||
local FFMPEG_VERSION="ffmpeg-4.0.2-win32-static"
|
|
||||||
local FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win32/static/${FFMPEG_VERSION}.zip"
|
|
||||||
local ROOT=${1-$DEFAULT_ROOT}
|
|
||||||
local OUTPUT_DIR=${2-$DEFAULT_OUTPUT_DIR}
|
|
||||||
local PKG_DIR=${3-$DEFAULT_PKG_DIR}
|
|
||||||
local ARCHIVE_CMD="zip -r"
|
|
||||||
# Package portable build result
|
|
||||||
if [ -d ${OUTPUT_DIR} ]; then
|
|
||||||
echo -e "${CYAN}Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}'.${NC}"
|
|
||||||
local TEMP_DIR="$(mktemp -d)"
|
|
||||||
wget ${NSSM_URL} -O ${TEMP_DIR}/nssm.zip
|
|
||||||
wget ${FFMPEG_URL} -O ${TEMP_DIR}/ffmpeg.zip
|
|
||||||
unzip ${TEMP_DIR}/nssm.zip -d $TEMP_DIR
|
|
||||||
cp ${TEMP_DIR}/${NSSM_VERSION}/win32/nssm.exe ${OUTPUT_DIR}/nssm.exe
|
|
||||||
unzip ${TEMP_DIR}/ffmpeg.zip -d $TEMP_DIR
|
|
||||||
cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${OUTPUT_DIR}/ffmpeg.exe
|
|
||||||
cp ${TEMP_DIR}/${FFMPEG_VERSION}/bin/ffprobe.exe ${OUTPUT_DIR}/ffprobe.exe
|
|
||||||
rm -r ${TEMP_DIR}
|
|
||||||
cp ${ROOT}/deployment/win-generic/install-jellyfin.ps1 ${OUTPUT_DIR}/install-jellyfin.ps1
|
|
||||||
cp ${ROOT}/deployment/win-generic/install.bat ${OUTPUT_DIR}/install.bat
|
|
||||||
mkdir -p ${PKG_DIR}
|
|
||||||
pushd ${OUTPUT_DIR}
|
|
||||||
${ARCHIVE_CMD} ${ROOT}/${PKG_DIR}/`basename "${OUTPUT_DIR}"`.zip .
|
|
||||||
popd
|
|
||||||
local EXIT_CODE=$?
|
|
||||||
if [ $EXIT_CODE -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[DONE] Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}' complete.${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[FAIL] Packaging build in '${OUTPUT_DIR}' for `basename "${OUTPUT_DIR}"` to '${PKG_DIR}' with root '${ROOT}' FAILED.${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}[FAIL] Build artifacts do not exist for ${OUTPUT_DIR}. Run build.sh first.${NC}"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
source ../common.build.sh
|
source ../common.build.sh
|
||||||
|
|
||||||
VERSION=`get_version ../..`
|
VERSION=`get_version ../..`
|
||||||
|
|
||||||
package_win32 ../.. `pwd`/dist/jellyfin_${VERSION}
|
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
|
||||||
|
|
||||||
#TODO setup and maybe change above code to produce the Windows native zip format.
|
#TODO setup and maybe change above code to produce the Windows native zip format.
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0">
|
|
||||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
|
||||||
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
|
|
||||||
<Rule Id="SA1101" Action="None" />
|
|
||||||
<!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
|
|
||||||
<Rule Id="SA1200" Action="None" />
|
|
||||||
<!-- disable warning SA1309: Fields must not begin with an underscore -->
|
|
||||||
<Rule Id="SA1309" Action="None" />
|
|
||||||
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
|
|
||||||
<Rule Id="SA1633" Action="None" />
|
|
||||||
</Rules>
|
|
||||||
</RuleSet>
|
|
||||||
Reference in New Issue
Block a user