Compare commits

...

57 Commits

Author SHA1 Message Date
Andrew Rabert
e0315b5695 Merge pull request #702 from jellyfin/win-build
Fix debug build on windows
2019-01-25 08:36:21 -05:00
Bond-009
fd36bbede8 Fix debug build on windows 2019-01-25 12:42:45 +01:00
Andrew Rabert
b4fdfb562d Merge pull request #700 from jellyfin/dev
Dev sync
2019-01-24 23:03:54 -05:00
Andrew Rabert
48ad18d12b Merge pull request #452 from Bond-009/activitydb
Use EF Core for Activity database
2019-01-24 23:02:20 -05:00
Andrew Rabert
fe197415ca Merge pull request #657 from Bond-009/true
Remove conditions that are always true/false
2019-01-24 23:02:10 -05:00
Andrew Rabert
0231bd88a7 Merge pull request #672 from cvium/kill_unconditional_caching
Remove unconditional caching, modified since header and use ETags
2019-01-24 23:01:52 -05:00
Andrew Rabert
15806de2aa Merge pull request #681 from LogicalPhallacy/buildscriptfix
Fix Windows build script errors + pin ffmpeg to 4.0
2019-01-24 23:01:20 -05:00
Andrew Rabert
68a7ed6b7c Merge pull request #687 from Bond-009/stylecop
Fix some analyzer warnings
2019-01-24 23:01:07 -05:00
Andrew Rabert
48e1a55d9e Merge pull request #689 from Wuerfelbecher/fix-fedora-build
Fix RPM package build for fedora
2019-01-24 23:00:51 -05:00
Andrew Rabert
2c9e056d52 Merge pull request #699 from jellyfin/dev
Dev sync
2019-01-24 22:57:44 -05:00
Bond_009
8191efb90c Fix some analyzer warnings
Some changes:
* Don't omit braces
* Fix culture sensitive string compare
* Define accessibility functions

I restricted myself to these 5 files, for now :p
2019-01-24 20:14:19 +01:00
Joshua M. Boniface
1d7d4c5738 Merge pull request #686 from Bond-009/analyzers
Disable some StyleCop warnings
2019-01-24 14:07:01 -05:00
hawken
3701ce4037 Eliminate some compile warnings (#600)
* Remove some unused variables
* remove duplicate "using .." by sorting and deduping the list
* Remove things that already exist in the parent class (in one case I moved some documentation to the parent)
* EntityBodyData and and NextSearch were never set (only read), removed
* _timeout was never read, subsequently _timer became unused. part of a TODO timeout functionality that was not implemented yet
2019-01-24 18:56:43 +01:00
Claus Vium
b2ffad9ce9 Set Parent to current query Item if ParentId is empty (#680)
**Changes**
Fixed a faulty change in logic that occurred during the Christmas GPL nightmare.

I stepped through Emby 3.5.2 and stumbled over a difference in the query between Jellyfin and Emby, where in Emby, query.ParentId was set to the item itself. So I did a little digging in the old GPLv2 source and found this https://github.com/MediaBrowser/Emby/blob/3.4.1.8/MediaBrowser.Controller/Entities/Folder.cs#L720

**Issues**
Fixes #673
2019-01-24 18:48:37 +01:00
Vasily
f4015f82e0 Update jellyfin.ruleset
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-01-24 18:39:51 +01:00
Phallacy
cab7fa2671 added self to contributors list 2019-01-24 07:36:45 -08:00
Thomas Büttner
6a3ed5d519 Added macOS to the docker sudo exception 2019-01-24 16:24:00 +01:00
Thomas Büttner
f2d0d1f646 Make the docker group check BSD compatible 2019-01-24 15:24:04 +01:00
Phallacy
d6a729c119 parameterized ffmpeg and nssm stuff 2019-01-24 01:56:56 -08:00
Phallacy
4e7675e78f Updated Packaging Windows from Linux for parity 2019-01-24 01:42:34 -08:00
Bond_009
b6f5c33191 Disable SA1633 2019-01-23 23:51:46 +01:00
Bond_009
1cbcddfbd2 Disable SA1309 2019-01-23 23:30:38 +01:00
Bond_009
80d011bb4f Disable SA1200 2019-01-23 23:30:03 +01:00
Bond-009
59ff083f5d Add comment 2019-01-23 23:21:55 +01:00
Thomas Büttner
cc3b1e5cc8 Fix package build for fedora 2019-01-23 21:14:10 +01:00
Bond_009
58591da0b8 Disable warning for calling local function without this. prefixed 2019-01-23 19:17:45 +01:00
Bond_009
905a253ff5 Suffix async methods with Async 2019-01-23 19:09:34 +01:00
Bond_009
3cd31cadf8 No need to check if the dir exists 2019-01-23 18:34:34 +01:00
Bond_009
48da8f429e Return a true IEnumerable 2019-01-23 18:34:34 +01:00
Bond_009
8d9428ebdc Ensure DB exists 2019-01-23 18:34:34 +01:00
Bond_009
f6f0a8a481 Use EF Core for Activity database 2019-01-23 16:39:12 +01:00
Phallacy
a30687a15a Quick fix to erroring build scripts and pulling the supported ffmpeg 2019-01-23 00:41:38 -08:00
LogicalPhallacy
404bd04cbc Merge pull request #8 from jellyfin/master
rebase to latest master
2019-01-23 00:31:35 -08:00
Andrew Rabert
bd550ef996 Fix arm32 Docker 2019-01-22 20:25:41 -05:00
Andrew Rabert
becbad981c Merge pull request #535 from Bond-009/streambuilder
Clean up streambuilder
2019-01-22 19:40:59 -05:00
Andrew Rabert
3d01aa1ae4 Merge pull request #656 from hawken93/more_logging
Do some logging in MediaInfoService
2019-01-22 19:38:43 -05:00
Joshua M. Boniface
0fec7994a8 Merge pull request #663 from nvllsvm/tl
Use TagLibSharp Nuget package
2019-01-22 19:35:09 -05:00
Andrew Rabert
090fea21ea Merge pull request #668 from hawken93/prepare_for_reports_module
Return Audio objects from MusicAlbum.Tracks
2019-01-22 19:34:29 -05:00
Andrew Rabert
52dd570142 Merge pull request #671 from Tthecreator/dev
Set EnableRaisingEvents correctly
2019-01-22 19:31:33 -05:00
Bond_009
722120af74 Remove conditions that are always true/false 2019-01-22 23:49:51 +01:00
Claus Vium
df5e87409a Fix trim input 2019-01-22 22:40:06 +01:00
Claus Vium
94789860b1 Trim quotes from If-None-Match 2019-01-22 22:37:26 +01:00
Claus Vium
fd6d35e1d0 Remove unconditional caching, modified since header and use ETags 2019-01-22 21:18:48 +01:00
Tthecreator
cce90d2b56 removed excess newlines 2019-01-22 19:42:58 +01:00
Tthecreator
0b80902cc8 Set EnableRaisingEvents correctly for SubtitleEncoder 2019-01-22 16:52:26 +01:00
Tthecreator
189b99df16 Merge pull request #1 from jellyfin/dev
Update from jellyfin repo
2019-01-22 15:22:42 +00:00
hawken
490e22c790 Add comments to SqliteItemRepository to help out whoever is next 2019-01-22 12:20:36 +00:00
hawken
a356c1417a Change MusicAlbum.Tracks to return Audio items 2019-01-22 12:19:50 +00:00
hawken
07cba6cbcf Do some logging in MediaInfoService 2019-01-22 10:24:00 +00:00
Andrew Rabert
bc8a0eeead Use TagLibSharp Nuget package 2019-01-21 19:22:08 -05:00
Bond_009
2f8f9e6853 Address comments 2019-01-21 16:57:10 +01:00
Bond_009
06d9423f00 Clean up last bits 2019-01-21 16:48:45 +01:00
Bond_009
37be6c87eb Make ConditionProcessor static 2019-01-21 16:48:45 +01:00
Bond_009
c9b88ab741 Clean up streambuilder 2019-01-21 16:48:45 +01:00
LogicalPhallacy
8ff89fdc0c Merge pull request #5 from jellyfin/dev
Dev
2019-01-05 16:22:18 -08:00
Phallacy
4eaeee7be2 minor: copy install.bat for rename 2019-01-01 20:28:29 -08:00
LogicalPhallacy
443218e3f1 Merge pull request #4 from jellyfin/dev
Dev
2019-01-01 11:47:57 -08:00
56 changed files with 809 additions and 990 deletions

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "ThirdParty/taglib-sharp"]
path = ThirdParty/taglib-sharp
url = https://github.com/mono/taglib-sharp.git
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
path = MediaBrowser.WebDashboard/jellyfin-web
url = https://github.com/jellyfin/jellyfin-web.git

View File

@@ -15,6 +15,7 @@
- [cvium](https://github.com/cvium)
- [wtayl0r](https://github.com/wtayl0r)
- [TtheCreator](https://github.com/Tthecreator)
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
# Emby Contributors

View File

@@ -932,13 +932,7 @@ namespace Emby.Dlna.Didl
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = null;
// Finally, just use the image from the item
if (imageInfo == null)
{
imageInfo = GetImageInfo(item);
}
ImageDownloadInfo imageInfo = GetImageInfo(item);;
if (imageInfo == null)
{

View File

@@ -270,17 +270,10 @@ namespace Emby.Drawing
// create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
if (bitmap != null)
{
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin;
}
else
{
origin = GetSKEncodedOrigin(orientation);
}
origin = codec.EncodedOrigin;
return bitmap;
}

View File

@@ -30,8 +30,11 @@ namespace Emby.Naming.AudioBook
{
throw new ArgumentNullException(nameof(path));
}
if (IsDirectory)
if (IsDirectory) // TODO
{
return null;
}
var extension = Path.GetExtension(path) ?? string.Empty;
// Check supported extensions

View File

@@ -3,13 +3,16 @@
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\ThirdParty\taglib-sharp\src\taglib-sharp.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0-beta" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -92,18 +93,18 @@ namespace Emby.Server.Implementations.Activity
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
}
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
Type = NotificationType.CameraImageUploaded.ToString()
});
}
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
Type = NotificationType.UserLockedOut.ToString(),
@@ -111,9 +112,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
async void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
@@ -122,7 +123,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
async void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var item = e.MediaInfo;
@@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
@@ -153,7 +154,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var item = e.MediaInfo;
@@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType),
@@ -237,7 +238,7 @@ namespace Emby.Server.Implementations.Activity
return null;
}
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
async void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Activity
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionEnded",
@@ -263,11 +264,11 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
async void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{
var user = e.Argument.User;
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
Type = "AuthenticationSucceeded",
@@ -276,9 +277,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
async void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
Type = "AuthenticationFailed",
@@ -287,9 +288,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
@@ -297,27 +298,27 @@ namespace Emby.Server.Implementations.Activity
});
}
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
async void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
Type = "NamedConfigurationUpdated"
});
}
void _config_ConfigurationUpdated(object sender, EventArgs e)
async void _config_ConfigurationUpdated(object sender, EventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
Type = "ServerConfigurationUpdated"
});
}
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
async void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
Type = "UserPolicyUpdated",
@@ -325,18 +326,18 @@ namespace Emby.Server.Implementations.Activity
});
}
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
async void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
Type = "UserDeleted"
});
}
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
async void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
Type = "UserPasswordChanged",
@@ -344,9 +345,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
Type = "UserCreated",
@@ -354,9 +355,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
async void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitlesDownloaded",
@@ -365,7 +366,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
async void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@@ -382,7 +383,7 @@ namespace Emby.Server.Implementations.Activity
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionStarted",
@@ -391,9 +392,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(),
@@ -402,18 +403,18 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
Type = NotificationType.PluginUninstalled.ToString()
});
}
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
Type = NotificationType.PluginInstalled.ToString(),
@@ -421,11 +422,11 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
var installationInfo = e.InstallationInfo;
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(),
@@ -434,7 +435,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
{
var result = e.Result;
var task = e.Task;
@@ -461,7 +462,7 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
Type = NotificationType.TaskFailed.ToString(),
@@ -472,11 +473,11 @@ namespace Emby.Server.Implementations.Activity
}
}
private void CreateLogEntry(ActivityLogEntry entry)
private async Task CreateLogEntry(ActivityLogEntry entry)
{
try
{
_activityManager.Create(entry);
await _activityManager.CreateAsync(entry);
}
catch
{

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
@@ -26,20 +27,38 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
public void Create(ActivityLogEntry entry)
public async Task CreateAsync(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
_repo.Create(entry);
await _repo.CreateAsync(entry);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
public IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
var result = _repo.GetActivityLogEntries();
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
if (minDate.HasValue)
{
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);
@@ -50,12 +69,7 @@ namespace Emby.Server.Implementations.Activity
}
}
return result;
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
return result.AsEnumerable();
}
}
}

View File

@@ -1,310 +1,37 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using System.Threading.Tasks;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
using Microsoft.EntityFrameworkCore;
namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
public class ActivityRepository : DbContext, IActivityRepository
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
protected IFileSystem FileSystem { get; private set; }
protected string _dataDirPath;
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
public DbSet<ActivityLogEntry> ActivityLogs { get; set; }
public ActivityRepository(string dataDirPath)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
FileSystem = fileSystem;
_dataDirPath = dataDirPath;
}
public void Initialize()
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
// Ensure the dir exists
Directory.CreateDirectory(_dataDirPath);
FileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
optionsBuilder.UseSqlite($"Filename={Path.Combine(_dataDirPath, "activitylog.sqlite.db")}");
}
private void InitializeInternal()
public async Task CreateAsync(ActivityLogEntry entry)
{
using (var connection = CreateConnection())
{
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);
}
await ActivityLogs.AddAsync(entry);
await SaveChangesAsync();
}
private void TryMigrate(ManagedConnection connection)
{
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;
}
public IQueryable<ActivityLogEntry> GetActivityLogEntries()
=> ActivityLogs;
}
}

View File

@@ -709,7 +709,7 @@ namespace Emby.Server.Implementations
}
}
public void Init()
public async Task InitAsync()
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -739,7 +739,7 @@ namespace Emby.Server.Implementations
SetHttpLimit();
RegisterResources();
await RegisterResourcesAsync();
FindParts();
}
@@ -754,7 +754,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
protected void RegisterResources()
protected async Task RegisterResourcesAsync()
{
RegisterSingleInstance(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this);
@@ -931,7 +931,7 @@ namespace Emby.Server.Implementations
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager);
var activityLogRepo = GetActivityLogRepository();
var activityLogRepo = await GetActivityLogRepositoryAsync();
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
@@ -1146,11 +1146,11 @@ namespace Emby.Server.Implementations
return repo;
}
private IActivityRepository GetActivityLogRepository()
private async Task<IActivityRepository> GetActivityLogRepositoryAsync()
{
var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
var repo = new ActivityRepository(ServerConfigurationManager.ApplicationPaths.DataPath);
repo.Initialize();
await repo.Database.EnsureCreatedAsync();
return repo;
}

View File

@@ -681,22 +681,17 @@ namespace Emby.Server.Implementations.Channels
// Find the corresponding channel provider plugin
var channelProvider = GetChannelProvider(channel);
var user = query.User;
ChannelItemSortField? sortField = null;
var sortDescending = false;
var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel;
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
var itemsResult = await GetChannelItems(channelProvider,
user,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
sortField,
sortDescending,
null,
false,
cancellationToken)
.ConfigureAwait(false);
if (query.ParentId.Equals(Guid.Empty))
if (query.ParentId == Guid.Empty)
{
query.Parent = channel;
}

View File

@@ -4712,9 +4712,21 @@ namespace Emby.Server.Implementations.Data
continue;
}
var paramName = "@HasAnyProviderId" + index;
// 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;
// this is a search for the placeholder
hasProviderIds.Add("ProviderIds like " + paramName + "");
// this replaces the placeholder with a value, here: %key=val%
if (statement != null)
{
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");

View File

@@ -105,26 +105,22 @@ namespace Emby.Server.Implementations.Diagnostics
{
return _process.WaitForExit(timeMs);
}
public Task<bool> WaitForExitAsync(int timeMs)
{
//if (_process.WaitForExit(100))
//{
// return Task.FromResult(true);
//}
//timeMs -= 100;
timeMs = Math.Max(0, timeMs);
var tcs = new TaskCompletionSource<bool>();
var cancellationToken = new CancellationTokenSource(timeMs).Token;
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
if (HasExited)
{
return Task.FromResult(true);
}
timeMs = Math.Max(0, timeMs);
var tcs = new TaskCompletionSource<bool>();
var cancellationToken = new CancellationTokenSource(timeMs).Token;
_process.Exited += (sender, args) => tcs.TrySetResult(true);
cancellationToken.Register(() => tcs.TrySetResult(HasExited));

View File

@@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SimpleInjector" Version="4.4.2" />

View File

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer
private long RangeStart { get; set; }
private long RangeEnd { get; set; }
private long RangeLength { get; set; }
private long TotalContentLength { get; set; }
public long TotalContentLength { get; set; }
public Action OnComplete { get; set; }
public Action OnError { get; set; }

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
@@ -423,13 +424,11 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
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;
if (!noCache)
{
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
if (IsNotModified(requestContext, cacheKey))
{
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
@@ -442,7 +441,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
return null;
}
@@ -532,10 +531,11 @@ namespace Emby.Server.Implementations.HttpServer
public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
{
var cacheKey = options.CacheKey;
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))
{
var key = cacheKey.ToString("N");
@@ -554,8 +554,6 @@ namespace Emby.Server.Implementations.HttpServer
var factoryFn = options.ContentFactory;
var responseHeaders = options.ResponseHeaders;
//var requestedCompressionType = GetCompressionType(requestContext);
var rangeHeader = requestContext.Headers.Get("Range");
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
@@ -568,10 +566,21 @@ namespace Emby.Server.Implementations.HttpServer
};
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;
}
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;
if (!totalContentLength.HasValue)
@@ -594,6 +603,7 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
else
@@ -618,6 +628,7 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
}
@@ -630,16 +641,8 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the caching responseHeaders.
/// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, 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)
{
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
@@ -692,28 +695,15 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="lastDateModified">The last date modified.</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>
private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
{
//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");
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
// 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)
&& cacheKey.Equals(ifNoneMatch))
{

View File

@@ -469,7 +469,7 @@ namespace Emby.Server.Implementations.Library
}
// TODO: Don't hardcode this
var isAudio = false;
const bool isAudio = false;
try
{
@@ -480,9 +480,11 @@ namespace Emby.Server.Implementations.Library
else
{
// hack - these two values were taken from LiveTVMediaSourceProvider
var cacheKey = request.OpenToken;
string cacheKey = request.OpenToken;
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false);
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -491,6 +493,7 @@ namespace Emby.Server.Implementations.Library
AddMediaInfo(mediaSource, isAudio);
}
// TODO: @bond Fix
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.LogInformation("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);

View File

@@ -86,12 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (isBooksCollectionType)
{
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
return null;
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
@@ -145,36 +140,19 @@ 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)
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
var supportsMultiVersion = false;
const bool supportsMultiVersion = false;
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
// If we were supporting this we'd be checking filesFromOtherItems
var hasOtherItems = false;
if (!hasOtherItems)
{
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);
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
}
return null;
@@ -194,11 +172,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
leftOver.Add(child);
}
else if (IsIgnored(child.Name))
{
}
else
else if (!IsIgnored(child.Name))
{
files.Add(child);
}

View File

@@ -410,7 +410,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
// TODO: Allow GetMultiDiscMovie in here
var supportsMultiVersion = true;
const bool supportsMultiVersion = true;
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();

View File

@@ -153,16 +153,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
bool? isNamed = null;
bool? isOptimistic = null;
if (!isTvContentType)
{
isNamed = true;
isOptimistic = false;
}
var episodeInfo = episodeResolver.Resolve(fullName, false, isNamed, isOptimistic, fillExtendedInfo: false);
var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false);
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
{
return true;

View File

@@ -322,17 +322,14 @@ namespace Emby.Server.Implementations.Library
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
}
if (user != null)
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
throw new SecurityException("Forbidden.");
}
throw new SecurityException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
throw new SecurityException("User is not allowed access at this time.");
}
if (!user.IsParentalScheduleAllowed())
{
throw new SecurityException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@@ -463,26 +460,26 @@ namespace Emby.Server.Implementations.Library
{
user.Policy.InvalidLoginAttemptCount = newValue;
var maxCount = user.Policy.IsAdministrator ?
3 :
5;
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
// TODO: Fix
/*
var fireLockout = false;
if (newValue >= maxCount)
{
//_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
//user.Policy.IsDisabled = true;
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
user.Policy.IsDisabled = true;
//fireLockout = true;
}
fireLockout = true;
}*/
UpdateUserPolicy(user, user.Policy, false);
if (fireLockout)
/* if (fireLockout)
{
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
}
}*/
}
}

View File

@@ -1133,8 +1133,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IgnoreIndex = true
};
var isAudio = false;
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
@@ -1149,12 +1149,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
{
return Task.FromResult(0);
return Task.CompletedTask;
}
public Task ResetTuner(string id, CancellationToken cancellationToken)
{
return Task.FromResult(0);
return Task.CompletedTask;
}
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)

View File

@@ -175,12 +175,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
var videoStream = mediaSource.VideoStream;
string videoDecoder = null;
if (!string.IsNullOrEmpty(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
if (mediaSource.ReadAtNativeFramerate)
{

View File

@@ -354,10 +354,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int? height = null;
bool isInterlaced = true;
string videoCodec = null;
string audioCodec = null;
int? videoBitrate = null;
int? audioBitrate = null;
var isHd = channelInfo.IsHD ?? true;
@@ -427,20 +425,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
if (channelInfo != null)
if (string.IsNullOrWhiteSpace(videoCodec))
{
if (string.IsNullOrWhiteSpace(videoCodec))
{
videoCodec = channelInfo.VideoCodec;
}
audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
audioBitrate = isHd ? 448000 : 192000;
videoCodec = channelInfo.VideoCodec;
}
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
int? audioBitrate = isHd ? 448000 : 192000;
// normalize
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))

View File

@@ -27,6 +27,10 @@
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />

View File

@@ -99,7 +99,7 @@ namespace Jellyfin.Server
new SystemEvents(),
new NetworkManager(_loggerFactory, environmentInfo)))
{
appHost.Init();
await appHost.InitAsync();
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
@@ -108,7 +108,6 @@ namespace Jellyfin.Server
await appHost.RunStartupTasks();
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
@@ -167,7 +166,6 @@ namespace Jellyfin.Server
{
Directory.CreateDirectory(programDataPath);
}
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
@@ -224,7 +222,7 @@ namespace Jellyfin.Server
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
{
await rscstr.CopyToAsync(fstr);
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
}
var configuration = new ConfigurationBuilder()
@@ -334,11 +332,9 @@ namespace Jellyfin.Server
}
else
{
commandLineArgsString = string.Join(" ",
Environment.GetCommandLineArgs()
.Skip(1)
.Select(NormalizeCommandLineArgument)
);
commandLineArgsString = string.Join(
" ",
Environment.GetCommandLineArgs().Skip(1).Select(NormalizeCommandLineArgument));
}
_logger.LogInformation("Executable: {0}", module);

View File

@@ -15,19 +15,27 @@ namespace Jellyfin.SocketSharp
{
int ap = header.IndexOf(attr);
if (ap == -1)
{
return null;
}
ap += attr.Length;
if (ap >= header.Length)
{
return null;
}
char ending = header[ap];
if (ending != '"')
{
ending = ' ';
}
int end = header.IndexOf(ending, ap + 1);
if (end == -1)
{
return ending == '"' ? null : header.Substring(ap);
}
return header.Substring(ap + 1, end - ap - 1);
}
@@ -36,7 +44,9 @@ namespace Jellyfin.SocketSharp
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
{
return;
}
using (var requestStream = InputStream)
{
@@ -124,7 +134,9 @@ namespace Jellyfin.SocketSharp
{
string v = "\"" + value + "\"";
if (v.Length > 20)
{
v = v.Substring(0, 16) + "...\"";
}
string msg = string.Format("A potentially dangerous Request.{0} value was " +
"detected from the client ({1}={2}).", name, key, v);
@@ -135,21 +147,23 @@ namespace Jellyfin.SocketSharp
static void ValidateNameValueCollection(string name, QueryParamCollection coll)
{
if (coll == null)
{
return;
}
foreach (var pair in coll)
{
var key = pair.Name;
var val = pair.Value;
if (val != null && val.Length > 0 && IsInvalidString(val))
{
ThrowValidationException(name, key, val);
}
}
}
internal static bool IsInvalidString(string val)
{
return IsInvalidString(val, out var validationFailureIndex);
}
=> IsInvalidString(val, out var validationFailureIndex);
internal static bool IsInvalidString(string val, out int validationFailureIndex)
{
@@ -157,7 +171,9 @@ namespace Jellyfin.SocketSharp
int len = val.Length;
if (len < 2)
{
return false;
}
char current = val[0];
for (int idx = 1; idx < len; idx++)
@@ -195,10 +211,15 @@ namespace Jellyfin.SocketSharp
bool IsContentType(string ct, bool starts_with)
{
if (ct == null || ContentType == null) return false;
if (ct == null || ContentType == null)
{
return false;
}
if (starts_with)
{
return StrUtils.StartsWith(ContentType, ct, true);
}
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
}
@@ -231,7 +252,9 @@ namespace Jellyfin.SocketSharp
break;
}
else
{
value.Append((char)c);
}
}
if (c == -1)
{
@@ -240,22 +263,26 @@ namespace Jellyfin.SocketSharp
}
}
else if (c == '&')
{
AddRawKeyValue(form, key, value);
}
else
{
key.Append((char)c);
}
}
if (c == -1)
{
AddRawKeyValue(form, key, value);
}
}
}
}
}
void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
{
string decodedKey = WebUtility.UrlDecode(key.ToString());
form.Add(decodedKey,
WebUtility.UrlDecode(value.ToString()));
form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
key.Length = 0;
value.Length = 0;
@@ -271,7 +298,9 @@ namespace Jellyfin.SocketSharp
foreach (var pair in this)
{
if (result.Length > 0)
{
result.Append('&');
}
var key = pair.Name;
if (key != null && key.Length > 0)
@@ -314,33 +343,52 @@ namespace Jellyfin.SocketSharp
public override int Read(byte[] buffer, int dest_offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (dest_offset < 0)
{
throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
}
int len = buffer.Length;
if (dest_offset > len)
{
throw new ArgumentException("destination offset is beyond array size");
}
// reordered to avoid possible integer overflow
if (dest_offset > len - count)
{
throw new ArgumentException("Reading would overrun buffer");
}
if (count > end - position)
{
count = (int)(end - position);
}
if (count <= 0)
{
return 0;
}
s.Position = position;
int result = s.Read(buffer, dest_offset, count);
if (result > 0)
{
position += result;
}
else
{
position = end;
}
return result;
}
@@ -348,14 +396,20 @@ namespace Jellyfin.SocketSharp
public override int ReadByte()
{
if (position >= end)
{
return -1;
}
s.Position = position;
int result = s.ReadByte();
if (result < 0)
{
position = end;
}
else
{
position++;
}
return result;
}
@@ -380,7 +434,9 @@ namespace Jellyfin.SocketSharp
long virt = real - offset;
if (virt < 0 || virt > Length)
{
throw new ArgumentException();
}
position = s.Seek(real, SeekOrigin.Begin);
return position;
@@ -410,7 +466,9 @@ namespace Jellyfin.SocketSharp
set
{
if (value > Length)
throw new ArgumentOutOfRangeException();
{
throw new ArgumentOutOfRangeException(nameof(value));
}
position = Seek(value, SeekOrigin.Begin);
}
@@ -438,7 +496,7 @@ namespace Jellyfin.SocketSharp
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal sealed class StrUtils
internal static class StrUtils
{
public static bool StartsWith(string str1, string str2, bool ignore_case)
{
@@ -455,11 +513,15 @@ namespace Jellyfin.SocketSharp
{
int l2 = str2.Length;
if (l2 == 0)
{
return true;
}
int l1 = str1.Length;
if (l2 > l1)
{
return false;
}
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
@@ -493,7 +555,7 @@ namespace Jellyfin.SocketSharp
Encoding encoding;
StringBuilder sb;
const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
const byte LF = (byte)'\n', CR = (byte)'\r';
// See RFC 2046
// In the case of multipart entities, in which one or more different
@@ -520,7 +582,7 @@ namespace Jellyfin.SocketSharp
sb = new StringBuilder();
}
string ReadLine()
private string ReadLine()
{
// CRLF or LF are ok as line endings.
bool got_cr = false;
@@ -543,58 +605,86 @@ namespace Jellyfin.SocketSharp
}
if (got_cr)
{
sb.Length--;
}
return sb.ToString();
}
static string GetContentDispositionAttribute(string l, string name)
private static string GetContentDispositionAttribute(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
{
return null;
}
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
{
return null;
}
if (begin == end)
return "";
{
return string.Empty;
}
return l.Substring(begin, end - begin);
}
string GetContentDispositionAttributeWithEncoding(string l, string name)
private string GetContentDispositionAttributeWithEncoding(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
{
return null;
}
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
{
return null;
}
if (begin == end)
return "";
{
return string.Empty;
}
string temp = l.Substring(begin, end - begin);
byte[] source = new byte[temp.Length];
for (int i = temp.Length - 1; i >= 0; i--)
{
source[i] = (byte)temp[i];
}
return encoding.GetString(source, 0, source.Length);
}
bool ReadBoundary()
private bool ReadBoundary()
{
try
{
string line = ReadLine();
while (line == "")
while (line == string.Empty)
{
line = ReadLine();
}
if (line[0] != '-' || line[1] != '-')
{
return false;
}
if (!StrUtils.EndsWith(line, boundary, false))
{
return true;
}
}
catch
{
@@ -603,25 +693,31 @@ namespace Jellyfin.SocketSharp
return false;
}
string ReadHeaders()
private string ReadHeaders()
{
string s = ReadLine();
if (s == "")
if (s.Length == 0)
{
return null;
}
return s;
}
bool CompareBytes(byte[] orig, byte[] other)
private static bool CompareBytes(byte[] orig, byte[] other)
{
for (int i = orig.Length - 1; i >= 0; i--)
{
if (orig[i] != other[i])
{
return false;
}
}
return true;
}
long MoveToNextBoundary()
private long MoveToNextBoundary()
{
long retval = 0;
bool got_cr = false;
@@ -631,13 +727,18 @@ namespace Jellyfin.SocketSharp
while (true)
{
if (c == -1)
{
return -1;
}
if (state == 0 && c == LF)
{
retval = data.Position - 1;
if (got_cr)
{
retval--;
}
state = 1;
c = data.ReadByte();
}
@@ -650,7 +751,9 @@ namespace Jellyfin.SocketSharp
{
c = data.ReadByte();
if (c == -1)
{
return -1;
}
if (c != '-')
{
@@ -662,7 +765,9 @@ namespace Jellyfin.SocketSharp
int nread = data.Read(buffer, 0, buffer.Length);
int bl = buffer.Length;
if (nread != bl)
{
return -1;
}
if (!CompareBytes(boundary_bytes, buffer))
{
@@ -673,6 +778,7 @@ namespace Jellyfin.SocketSharp
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
@@ -690,12 +796,16 @@ namespace Jellyfin.SocketSharp
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
}
break;
}
else
@@ -711,7 +821,9 @@ namespace Jellyfin.SocketSharp
public Element ReadNextElement()
{
if (at_eof || ReadBoundary())
{
return null;
}
var elem = new Element();
string header;
@@ -734,19 +846,27 @@ namespace Jellyfin.SocketSharp
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
{
return null;
}
elem.Length = pos - start;
return elem;
}
static string StripPath(string path)
private static string StripPath(string path)
{
if (path == null || path.Length == 0)
{
return path;
}
if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
&& !path.StartsWith("\\\\", StringComparison.Ordinal))
{
return path;
}
return path.Substring(path.LastIndexOf('\\') + 1);
}
}

View File

@@ -83,15 +83,15 @@ namespace Jellyfin.SocketSharp
private void ProcessContext(HttpListenerContext context)
{
//InitTask(context, _disposeCancellationToken);
Task.Run(() => InitTask(context, _disposeCancellationToken));
var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
}
private void LogRequest(ILogger logger, HttpListenerRequest request)
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
logger.LogInformation("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
logger.LogInformation("{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@@ -196,7 +196,7 @@ namespace Jellyfin.SocketSharp
{
try
{
ctx.Response.StatusCode = 200;
ctx.Response.StatusCode = statusCode;
ctx.Response.Close();
}
catch (ObjectDisposedException)

View File

@@ -242,7 +242,6 @@ namespace Jellyfin.SocketSharp
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
}
public const string Xml = "application/xml";
private static string GetQueryStringContentType(IRequest httpReq)
{
var format = httpReq.QueryString["format"];
@@ -250,22 +249,40 @@ namespace Jellyfin.SocketSharp
{
const int formatMaxLength = 4;
var pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength) return null;
if (pi[0] == '/') pi = pi.Substring(1);
if (pi == null || pi.Length <= formatMaxLength)
{
return null;
}
if (pi[0] == '/')
{
pi = pi.Substring(1);
}
format = LeftPart(pi, '/');
if (format.Length > formatMaxLength) return null;
if (format.Length > formatMaxLength)
{
return null;
}
}
format = LeftPart(format, '.').ToLower();
if (format.Contains("json")) return "application/json";
if (format.Contains("xml")) return Xml;
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
{
return "application/json";
}
if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
{
return "application/xml";
}
return null;
}
public static string LeftPart(string strVal, char needle)
{
if (strVal == null) return null;
if (strVal == null)
{
return null;
}
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
@@ -283,14 +300,14 @@ namespace Jellyfin.SocketSharp
{
var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?");
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal);
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
this.pathInfo = GetPathInfo(
path,
mode,
mode ?? "");
mode ?? string.Empty);
}
else
{
@@ -307,18 +324,27 @@ namespace Jellyfin.SocketSharp
private static string GetPathInfo(string fullPath, string mode, string appPath)
{
var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
if (!string.IsNullOrEmpty(pathInfo))
{
return pathInfo;
}
//Wildcard mode relies on this to work out the handlerPath
pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
if (!string.IsNullOrEmpty(pathInfo))
{
return pathInfo;
}
return fullPath;
}
private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
{
if (mappedPathRoot == null) return null;
if (mappedPathRoot == null)
{
return null;
}
var sbPathInfo = new StringBuilder();
var fullPathParts = fullPath.Split('/');
@@ -345,7 +371,10 @@ namespace Jellyfin.SocketSharp
}
}
}
if (!pathRootFound) return null;
if (!pathRootFound)
{
return null;
}
var path = sbPathInfo.ToString();
return path.Length > 1 ? path.TrimEnd('/') : "/";
@@ -400,7 +429,10 @@ namespace Jellyfin.SocketSharp
public static Encoding GetEncoding(string contentTypeHeader)
{
var param = GetParameter(contentTypeHeader, "charset=");
if (param == null) return null;
if (param == null)
{
return null;
}
try
{
return Encoding.GetEncoding(param);
@@ -423,7 +455,9 @@ namespace Jellyfin.SocketSharp
if (httpFiles == null)
{
if (files == null)
return httpFiles = new IHttpFile[0];
{
return httpFiles = Array.Empty<IHttpFile>();
}
httpFiles = new IHttpFile[files.Count];
var i = 0;

View File

@@ -839,7 +839,7 @@ namespace MediaBrowser.Api.Library
{
try
{
_activityManager.Create(new ActivityLogEntry
_activityManager.CreateAsync(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
Type = "UserDownloadingContent",

View File

@@ -74,8 +74,19 @@ namespace MediaBrowser.Api.Playback
private readonly IUserManager _userManager;
private readonly IJsonSerializer _json;
private readonly IAuthorizationContext _authContext;
private readonly ILogger _logger;
public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, IJsonSerializer json, IAuthorizationContext authContext)
public MediaInfoService(
IMediaSourceManager mediaSourceManager,
IDeviceManager deviceManager,
ILibraryManager libraryManager,
IServerConfigurationManager config,
INetworkManager networkManager,
IMediaEncoder mediaEncoder,
IUserManager userManager,
IJsonSerializer json,
IAuthorizationContext authContext,
ILoggerFactory loggerFactory)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
@@ -86,6 +97,7 @@ namespace MediaBrowser.Api.Playback
_userManager = userManager;
_json = json;
_authContext = authContext;
_logger = loggerFactory.CreateLogger(nameof(MediaInfoService));
}
public object Get(GetBitrateTestBytes request)
@@ -165,7 +177,7 @@ namespace MediaBrowser.Api.Playback
var profile = request.DeviceProfile;
//Logger.Info("GetPostedPlaybackInfo profile: {0}", _json.SerializeToString(profile));
//Logger.LogInformation("GetPostedPlaybackInfo profile: {profile}", _json.SerializeToString(profile));
if (profile == null)
{
@@ -262,7 +274,7 @@ namespace MediaBrowser.Api.Playback
catch (Exception ex)
{
mediaSources = new List<MediaSourceInfo>();
// TODO Log exception
_logger.LogError(ex, "Could not find media sources for item id {id}", id);
// TODO PlaybackException ??
//result.ErrorCode = ex.ErrorCode;
}

View File

@@ -19,6 +19,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
@@ -75,7 +76,24 @@ namespace MediaBrowser.Api.Playback
[Authenticated]
public class UniversalAudioService : BaseApiService
{
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)
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,
ILoggerFactory loggerFactory)
{
ServerConfigurationManager = serverConfigurationManager;
UserManager = userManager;
@@ -93,6 +111,8 @@ namespace MediaBrowser.Api.Playback
ImageProcessor = imageProcessor;
NetworkManager = networkManager;
EnvironmentInfo = environmentInfo;
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger(nameof(UniversalAudioService));
}
protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
@@ -111,6 +131,8 @@ namespace MediaBrowser.Api.Playback
protected IImageProcessor ImageProcessor { get; private set; }
protected INetworkManager NetworkManager { get; private set; }
protected IEnvironmentInfo EnvironmentInfo { get; private set; }
private ILoggerFactory _loggerFactory;
private ILogger _logger;
public Task<object> Get(GetUniversalAudioStream request)
{
@@ -221,7 +243,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext)
var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext, _loggerFactory)
{
Request = Request
};

View File

@@ -131,7 +131,7 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
void Init();
Task InitAsync();
/// <summary>
/// Creates the instance.

View File

@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
/// <value>The tracks.</value>
[IgnoreDataMember]
public IEnumerable<BaseItem> Tracks => GetRecursiveChildren(i => i is Audio);
public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{

View File

@@ -357,7 +357,7 @@ namespace MediaBrowser.Controller.Entities
{
var list = new List<Tuple<StringBuilder, bool>>();
int thisMarker = 0, thisNumericChunk = 0;
int thisMarker = 0;
while (thisMarker < s1.Length)
{

View File

@@ -644,12 +644,9 @@ namespace MediaBrowser.Controller.Entities
return PostFilterAndSort(items, query, true);
}
if (!(this is UserRootFolder) && !(this is AggregateFolder))
if (!(this is UserRootFolder) && !(this is AggregateFolder) && query.ParentId == Guid.Empty)
{
if (!query.ParentId.Equals(Guid.Empty))
{
query.Parent = this;
}
query.Parent = this;
}
if (RequiresPostFiltering2(query))

View File

@@ -437,7 +437,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
UseShellExecute = false,
FileName = _mediaEncoder.EncoderPath,
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
EnableRaisingEvents = true,
IsHidden = true,
ErrorDialog = false
});
@@ -574,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
CreateNoWindow = true,
UseShellExecute = false,
EnableRaisingEvents = true,
FileName = _mediaEncoder.EncoderPath,
Arguments = processArgs,
IsHidden = true,

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Model.Activity
{
@@ -8,10 +9,8 @@ namespace MediaBrowser.Model.Activity
{
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
void Create(ActivityLogEntry entry);
Task CreateAsync(ActivityLogEntry entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
}
}

View File

@@ -1,12 +1,12 @@
using System;
using MediaBrowser.Model.Querying;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Model.Activity
{
public interface IActivityRepository
{
void Create(ActivityLogEntry entry);
Task CreateAsync(ActivityLogEntry entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit);
IQueryable<ActivityLogEntry> GetActivityLogEntries();
}
}

View File

@@ -66,21 +66,21 @@ namespace MediaBrowser.Model.Dlna
return MaxBitrate;
}
if (Profile != null)
if (Profile == null)
{
if (Context == EncodingContext.Static)
{
if (isAudio && Profile.MaxStaticMusicBitrate.HasValue)
{
return Profile.MaxStaticMusicBitrate;
}
return Profile.MaxStaticBitrate;
}
return Profile.MaxStreamingBitrate;
return null;
}
return null;
if (Context == EncodingContext.Static)
{
if (isAudio && Profile.MaxStaticMusicBitrate.HasValue)
{
return Profile.MaxStaticMusicBitrate;
}
return Profile.MaxStaticBitrate;
}
return Profile.MaxStreamingBitrate;
}
}
}

View File

@@ -5,9 +5,10 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{
public class ConditionProcessor
public static class ConditionProcessor
{
public bool IsVideoConditionSatisfied(ProfileCondition condition,
public static bool IsVideoConditionSatisfied(
ProfileCondition condition,
int? width,
int? height,
int? videoBitDepth,
@@ -64,7 +65,7 @@ namespace MediaBrowser.Model.Dlna
}
}
public bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
public static bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
{
switch (condition.Property)
{
@@ -77,7 +78,7 @@ namespace MediaBrowser.Model.Dlna
}
}
public bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
public static bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
{
switch (condition.Property)
{
@@ -94,7 +95,8 @@ namespace MediaBrowser.Model.Dlna
}
}
public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
public static bool IsVideoAudioConditionSatisfied(
ProfileCondition condition,
int? audioChannels,
int? audioBitrate,
int? audioSampleRate,
@@ -121,7 +123,7 @@ namespace MediaBrowser.Model.Dlna
}
}
private bool IsConditionSatisfied(ProfileCondition condition, int? currentValue)
private static bool IsConditionSatisfied(ProfileCondition condition, int? currentValue)
{
if (!currentValue.HasValue)
{
@@ -150,7 +152,7 @@ namespace MediaBrowser.Model.Dlna
return false;
}
private bool IsConditionSatisfied(ProfileCondition condition, string currentValue)
private static bool IsConditionSatisfied(ProfileCondition condition, string currentValue)
{
if (string.IsNullOrEmpty(currentValue))
{
@@ -175,7 +177,7 @@ namespace MediaBrowser.Model.Dlna
}
}
private bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
private static bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
{
if (!currentValue.HasValue)
{
@@ -199,7 +201,7 @@ namespace MediaBrowser.Model.Dlna
return false;
}
private bool IsConditionSatisfied(ProfileCondition condition, float currentValue)
private static bool IsConditionSatisfied(ProfileCondition condition, float currentValue)
{
if (currentValue <= 0)
{
@@ -227,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
return false;
}
private bool IsConditionSatisfied(ProfileCondition condition, double? currentValue)
private static bool IsConditionSatisfied(ProfileCondition condition, double? currentValue)
{
if (!currentValue.HasValue)
{
@@ -255,7 +257,7 @@ namespace MediaBrowser.Model.Dlna
return false;
}
private bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp? timestamp)
private static bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp? timestamp)
{
if (!timestamp.HasValue)
{

View File

@@ -188,12 +188,10 @@ namespace MediaBrowser.Model.Dlna
continue;
}
var conditionProcessor = new ConditionProcessor();
var anyOff = false;
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;
break;
@@ -235,12 +233,10 @@ namespace MediaBrowser.Model.Dlna
continue;
}
var conditionProcessor = new ConditionProcessor();
var anyOff = false;
foreach (var c in i.Conditions)
{
if (!conditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
if (!ConditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
{
anyOff = true;
break;
@@ -301,12 +297,10 @@ namespace MediaBrowser.Model.Dlna
continue;
}
var conditionProcessor = new ConditionProcessor();
var anyOff = false;
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;
break;

View File

@@ -93,19 +93,10 @@ namespace MediaBrowser.Model.Dlna
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
}
private StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
{
var sorted = SortMediaSources(streams, maxBitrate);
private static StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate)
=> SortMediaSources(streams, maxBitrate).FirstOrDefault();
foreach (StreamInfo stream in sorted)
{
return stream;
}
return null;
}
private StreamInfo[] SortMediaSources(List<StreamInfo> streams, long maxBitrate)
private static IOrderedEnumerable<StreamInfo> SortMediaSources(List<StreamInfo> streams, long maxBitrate)
{
return streams.OrderBy(i =>
{
@@ -151,25 +142,17 @@ namespace MediaBrowser.Model.Dlna
return 0;
}).ThenBy(streams.IndexOf).ToArray();
}).ThenBy(streams.IndexOf);
}
private TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
private static TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
{
switch (condition.Property)
{
case ProfileConditionValue.AudioBitrate:
if (condition.Condition == ProfileConditionType.LessThanEqual)
{
return TranscodeReason.AudioBitrateNotSupported;
}
return TranscodeReason.AudioBitrateNotSupported;
case ProfileConditionValue.AudioChannels:
if (condition.Condition == ProfileConditionType.LessThanEqual)
{
return TranscodeReason.AudioChannelsNotSupported;
}
return TranscodeReason.AudioChannelsNotSupported;
case ProfileConditionValue.AudioProfile:
@@ -246,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
}
}
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string unused1, DeviceProfile profile, DlnaProfileType type)
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string _, DeviceProfile profile, DlnaProfileType type)
{
if (string.IsNullOrEmpty(inputContainer))
{
@@ -266,12 +249,10 @@ namespace MediaBrowser.Model.Dlna
{
foreach (var directPlayProfile in profile.DirectPlayProfiles)
{
if (directPlayProfile.Type == type)
if (directPlayProfile.Type == type
&& directPlayProfile.SupportsContainer(format))
{
if (directPlayProfile.SupportsContainer(format))
{
return format;
}
return format;
}
}
}
@@ -282,9 +263,7 @@ namespace MediaBrowser.Model.Dlna
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
{
var transcodeReasons = new List<TranscodeReason>();
var playlistItem = new StreamInfo
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
@@ -313,18 +292,16 @@ namespace MediaBrowser.Model.Dlna
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
var directPlayMethods = directPlayInfo.Item1;
transcodeReasons.AddRange(directPlayInfo.Item2);
var transcodeReasons = directPlayInfo.Item2.ToList();
var conditionProcessor = new ConditionProcessor();
int? inputAudioChannels = audioStream?.Channels;
int? inputAudioBitrate = audioStream?.BitDepth;
int? inputAudioSampleRate = audioStream?.SampleRate;
int? inputAudioBitDepth = audioStream.BitDepth;
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)
if (directPlayMethods.Count() > 0)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
string audioCodec = audioStream?.Codec;
// Make sure audio codec profiles are satisfied
var conditions = new List<ProfileCondition>();
@@ -335,7 +312,7 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
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);
applyConditions = false;
@@ -345,10 +322,7 @@ namespace MediaBrowser.Model.Dlna
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
conditions.AddRange(i.Conditions);
}
}
}
@@ -356,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
bool all = true;
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);
var transcodeReason = GetTranscodeReasonForFailedCondition(c);
@@ -385,13 +359,12 @@ namespace MediaBrowser.Model.Dlna
TranscodingProfile transcodingProfile = null;
foreach (var i in options.Profile.TranscodingProfiles)
{
if (i.Type == playlistItem.MediaType && i.Context == options.Context)
if (i.Type == playlistItem.MediaType
&& i.Context == options.Context
&& _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
{
if (_transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
{
transcodingProfile = i;
break;
}
transcodingProfile = i;
break;
}
}
@@ -421,7 +394,7 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
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);
applyConditions = false;
@@ -463,7 +436,7 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
private long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
private static long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
{
if (item.Protocol == MediaProtocol.File)
{
@@ -473,66 +446,57 @@ namespace MediaBrowser.Model.Dlna
return options.GetMaxBitrate(isAudio);
}
private Tuple<List<PlayMethod>, List<TranscodeReason>> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
private (IEnumerable<PlayMethod>, IEnumerable<TranscodeReason>) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
var transcodeReasons = new List<TranscodeReason>();
DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
DirectPlayProfile directPlayProfile = null;
foreach (var i in options.Profile.DirectPlayProfiles)
if (directPlayProfile == null)
{
if (i.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(i, item, audioStream))
{
directPlayProfile = i;
break;
}
}
var playMethods = new List<PlayMethod>();
if (directPlayProfile != null)
{
// While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream)
{
if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
}
else
{
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
}
}
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
}
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");
return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
}
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 (item.SupportsDirectStream)
{
if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
}
else
{
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
}
}
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
}
else
{
transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
}
}
if (playMethods.Count > 0)
{
transcodeReasons.Clear();
@@ -542,41 +506,25 @@ namespace MediaBrowser.Model.Dlna
transcodeReasons = transcodeReasons.Distinct().ToList();
}
return new Tuple<List<PlayMethod>, List<TranscodeReason>>(playMethods, transcodeReasons);
return (playMethods, transcodeReasons);
}
private List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
private static List<TranscodeReason> GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
{
var list = new List<TranscodeReason>();
var containerSupported = false;
var audioSupported = false;
var videoSupported = false;
foreach (var profile in directPlayProfiles)
{
audioSupported = false;
videoSupported = false;
// Check container type
if (profile.SupportsContainer(item.Container))
{
containerSupported = true;
if (videoStream != null)
{
if (profile.SupportsVideoCodec(videoStream.Codec))
{
videoSupported = true;
}
}
videoSupported = videoStream != null && profile.SupportsVideoCodec(videoStream.Codec);
if (audioStream != null)
{
if (profile.SupportsAudioCodec(audioStream.Codec))
{
audioSupported = true;
}
}
audioSupported = audioStream != null && profile.SupportsAudioCodec(audioStream.Codec);
if (videoSupported && audioSupported)
{
@@ -585,6 +533,7 @@ namespace MediaBrowser.Model.Dlna
}
}
var list = new List<TranscodeReason>();
if (!containerSupported)
{
list.Add(TranscodeReason.ContainerNotSupported);
@@ -603,18 +552,17 @@ namespace MediaBrowser.Model.Dlna
return list;
}
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
{
int highestScore = -1;
foreach (var stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
if (stream.Type == MediaStreamType.Subtitle
&& stream.Score.HasValue
&& stream.Score.Value > highestScore)
{
if (stream.Score.Value > highestScore)
{
highestScore = stream.Score.Value;
}
highestScore = stream.Score.Value;
}
}
@@ -646,7 +594,7 @@ namespace MediaBrowser.Model.Dlna
return item.DefaultSubtitleStreamIndex;
}
private void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
private static void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
{
if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
{
@@ -686,12 +634,10 @@ namespace MediaBrowser.Model.Dlna
}
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))
{
if (int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out var transcodingMaxAudioChannels))
{
playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
}
playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
}
}
@@ -702,9 +648,7 @@ namespace MediaBrowser.Model.Dlna
throw new ArgumentNullException(nameof(item));
}
var transcodeReasons = new List<TranscodeReason>();
var playlistItem = new StreamInfo
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Video,
@@ -737,6 +681,8 @@ namespace MediaBrowser.Model.Dlna
isEligibleForDirectPlay,
isEligibleForDirectStream);
var transcodeReasons = new List<TranscodeReason>();
if (isEligibleForDirectPlay || isEligibleForDirectStream)
{
// See if it can be direct played
@@ -803,8 +749,6 @@ namespace MediaBrowser.Model.Dlna
SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
var conditionProcessor = new ConditionProcessor();
var isFirstAppliedCodecProfile = true;
foreach (var i in options.Profile.CodecProfiles)
{
@@ -813,26 +757,26 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
int? width = videoStream == null ? null : videoStream.Width;
int? height = videoStream == null ? null : videoStream.Height;
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
double? videoLevel = videoStream == null ? null : videoStream.Level;
string videoProfile = videoStream == null ? null : videoStream.Profile;
int? width = videoStream?.Width;
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 == 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;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? packetLength = videoStream?.PacketLength;
int? refFrames = videoStream?.RefFrames;
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
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);
applyConditions = false;
@@ -876,7 +820,7 @@ namespace MediaBrowser.Model.Dlna
int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate;
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);
applyConditions = false;
@@ -922,7 +866,7 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
private int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream)
private static int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream)
{
if ((audioStream.Channels ?? 0) >= 6)
{
@@ -932,33 +876,37 @@ namespace MediaBrowser.Model.Dlna
return 192000;
}
private int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item)
private static int GetAudioBitrate(string subProtocol, long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item)
{
var targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
string targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
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? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
int defaultBitrate;
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.
// Any attempts to transcode over 64k will fail
if (audioStream.Channels.HasValue &&
audioStream.Channels.Value == 1)
if (audioStream.Channels == 1
&& (audioStream.BitRate ?? 0) < 64000)
{
if ((audioStream.BitRate ?? 0) < 64000)
{
encoderAudioBitrateLimit = 64000;
}
encoderAudioBitrateLimit = 64000;
}
}
@@ -970,19 +918,17 @@ namespace MediaBrowser.Model.Dlna
return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
}
private int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
private static int GetMaxAudioBitrateForTotalBitrate(long totalBitrate)
{
if (totalBitrate <= 640000)
{
return 128000;
}
if (totalBitrate <= 2000000)
else if (totalBitrate <= 2000000)
{
return 384000;
}
if (totalBitrate <= 3000000)
else if (totalBitrate <= 3000000)
{
return 448000;
}
@@ -990,24 +936,25 @@ namespace MediaBrowser.Model.Dlna
return 640000;
}
private Tuple<PlayMethod?, List<TranscodeReason>> GetVideoDirectPlayProfile(VideoOptions options,
private (PlayMethod?, List<TranscodeReason>) GetVideoDirectPlayProfile(
VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
MediaStream audioStream,
bool isEligibleForDirectPlay,
bool isEligibleForDirectStream)
{
DeviceProfile profile = options.Profile;
if (options.ForceDirectPlay)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectPlay, new List<TranscodeReason>());
return (PlayMethod.DirectPlay, new List<TranscodeReason>());
}
if (options.ForceDirectStream)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
return (PlayMethod.DirectStream, new List<TranscodeReason>());
}
DeviceProfile profile = options.Profile;
// See if it can be direct played
DirectPlayProfile directPlay = null;
foreach (var i in profile.DirectPlayProfiles)
@@ -1025,7 +972,7 @@ namespace MediaBrowser.Model.Dlna
profile.Name ?? "Unknown Profile",
mediaSource.Path ?? "Unknown path");
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
}
string container = mediaSource.Container;
@@ -1033,8 +980,8 @@ namespace MediaBrowser.Model.Dlna
var conditions = new List<ProfileCondition>();
foreach (var i in profile.ContainerProfiles)
{
if (i.Type == DlnaProfileType.Video &&
i.ContainsContainer(container))
if (i.Type == DlnaProfileType.Video
&& i.ContainsContainer(container))
{
foreach (var c in i.Conditions)
{
@@ -1043,29 +990,27 @@ namespace MediaBrowser.Model.Dlna
}
}
var conditionProcessor = new ConditionProcessor();
int? width = videoStream == null ? null : videoStream.Width;
int? height = videoStream == null ? null : videoStream.Height;
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
double? videoLevel = videoStream == null ? null : videoStream.Level;
string videoProfile = videoStream == null ? null : videoStream.Profile;
int? width = videoStream?.Width;
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 == 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;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = 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;
int? audioBitrate = audioStream?.BitRate;
int? audioChannels = audioStream?.Channels;
string audioProfile = audioStream?.Profile;
int? audioSampleRate = audioStream?.SampleRate;
int? audioBitDepth = audioStream?.BitDepth;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? packetLength = videoStream?.PacketLength;
int? refFrames = videoStream?.RefFrames;
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
@@ -1073,20 +1018,20 @@ namespace MediaBrowser.Model.Dlna
// Check container 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);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
: new List<TranscodeReason>();
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
return (null, transcodeReasons);
}
}
string videoCodec = videoStream == null ? null : videoStream.Codec;
string videoCodec = videoStream?.Codec;
conditions = new List<ProfileCondition>();
foreach (var i in profile.CodecProfiles)
@@ -1096,7 +1041,7 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
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);
applyConditions = false;
@@ -1116,23 +1061,22 @@ namespace MediaBrowser.Model.Dlna
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);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
: new List<TranscodeReason>();
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
return (null, transcodeReasons);
}
}
if (audioStream != null)
{
string audioCodec = audioStream.Codec;
conditions = new List<ProfileCondition>();
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
@@ -1143,7 +1087,7 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
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);
applyConditions = false;
@@ -1163,26 +1107,26 @@ namespace MediaBrowser.Model.Dlna
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);
var transcodeReason = GetTranscodeReasonForFailedCondition(i);
var transcodeReasons = transcodeReason.HasValue
? new List<TranscodeReason> { transcodeReason.Value }
: new List<TranscodeReason> { };
: new List<TranscodeReason>();
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, transcodeReasons);
return (null, transcodeReasons);
}
}
}
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{
return new Tuple<PlayMethod?, List<TranscodeReason>>(PlayMethod.DirectStream, new List<TranscodeReason>());
return (PlayMethod.DirectStream, new List<TranscodeReason>());
}
return new Tuple<PlayMethod?, List<TranscodeReason>>(null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
return (null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
}
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
@@ -1197,7 +1141,8 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
private ValueTuple<bool, TranscodeReason?> IsEligibleForDirectPlay(MediaSourceInfo item,
private (bool directPlay, TranscodeReason? reason) IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
MediaStream subtitleStream,
VideoOptions options,
@@ -1210,21 +1155,23 @@ namespace MediaBrowser.Model.Dlna
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
_logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod);
return new ValueTuple<bool, TranscodeReason?>(false, TranscodeReason.SubtitleCodecNotSupported);
return (false, TranscodeReason.SubtitleCodecNotSupported);
}
}
var result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
if (result)
{
return new ValueTuple<bool, TranscodeReason?>(result, null);
}
return new ValueTuple<bool, TranscodeReason?>(result, TranscodeReason.ContainerBitrateExceedsLimit);
return (result, result ? (TranscodeReason?)null : TranscodeReason.ContainerBitrateExceedsLimit);
}
public static SubtitleProfile GetSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, string outputContainer, string transcodingSubProtocol)
public static SubtitleProfile GetSubtitleProfile(
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)))
{
@@ -1301,27 +1248,20 @@ namespace MediaBrowser.Model.Dlna
{
if (!string.IsNullOrEmpty(transcodingContainer))
{
var normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts"))
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts")
|| ContainerProfile.ContainsContainer(normalizedContainers, "mpegts")
|| ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mpegts"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
{
return false;
}
if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") ||
ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
else if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv")
|| ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
{
return true;
}
}
return false;
}
@@ -1388,22 +1328,22 @@ namespace MediaBrowser.Model.Dlna
return true;
}
var requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
var itemBitrate = item.Bitrate ??
40000000;
int itemBitrate = item.Bitrate ?? 40000000;
if (itemBitrate > requestedMaxBitrate)
{
_logger.LogInformation("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", itemBitrate.ToString(CultureInfo.InvariantCulture), requestedMaxBitrate.ToString(CultureInfo.InvariantCulture));
_logger.LogInformation("Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
playMethod, itemBitrate, requestedMaxBitrate);
return false;
}
return true;
}
private void ValidateInput(VideoOptions options)
private static void ValidateInput(VideoOptions options)
{
ValidateAudioInput(options);
@@ -1418,7 +1358,7 @@ namespace MediaBrowser.Model.Dlna
}
}
private void ValidateAudioInput(AudioOptions options)
private static void ValidateAudioInput(AudioOptions options)
{
if (options.ItemId.Equals(Guid.Empty))
{
@@ -1438,32 +1378,6 @@ 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)
{
foreach (ProfileCondition condition in conditions)
@@ -1838,7 +1752,7 @@ namespace MediaBrowser.Model.Dlna
}
}
private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
{
// Check container type
if (!profile.SupportsContainer(item.Container))
@@ -1847,7 +1761,7 @@ namespace MediaBrowser.Model.Dlna
}
// Check audio codec
string audioCodec = audioStream == null ? null : audioStream.Codec;
string audioCodec = audioStream?.Codec;
if (!profile.SupportsAudioCodec(audioCodec))
{
return false;
@@ -1865,20 +1779,16 @@ namespace MediaBrowser.Model.Dlna
}
// Check video codec
string videoCodec = videoStream == null ? null : videoStream.Codec;
string videoCodec = videoStream?.Codec;
if (!profile.SupportsVideoCodec(videoCodec))
{
return false;
}
// Check audio codec
if (audioStream != null)
if (audioStream != null && !profile.SupportsAudioCodec(audioStream.Codec))
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
if (!profile.SupportsAudioCodec(audioCodec))
{
return false;
}
return false;
}
return true;

View File

@@ -42,8 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
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}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}"
@@ -144,10 +142,6 @@ Global
{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.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.Build.0 = Debug|Any CPU
{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -47,7 +47,6 @@ namespace Mono.Nat
{
public event EventHandler<DeviceEventArgs> DeviceFound;
private DateTime nextSearch;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
@@ -98,11 +97,6 @@ namespace Mono.Nat
{
}
public DateTime NextSearch
{
get { return nextSearch; }
}
private void OnDeviceFound(DeviceEventArgs args)
{
if (DeviceFound != null)

View File

@@ -13,12 +13,6 @@ namespace SocketHttpListener
#endregion
#region Internal Fields
internal byte[] EntityBodyData;
#endregion
#region Protected Fields
protected const string CrLf = "\r\n";
@@ -37,18 +31,6 @@ namespace SocketHttpListener
#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 Version ProtocolVersion => _version;

View File

@@ -115,10 +115,6 @@ namespace SocketHttpListener
output.Append(CrLf);
var entity = EntityBody;
if (entity.Length > 0)
output.Append(entity);
return output.ToString();
}

View File

@@ -32,8 +32,6 @@ namespace SocketHttpListener.Net
int _reuses;
bool _contextBound;
bool secure;
int _timeout = 90000; // 90k ms for first request, 15k ms from then on
private Timer _timer;
IPEndPoint local_ep;
HttpListener _lastListener;
X509Certificate cert;
@@ -91,8 +89,6 @@ namespace SocketHttpListener.Net
public async Task Init()
{
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
if (ssl_stream != null)
{
var enableAsync = true;
@@ -162,14 +158,10 @@ namespace SocketHttpListener.Net
_buffer = new byte[BufferSize];
try
{
if (_reuses == 1)
_timeout = 15000;
//_timer.Change(_timeout, Timeout.Infinite);
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
}
catch
{
//_timer.Change(Timeout.Infinite, Timeout.Infinite);
CloseSocket();
Unbind();
}
@@ -216,7 +208,6 @@ namespace SocketHttpListener.Net
private void OnReadInternal(IAsyncResult ares)
{
//_timer.Change(Timeout.Infinite, Timeout.Infinite);
int nread = -1;
try
{

View File

@@ -24,7 +24,6 @@ namespace SocketHttpListener
{
#region Private Fields
private string _base64Key;
private Action _closeContext;
private CompressionMethod _compression;
private WebSocketContext _context;
@@ -35,20 +34,12 @@ namespace SocketHttpListener
private object _forMessageEventQueue;
private object _forSend;
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private Func<WebSocketContext, string>
_handshakeRequestChecker;
private Queue<MessageEventArgs> _messageEventQueue;
private uint _nonceCount;
private string _origin;
private bool _preAuth;
private string _protocol;
private string[] _protocols;
private Uri _proxyUri;
private volatile WebSocketState _readyState;
private AutoResetEvent _receivePong;
private bool _secure;
private Stream _stream;
private Uri _uri;
private const string _version = "13";
#endregion

View File

@@ -23,8 +23,7 @@ get_version()
(
local ROOT=${1-$DEFAULT_ROOT}
grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' \
| sed -E 's/.0$//'
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/'
)
# Run a build

View File

@@ -4,4 +4,15 @@ source ../common.build.sh
VERSION=`get_version ../..`
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
package_temporary_dir="`pwd`/pkg-dist-tmp"
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"

View File

@@ -18,10 +18,15 @@ output_dir="`pwd`/pkg-dist"
pkg_src_dir="`pwd`/pkg-src"
current_user="`whoami`"
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() {
set +o errexit
docker image rm $image_name --force
$docker_sudo docker image rm $image_name --force
rm -rf "$package_temporary_dir"
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
}
@@ -30,7 +35,7 @@ GNU_TAR=1
mkdir -p "$package_temporary_dir"
echo "Bundling all sources for RPM build."
tar \
--transform "s,^\.,jellyfin-${VERSION}" \
--transform "s,^\.,jellyfin-${VERSION}," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
@@ -42,10 +47,8 @@ tar \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-Jcvf \
"$package_temporary_dir/jellyfin-${VERSION}.tar.xz" \
-C "../.." \
./ || true && GNU_TAR=0
-zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./ || GNU_TAR=0
if [ $GNU_TAR -eq 0 ]; then
echo "The installed tar binary did not support --transform. Using workaround."
@@ -75,9 +78,9 @@ if [ $GNU_TAR -eq 0 ]; then
tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}"
fi
docker build ../.. -t "$image_name" -f ./Dockerfile
$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile
mkdir -p "$output_dir"
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_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 \;'
chown -R "$current_user" "$package_temporary_dir" \
|| sudo chown -R "$current_user" "$package_temporary_dir"
mv "$package_temporary_dir"/*.rpm "$output_dir"

View File

@@ -40,21 +40,21 @@ function Install-FFMPEG {
Write-Warning "FFMPEG will not be installed"
}elseif($Architecture -eq 'x64'){
Write-Verbose "Downloading 64 bit FFMPEG"
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.1-win64-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0.2-win64-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
}else{
Write-Verbose "Downloading 32 bit FFMPEG"
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.1-win32-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.0.2-win32-static.zip -UseBasicParsing -OutFile "$tempdir/fmmpeg.zip" | Write-Verbose
}
Expand-Archive "$tempdir/fmmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" | Write-Verbose
if($Architecture -eq 'x64'){
Write-Verbose "Copying Binaries to Jellyfin location"
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.1-win64-static/bin" | ForEach-Object {
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.0.2-win64-static/bin" | ForEach-Object {
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
}
}else{
Write-Verbose "Copying Binaries to Jellyfin location"
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.1-win32-static/bin" | ForEach-Object {
Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.0.2-win32-static/bin" | ForEach-Object {
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
}
}
@@ -102,8 +102,8 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
Write-Verbose "Starting NSSM Install"
Install-NSSM $InstallLocation $Architecture
}
Copy-Item .\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
Copy-Item .\install.bat $InstallLocation\install.bat
Copy-Item .\deployment\win-generic\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
Copy-Item .\deployment\win-generic\install.bat $InstallLocation\install.bat
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force
}

View File

@@ -1,9 +1,46 @@
#!/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
VERSION=`get_version ../..`
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
package_win64 ../.. `pwd`/dist/jellyfin_${VERSION}
#TODO setup and maybe change above code to produce the Windows native zip format.

View File

@@ -1,9 +1,45 @@
#!/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
VERSION=`get_version ../..`
package_portable ../.. `pwd`/dist/jellyfin_${VERSION}
package_win32 ../.. `pwd`/dist/jellyfin_${VERSION}
#TODO setup and maybe change above code to produce the Windows native zip format.

13
jellyfin.ruleset Normal file
View File

@@ -0,0 +1,13 @@
<?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>