diff options
293 files changed, 24739 insertions, 2946 deletions
diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index cba9c33b2..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 120 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 21 -# Issues with these labels will never be considered stale -exemptLabels: - - regression - - security - - dotnet-3.0-future - - roadmap - - future - - feature - - enhancement - - confirmed -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments. - - If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label. - - This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html). -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false - -# Disable automatic closing of pull requests -pulls: - daysUntilClose: false diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ea1d30cdf..cffe27f6c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: dotnet-version: '6.0.x' diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 3e9346840..59018c01c 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -14,10 +14,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 with: - ref: ${{ github.event.pull_request.head.ref }} + ref: ${{ github.event.pull_request.head.sha }} repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: dotnet-version: '6.0.x' - name: Generate openapi.json @@ -41,7 +41,7 @@ jobs: with: ref: ${{ github.base_ref }} - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: dotnet-version: '6.0.x' - name: Generate openapi.json diff --git a/.github/workflows/repo-stale.yaml b/.github/workflows/repo-stale.yaml new file mode 100644 index 000000000..63ded4140 --- /dev/null +++ b/.github/workflows/repo-stale.yaml @@ -0,0 +1,27 @@ +name: Issue Stale Check + +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + if: ${{ contains(github.repository, 'jellyfin/') }} + steps: + - uses: actions/stale@v4.1.0 + with: + repo-token: ${{ secrets.JF_BOT_TOKEN }} + days-before-stale: 120 + days-before-pr-stale: -1 + days-before-close: 21 + days-before-pr-close: -1 + exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed + stale-issue-label: stale + stale-issue-message: |- + This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments. + + If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label. + + This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html). diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 000000000..dc291e22a --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1 @@ +P:System.Threading.Tasks.Task`1.Result diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d52e13324..76b3c1dcb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -45,6 +45,7 @@ - [Froghut](https://github.com/Froghut) - [fruhnow](https://github.com/fruhnow) - [geilername](https://github.com/geilername) + - [GermanCoding](https://github.com/GermanCoding) - [gnattu](https://github.com/gnattu) - [GodTamIt](https://github.com/GodTamIt) - [grafixeyehero](https://github.com/grafixeyehero) @@ -76,6 +77,7 @@ - [mitchfizz05](https://github.com/mitchfizz05) - [MrTimscampi](https://github.com/MrTimscampi) - [n8225](https://github.com/n8225) + - [Nalsai](https://github.com/Nalsai) - [Narfinger](https://github.com/Narfinger) - [NathanPickard](https://github.com/NathanPickard) - [neilsb](https://github.com/neilsb) @@ -151,6 +153,7 @@ - [peterspenler](https://github.com/peterspenler) - [MBR-0001](https://github.com/MBR-0001) - [jonas-resch](https://github.com/jonas-resch) + - [vgambier](https://github.com/vgambier) # Emby Contributors @@ -217,3 +220,4 @@ - [olsh](https://github.com/olsh) - [lbenini](https://github.com/lbenini) - [gnuyent](https://github.com/gnuyent) + - [Matthew Jones](https://github.com/matthew-jones-uk) diff --git a/Directory.Build.props b/Directory.Build.props index b27782918..efcfb7224 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,4 +14,8 @@ <AnalysisMode>AllEnabledByDefault</AnalysisMode> </PropertyGroup> + <ItemGroup> + <AdditionalFiles Include="$(SolutionDir)/BannedSymbols.txt" /> + </ItemGroup> + </Project> diff --git a/Dockerfile b/Dockerfile index e133c0819..c3038b1d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,10 +22,10 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" # https://github.com/intel/compute-runtime/releases -ARG GMMLIB_VERSION=21.2.1 -ARG IGC_VERSION=1.0.8517 -ARG NEO_VERSION=21.35.20826 -ARG LEVEL_ZERO_VERSION=1.2.20826 +ARG GMMLIB_VERSION=22.0.2 +ARG IGC_VERSION=1.0.10395 +ARG NEO_VERSION=22.08.22549 +ARG LEVEL_ZERO_VERSION=1.3.22549 # Install dependencies: # mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. @@ -48,8 +48,7 @@ RUN apt-get update \ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \ + && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \ && dpkg -i *.deb \ && cd .. \ @@ -83,7 +82,7 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config /media +VOLUME /cache /config ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ diff --git a/Dockerfile.arm b/Dockerfile.arm index a46fa331d..b25e6039f 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -74,7 +74,7 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config /media +VOLUME /cache /config ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 1279c47f8..d0be834dd 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -65,7 +65,7 @@ COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web EXPOSE 8096 -VOLUME /cache /config /media +VOLUME /cache /config ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 0cd1a0daf..57170bb31 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -619,7 +619,7 @@ namespace Emby.Dlna.ContentDirectory var queryResult = folder.GetItems(query); - return ToResult(queryResult); + return ToResult(startIndex, queryResult); } /// <summary> @@ -642,7 +642,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(startIndex, result); } /// <summary> @@ -707,11 +707,10 @@ namespace Emby.Dlna.ContentDirectory serverItems = serverItems[..limit.Value]; } - return new QueryResult<ServerItem> - { - Items = serverItems, - TotalRecordCount = serverItems.Length - }; + return new QueryResult<ServerItem>( + startIndex, + serverItems.Length, + serverItems); } /// <summary> @@ -764,11 +763,10 @@ namespace Emby.Dlna.ContentDirectory array = array[..limit.Value]; } - return new QueryResult<ServerItem> - { - Items = array, - TotalRecordCount = array.Length - }; + return new QueryResult<ServerItem>( + startIndex, + array.Length, + array); } /// <summary> @@ -790,11 +788,10 @@ namespace Emby.Dlna.ContentDirectory .Select(i => new ServerItem(i, StubType.Folder)) .ToArray(); - return new QueryResult<ServerItem> - { - Items = items, - TotalRecordCount = totalRecordCount - }; + return new QueryResult<ServerItem>( + startIndex, + totalRecordCount, + items); } /// <summary> @@ -850,11 +847,10 @@ namespace Emby.Dlna.ContentDirectory serverItems = serverItems[..limit.Value]; } - return new QueryResult<ServerItem> - { - Items = serverItems, - TotalRecordCount = serverItems.Length - }; + return new QueryResult<ServerItem>( + startIndex, + serverItems.Length, + serverItems); } /// <summary> @@ -879,7 +875,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(query.StartIndex, result); } /// <summary> @@ -894,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(query.StartIndex, result); } /// <summary> @@ -914,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(query.StartIndex, result); } /// <summary> @@ -931,7 +927,7 @@ namespace Emby.Dlna.ContentDirectory query.AncestorIds = new[] { parent.Id }; var genresResult = _libraryManager.GetGenres(query); - return ToResult(genresResult); + return ToResult(query.StartIndex, genresResult); } /// <summary> @@ -947,7 +943,7 @@ namespace Emby.Dlna.ContentDirectory query.AncestorIds = new[] { parent.Id }; var genresResult = _libraryManager.GetMusicGenres(query); - return ToResult(genresResult); + return ToResult(query.StartIndex, genresResult); } /// <summary> @@ -963,7 +959,7 @@ namespace Emby.Dlna.ContentDirectory query.AncestorIds = new[] { parent.Id }; var artists = _libraryManager.GetAlbumArtists(query); - return ToResult(artists); + return ToResult(query.StartIndex, artists); } /// <summary> @@ -978,7 +974,7 @@ namespace Emby.Dlna.ContentDirectory query.OrderBy = Array.Empty<(string, SortOrder)>(); query.AncestorIds = new[] { parent.Id }; var artists = _libraryManager.GetArtists(query); - return ToResult(artists); + return ToResult(query.StartIndex, artists); } /// <summary> @@ -994,7 +990,7 @@ namespace Emby.Dlna.ContentDirectory query.AncestorIds = new[] { parent.Id }; query.IsFavorite = true; var artists = _libraryManager.GetArtists(query); - return ToResult(artists); + return ToResult(query.StartIndex, artists); } /// <summary> @@ -1010,7 +1006,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(query.StartIndex, result); } /// <summary> @@ -1034,7 +1030,7 @@ namespace Emby.Dlna.ContentDirectory new[] { parent }, query.DtoOptions); - return ToResult(result); + return ToResult(query.StartIndex, result); } /// <summary> @@ -1060,7 +1056,7 @@ namespace Emby.Dlna.ContentDirectory }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); - return ToResult(items); + return ToResult(query.StartIndex, items); } /// <summary> @@ -1087,7 +1083,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(startIndex, result); } /// <summary> @@ -1118,7 +1114,7 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(startIndex, result); } /// <summary> @@ -1145,33 +1141,34 @@ namespace Emby.Dlna.ContentDirectory var result = _libraryManager.GetItemsResult(query); - return ToResult(result); + return ToResult(startIndex, result); } /// <summary> /// Converts <see cref="IReadOnlyCollection{BaseItem}"/> into a <see cref="QueryResult{ServerItem}"/>. /// </summary> + /// <param name="startIndex">The start index.</param> /// <param name="result">An array of <see cref="BaseItem"/>.</param> /// <returns>A <see cref="QueryResult{ServerItem}"/>.</returns> - private static QueryResult<ServerItem> ToResult(IReadOnlyCollection<BaseItem> result) + private static QueryResult<ServerItem> ToResult(int? startIndex, IReadOnlyCollection<BaseItem> result) { var serverItems = result .Select(i => new ServerItem(i, null)) .ToArray(); - return new QueryResult<ServerItem> - { - TotalRecordCount = result.Count, - Items = serverItems - }; + return new QueryResult<ServerItem>( + startIndex, + result.Count, + serverItems); } /// <summary> /// Converts a <see cref="QueryResult{BaseItem}"/> to a <see cref="QueryResult{ServerItem}"/>. /// </summary> + /// <param name="startIndex">The index the result started at.</param> /// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> - private static QueryResult<ServerItem> ToResult(QueryResult<BaseItem> result) + private static QueryResult<ServerItem> ToResult(int? startIndex, QueryResult<BaseItem> result) { var length = result.Items.Count; var serverItems = new ServerItem[length]; @@ -1180,19 +1177,19 @@ namespace Emby.Dlna.ContentDirectory serverItems[i] = new ServerItem(result.Items[i], null); } - return new QueryResult<ServerItem> - { - TotalRecordCount = result.TotalRecordCount, - Items = serverItems - }; + return new QueryResult<ServerItem>( + startIndex, + result.TotalRecordCount, + serverItems); } /// <summary> /// Converts a query result to a <see cref="QueryResult{ServerItem}"/>. /// </summary> + /// <param name="startIndex">The start index.</param> /// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> - private static QueryResult<ServerItem> ToResult(QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result) + private static QueryResult<ServerItem> ToResult(int? startIndex, QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result) { var length = result.Items.Count; var serverItems = new ServerItem[length]; @@ -1201,11 +1198,10 @@ namespace Emby.Dlna.ContentDirectory serverItems[i] = new ServerItem(result.Items[i].Item, null); } - return new QueryResult<ServerItem> - { - TotalRecordCount = result.TotalRecordCount, - Items = serverItems - }; + return new QueryResult<ServerItem>( + startIndex, + result.TotalRecordCount, + serverItems); } /// <summary> diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f2a0548c2..192128c7e 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -343,7 +343,8 @@ namespace Emby.Dlna var fileOptions = AsyncFile.WriteOptions; fileOptions.Mode = FileMode.Create; fileOptions.PreallocationSize = length; - using (var fileStream = new FileStream(path, fileOptions)) + var fileStream = new FileStream(path, fileOptions); + await using (fileStream.ConfigureAwait(false)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index fd95041fe..bf0272e83 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -28,6 +28,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index d0c9df68e..e147cb977 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -180,7 +180,7 @@ namespace Emby.Dlna.PlayTo _currentPlaylistIndex = currentItemIndex; } - await SendNextTrackMessage(currentItemIndex, CancellationToken.None); + await SendNextTrackMessage(currentItemIndex, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -453,7 +453,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); return; } @@ -569,7 +569,7 @@ namespace Emby.Dlna.PlayTo streamInfo.TargetVideoCodecTag, streamInfo.IsTargetAVC); - return list.Count == 0 ? null : list[0]; + return list.FirstOrDefault(); } return null; @@ -654,7 +654,7 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); // Send a message to the DLNA device to notify what is the next track in the play list. - await SendNextTrackMessage(index, cancellationToken); + await SendNextTrackMessage(index, cancellationToken).ConfigureAwait(false); var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) @@ -771,7 +771,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); if (EnableClientSideSeek(newItem.StreamInfo)) { @@ -800,7 +800,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) { @@ -883,7 +883,7 @@ namespace Emby.Dlna.PlayTo private class StreamParams { - private MediaSourceInfo mediaSource; + private MediaSourceInfo _mediaSource; private IMediaSourceManager _mediaSourceManager; public Guid ItemId { get; set; } @@ -908,24 +908,22 @@ namespace Emby.Dlna.PlayTo public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken) { - if (mediaSource != null) + if (_mediaSource != null) { - return mediaSource; + return _mediaSource; } - var hasMediaSources = Item as IHasMediaSources; - - if (hasMediaSources == null) + if (Item is not IHasMediaSources) { return null; } if (_mediaSourceManager != null) { - mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); + _mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); } - return mediaSource; + return _mediaSource; } private static Guid GetItemId(string url) diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index b9a2c5d5d..9bcf6b2ea 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -27,6 +27,10 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 1c05aa916..d0a26b713 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -44,6 +44,12 @@ namespace Emby.Drawing } /// <inheritdoc /> + public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops) + { + throw new NotImplementedException(); + } + + /// <inheritdoc /> public string GetImageBlurHash(int xComp, int yComp, string path) { throw new NotImplementedException(); diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index eb211050f..de9c75da2 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -23,47 +23,60 @@ namespace Emby.Naming.Common { VideoFileExtensions = new[] { - ".m4v", + ".001", + ".3g2", ".3gp", - ".nsv", - ".ts", - ".ty", - ".strm", - ".rm", - ".rmvb", - ".ifo", - ".mov", - ".qt", - ".divx", - ".xvid", - ".bivx", - ".vob", - ".nrg", - ".img", - ".iso", - ".pva", - ".wmv", + ".amv", ".asf", ".asx", - ".ogm", - ".m2v", ".avi", ".bin", + ".bivx", + ".divx", + ".dv", ".dvr-ms", - ".mpg", - ".mpeg", - ".mp4", + ".f4v", + ".fli", + ".flv", + ".ifo", + ".img", + ".iso", + ".m2t", + ".m2ts", + ".m2v", + ".m4v", ".mkv", - ".avc", - ".vp3", - ".svq3", + ".mk3d", + ".mov", + ".mp2", + ".mp4", + ".mpe", + ".mpeg", + ".mpg", + ".mts", + ".mxf", + ".nrg", + ".nsv", ".nuv", + ".ogg", + ".ogm", + ".ogv", + ".pva", + ".qt", + ".rec", + ".rm", + ".rmvb", + ".svq3", + ".tp", + ".ts", + ".ty", ".viv", - ".dv", - ".fli", - ".flv", - ".001", - ".tp" + ".vob", + ".vp3", + ".webm", + ".wmv", + ".wtv", + ".xvid" }; VideoFlagDelimiters = new[] @@ -149,32 +162,20 @@ namespace Emby.Naming.Common SubtitleFileExtensions = new[] { + ".ass", + ".mks", + ".sami", + ".smi", ".srt", ".ssa", - ".ass", - ".sub" - }; - - SubtitleFlagDelimiters = new[] - { - '.' - }; - - SubtitleForcedFlags = new[] - { - "foreign", - "forced" - }; - - SubtitleDefaultFlags = new[] - { - "default" + ".sub", + ".vtt", }; AlbumStackingPrefixes = new[] { - "disc", "cd", + "disc", "disk", "vol", "volume" @@ -182,68 +183,101 @@ namespace Emby.Naming.Common AudioFileExtensions = new[] { - ".nsv", - ".m4a", - ".flac", + ".669", + ".3gp", + ".aa", ".aac", - ".strm", - ".pls", - ".rm", - ".mpa", - ".wav", - ".wma", - ".ogg", - ".opus", - ".mp3", - ".mp2", - ".mod", + ".aax", + ".ac3", + ".act", + ".adp", + ".adplug", + ".adx", + ".afc", ".amf", - ".669", + ".aif", + ".aiff", + ".alac", + ".amr", + ".ape", + ".ast", + ".au", + ".awb", + ".cda", + ".cue", ".dmf", + ".dsf", ".dsm", + ".dsp", + ".dts", + ".dvf", ".far", + ".flac", ".gdm", + ".gsm", + ".gym", + ".hps", ".imf", ".it", ".m15", + ".m4a", + ".m4b", + ".mac", ".med", + ".mka", + ".mmf", + ".mod", + ".mogg", + ".mp2", + ".mp3", + ".mpa", + ".mpc", + ".mpp", + ".mp+", + ".msv", + ".nmf", + ".nsf", + ".nsv", + ".oga", + ".ogg", ".okt", + ".opus", + ".pls", + ".ra", + ".rf64", + ".rm", ".s3m", - ".stm", ".sfx", + ".shn", + ".sid", + ".spc", + ".stm", + ".strm", ".ult", ".uni", - ".xm", - ".sid", - ".ac3", - ".dts", - ".cue", - ".aif", - ".aiff", - ".ape", - ".mac", - ".mpc", - ".mp+", - ".mpp", - ".shn", + ".vox", + ".wav", + ".wma", ".wv", - ".nsf", - ".spc", - ".gym", - ".adplug", - ".adx", - ".dsp", - ".adp", - ".ymf", - ".ast", - ".afc", - ".hps", + ".xm", ".xsp", - ".acc", - ".m4b", - ".oga", - ".dsf", - ".mka" + ".ymf" + }; + + MediaFlagDelimiters = new[] + { + '.' + }; + + MediaForcedFlags = new[] + { + "foreign", + "forced" + }; + + MediaDefaultFlags = new[] + { + "default" }; EpisodeExpressions = new[] @@ -648,45 +682,6 @@ namespace Emby.Naming.Common @"^\s*(?<name>[^ ].*?)\s*$" }; - var extensions = VideoFileExtensions.ToList(); - - extensions.AddRange(new[] - { - ".mkv", - ".m2t", - ".m2ts", - ".img", - ".iso", - ".mk3d", - ".ts", - ".rmvb", - ".mov", - ".avi", - ".mpg", - ".mpeg", - ".wmv", - ".mp4", - ".divx", - ".dvr-ms", - ".wtv", - ".ogm", - ".ogv", - ".asf", - ".m4v", - ".flv", - ".f4v", - ".3gp", - ".webm", - ".mts", - ".m2v", - ".rec", - ".mxf" - }); - - VideoFileExtensions = extensions - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); - MultipleEpisodeExpressions = new[] { @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @@ -718,29 +713,29 @@ namespace Emby.Naming.Common public string[] AudioFileExtensions { get; set; } /// <summary> - /// Gets or sets list of album stacking prefixes. + /// Gets or sets list of external media flag delimiters. /// </summary> - public string[] AlbumStackingPrefixes { get; set; } + public char[] MediaFlagDelimiters { get; set; } /// <summary> - /// Gets or sets list of subtitle file extensions. + /// Gets or sets list of external media forced flags. /// </summary> - public string[] SubtitleFileExtensions { get; set; } + public string[] MediaForcedFlags { get; set; } /// <summary> - /// Gets or sets list of subtitles flag delimiters. + /// Gets or sets list of external media default flags. /// </summary> - public char[] SubtitleFlagDelimiters { get; set; } + public string[] MediaDefaultFlags { get; set; } /// <summary> - /// Gets or sets list of subtitle forced flags. + /// Gets or sets list of album stacking prefixes. /// </summary> - public string[] SubtitleForcedFlags { get; set; } + public string[] AlbumStackingPrefixes { get; set; } /// <summary> - /// Gets or sets list of subtitle default flags. + /// Gets or sets list of subtitle file extensions. /// </summary> - public string[] SubtitleDefaultFlags { get; set; } + public string[] SubtitleFileExtensions { get; set; } /// <summary> /// Gets or sets list of episode regular expressions. diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 433ad137b..781c99ae2 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -47,6 +47,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs new file mode 100644 index 000000000..3bde3a1cf --- /dev/null +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Jellyfin.Extensions; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; + +namespace Emby.Naming.ExternalFiles +{ + /// <summary> + /// External media file parser class. + /// </summary> + public class ExternalPathParser + { + private readonly NamingOptions _namingOptions; + private readonly DlnaProfileType _type; + private readonly ILocalizationManager _localizationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ExternalPathParser"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param> + /// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param> + public ExternalPathParser(NamingOptions namingOptions, ILocalizationManager localizationManager, DlnaProfileType type) + { + _localizationManager = localizationManager; + _namingOptions = namingOptions; + _type = type; + } + + /// <summary> + /// Parse filename and extract information. + /// </summary> + /// <param name="path">Path to file.</param> + /// <param name="extraString">Part of the filename only containing the extra information.</param> + /// <returns>Returns null or an <see cref="ExternalPathParserResult"/> object if parsing is successful.</returns> + public ExternalPathParserResult? ParseFile(string path, string? extraString) + { + if (path.Length == 0) + { + return null; + } + + var extension = Path.GetExtension(path); + if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + && !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + var pathInfo = new ExternalPathParserResult(path); + + if (string.IsNullOrEmpty(extraString)) + { + return pathInfo; + } + + foreach (var separator in _namingOptions.MediaFlagDelimiters) + { + var languageString = extraString; + var titleString = string.Empty; + const int SeparatorLength = 1; + + while (languageString.Length > 0) + { + int lastSeparator = languageString.LastIndexOf(separator); + + if (lastSeparator == -1) + { + break; + } + + string currentSlice = languageString[lastSeparator..]; + string currentSliceWithoutSeparator = currentSlice[SeparatorLength..]; + + if (_namingOptions.MediaDefaultFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) + { + pathInfo.IsDefault = true; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + languageString = languageString[..lastSeparator]; + continue; + } + + if (_namingOptions.MediaForcedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) + { + pathInfo.IsForced = true; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + languageString = languageString[..lastSeparator]; + continue; + } + + // Try to translate to three character code + var culture = _localizationManager.FindLanguageInfo(currentSliceWithoutSeparator); + + if (culture != null && pathInfo.Language == null) + { + pathInfo.Language = culture.ThreeLetterISOLanguageName; + extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); + } + else + { + titleString = currentSlice + titleString; + } + + languageString = languageString[..lastSeparator]; + } + + pathInfo.Title = titleString.Length >= SeparatorLength ? titleString[SeparatorLength..] : null; + } + + return pathInfo; + } + } +} diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/ExternalFiles/ExternalPathParserResult.cs index 1fb2e0dc8..1cc773a2e 100644 --- a/Emby.Naming/Subtitles/SubtitleInfo.cs +++ b/Emby.Naming/ExternalFiles/ExternalPathParserResult.cs @@ -1,17 +1,17 @@ -namespace Emby.Naming.Subtitles +namespace Emby.Naming.ExternalFiles { /// <summary> - /// Class holding information about subtitle. + /// Class holding information about external files. /// </summary> - public class SubtitleInfo + public class ExternalPathParserResult { /// <summary> - /// Initializes a new instance of the <see cref="SubtitleInfo"/> class. + /// Initializes a new instance of the <see cref="ExternalPathParserResult"/> class. /// </summary> /// <param name="path">Path to file.</param> - /// <param name="isDefault">Is subtitle default.</param> - /// <param name="isForced">Is subtitle forced.</param> - public SubtitleInfo(string path, bool isDefault, bool isForced) + /// <param name="isDefault">Is default.</param> + /// <param name="isForced">Is forced.</param> + public ExternalPathParserResult(string path, bool isDefault = false, bool isForced = false) { Path = path; IsDefault = isDefault; @@ -31,6 +31,12 @@ namespace Emby.Naming.Subtitles public string? Language { get; set; } /// <summary> + /// Gets or sets the title. + /// </summary> + /// <value>The title.</value> + public string? Title { get; set; } + + /// <summary> /// Gets or sets a value indicating whether this instance is default. /// </summary> /// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value> diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs deleted file mode 100644 index 5809c512a..000000000 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using Emby.Naming.Common; -using Jellyfin.Extensions; - -namespace Emby.Naming.Subtitles -{ - /// <summary> - /// Subtitle Parser class. - /// </summary> - public class SubtitleParser - { - private readonly NamingOptions _options; - - /// <summary> - /// Initializes a new instance of the <see cref="SubtitleParser"/> class. - /// </summary> - /// <param name="options"><see cref="NamingOptions"/> object containing SubtitleFileExtensions, SubtitleDefaultFlags, SubtitleForcedFlags and SubtitleFlagDelimiters.</param> - public SubtitleParser(NamingOptions options) - { - _options = options; - } - - /// <summary> - /// Parse file to determine if is subtitle and <see cref="SubtitleInfo"/>. - /// </summary> - /// <param name="path">Path to file.</param> - /// <returns>Returns null or <see cref="SubtitleInfo"/> object if parsing is successful.</returns> - public SubtitleInfo? ParseFile(string path) - { - if (path.Length == 0) - { - return null; - } - - var extension = Path.GetExtension(path); - if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - var flags = GetFlags(path); - var info = new SubtitleInfo( - path, - _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)), - _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase))); - - var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase) - && !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - // Should have a name, language and file extension - if (parts.Count >= 3) - { - info.Language = parts[^2]; - } - - return info; - } - - private string[] GetFlags(string path) - { - // Note: the tags need be surrounded be either a space ( ), hyphen -, dot . or underscore _. - - var file = Path.GetFileName(path); - - return file.Split(_options.SubtitleFlagDelimiters, StringSplitOptions.RemoveEmptyEntries); - } - } -} diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 7fd2e9bb4..fa7709f2a 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -23,6 +23,10 @@ <!-- Code analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 4964265c9..36419decf 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -26,6 +26,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 814c10196..82294644b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -44,9 +44,9 @@ using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; -using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; +using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; using MediaBrowser.Common; @@ -103,6 +103,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; namespace Emby.Server.Implementations @@ -149,7 +150,7 @@ namespace Emby.Server.Implementations /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param> - public ApplicationHost( + protected ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, @@ -184,6 +185,11 @@ namespace Emby.Server.Implementations public event EventHandler HasPendingRestartChanged; /// <summary> + /// Gets the value of the PublishedServerUrl setting. + /// </summary> + private string PublishedServerUrl => _startupConfig[AddressOverrideKey]; + + /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> public bool CanSelfRestart => _startupOptions.RestartPath != null; @@ -259,11 +265,6 @@ namespace Emby.Server.Implementations /// </summary> public int HttpsPort { get; private set; } - /// <summary> - /// Gets the value of the PublishedServerUrl setting. - /// </summary> - public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; - /// <inheritdoc /> public Version ApplicationVersion { get; } @@ -999,6 +1000,9 @@ namespace Emby.Server.Implementations // Network yield return typeof(NetworkManager).Assembly; + // Hls + yield return typeof(DynamicHlsPlaylistGenerator).Assembly; + foreach (var i in GetAssembliesWithPartsInternal()) { yield return i; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 43c8a451b..09429c73f 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Channels /// <summary> /// The LiveTV channel manager. /// </summary> - public class ChannelManager : IChannelManager + public class ChannelManager : IChannelManager, IDisposable { private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; @@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.Channels private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="ChannelManager"/> class. @@ -264,11 +265,10 @@ namespace Emby.Server.Implementations.Channels } } - return new QueryResult<Channel> - { - Items = all, - TotalRecordCount = totalCount - }; + return new QueryResult<Channel>( + query.StartIndex, + totalCount, + all); } /// <inheritdoc /> @@ -285,11 +285,10 @@ namespace Emby.Server.Implementations.Channels // TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues. var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); - var result = new QueryResult<BaseItemDto> - { - Items = returnItems, - TotalRecordCount = internalResult.TotalRecordCount - }; + var result = new QueryResult<BaseItemDto>( + query.StartIndex, + internalResult.TotalRecordCount, + returnItems); return result; } @@ -333,7 +332,7 @@ namespace Emby.Server.Implementations.Channels private Channel GetChannelEntity(IChannel channel) { - return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result; + return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).GetAwaiter().GetResult(); } private MediaSourceInfo[] GetSavedMediaSources(BaseItem item) @@ -620,11 +619,10 @@ namespace Emby.Server.Implementations.Channels var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User); - var result = new QueryResult<BaseItemDto> - { - Items = returnItems, - TotalRecordCount = totalRecordCount - }; + var result = new QueryResult<BaseItemDto>( + query.StartIndex, + totalRecordCount, + returnItems); return result; } @@ -786,11 +784,10 @@ namespace Emby.Server.Implementations.Channels var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User); - var result = new QueryResult<BaseItemDto> - { - Items = returnItems, - TotalRecordCount = internalResult.TotalRecordCount - }; + var result = new QueryResult<BaseItemDto>( + query.StartIndex, + internalResult.TotalRecordCount, + returnItems); return result; } @@ -1217,5 +1214,31 @@ namespace Emby.Server.Implementations.Channels return result; } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _resourcePool?.Dispose(); + } + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index e5dde48d8..cfd08e653 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Channels public string Key => "RefreshInternetChannels"; /// <inheritdoc /> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var manager = (ChannelManager)_channelManager; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5ab9e02fe..b3b383bfd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -317,11 +317,6 @@ namespace Emby.Server.Implementations.Data IImageProcessor imageProcessor) : base(logger) { - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } - _config = config; _appHost = appHost; _localization = localization; @@ -334,9 +329,6 @@ namespace Emby.Server.Implementations.Data } /// <inheritdoc /> - public string Name => "SQLite"; - - /// <inheritdoc /> protected override int? CacheSize => 20000; /// <inheritdoc /> @@ -573,22 +565,6 @@ namespace Emby.Server.Implementations.Data userDataRepo.Initialize(userManager, WriteLock, WriteConnection); } - /// <summary> - /// Save a standard item in the repo. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception> - public void SaveItem(BaseItem item, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - SaveItems(new[] { item }, cancellationToken); - } - public void SaveImages(BaseItem item) { if (item == null) @@ -605,7 +581,7 @@ namespace Emby.Server.Implementations.Data { using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { - saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); + saveImagesStatement.TryBind("@Id", item.Id); saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); saveImagesStatement.MoveNext(); @@ -750,7 +726,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -780,7 +756,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); var parentId = item.ParentId; - if (parentId.Equals(Guid.Empty)) + if (parentId.Equals(default)) { saveItemStatement.TryBindNull("@ParentId"); } @@ -975,7 +951,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBind("@SeasonName", episode.SeasonName); - var nullableSeasonId = episode.SeasonId == Guid.Empty ? (Guid?)null : episode.SeasonId; + var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId; saveItemStatement.TryBind("@SeasonId", nullableSeasonId); } @@ -987,7 +963,7 @@ namespace Emby.Server.Implementations.Data if (item is IHasSeries hasSeries) { - var nullableSeriesId = hasSeries.SeriesId.Equals(Guid.Empty) ? (Guid?)null : hasSeries.SeriesId; + var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId; saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); @@ -1060,7 +1036,7 @@ namespace Emby.Server.Implementations.Data } Guid ownerId = item.OwnerId; - if (ownerId == Guid.Empty) + if (ownerId.Equals(default)) { saveItemStatement.TryBindNull("@OwnerId"); } @@ -1198,13 +1174,15 @@ namespace Emby.Server.Implementations.Data bldr.Append(Delimiter) // Replace delimiters with other characters. // This can be removed when we migrate to a proper DB. - .Append(hash.Replace('*', '/').Replace('|', '\\')); + .Append(hash.Replace(Delimiter, '/').Replace('|', '\\')); } } internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value) { - var nextSegment = value.IndexOf('*'); + const char Delimiter = '*'; + + var nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { return null; @@ -1212,7 +1190,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> path = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { return null; @@ -1220,7 +1198,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> dateModified = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { nextSegment = value.Length; @@ -1257,7 +1235,7 @@ namespace Emby.Server.Implementations.Data if (nextSegment + 1 < value.Length - 1) { value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1 || nextSegment == value.Length) { return image; @@ -1266,7 +1244,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> widthSpan = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { nextSegment = value.Length; @@ -1292,7 +1270,7 @@ namespace Emby.Server.Implementations.Data var c = value[i]; blurHashSpan[i] = c switch { - '/' => '*', + '/' => Delimiter, '\\' => '|', _ => c }; @@ -1314,7 +1292,7 @@ namespace Emby.Server.Implementations.Data /// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception> public BaseItem RetrieveItem(Guid id) { - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -2086,7 +2064,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - if (id.Equals(Guid.Empty)) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -2492,12 +2470,12 @@ namespace Emby.Server.Implementations.Data searchTerm = GetCleanValue(searchTerm); var commandText = statement.SQL; - if (commandText.IndexOf("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); } - if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SearchTermContains", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); } @@ -2514,17 +2492,17 @@ namespace Emby.Server.Implementations.Data var commandText = statement.SQL; - if (commandText.IndexOf("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@ItemOfficialRating", item.OfficialRating); } - if (commandText.IndexOf("@ItemProductionYear", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@ItemProductionYear", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0); } - if (commandText.IndexOf("@SimilarItemId", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SimilarItemId", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SimilarItemId", item.Id); } @@ -2758,12 +2736,12 @@ namespace Emby.Server.Implementations.Data foreach (var providerId in newItem.ProviderIds) { - if (providerId.Key == MetadataProvider.TmdbCollection.ToString()) + if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal)) { continue; } - if (item.GetProviderId(providerId.Key) == providerId.Value) + if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal)) { if (newItem.SourceType == SourceType.Library) { @@ -2810,11 +2788,10 @@ namespace Emby.Server.Implementations.Data if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) { var returnList = GetItemList(query); - return new QueryResult<BaseItem> - { - Items = returnList, - TotalRecordCount = returnList.Count - }; + return new QueryResult<BaseItem>( + query.StartIndex, + returnList.Count, + returnList); } var now = DateTime.UtcNow; @@ -2978,6 +2955,7 @@ namespace Emby.Server.Implementations.Data ReadTransactionMode); } + result.StartIndex = query.StartIndex ?? 0; result.Items = list; return result; } @@ -3185,220 +3163,6 @@ namespace Emby.Server.Implementations.Data return list; } - public List<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - CheckDisposed(); - - var now = DateTime.UtcNow; - - var columns = new List<string> { "guid", "path" }; - SetFinalColumnsToSelect(query, columns); - var commandText = "select " + string.Join(',', columns) + FromText; - - var whereClauses = GetWhereClauses(query, null); - if (whereClauses.Count != 0) - { - commandText += " where " + string.Join(" AND ", whereClauses); - } - - commandText += GetGroupBy(query) - + GetOrderByText(query); - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var list = new List<Tuple<Guid, string>>(); - using (var connection = GetConnection(true)) - { - using (var statement = PrepareStatement(connection, commandText)) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - // Running this again will bind the params - GetWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - var id = row.GetGuid(0); - - row.TryGetString(1, out var path); - - list.Add(new Tuple<Guid, string>(id, path)); - } - } - } - - LogQueryTime("GetItemIdsWithPath", commandText, now); - - return list; - } - - public QueryResult<Guid> GetItemIds(InternalItemsQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - CheckDisposed(); - - if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) - { - var returnList = GetItemIdsList(query); - return new QueryResult<Guid> - { - Items = returnList, - TotalRecordCount = returnList.Count - }; - } - - var now = DateTime.UtcNow; - - var columns = new List<string> { "guid" }; - SetFinalColumnsToSelect(query, columns); - var commandText = "select " - + string.Join(',', columns) - + FromText - + GetJoinUserDataText(query); - - var whereClauses = GetWhereClauses(query, null); - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses); - - commandText += whereText - + GetGroupBy(query) - + GetOrderByText(query); - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; - - var statementTexts = new List<string>(); - if (!isReturningZeroItems) - { - statementTexts.Add(commandText); - } - - if (query.EnableTotalRecordCount) - { - commandText = string.Empty; - - List<string> columnsToSelect; - if (EnableGroupByPresentationUniqueKey(query)) - { - columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" }; - } - else if (query.GroupBySeriesPresentationUniqueKey) - { - columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" }; - } - else - { - columnsToSelect = new List<string> { "count (guid)" }; - } - - SetFinalColumnsToSelect(query, columnsToSelect); - commandText += " select " + string.Join(',', columnsToSelect) + FromText; - - commandText += GetJoinUserDataText(query) - + whereText; - statementTexts.Add(commandText); - } - - var list = new List<Guid>(); - var result = new QueryResult<Guid>(); - using (var connection = GetConnection(true)) - { - connection.RunInTransaction( - db => - { - var statements = PrepareAll(db, statementTexts); - - if (!isReturningZeroItems) - { - using (var statement = statements[0]) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row[0].ReadGuidFromBlob()); - } - } - } - - if (query.EnableTotalRecordCount) - { - using (var statement = statements[statements.Length - 1]) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - } - }, - ReadTransactionMode); - } - - LogQueryTime("GetItemIds", commandText, now); - - result.Items = list; - return result; - } - private bool IsAlphaNumeric(string str) { if (string.IsNullOrWhiteSpace(str)) @@ -3665,7 +3429,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"ChannelId in ({inClause})"); } - if (!query.ParentId.Equals(Guid.Empty)) + if (!query.ParentId.Equals(default)) { whereClauses.Add("ParentId=@ParentId"); statement?.TryBind("@ParentId", query.ParentId); @@ -4025,7 +3789,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4046,7 +3810,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4067,7 +3831,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4088,7 +3852,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); if (statement != null) { - statement.TryBind(paramName, albumId.ToByteArray()); + statement.TryBind(paramName, albumId); } index++; @@ -4109,7 +3873,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4130,7 +3894,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))"); if (statement != null) { - statement.TryBind(paramName, genreId.ToByteArray()); + statement.TryBind(paramName, genreId); } index++; @@ -4209,7 +3973,7 @@ namespace Emby.Server.Implementations.Data if (statement != null) { - statement.TryBind(paramName, studioId.ToByteArray()); + statement.TryBind(paramName, studioId); } index++; @@ -4494,7 +4258,7 @@ namespace Emby.Server.Implementations.Data var index = 0; foreach (var pair in query.ExcludeProviderIds) { - if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase)) { continue; } @@ -4524,7 +4288,7 @@ namespace Emby.Server.Implementations.Data var index = 0; foreach (var pair in query.HasAnyProviderId) { - if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase)) { continue; } @@ -4942,7 +4706,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void DeleteItem(Guid id) { - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -4954,7 +4718,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type connection.RunInTransaction( db => { - var idBlob = id.ToByteArray(); + Span<byte> idBlob = stackalloc byte[16]; + id.TryWriteBytes(idBlob); // Delete people ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); @@ -5003,7 +4768,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (whereClauses.Count != 0) { - commandText.Append(" where ").Append(string.Join(" AND ", whereClauses)); + commandText.Append(" where ").AppendJoin(" AND ", whereClauses); } commandText.Append(" order by ListOrder"); @@ -5089,16 +4854,16 @@ AND Type = @InternalPersonType)"); statement?.TryBind("@InternalPersonType", typeof(Person).FullName); } - if (!query.ItemId.Equals(Guid.Empty)) + if (!query.ItemId.Equals(default)) { whereClauses.Add("ItemId=@ItemId"); - statement?.TryBind("@ItemId", query.ItemId.ToByteArray()); + statement?.TryBind("@ItemId", query.ItemId); } - if (!query.AppearsInItemId.Equals(Guid.Empty)) + if (!query.AppearsInItemId.Equals(default)) { whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); - statement?.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); + statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); } var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); @@ -5151,7 +4916,7 @@ AND Type = @InternalPersonType)"); private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5599,6 +5364,7 @@ AND Type = @InternalPersonType)"); result.TotalRecordCount = list.Count; } + result.StartIndex = query.StartIndex ?? 0; result.Items = list; return result; @@ -5682,7 +5448,7 @@ AND Type = @InternalPersonType)"); private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5758,7 +5524,7 @@ AND Type = @InternalPersonType)"); public void UpdatePeople(Guid itemId, List<PersonInfo> people) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5891,7 +5657,7 @@ AND Type = @InternalPersonType)"); using (var statement = PrepareStatement(connection, cmdText)) { - statement.TryBind("@ItemId", query.ItemId.ToByteArray()); + statement.TryBind("@ItemId", query.ItemId); if (query.Type.HasValue) { @@ -5913,11 +5679,11 @@ AND Type = @InternalPersonType)"); } } - public void SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken) + public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken) { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -5945,7 +5711,7 @@ AND Type = @InternalPersonType)"); } } - private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db) + private void InsertMediaStreams(byte[] idBlob, IReadOnlyList<MediaStream> streams, IDatabaseConnection db) { const int Limit = 10; var startIndex = 0; @@ -6253,7 +6019,7 @@ AND Type = @InternalPersonType)"); CancellationToken cancellationToken) { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentException("Guid can't be empty.", nameof(id)); } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 80b8f9ebf..ba86dc156 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -26,9 +26,6 @@ namespace Emby.Server.Implementations.Data DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); } - /// <inheritdoc /> - public string Name => "SQLite"; - /// <summary> /// Opens the connection to the database. /// </summary> @@ -102,7 +99,7 @@ namespace Emby.Server.Implementations.Data continue; } - statement.TryBind("@UserId", user.Id.ToByteArray()); + statement.TryBind("@UserId", user.Id); statement.TryBind("@InternalUserId", user.InternalId); statement.MoveNext(); @@ -390,6 +387,7 @@ namespace Emby.Server.Implementations.Data return userData; } +#pragma warning disable CA2215 /// <inheritdoc/> /// <remarks> /// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and @@ -398,6 +396,10 @@ namespace Emby.Server.Implementations.Data /// </remarks> protected override void Dispose(bool dispose) { + // The write lock and connection for the item repository are shared with the user data repository + // since they point to the same database. The item repo has responsibility for disposing these two objects, + // so the user data repo should not attempt to dispose them as well } +#pragma warning restore CA2215 } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 88d9303a5..2b2190b16 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -21,7 +21,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -458,11 +457,6 @@ namespace Emby.Server.Implementations.Dto } } - private string GetDtoId(BaseItem item) - { - return item.Id.ToString("N", CultureInfo.InvariantCulture); - } - private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item) { if (!string.IsNullOrEmpty(item.Album)) @@ -584,7 +578,7 @@ namespace Emby.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out Person entity)) { baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary); - baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); + baseItemPerson.Id = entity.Id; if (dto.ImageBlurHashes != null) { // Only add BlurHash for the person's image. @@ -743,8 +737,7 @@ namespace Emby.Server.Implementations.Dto dto.Tags = item.Tags; } - var hasAspectRatio = item as IHasAspectRatio; - if (hasAspectRatio != null) + if (item is IHasAspectRatio hasAspectRatio) { dto.AspectRatio = hasAspectRatio.AspectRatio; } @@ -894,15 +887,13 @@ namespace Emby.Server.Implementations.Dto dto.CommunityRating = item.CommunityRating; } - var supportsPlaceHolders = item as ISupportsPlaceHolders; - if (supportsPlaceHolders != null && supportsPlaceHolders.IsPlaceHolder) + if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder) { dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; } // Add audio info - var audio = item as Audio; - if (audio != null) + if (item is Audio audio) { dto.Album = audio.Album; if (audio.ExtraType.HasValue) @@ -975,8 +966,7 @@ namespace Emby.Server.Implementations.Dto }).Where(i => i != null).ToArray(); } - var hasAlbumArtist = item as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (item is IHasAlbumArtist hasAlbumArtist) { dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); @@ -1324,7 +1314,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { - dto.ParentLogoItemId = GetDtoId(parent); + dto.ParentLogoItemId = parent.Id; dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); } } @@ -1335,7 +1325,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { - dto.ParentArtItemId = GetDtoId(parent); + dto.ParentArtItemId = parent.Id; dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image); } } @@ -1346,7 +1336,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { - dto.ParentThumbItemId = GetDtoId(parent); + dto.ParentThumbItemId = parent.Id; dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); } } @@ -1357,7 +1347,7 @@ namespace Emby.Server.Implementations.Dto if (images.Count > 0) { - dto.ParentBackdropItemId = GetDtoId(parent); + dto.ParentBackdropItemId = parent.Id; dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images); } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 1e09a98cf..a5cc125ec 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -26,12 +26,12 @@ <PackageReference Include="DiscUtils.Udf" Version="0.16.13" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" /> + <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.2" /> <PackageReference Include="Mono.Nat" Version="3.0.2" /> - <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" /> + <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" /> <PackageReference Include="sharpcompress" Version="0.30.1" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="DotNet.Glob" Version="3.1.3" /> @@ -55,6 +55,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index feaccf9fa..34fdfbe8d 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -3,6 +3,8 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Udp; +using Jellyfin.Networking.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using Microsoft.Extensions.Configuration; @@ -26,6 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ILogger<UdpServerEntryPoint> _logger; private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; + private readonly IConfigurationManager _configurationManager; /// <summary> /// The UDP server. @@ -40,14 +43,17 @@ namespace Emby.Server.Implementations.EntryPoints /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param> /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param> /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> public UdpServerEntryPoint( ILogger<UdpServerEntryPoint> logger, IServerApplicationHost appHost, - IConfiguration configuration) + IConfiguration configuration, + IConfigurationManager configurationManager) { _logger = logger; _appHost = appHost; _config = configuration; + _configurationManager = configurationManager; } /// <inheritdoc /> @@ -55,6 +61,11 @@ namespace Emby.Server.Implementations.EntryPoints { CheckDisposed(); + if (_configurationManager.GetNetworkConfiguration().AutoDiscovery) + { + return Task.CompletedTask; + } + try { _udpServer = new UdpServer(_logger, _appHost, _config, PortNumber); diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 5c86dbbb7..399ece7fd 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -581,7 +581,7 @@ namespace Emby.Server.Implementations.IO } /// <inheritdoc /> - public virtual List<FileSystemMetadata> GetDrives() + public virtual IEnumerable<FileSystemMetadata> GetDrives() { // check for ready state to avoid waiting for drives to timeout // some drives on linux have no actual size or are used for other purposes @@ -595,7 +595,7 @@ namespace Emby.Server.Implementations.IO Name = d.Name, FullName = d.RootDirectory.FullName, IsDirectory = true - }).ToList(); + }); } /// <inheritdoc /> @@ -704,6 +704,18 @@ namespace Emby.Server.Implementations.IO return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive)); } + /// <inheritdoc /> + public virtual bool DirectoryExists(string path) + { + return Directory.Exists(path); + } + + /// <inheritdoc /> + public virtual bool FileExists(string path) + { + return File.Exists(path); + } + private EnumerationOptions GetEnumerationOptions(bool recursive) { return new EnumerationOptions diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 758986945..57c2f1a5e 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Images protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items) { - var useBackdrop = primaryItem is CollectionFolder; + var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView; return items .Select(i => { diff --git a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs index 1c69056d2..6fc7f1ac3 100644 --- a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Images { private readonly ILibraryManager _libraryManager; - public BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + protected BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 7958eb8f5..8a0e627b9 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -42,6 +42,10 @@ namespace Emby.Server.Implementations.Images { includeItemTypes = new[] { BaseItemKind.MusicAlbum }; } + else if (string.Equals(viewType, CollectionType.MusicVideos, StringComparison.Ordinal)) + { + includeItemTypes = new[] { BaseItemKind.MusicVideo }; + } else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal)) { includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 575680653..9f9a4902a 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -84,16 +84,20 @@ namespace Emby.Server.Implementations.Images }).GroupBy(x => x.Id) .Select(x => x.First()); + List<BaseItem> returnItems; if (isUsingCollectionStrip) { - return items + returnItems = items .Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)) .ToList(); + returnItems.Shuffle(); + return returnItems; } - - return items + returnItems = items .Where(i => i.HasImage(ImageType.Primary)) .ToList(); + returnItems.Shuffle(); + return returnItems; } protected override bool Supports(BaseItem item) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index bd0c178fd..0770bdbc3 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -45,8 +45,8 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; -using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Library /// Initializes a new instance of the <see cref="LibraryManager" /> class. /// </summary> /// <param name="appHost">The application host.</param> - /// <param name="logger">The logger.</param> + /// <param name="loggerFactory">The logger factory.</param> /// <param name="taskManager">The task manager.</param> /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Library /// <param name="namingOptions">The naming options.</param> public LibraryManager( IServerApplicationHost appHost, - ILogger<LibraryManager> logger, + ILoggerFactory loggerFactory, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Library NamingOptions namingOptions) { _appHost = appHost; - _logger = logger; + _logger = loggerFactory.CreateLogger<LibraryManager>(); _taskManager = taskManager; _userManager = userManager; _configurationManager = configurationManager; @@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.Library _memoryCache = memoryCache; _namingOptions = namingOptions; - _extraResolver = new ExtraResolver(namingOptions); + _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions); _configurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -680,9 +680,7 @@ namespace Emby.Server.Implementations.Library if (result?.Items.Count > 0) { - var items = new List<BaseItem>(); - items.AddRange(result.Items); - + var items = result.Items; foreach (var item in items) { ResolverHelper.SetInitialItemValues(item, parent, this, directoryService); @@ -1007,14 +1005,8 @@ namespace Emby.Server.Implementations.Library return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); } - /// <summary> - /// Validate and refresh the People sub-set of the IBN. - /// The items are stored in the db but not loaded into memory until actually requested by an operation. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken) { // Ensure the location is available. Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath); @@ -1037,15 +1029,6 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Queues the library scan. - /// </summary> - public void QueueLibraryScan() - { - // Just run the scheduled task so that the user can see it - _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>(); - } - - /// <summary> /// Validates the media library internal. /// </summary> /// <param name="progress">The progress.</param> @@ -1360,10 +1343,10 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetItems(query); } - return new QueryResult<BaseItem> - { - Items = _itemRepository.GetItemList(query) - }; + return new QueryResult<BaseItem>( + query.StartIndex, + null, + _itemRepository.GetItemList(query)); } public List<Guid> GetItemIds(InternalItemsQuery query) @@ -1493,10 +1476,10 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetItems(query); } - return new QueryResult<BaseItem> - { - Items = _itemRepository.GetItemList(query) - }; + return new QueryResult<BaseItem>( + query.StartIndex, + null, + _itemRepository.GetItemList(query)); } private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents) @@ -1651,27 +1634,6 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Gets all intro files. - /// </summary> - /// <returns>IEnumerable{System.String}.</returns> - public IEnumerable<string> GetAllIntroFiles() - { - return IntroProviders.SelectMany(i => - { - try - { - return i.GetAllIntroFiles().ToList(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting intro files"); - - return new List<string>(); - } - }); - } - - /// <summary> /// Resolves the intro. /// </summary> /// <param name="info">The info.</param> @@ -2014,16 +1976,16 @@ namespace Emby.Server.Implementations.Library public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken); - public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) + public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) { if (item.IsFileProtocol) { - ProviderManager.SaveMetadata(item, updateReason); + await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false); } item.DateLastSaved = DateTime.UtcNow; - return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate); + await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); } /// <summary> @@ -2469,24 +2431,6 @@ namespace Emby.Server.Implementations.Library return item; } - public void AddExternalSubtitleStreams( - List<MediaStream> streams, - string videoPath, - string[] files) - { - new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); - } - - public BaseItem GetParentItem(string parentId, Guid? userId) - { - if (string.IsNullOrEmpty(parentId)) - { - return GetParentItem((Guid?)null, userId); - } - - return GetParentItem(new Guid(parentId), userId); - } - public BaseItem GetParentItem(Guid? parentId, Guid? userId) { if (parentId.HasValue) @@ -2494,7 +2438,7 @@ namespace Emby.Server.Implementations.Library return GetItemById(parentId.Value); } - if (userId.HasValue && userId != Guid.Empty) + if (userId.HasValue && !userId.Equals(default)) { return GetUserRootFolder(); } @@ -2686,7 +2630,7 @@ namespace Emby.Server.Implementations.Library }; } - public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) + public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); if (ownerVideoInfo == null) @@ -2784,16 +2728,6 @@ namespace Emby.Server.Implementations.Library return path; } - public string SubstitutePath(string path, string from, string to) - { - if (path.TryReplaceSubPath(from, to, out var newPath)) - { - return newPath; - } - - return path; - } - public List<PersonInfo> GetPeople(InternalPeopleQuery query) { return _itemRepository.GetPeople(query); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index a414e7e16..eb95977ef 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -344,7 +344,7 @@ namespace Emby.Server.Implementations.Library return sources; } - private string[] NormalizeLanguage(string language) + private IReadOnlyList<string> NormalizeLanguage(string language) { if (string.IsNullOrEmpty(language)) { diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index da0c89c13..c5abb9a0a 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -13,10 +11,9 @@ namespace Emby.Server.Implementations.Library { public static class MediaStreamSelector { - public static int? GetDefaultAudioStreamIndex(List<MediaStream> streams, string[] preferredLanguages, bool preferDefaultTrack) + public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack) { - streams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages) - .ToList(); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages); if (preferDefaultTrack) { @@ -28,24 +25,15 @@ namespace Emby.Server.Implementations.Library } } - var stream = streams.FirstOrDefault(); - - if (stream != null) - { - return stream.Index; - } - - return null; + return sortedStreams.FirstOrDefault()?.Index; } public static int? GetDefaultSubtitleStreamIndex( IEnumerable<MediaStream> streams, - string[] preferredLanguages, + IReadOnlyList<string> preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { - MediaStream stream = null; - if (mode == SubtitlePlaybackMode.None) { return null; @@ -59,6 +47,7 @@ namespace Emby.Server.Implementations.Library .ThenByDescending(x => x.IsDefault) .ToList(); + MediaStream? stream = null; if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic @@ -95,26 +84,27 @@ namespace Emby.Server.Implementations.Library return stream?.Index; } - private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences) + private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, IReadOnlyList<string> languagePreferences) { // Give some preference to external text subs for better performance - return streams.Where(i => i.Type == type) + return streams + .Where(i => i.Type == type) .OrderBy(i => - { - var index = FindIndex(languagePreferences, i.Language); - - return index == -1 ? 100 : index; - }) - .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) - .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) - .ThenBy(i => i.Index); + { + var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase)); + + return index == -1 ? 100 : index; + }) + .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) + .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) + .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) + .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) + .ThenBy(i => i.Index); } public static void SetSubtitleStreamScores( - List<MediaStream> streams, - string[] preferredLanguages, + IReadOnlyList<MediaStream> streams, + IReadOnlyList<string> preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { @@ -123,15 +113,14 @@ namespace Emby.Server.Implementations.Library return; } - streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) - .ToList(); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages); var filteredStreams = new List<MediaStream>(); if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic - filteredStreams = streams.Where(s => s.IsForced || s.IsDefault) + filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault) .ToList(); } else if (mode == SubtitlePlaybackMode.Smart) @@ -139,54 +128,37 @@ namespace Emby.Server.Implementations.Library // Prefer smart logic over embedded metadata if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) { - filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) + filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) .ToList(); } } else if (mode == SubtitlePlaybackMode.Always) { // always load the most suitable full subtitles - filteredStreams = streams.Where(s => !s.IsForced) - .ToList(); + filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList(); } else if (mode == SubtitlePlaybackMode.OnlyForced) { // always load the most suitable full subtitles - filteredStreams = streams.Where(s => s.IsForced).ToList(); + filteredStreams = sortedStreams.Where(s => s.IsForced).ToList(); } // load forced subs if we have found no suitable full subtitles - if (filteredStreams.Count == 0) - { - filteredStreams = streams - .Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } + var iterStreams = filteredStreams.Count == 0 + ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + : filteredStreams; - foreach (var stream in filteredStreams) + foreach (var stream in iterStreams) { stream.Score = GetSubtitleScore(stream, preferredLanguages); } } - private static int FindIndex(string[] list, string value) - { - for (var i = 0; i < list.Length; i++) - { - if (string.Equals(list[i], value, StringComparison.OrdinalIgnoreCase)) - { - return i; - } - } - - return -1; - } - - private static int GetSubtitleScore(MediaStream stream, string[] languagePreferences) + private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences) { var values = new List<int>(); - var index = FindIndex(languagePreferences, stream.Language); + var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase)); values.Add(index == -1 ? 0 : 100 - index); diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 9222a9479..3d6b9f3b6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers { @@ -22,8 +23,11 @@ namespace Emby.Server.Implementations.Library.Resolvers public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T> where T : Video, new() { - protected BaseVideoResolver(NamingOptions namingOptions) + private readonly ILogger _logger; + + protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions) { + _logger = logger; NamingOptions = namingOptions; } @@ -156,19 +160,26 @@ namespace Emby.Server.Implementations.Library.Resolvers } else { - // use disc-utils, both DVDs and BDs use UDF filesystem - using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) - using (UdfReader udfReader = new UdfReader(videoFileStream)) + try { - if (udfReader.DirectoryExists("VIDEO_TS")) - { - video.IsoType = IsoType.Dvd; - } - else if (udfReader.DirectoryExists("BDMV")) + // use disc-utils, both DVDs and BDs use UDF filesystem + using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) + using (UdfReader udfReader = new UdfReader(videoFileStream)) { - video.IsoType = IsoType.BluRay; + if (udfReader.DirectoryExists("VIDEO_TS")) + { + video.IsoType = IsoType.Dvd; + } + else if (udfReader.DirectoryExists("BDMV")) + { + video.IsoType = IsoType.BluRay; + } } } + catch (Exception ex) + { + _logger.LogError(ex, "Error opening UDF/ISO image: {Value}", video.Path ?? video.Name); + } } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs index 807913b5d..408e640f9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs @@ -6,6 +6,7 @@ using Emby.Naming.Video; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; using static Emby.Naming.Video.ExtraRuleResolver; namespace Emby.Server.Implementations.Library.Resolvers @@ -22,12 +23,13 @@ namespace Emby.Server.Implementations.Library.Resolvers /// <summary> /// Initializes a new instance of the <see cref="ExtraResolver"/> class. /// </summary> + /// <param name="logger">The logger.</param> /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param> - public ExtraResolver(NamingOptions namingOptions) + public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions) { _namingOptions = namingOptions; - _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) }; - _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) }; + _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions) }; + _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(logger, namingOptions) }; } /// <summary> diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs index b8554bd51..5e33b402d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs @@ -1,7 +1,8 @@ -#nullable disable +#nullable disable using Emby.Naming.Common; using MediaBrowser.Controller.Entities; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers { @@ -15,9 +16,10 @@ namespace Emby.Server.Implementations.Library.Resolvers /// <summary> /// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class. /// </summary> + /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public GenericVideoResolver(NamingOptions namingOptions) - : base(namingOptions) + public GenericVideoResolver(ILogger logger, NamingOptions namingOptions) + : base(logger, namingOptions) { } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 122e9654a..140c4272e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers.Movies { @@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies CollectionType.Movies, CollectionType.HomeVideos, CollectionType.MusicVideos, - CollectionType.Movies, + CollectionType.TvShows, CollectionType.Photos }; @@ -40,9 +41,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// Initializes a new instance of the <see cref="MovieResolver"/> class. /// </summary> /// <param name="imageProcessor">The image processor.</param> + /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions) - : base(namingOptions) + public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions) + : base(logger, namingOptions) { _imageProcessor = imageProcessor; } @@ -221,6 +223,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return ResolveVideos<Movie>(parent, files, true, collectionType, true); } + if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + return ResolveVideos<Episode>(parent, files, true, collectionType, true); + } + return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index be9905647..bfa73af2f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -6,6 +6,7 @@ using Emby.Naming.Common; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers.TV { @@ -17,9 +18,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// <summary> /// Initializes a new instance of the <see cref="EpisodeResolver"/> class. /// </summary> + /// <param name="logger">The logger.</param> /// <param name="namingOptions">The naming options.</param> - public EpisodeResolver(NamingOptions namingOptions) - : base(namingOptions) + public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions) + : base(logger, namingOptions) { } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 55911933a..70d9cbc98 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -48,12 +48,10 @@ namespace Emby.Server.Implementations.Library results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count)); } - return new QueryResult<SearchHintInfo> - { - TotalRecordCount = totalRecordCount, - - Items = results - }; + return new QueryResult<SearchHintInfo>( + query.StartIndex, + totalRecordCount, + results); } private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value) diff --git a/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs new file mode 100644 index 000000000..320685b1f --- /dev/null +++ b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// The splashscreen post scan task. +/// </summary> +public class SplashscreenPostScanTask : ILibraryPostScanTask +{ + private readonly IItemRepository _itemRepository; + private readonly IImageEncoder _imageEncoder; + private readonly ILogger<SplashscreenPostScanTask> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="SplashscreenPostScanTask"/> class. + /// </summary> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger{SplashscreenPostScanTask}"/> interface.</param> + public SplashscreenPostScanTask( + IItemRepository itemRepository, + IImageEncoder imageEncoder, + ILogger<SplashscreenPostScanTask> logger) + { + _itemRepository = itemRepository; + _imageEncoder = imageEncoder; + _logger = logger; + } + + /// <inheritdoc /> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList(); + var backdrops = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList(); + if (backdrops.Count == 0) + { + // Thumb images fit better because they include the title in the image but are not provided with TMDb. + // Using backdrops as a fallback to generate an image at all + _logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen"); + backdrops = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList(); + } + + _imageEncoder.CreateSplashscreen(posters, backdrops); + return Task.CompletedTask; + } + + private IReadOnlyList<BaseItem> GetItemsWithImageType(ImageType imageType) + { + // TODO make included libraries configurable + return _itemRepository.GetItemList(new InternalItemsQuery + { + CollapseBoxSetItems = false, + Recursive = true, + DtoOptions = new DtoOptions(false), + ImageTypes = new[] { imageType }, + Limit = 30, + // TODO max parental rating configurable + MaxParentalRating = 10, + OrderBy = new[] + { + (ItemSortBy.Random, SortOrder.Ascending) + }, + IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Series } + }); + } +} diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 6937cc097..b2d25fdae 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); - using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { onStarted(); @@ -56,14 +56,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV using var durationToken = new CancellationTokenSource(duration); using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); var linkedCancellationToken = cancellationTokenSource.Token; - - await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream()); - await _streamHelper.CopyToAsync( - fileStream, - output, - IODefaults.CopyToBufferSize, - 1000, - linkedCancellationToken).ConfigureAwait(false); + var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream()); + await using (fileStream.ConfigureAwait(false)) + { + await _streamHelper.CopyToAsync( + fileStream, + output, + IODefaults.CopyToBufferSize, + 1000, + linkedCancellationToken).ConfigureAwait(false); + } } _logger.LogInformation("Recording completed: {FilePath}", targetFile); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index e7834ffd6..bba584854 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1819,16 +1819,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (timer.IsProgramSeries) { - SaveSeriesNfo(timer, seriesPath); - SaveVideoNfo(timer, recordingPath, program, false); + await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false); + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); } else if (!timer.IsMovie || timer.IsSports || timer.IsNews) { - SaveVideoNfo(timer, recordingPath, program, true); + await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false); } else { - SaveVideoNfo(timer, recordingPath, program, false); + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); } await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); @@ -1839,7 +1839,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private void SaveSeriesNfo(TimerInfo timer, string seriesPath) + private async Task SaveSeriesNfoAsync(TimerInfo timer, string seriesPath) { var nfoPath = Path.Combine(seriesPath, "tvshow.nfo"); @@ -1848,61 +1848,62 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) + await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { Indent = true, - Encoding = Encoding.UTF8 + Encoding = Encoding.UTF8, + Async = true }; - using (var writer = XmlWriter.Create(stream, settings)) + await using (var writer = XmlWriter.Create(stream, settings)) { - writer.WriteStartDocument(true); - writer.WriteStartElement("tvshow"); + await writer.WriteStartDocumentAsync(true).ConfigureAwait(false); + await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false); string id; if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id)) { - writer.WriteElementString("id", id); + await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false); } if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id)) { - writer.WriteElementString("imdb_id", id); + await writer.WriteElementStringAsync(null, "imdb_id", null, id).ConfigureAwait(false); } if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id)) { - writer.WriteElementString("tmdbid", id); + await writer.WriteElementStringAsync(null, "tmdbid", null, id).ConfigureAwait(false); } if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id)) { - writer.WriteElementString("zap2itid", id); + await writer.WriteElementStringAsync(null, "zap2itid", null, id).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(timer.Name)) { - writer.WriteElementString("title", timer.Name); + await writer.WriteElementStringAsync(null, "title", null, timer.Name).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(timer.OfficialRating)) { - writer.WriteElementString("mpaa", timer.OfficialRating); + await writer.WriteElementStringAsync(null, "mpaa", null, timer.OfficialRating).ConfigureAwait(false); } foreach (var genre in timer.Genres) { - writer.WriteElementString("genre", genre); + await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false); } - writer.WriteEndElement(); - writer.WriteEndDocument(); + await writer.WriteEndElementAsync().ConfigureAwait(false); + await writer.WriteEndDocumentAsync().ConfigureAwait(false); } } } - private void SaveVideoNfo(TimerInfo timer, string recordingPath, BaseItem item, bool lockData) + private async Task SaveVideoNfoAsync(TimerInfo timer, string recordingPath, BaseItem item, bool lockData) { var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); @@ -1911,29 +1912,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) + await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { Indent = true, - Encoding = Encoding.UTF8 + Encoding = Encoding.UTF8, + Async = true }; var options = _config.GetNfoConfiguration(); var isSeriesEpisode = timer.IsProgramSeries; - using (var writer = XmlWriter.Create(stream, settings)) + await using (var writer = XmlWriter.Create(stream, settings)) { - writer.WriteStartDocument(true); + await writer.WriteStartDocumentAsync(true).ConfigureAwait(false); if (isSeriesEpisode) { - writer.WriteStartElement("episodedetails"); + await writer.WriteStartElementAsync(null, "episodedetails", null).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle)) { - writer.WriteElementString("title", timer.EpisodeTitle); + await writer.WriteElementStringAsync(null, "title", null, timer.EpisodeTitle).ConfigureAwait(false); } var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null); @@ -1942,76 +1944,84 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var formatString = options.ReleaseDateFormat; - writer.WriteElementString( + await writer.WriteElementStringAsync( + null, "aired", - premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); + null, + premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (item.IndexNumber.HasValue) { - writer.WriteElementString("episode", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "episode", null, item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (item.ParentIndexNumber.HasValue) { - writer.WriteElementString("season", item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "season", null, item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } } else { - writer.WriteStartElement("movie"); + await writer.WriteStartElementAsync(null, "movie", null); if (!string.IsNullOrWhiteSpace(item.Name)) { - writer.WriteElementString("title", item.Name); + await writer.WriteElementStringAsync(null, "title", null, item.Name).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(item.OriginalTitle)) { - writer.WriteElementString("originaltitle", item.OriginalTitle); + await writer.WriteElementStringAsync(null, "originaltitle", null, item.OriginalTitle).ConfigureAwait(false); } if (item.PremiereDate.HasValue) { var formatString = options.ReleaseDateFormat; - writer.WriteElementString( + await writer.WriteElementStringAsync( + null, "premiered", - item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); - writer.WriteElementString( + null, + item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false); + await writer.WriteElementStringAsync( + null, "releasedate", - item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); + null, + item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false); } } - writer.WriteElementString( + await writer.WriteElementStringAsync( + null, "dateadded", - DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)); + null, + DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false); if (item.ProductionYear.HasValue) { - writer.WriteElementString("year", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.OfficialRating)) { - writer.WriteElementString("mpaa", item.OfficialRating); + await writer.WriteElementStringAsync(null, "mpaa", null, item.OfficialRating).ConfigureAwait(false); } var overview = (item.Overview ?? string.Empty) .StripHtml() .Replace(""", "'", StringComparison.Ordinal); - writer.WriteElementString("plot", overview); + await writer.WriteElementStringAsync(null, "plot", null, overview).ConfigureAwait(false); if (item.CommunityRating.HasValue) { - writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } foreach (var genre in item.Genres) { - writer.WriteElementString("genre", genre); + await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false); } var people = item.Id.Equals(Guid.Empty) ? new List<PersonInfo>() : _libraryManager.GetPeople(item); @@ -2023,7 +2033,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var person in directors) { - writer.WriteElementString("director", person); + await writer.WriteElementStringAsync(null, "director", null, person).ConfigureAwait(false); } var writers = people @@ -2034,19 +2044,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var person in writers) { - writer.WriteElementString("writer", person); + await writer.WriteElementStringAsync(null, "writer", null, person).ConfigureAwait(false); } foreach (var person in writers) { - writer.WriteElementString("credits", person); + await writer.WriteElementStringAsync(null, "credits", null, person).ConfigureAwait(false); } var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection); if (!string.IsNullOrEmpty(tmdbCollection)) { - writer.WriteElementString("collectionnumber", tmdbCollection); + await writer.WriteElementStringAsync(null, "collectionnumber", null, tmdbCollection).ConfigureAwait(false); } var imdb = item.GetProviderId(MetadataProvider.Imdb); @@ -2054,10 +2064,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (!isSeriesEpisode) { - writer.WriteElementString("id", imdb); + await writer.WriteElementStringAsync(null, "id", null, imdb).ConfigureAwait(false); } - writer.WriteElementString("imdbid", imdb); + await writer.WriteElementStringAsync(null, "imdbid", null, imdb).ConfigureAwait(false); // No need to lock if we have identified the content already lockData = false; @@ -2066,7 +2076,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var tvdb = item.GetProviderId(MetadataProvider.Tvdb); if (!string.IsNullOrEmpty(tvdb)) { - writer.WriteElementString("tvdbid", tvdb); + await writer.WriteElementStringAsync(null, "tvdbid", null, tvdb).ConfigureAwait(false); // No need to lock if we have identified the content already lockData = false; @@ -2075,7 +2085,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var tmdb = item.GetProviderId(MetadataProvider.Tmdb); if (!string.IsNullOrEmpty(tmdb)) { - writer.WriteElementString("tmdbid", tmdb); + await writer.WriteElementStringAsync(null, "tmdbid", null, tmdb).ConfigureAwait(false); // No need to lock if we have identified the content already lockData = false; @@ -2083,26 +2093,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (lockData) { - writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); + await writer.WriteElementStringAsync(null, "lockdata", null, "true").ConfigureAwait(false); } if (item.CriticRating.HasValue) { - writer.WriteElementString("criticrating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "criticrating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(item.Tagline)) { - writer.WriteElementString("tagline", item.Tagline); + await writer.WriteElementStringAsync(null, "tagline", null, item.Tagline).ConfigureAwait(false); } foreach (var studio in item.Studios) { - writer.WriteElementString("studio", studio); + await writer.WriteElementStringAsync(null, "studio", null, studio).ConfigureAwait(false); } - writer.WriteEndElement(); - writer.WriteEndDocument(); + await writer.WriteEndElementAsync().ConfigureAwait(false); + await writer.WriteEndDocumentAsync().ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 7fa47e7db..582e61d79 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -24,18 +24,19 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public class EncodedRecorder : IRecorder + public class EncodedRecorder : IRecorder, IDisposable { private readonly ILogger _logger; private readonly IMediaEncoder _mediaEncoder; private readonly IServerApplicationPaths _appPaths; - private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); + private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); private readonly IServerConfigurationManager _serverConfigurationManager; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private bool _hasExited; private Stream _logFileStream; private string _targetPath; private Process _process; + private bool _disposed = false; public EncodedRecorder( ILogger logger, @@ -323,5 +324,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogError(ex, "Error reading ffmpeg recording log"); } } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _logFileStream?.Dispose(); + _process?.Dispose(); + } + + _logFileStream = null; + _process = null; + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index a8440102d..ffa0d9b6a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.Listings { - public class SchedulesDirect : IListingsProvider + public class SchedulesDirect : IListingsProvider, IDisposable { private const string ApiUrl = "https://json.schedulesdirect.org/20141201"; @@ -39,6 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private DateTime _lastErrorResponse; + private bool _disposed = false; public SchedulesDirect( ILogger<SchedulesDirect> logger, @@ -58,8 +59,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var dates = new List<string>(); - var start = new List<DateTime> { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date; - var end = new List<DateTime> { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date; + var start = new[] { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date; + var end = new[] { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date; while (start <= end) { @@ -822,5 +823,31 @@ namespace Emby.Server.Implementations.LiveTv.Listings return list; } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _tokenSemaphore?.Dispose(); + } + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 323b96021..fbce7af2d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); - dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); + dto.ParentThumbItemId = librarySeries.Id; } catch (Exception ex) { @@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(librarySeries, image) }; - dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); + dto.ParentBackdropItemId = librarySeries.Id; } catch (Exception ex) { @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.LiveTv _imageProcessor.GetImageCacheTag(program, image) }; - dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); + dto.ParentBackdropItemId = program.Id; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index aa3598c8b..71a29e3cb 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv /// <summary> /// Class LiveTvManager. /// </summary> - public class LiveTvManager : ILiveTvManager, IDisposable + public class LiveTvManager : ILiveTvManager { private const int MaxGuideDays = 14; private const string ExternalServiceTag = "ExternalServiceId"; @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.LiveTv private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>(); private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>(); - private bool _disposed = false; - public LiveTvManager( IServerConfigurationManager config, ILogger<LiveTvManager> logger, @@ -312,7 +310,7 @@ namespace Emby.Server.Implementations.LiveTv { if (isVideo) { - mediaSource.MediaStreams.AddRange(new List<MediaStream> + mediaSource.MediaStreams = new MediaStream[] { new MediaStream { @@ -329,11 +327,11 @@ namespace Emby.Server.Implementations.LiveTv // Set the index to -1 because we don't know the exact index of the audio stream within the container Index = -1 } - }); + }; } else { - mediaSource.MediaStreams.AddRange(new List<MediaStream> + mediaSource.MediaStreams = new MediaStream[] { new MediaStream { @@ -341,7 +339,7 @@ namespace Emby.Server.Implementations.LiveTv // Set the index to -1 because we don't know the exact index of the audio stream within the container Index = -1 } - }); + }; } } @@ -857,11 +855,10 @@ namespace Emby.Server.Implementations.LiveTv var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user); - return new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = queryResult.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + query.StartIndex, + queryResult.TotalRecordCount, + returnArray); } public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken) @@ -910,29 +907,27 @@ namespace Emby.Server.Implementations.LiveTv programs = programs.Take(query.Limit.Value); } - return new QueryResult<BaseItem> - { - Items = programs.ToArray(), - TotalRecordCount = totalCount - }; + return new QueryResult<BaseItem>( + query.StartIndex, + totalCount, + programs.ToArray()); } - public QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken) + public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken) { if (!(query.IsAiring ?? false)) { - return GetPrograms(query, options, cancellationToken).Result; + return GetPrograms(query, options, cancellationToken); } RemoveFields(options); var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken); - return new QueryResult<BaseItemDto> - { - Items = _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User), - TotalRecordCount = internalResult.TotalRecordCount - }; + return Task.FromResult(new QueryResult<BaseItemDto>( + query.StartIndex, + internalResult.TotalRecordCount, + _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User))); } private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount) @@ -1541,11 +1536,10 @@ namespace Emby.Server.Implementations.LiveTv var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); - return new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = internalResult.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + query.StartIndex, + internalResult.TotalRecordCount, + returnArray); } private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationToken) @@ -1615,11 +1609,7 @@ namespace Emby.Server.Implementations.LiveTv .OrderBy(i => i.StartDate) .ToArray(); - return new QueryResult<TimerInfo> - { - Items = returnArray, - TotalRecordCount = returnArray.Length - }; + return new QueryResult<TimerInfo>(returnArray); } public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken) @@ -1701,11 +1691,7 @@ namespace Emby.Server.Implementations.LiveTv .OrderBy(i => i.StartDate) .ToArray(); - return new QueryResult<TimerInfoDto> - { - Items = returnArray, - TotalRecordCount = returnArray.Length - }; + return new QueryResult<TimerInfoDto>(returnArray); } public async Task CancelTimer(string id) @@ -1801,11 +1787,7 @@ namespace Emby.Server.Implementations.LiveTv .Select(i => i.Item1) .ToArray(); - return new QueryResult<SeriesTimerInfo> - { - Items = returnArray, - TotalRecordCount = returnArray.Length - }; + return new QueryResult<SeriesTimerInfo>(returnArray); } public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken) @@ -1855,11 +1837,7 @@ namespace Emby.Server.Implementations.LiveTv }) .ToArray(); - return new QueryResult<SeriesTimerInfoDto> - { - Items = returnArray, - TotalRecordCount = returnArray.Length - }; + return new QueryResult<SeriesTimerInfoDto>(returnArray); } public BaseItem GetLiveTvChannel(TimerInfo timer, ILiveTvService service) @@ -2112,36 +2090,6 @@ namespace Emby.Server.Implementations.LiveTv }; } - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - // TODO: Dispose stuff - } - - _services = null; - _listingProviders = null; - _tunerHosts = null; - - _disposed = true; - } - private LiveTvServiceInfo[] GetServiceInfos() { return Services.Select(GetServiceInfo).ToArray(); diff --git a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs index 15df0dcf1..72bbdd14a 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.LiveTv public string Key => "RefreshGuide"; /// <inheritdoc /> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var manager = (LiveTvManager)_liveTvManager; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 532790019..e0eaa8e58 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -446,7 +446,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Path = url, Protocol = MediaProtocol.Udp, - MediaStreams = new List<MediaStream> + MediaStreams = new MediaStream[] { new MediaStream { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index a5edd35cc..6195c7648 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - var taskCompletionSource = new TaskCompletionSource<bool>(); + var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); _ = StartStreaming( udpClient, @@ -186,7 +186,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var resolved = false; - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) + var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read); + await using (fileStream.ConfigureAwait(false)) { while (true) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 5581ba87c..2748794b3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -97,7 +97,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public Stream GetStream() { - var stream = GetInputStream(TempFilePath); + var stream = new FileStream( + TempFilePath, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + IODefaults.FileStreamBufferSize, + FileOptions.SequentialScan | FileOptions.Asynchronous); + bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; if (seekFile) { @@ -107,15 +114,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return stream; } - protected FileStream GetInputStream(string path) - => new FileStream( - path, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite, - IODefaults.FileStreamBufferSize, - FileOptions.SequentialScan | FileOptions.Asynchronous); - protected async Task DeleteTempFiles(string path, int retryCount = 0) { if (retryCount == 0) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index dd83f9a53..2a468e14d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -170,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { Path = path, Protocol = protocol, - MediaStreams = new List<MediaStream> + MediaStreams = new MediaStream[] { new MediaStream { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index ab4beb15b..e84e1e074 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts SetTempFilePath("ts"); - var taskCompletionSource = new TaskCompletionSource<bool>(); + var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 65a31e676..01a9969b4 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -77,7 +77,7 @@ "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", "Sync": "Synchronizace", "System": "Systém", - "TvShows": "TV seriály", + "TvShows": "Seriály", "User": "Uživatel", "UserCreatedWithName": "Uživatel {0} byl vytvořen", "UserDeletedWithName": "Uživatel {0} byl smazán", @@ -120,5 +120,7 @@ "Forced": "Vynucené", "Default": "Výchozí", "TaskOptimizeDatabaseDescription": "Zmenší databázi a odstraní prázdné místo. Spuštění této úlohy po skenování knihovny či jiných změnách databáze může zlepšit výkon.", - "TaskOptimizeDatabase": "Optimalizovat databázi" + "TaskOptimizeDatabase": "Optimalizovat databázi", + "TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.", + "TaskKeyframeExtractor": "Vytahovač klíčových snímků" } diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json index 7fc27e18a..981614005 100644 --- a/Emby.Server.Implementations/Localization/Core/cy.json +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -54,5 +54,57 @@ "Undefined": "Heb ddiffiniad", "TvShows": "Rhaglenni teledu", "HeaderLiveTV": "Teledu Byw", - "User": "Defnyddiwr" + "User": "Defnyddiwr", + "TaskCleanLogsDescription": "Dileu ffeiliau log sy'n fwy na {0} diwrnod oed.", + "TaskCleanLogs": "Glanhau ffolder log", + "TaskRefreshLibraryDescription": "Sganio'ch llyfrgell gyfryngau am ffeiliau newydd ac yn adnewyddu metaddata.", + "TaskRefreshLibrary": "Sganwich Llyfrgell Cyfryngau", + "TaskCleanActivityLogDescription": "Yn dileu cofnodion log gweithgaredd sy'n hŷn na'r oedran a nodwyd.", + "TaskCleanActivityLog": "Glanhau Log Gweithgaredd", + "SubtitleDownloadFailureFromForItem": "Methodd is-deitlau lawrlwytho o {0} ar gyfer {1}", + "NotificationOptionPluginError": "Methodd ategyn", + "NotificationOptionAudioPlaybackStopped": "Stopiwyd chwarae sain", + "NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain", + "MessageServerConfigurationUpdated": "Mae ffurfweddiad gweinydd wedi'i ddiweddaru", + "MessageNamedServerConfigurationUpdatedWithValue": "Mae adran ffurfweddu gweinydd {0} wedi'i diweddaru", + "FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu gan {0}", + "ValueHasBeenAddedToLibrary": "{0} wedi'i hychwanegu at eich llyfrgell gyfryngau", + "UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}", + "UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}", + "UserPolicyUpdatedWithName": "Polisi defnyddiwr wedi'i newid ar gyfer {0}", + "UserPasswordChangedWithName": "Cyfrinair wedi'i newid ar gyfer defnyddiwr {0}", + "UserOnlineFromDevice": "Mae {0} ar-lein o {1}", + "UserOfflineFromDevice": "Mae {0} wedi datgysylltu o {1}", + "UserLockedOutWithName": "Mae defnyddiwr {0} wedi'i gloi allan", + "UserDownloadingItemWithValues": "Mae {0} yn lawrlwytho {1}", + "UserDeletedWithName": "Defnyddiwr {0} wedi'i ddileu", + "UserCreatedWithName": "Defnyddiwr {0} wedi'i greu", + "StartupEmbyServerIsLoading": "Gweinydd Jellyfin yn llwytho. Triwch eto mewn ychydig.", + "ServerNameNeedsToBeRestarted": "Mae angen ailddechrau {0}", + "PluginUpdatedWithName": "{0} wedi'i ddiweddaru", + "PluginUninstalledWithName": "{0} wedi'i ddadosod", + "PluginInstalledWithName": "{0} wedi'i osod", + "NotificationOptionVideoPlaybackStopped": "Stopiwyd chwarae fideo", + "NotificationOptionVideoPlayback": "Dechreuwyd chwarae fideo", + "NotificationOptionUserLockedOut": "Defnyddiwr wedi'i gloi allan", + "NotificationOptionTaskFailed": "Methwyd cyflawni y dasg a drefnwyd", + "NotificationOptionServerRestartRequired": "Mae angen ailgychwyn y gweinydd", + "NotificationOptionPluginUpdateInstalled": "Diweddariad ategyn wedi'i osod", + "NotificationOptionPluginUninstalled": "Ategyn wedi'i ddadosod", + "NotificationOptionPluginInstalled": "Ategyn wedi'i osod", + "NotificationOptionNewLibraryContent": "Cynnwys newydd ar gael", + "NotificationOptionCameraImageUploaded": "Llun camera wedi'i huwchlwytho", + "NotificationOptionApplicationUpdateInstalled": "Diweddariad ap wedi'i osod", + "NotificationOptionApplicationUpdateAvailable": "Diweddariad ap ar gael", + "NewVersionIsAvailable": "Mae fersiwn diweddarach o'r gweinydd Jellyfin ar gael.", + "NameInstallFailed": "Gosodiad {0} wedi methu", + "MessageApplicationUpdatedTo": "Gweinydd Jellyfin wedi'i ddiweddaru i {0}", + "MessageApplicationUpdated": "Gweinydd Jellyfin wedi'i ddiweddaru", + "LabelIpAddressValue": "Cyfeiriad IP: {0}", + "ItemRemovedWithName": "{0} wedi'i dynnu o'r llyfrgell", + "ItemAddedWithName": "{0} wedi'i adio i'r llyfrgell", + "HeaderRecordingGroups": "Grwpiau Recordio", + "HeaderFavoriteSongs": "Ffefryn Ganeuon", + "HeaderFavoriteShows": "Ffefryn Shoeau", + "HeaderFavoriteEpisodes": "Ffefryn Rhaglenni" } diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 568a8e447..e06f8e6fe 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -119,5 +119,7 @@ "TaskDownloadMissingSubtitles": "Download missing subtitles", "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", "TaskOptimizeDatabase": "Optimize database", - "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance." + "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", + "TaskKeyframeExtractor": "Keyframe Extractor", + "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time." } diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 8abf7fa66..7d0fca47f 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -119,5 +119,7 @@ "HeaderRecordingGroups": "Rikordadaj Grupoj", "FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}", "CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}", - "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis" + "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis", + "TaskKeyframeExtractorDescription": "Eltiras ĉefkadrojn el videodosieroj por krei pli precizajn HLS-ludlistojn. Ĉi tiu tasko povas funkcii dum longa tempo.", + "TaskKeyframeExtractor": "Eltiri Ĉefkadrojn" } diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 432814dac..80ae16c5c 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -120,5 +120,7 @@ "Forced": "Forzado", "Default": "Predeterminado", "TaskOptimizeDatabase": "Optimizar base de datos", - "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos." + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos.", + "TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.", + "TaskKeyframeExtractor": "Extractor de Cuadros Clave" } diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index f8c69712e..4918f468b 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -120,5 +120,7 @@ "Forced": "Forzado", "Default": "Predeterminado", "TaskOptimizeDatabase": "Optimizar la base de datos", - "TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento." + "TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.", + "TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.", + "TaskKeyframeExtractor": "Extractor de Fotogramas Clave" } diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 6960ff007..8e8eef867 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -119,5 +119,6 @@ "TaskCleanActivityLogDescription": "ورودیهای قدیمیتر از سن تنظیم شده در سیاهه فعالیت را حذف میکند.", "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت", "Undefined": "تعریف نشده", - "TaskOptimizeDatabase": "بهینه سازی پایگاه داده" + "TaskOptimizeDatabase": "بهینه سازی پایگاه داده", + "TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد." } diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index 4a1f4f1d5..435de7363 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -119,5 +119,7 @@ "TaskCleanActivityLog": "Tyhjennä toimintahistoria", "Undefined": "Määrittelemätön", "TaskOptimizeDatabaseDescription": "Tiivistää ja puhdistaa tietokannan. Tämän toiminnon suorittaminen kirjastojen skannauksen tai muiden tietokantaan liittyvien muutoksien jälkeen voi parantaa suorituskykyä.", - "TaskOptimizeDatabase": "Optimoi tietokanta" + "TaskOptimizeDatabase": "Optimoi tietokanta", + "TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.", + "TaskKeyframeExtractor": "Avainkuvien purkain" } diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index bfafe7650..e56ae6071 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -1,7 +1,7 @@ { "Albums": "Albums", "AppDeviceValues": "Application : {0}, Appareil : {1}", - "Application": "Applications", + "Application": "Application", "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index 85de5925e..781cfcfa2 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -61,5 +61,10 @@ "LabelRunningTimeValue": "चलने का समय: {0}", "ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया", "Inherit": "इनहेरिट", - "NotificationOptionVideoPlaybackStopped": "चलचित्र रुका हुआ" + "NotificationOptionVideoPlaybackStopped": "चलचित्र रुका हुआ", + "PluginUninstalledWithName": "{0} अनइंस्टॉल हुए", + "PluginInstalledWithName": "{0} इंस्टॉल हुए", + "Plugin": "प्लग-इन", + "Playlists": "प्लेलिस्ट", + "Photos": "तस्वीरें" } diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index acde84aaf..2da936cff 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -120,5 +120,7 @@ "Forced": "Kényszerített", "Default": "Alapértelmezett", "TaskOptimizeDatabaseDescription": "Tömöríti az adatbázist és csonkolja a szabad helyet. A feladat futtatása a könyvtár beolvasása után, vagy egyéb, adatbázis-módosítást igénylő változtatások végrehajtása javíthatja a teljesítményt.", - "TaskOptimizeDatabase": "Adatbázis optimalizálása" + "TaskOptimizeDatabase": "Adatbázis optimalizálása", + "TaskKeyframeExtractor": "Kulcskockák kibontása", + "TaskKeyframeExtractorDescription": "Kulcskockákat bont ki a videofájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat." } diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 1b4a18deb..aaaf04712 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -120,5 +120,7 @@ "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady.", "TaskOptimizeDatabaseDescription": "Derekqordy qysyp, bos oryndy qysqartady. Būl tapsyrmany tasyğyşhanany skanerlegennen keiın nemese derekqorğa meñzeitın basqa özgertuler ıstelgennen keiın oryndau önımdılıktı damytuy mümkın.", - "TaskOptimizeDatabase": "Derekqordy oñtailandyru" + "TaskOptimizeDatabase": "Derekqordy oñtailandyru", + "TaskKeyframeExtractorDescription": "Naqtyraq HLS oynatu tızımderın jasau üşın beinefaildardan negızgı kadrlardy şyğarady. Būl tapsyrma ūzaq uaqytqa sozyluy mümkın.", + "TaskKeyframeExtractor": "Negızgı kadrlardy şyğaru" } diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json index 81aa996d9..401e68b2a 100644 --- a/Emby.Server.Implementations/Localization/Core/pr.json +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -1,7 +1,16 @@ { - "Books": "Libros", - "AuthenticationSucceededWithUserName": "{0} autentificado correctamente", + "Books": "Scrolls", + "AuthenticationSucceededWithUserName": "{0} passed yer trial", "Artists": "Artistas", "Songs": "Shantees", - "Albums": "Ships" + "Albums": "Tomes", + "Photos": "Paintings", + "NotificationOptionUserLockedOut": "Crewmate sent to the brig", + "HeaderContinueWatching": "Continue Yer Journey", + "Folders": "Chests", + "Application": "Captain", + "DeviceOnlineWithName": "{0} joined yer crew", + "DeviceOfflineWithName": "{0} abandoned ship", + "AppDeviceValues": "Captain: {0}, Ship: {1}", + "CameraImageUploadedFrom": "Yer looking glass has glimpsed another painting from {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index f8fad7b63..8af5449a7 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -117,5 +117,7 @@ "TaskCleanActivityLog": "Curăță Jurnalul de Activitate", "Undefined": "Nedefinit", "Forced": "Forțat", - "Default": "Implicit" + "Default": "Implicit", + "TaskOptimizeDatabaseDescription": "Compactează baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.", + "TaskOptimizeDatabase": "Optimizează baza de date" } diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index dc3793f1b..dd1e5d0ee 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -120,5 +120,7 @@ "Forced": "Форсир-ые", "Default": "По умолчанию", "TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.", - "TaskOptimizeDatabase": "Оптимизация базы данных" + "TaskOptimizeDatabase": "Оптимизация базы данных", + "TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.", + "TaskKeyframeExtractor": "Извлечение ключевых кадров" } diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index 98d763fcd..5548a74d2 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -119,5 +119,7 @@ "Forced": "கட்டாயப்படுத்தப்பட்டது", "Default": "இயல்புநிலை", "TaskOptimizeDatabaseDescription": "தரவுத்தளத்தை சுருக்கி, இலவச இடத்தை குறைக்கிறது. நூலகத்தை ஸ்கேன் செய்தபின் அல்லது தரவுத்தள மாற்றங்களைக் குறிக்கும் பிற மாற்றங்களைச் செய்தபின் இந்த பணியை இயக்குவது செயல்திறனை மேம்படுத்தக்கூடும்.", - "TaskOptimizeDatabase": "தரவுத்தளத்தை மேம்படுத்தவும்" + "TaskOptimizeDatabase": "தரவுத்தளத்தை மேம்படுத்தவும்", + "TaskKeyframeExtractorDescription": "மிகவும் துல்லியமான HLS பிளேலிஸ்ட்களை உருவாக்க வீடியோ கோப்புகளிலிருந்து கீஃப்ரேம்களைப் பிரித்தெடுக்கிறது. இந்த பணி நீண்ட காலமாக இருக்கலாம்.", + "TaskKeyframeExtractor": "கீஃப்ரேம் எக்ஸ்ட்ராக்டர்" } diff --git a/Emby.Server.Implementations/Localization/Core/ug.json b/Emby.Server.Implementations/Localization/Core/ug.json new file mode 100644 index 000000000..aea93c7fa --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ug.json @@ -0,0 +1,9 @@ +{ + "ChapterNameValue": "باب {0}", + "Channels": "قانال", + "CameraImageUploadedFrom": "{0} ئورۇندىن يېڭى سۈرەت چىقىرىلدى", + "Books": "كىتاب", + "AuthenticationSucceededWithUserName": "تىزىملىتىش مۇۋەپپەقىيەتلىك بول", + "Artists": "سەنئەتكار", + "Albums": "پىلاستىنكا" +} diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index d80f1760d..a9268c7d5 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -13,7 +13,7 @@ "Songs": "Bài Hát", "Sync": "Đồng Bộ", "ValueSpecialEpisodeName": "Đặc Biệt - {0}", - "Albums": "", + "Albums": "Album", "Artists": "Ca Sĩ", "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.", "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu", @@ -119,5 +119,7 @@ "Forced": "Bắt Buộc", "Default": "Mặc Định", "TaskOptimizeDatabaseDescription": "Thu gọn cơ sở dữ liệu và cắt bớt dung lượng trống. Chạy tác vụ này sau khi quét thư viện hoặc thực hiện các thay đổi khác ngụ ý sửa đổi cơ sở dữ liệu có thể cải thiện hiệu suất.", - "TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu" + "TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu", + "TaskKeyframeExtractor": "Trích Xuất Khung Hình", + "TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài." } diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index ac4eb644b..23d2819c3 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -120,5 +120,7 @@ "Forced": "强制的", "Default": "默认", "TaskOptimizeDatabaseDescription": "压缩数据库并优化可用空间,在扫描库或执行其他数据库修改后运行此任务可能会提高性能。", - "TaskOptimizeDatabase": "优化数据库" + "TaskOptimizeDatabase": "优化数据库", + "TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。", + "TaskKeyframeExtractor": "关键帧提取器" } diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 1cc97bc27..ac74da67d 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -103,7 +103,7 @@ "TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。", "TaskCleanTranscode": "清理轉碼目錄", "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", - "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的metadata。", + "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的元數據。", "TaskCleanLogsDescription": "刪除超過{0}天的日誌文件。", "TaskCleanLogs": "清理日誌目錄", "TaskRefreshLibrary": "掃描媒體庫", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index dbd70342a..281dbb00b 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -147,13 +147,7 @@ namespace Emby.Server.Implementations.Localization threeletterNames = new[] { parts[0], parts[1] }; } - list.Add(new CultureDto - { - DisplayName = name, - Name = name, - ThreeLetterISOLanguageNames = threeletterNames, - TwoLetterISOLanguageName = twoCharName - }); + list.Add(new CultureDto(name, name, twoCharName, threeletterNames)); } } diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 0c451ccb6..bbbca4fc0 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.Net { ThrowIfDisposed(); - var taskCompletion = new TaskCompletionSource<SocketReceiveResult>(); + var taskCompletion = new TaskCompletionSource<SocketReceiveResult>(TaskCreationOptions.RunContinuationsAsynchronously); bool isResultSet = false; Action<IAsyncResult> callback = callbackResult => @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Net { ThrowIfDisposed(); - var taskCompletion = new TaskCompletionSource<int>(); + var taskCompletion = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); bool isResultSet = false; Action<IAsyncResult> callback = callbackResult => diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 299f10544..2c4d6884d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.ScheduledTasks CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value)); } - await ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false); + await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false); status = TaskCompletionStatus.Completed; } @@ -757,6 +757,10 @@ namespace Emby.Server.Implementations.ScheduledTasks var trigger = triggerInfo.Item2; trigger.Triggered -= OnTriggerTriggered; trigger.Stop(); + if (trigger is IDisposable disposable) + { + disposable.Dispose(); + } } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index a5786a3d7..0bf0838fa 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -88,13 +88,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var videos = _libraryManager.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 79886cb52..776079044 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -57,12 +57,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public bool IsLogged => true; /// <inheritdoc /> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; if (!retentionDays.HasValue || retentionDays < 0) { - throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); + throw new InvalidOperationException($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); } var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 0941902fc..03935b384 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -79,19 +79,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var minDateModified = DateTime.UtcNow.AddDays(-30); try { - DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress); + DeleteCacheFilesFromDirectory(_applicationPaths.CachePath, minDateModified, progress, cancellationToken); } catch (DirectoryNotFoundException) { @@ -104,7 +99,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks try { - DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress); + DeleteCacheFilesFromDirectory(_applicationPaths.TempDirectory, minDateModified, progress, cancellationToken); } catch (DirectoryNotFoundException) { @@ -117,11 +112,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Deletes the cache files from directory with a last write time less than a given date. /// </summary> - /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> /// <param name="minDateModified">The min date modified.</param> /// <param name="progress">The progress.</param> - private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteCacheFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) { var filesToDelete = _fileSystem.GetFiles(directory, true) .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index fedb5deb0..9739d7327 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -69,13 +69,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { // Delete log files more than n days old var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 099d781cd..e4e565c64 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -78,18 +78,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var minDateModified = DateTime.UtcNow.AddDays(-1); progress.Report(50); - DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress); + DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken); return Task.CompletedTask; } @@ -97,11 +92,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Deletes the transcoded temp files from directory with a last write time less than a given date. /// </summary> - /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> /// <param name="minDateModified">The min date modified.</param> /// <param name="progress">The progress.</param> - private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteTempFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) { var filesToDelete = _fileSystem.GetFiles(directory, true) .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs index 35a4aeef6..98e45fa46 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -69,13 +69,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { _logger.LogInformation("Optimizing and vacuuming jellyfin.db..."); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 34780111b..7d60ea731 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -62,15 +62,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { - return _libraryManager.ValidatePeople(cancellationToken, progress); + return _libraryManager.ValidatePeopleAsync(progress, cancellationToken); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index b3973cecb..443649e6e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -68,13 +68,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }; } - /// <summary> - /// Update installed plugins. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns><see cref="Task" />.</returns> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { progress.Report(0); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index f7b3cfedc..065008157 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -25,8 +25,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class. /// </summary> - /// <param name="libraryManager">The library manager.</param> - /// <param name="localization">The localization manager.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; @@ -58,13 +58,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Executes the internal. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index dc5eb7391..63f11a22c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -8,10 +8,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that fires everyday. /// </summary> - public sealed class DailyTrigger : ITaskTrigger + public sealed class DailyTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _timeOfDay; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="DailyTrigger"/> class. @@ -71,6 +72,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -80,5 +82,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers { Triggered?.Invoke(this, EventArgs.Empty); } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 927f57e95..3eb800199 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -9,11 +9,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that runs repeatedly on an interval. /// </summary> - public sealed class IntervalTrigger : ITaskTrigger + public sealed class IntervalTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _interval; private DateTime _lastStartDate; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="IntervalTrigger"/> class. @@ -89,6 +90,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -104,5 +106,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers Triggered(this, EventArgs.Empty); } } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2392b20fd..fab49f2fb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -8,11 +8,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that fires on a weekly basis. /// </summary> - public sealed class WeeklyTrigger : ITaskTrigger + public sealed class WeeklyTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _timeOfDay; private readonly DayOfWeek _dayOfWeek; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class. @@ -94,6 +95,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -103,5 +105,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers { Triggered?.Invoke(this, EventArgs.Empty); } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6c679ea20..ab860ef67 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -424,9 +424,14 @@ namespace Emby.Server.Implementations.Session var nowPlayingQueue = info.NowPlayingQueue; - if (nowPlayingQueue != null) + if (nowPlayingQueue?.Length > 0) { session.NowPlayingQueue = nowPlayingQueue; + + var itemIds = nowPlayingQueue.Select(queue => queue.Id).ToArray(); + session.NowPlayingQueueFullItems = _dtoService.GetBaseItemDtos( + _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = itemIds }), + new DtoOptions(true)); } } diff --git a/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs new file mode 100644 index 000000000..e39280a10 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs @@ -0,0 +1,50 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class IndexNumberComparer. + /// </summary> + public class IndexNumberComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name => ItemSortBy.IndexNumber; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (!x.IndexNumber.HasValue) + { + return -1; + } + + if (!y.IndexNumber.HasValue) + { + return 1; + } + + return x.IndexNumber.Value.CompareTo(y.IndexNumber.Value); + } + } +} diff --git a/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs new file mode 100644 index 000000000..ffc4e0cad --- /dev/null +++ b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs @@ -0,0 +1,50 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + /// <summary> + /// Class ParentIndexNumberComparer. + /// </summary> + public class ParentIndexNumberComparer : IBaseItemComparer + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name => ItemSortBy.ParentIndexNumber; + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem? x, BaseItem? y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (!x.ParentIndexNumber.HasValue) + { + return -1; + } + + if (!y.ParentIndexNumber.HasValue) + { + return 1; + } + + return x.ParentIndexNumber.Value.CompareTo(y.ParentIndexNumber.Value); + } + } +} diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index c994ffc90..f8ba85af1 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -126,7 +126,8 @@ namespace Emby.Server.Implementations.TV parentsFolders.ToList()) .Cast<Episode>() .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey)) - .Select(GetUniqueSeriesKey); + .Select(GetUniqueSeriesKey) + .ToList(); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items, options); @@ -134,13 +135,21 @@ namespace Emby.Server.Implementations.TV return GetResult(episodes, request); } - public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions) + public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions) { // Avoid implicitly captured closure var currentUser = user; var allNextUp = seriesKeys - .Select(i => GetNextUp(i, currentUser, dtoOptions)); + .Select(i => GetNextUp(i, currentUser, dtoOptions, false)); + + if (request.EnableRewatching) + { + allNextUp = allNextUp.Concat( + seriesKeys.Select(i => GetNextUp(i, currentUser, dtoOptions, true)) + ) + .OrderByDescending(i => i.Item1); + } // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) @@ -186,9 +195,9 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// </summary> /// <returns>Task{Episode}.</returns> - private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions) + private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching) { - var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) + var lastQuery = new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, @@ -202,23 +211,43 @@ namespace Emby.Server.Implementations.TV Fields = new[] { ItemFields.SortName }, EnableImages = false } - }).Cast<Episode>().FirstOrDefault(); + }; + + if (rewatching) + { + // find last watched by date played, not by newest episode watched + lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }; + } + + var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault(); Func<Episode> getEpisode = () => { - var nextEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) + var nextQuery = new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, - IsPlayed = false, + IsPlayed = rewatching, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, MinSortName = lastWatchedEpisode?.SortName, DtoOptions = dtoOptions - }).Cast<Episode>().FirstOrDefault(); + }; + + Episode nextEpisode; + if (rewatching) + { + nextQuery.Limit = 2; + // get watched episode after most recently watched + nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().ElementAtOrDefault(1); + } + else + { + nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault(); + } if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) { @@ -228,7 +257,7 @@ namespace Emby.Server.Implementations.TV SeriesPresentationUniqueKey = seriesKey, ParentIndexNumber = 0, IncludeItemTypes = new[] { BaseItemKind.Episode }, - IsPlayed = false, + IsPlayed = rewatching, IsVirtualItem = false, DtoOptions = dtoOptions }) @@ -304,11 +333,10 @@ namespace Emby.Server.Implementations.TV items = items.Take(query.Limit.Value); } - return new QueryResult<BaseItem> - { - TotalRecordCount = totalCount, - Items = items.ToArray() - }; + return new QueryResult<BaseItem>( + query.StartIndex, + totalCount, + items.ToArray()); } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index c8ab99de4..937e792f5 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations.Udp { @@ -18,11 +19,6 @@ namespace Emby.Server.Implementations.Udp public sealed class UdpServer : IDisposable { /// <summary> - /// Address Override Configuration Key. - /// </summary> - public const string AddressOverrideConfigKey = "PublishedServerUrl"; - - /// <summary> /// The _logger. /// </summary> private readonly ILogger _logger; @@ -60,7 +56,7 @@ namespace Emby.Server.Implementations.Udp private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken) { - string? localUrl = _config[AddressOverrideConfigKey]; + string? localUrl = _config[AddressOverrideKey]; if (string.IsNullOrEmpty(localUrl)) { localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address); @@ -68,7 +64,7 @@ namespace Emby.Server.Implementations.Udp if (string.IsNullOrEmpty(localUrl)) { - _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined."); return; } diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index 8e0332d3e..593846adc 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -38,11 +38,7 @@ namespace Jellyfin.Api.Controllers { var keys = await _authenticationManager.GetApiKeys(); - return new QueryResult<AuthenticationInfo> - { - Items = keys, - TotalRecordCount = keys.Count - }; + return new QueryResult<AuthenticationInfo>(keys); } /// <summary> diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 3df975563..b54825775 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -243,11 +243,10 @@ namespace Jellyfin.Api.Controllers return dto; }); - return new QueryResult<BaseItemDto> - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + query.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); } /// <summary> @@ -447,11 +446,10 @@ namespace Jellyfin.Api.Controllers return dto; }); - return new QueryResult<BaseItemDto> - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + query.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); } /// <summary> diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 0b2604640..27eb22339 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -126,9 +126,11 @@ namespace Jellyfin.Api.Controllers HomeSectionType.SmallLibraryTiles, HomeSectionType.Resume, HomeSectionType.ResumeAudio, + HomeSectionType.ResumeBook, HomeSectionType.LiveTv, HomeSectionType.NextUp, - HomeSectionType.LatestMedia, HomeSectionType.None, + HomeSectionType.LatestMedia, + HomeSectionType.None, }; if (!Guid.TryParse(displayPreferencesId, out var itemId)) @@ -182,7 +184,7 @@ namespace Jellyfin.Api.Controllers var order = int.Parse(key.AsSpan().Slice("homesection".Length)); if (!Enum.TryParse<HomeSectionType>(displayPreferences.CustomPrefs[key], true, out var type)) { - type = order < 7 ? defaults[order] : HomeSectionType.None; + type = order < 8 ? defaults[order] : HomeSectionType.None; } displayPreferences.CustomPrefs.Remove(key); diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 7d587223f..af6916630 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -13,6 +13,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -28,7 +29,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Controllers { @@ -55,6 +55,7 @@ namespace Jellyfin.Api.Controllers private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ILogger<DynamicHlsController> _logger; private readonly EncodingHelper _encodingHelper; + private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator; private readonly DynamicHlsHelper _dynamicHlsHelper; private readonly EncodingOptions _encodingOptions; @@ -74,6 +75,7 @@ namespace Jellyfin.Api.Controllers /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param> /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + /// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param> public DynamicHlsController( ILibraryManager libraryManager, IUserManager userManager, @@ -87,7 +89,8 @@ namespace Jellyfin.Api.Controllers TranscodingJobHelper transcodingJobHelper, ILogger<DynamicHlsController> logger, DynamicHlsHelper dynamicHlsHelper, - EncodingHelper encodingHelper) + EncodingHelper encodingHelper, + IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator) { _libraryManager = libraryManager; _userManager = userManager; @@ -102,6 +105,7 @@ namespace Jellyfin.Api.Controllers _logger = logger; _dynamicHlsHelper = dynamicHlsHelper; _encodingHelper = encodingHelper; + _dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator; _encodingOptions = serverConfigurationManager.GetEncodingOptions(); } @@ -856,7 +860,7 @@ namespace Jellyfin.Api.Controllers StreamOptions = streamOptions }; - return await GetVariantPlaylistInternal(streamingRequest, "main", cancellationTokenSource) + return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) .ConfigureAwait(false); } @@ -1021,7 +1025,7 @@ namespace Jellyfin.Api.Controllers StreamOptions = streamOptions }; - return await GetVariantPlaylistInternal(streamingRequest, "main", cancellationTokenSource) + return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) .ConfigureAwait(false); } @@ -1032,13 +1036,15 @@ namespace Jellyfin.Api.Controllers /// <param name="playlistId">The playlist id.</param> /// <param name="segmentId">The segment id.</param> /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="runtimeTicks">The position of the requested segment in ticks.</param> + /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param> /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> /// <param name="params">The streaming parameters.</param> /// <param name="tag">The tag.</param> /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> /// <param name="playSessionId">The play session id.</param> /// <param name="segmentContainer">The segment container.</param> - /// <param name="segmentLength">The segment lenght.</param> + /// <param name="segmentLength">The desired segment length.</param> /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> @@ -1092,6 +1098,8 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string playlistId, [FromRoute, Required] int segmentId, [FromRoute, Required] string container, + [FromQuery, Required] long runtimeTicks, + [FromQuery, Required] long actualSegmentLengthTicks, [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, @@ -1145,6 +1153,8 @@ namespace Jellyfin.Api.Controllers var streamingRequest = new VideoRequestDto { Id = itemId, + CurrentRuntimeTicks = runtimeTicks, + ActualSegmentLengthTicks = actualSegmentLengthTicks, Container = container, Static = @static ?? false, Params = @params, @@ -1208,6 +1218,8 @@ namespace Jellyfin.Api.Controllers /// <param name="playlistId">The playlist id.</param> /// <param name="segmentId">The segment id.</param> /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param> + /// <param name="runtimeTicks">The position of the requested segment in ticks.</param> + /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param> /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param> /// <param name="params">The streaming parameters.</param> /// <param name="tag">The tag.</param> @@ -1267,6 +1279,8 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string playlistId, [FromRoute, Required] int segmentId, [FromRoute, Required] string container, + [FromQuery, Required] long runtimeTicks, + [FromQuery, Required] long actualSegmentLengthTicks, [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, @@ -1320,6 +1334,8 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, + CurrentRuntimeTicks = runtimeTicks, + ActualSegmentLengthTicks = actualSegmentLengthTicks, Static = @static ?? false, Params = @params, Tag = tag, @@ -1373,7 +1389,7 @@ namespace Jellyfin.Api.Controllers .ConfigureAwait(false); } - private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, string name, CancellationTokenSource cancellationTokenSource) + private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, CancellationTokenSource cancellationTokenSource) { using var state = await StreamingHelpers.GetStreamingState( streamingRequest, @@ -1392,60 +1408,16 @@ namespace Jellyfin.Api.Controllers cancellationTokenSource.Token) .ConfigureAwait(false); - Response.Headers.Add(HeaderNames.Expires, "0"); - - var segmentLengths = GetSegmentLengths(state); - - var segmentContainer = state.Request.SegmentContainer ?? "ts"; - - // http://ffmpeg.org/ffmpeg-all.html#toc-hls-2 - var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase); - var hlsVersion = isHlsInFmp4 ? "7" : "3"; - - var builder = new StringBuilder(128); - - builder.AppendLine("#EXTM3U") - .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD") - .Append("#EXT-X-VERSION:") - .Append(hlsVersion) - .AppendLine() - .Append("#EXT-X-TARGETDURATION:") - .Append(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength)) - .AppendLine() - .AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - - var index = 0; - var segmentExtension = EncodingHelper.GetSegmentFileExtension(streamingRequest.SegmentContainer); - var queryString = Request.QueryString; - - if (isHlsInFmp4) - { - builder.Append("#EXT-X-MAP:URI=\"") - .Append("hls1/") - .Append(name) - .Append("/-1") - .Append(segmentExtension) - .Append(queryString) - .Append('"') - .AppendLine(); - } - - foreach (var length in segmentLengths) - { - builder.Append("#EXTINF:") - .Append(length.ToString("0.0000", CultureInfo.InvariantCulture)) - .AppendLine(", nodesc") - .Append("hls1/") - .Append(name) - .Append('/') - .Append(index++) - .Append(segmentExtension) - .Append(queryString) - .AppendLine(); - } + var request = new CreateMainPlaylistRequest( + state.MediaPath, + state.SegmentLength * 1000, + state.RunTimeTicks ?? 0, + state.Request.SegmentContainer ?? string.Empty, + "hls1/main/", + Request.QueryString.ToString()); + var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request); - builder.AppendLine("#EXT-X-ENDLIST"); - return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")); + return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8")); } private async Task<ActionResult> GetDynamicSegment(StreamingRequestDto streamingRequest, int segmentId) @@ -1546,7 +1518,7 @@ namespace Jellyfin.Api.Controllers DeleteLastFile(playlistPath, segmentExtension, 0); } - streamingRequest.StartTimeTicks = GetStartPositionTicks(state, segmentId); + streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks; state.WaitForPath = segmentPath; job = await _transcodingJobHelper.StartFfMpeg( @@ -1897,7 +1869,7 @@ namespace Jellyfin.Api.Controllers { // Transcoding job is over, so assume all existing files are ready _logger.LogDebug("serving up {0} as transcode is over", segmentPath); - return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); + return GetSegmentResult(state, segmentPath, transcodingJob); } var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); @@ -1906,7 +1878,7 @@ namespace Jellyfin.Api.Controllers if (segmentIndex < currentTranscodingIndex) { _logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex); - return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); + return GetSegmentResult(state, segmentPath, transcodingJob); } } @@ -1921,8 +1893,8 @@ namespace Jellyfin.Api.Controllers { if (transcodingJob.HasExited || System.IO.File.Exists(nextSegmentPath)) { - _logger.LogDebug("serving up {0} as it deemed ready", segmentPath); - return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); + _logger.LogDebug("Serving up {SegmentPath} as it deemed ready", segmentPath); + return GetSegmentResult(state, segmentPath, transcodingJob); } } else @@ -1953,16 +1925,16 @@ namespace Jellyfin.Api.Controllers _logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath); } - return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); + return GetSegmentResult(state, segmentPath, transcodingJob); } - private ActionResult GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJobDto? transcodingJob) + private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob) { - var segmentEndingPositionTicks = GetEndPositionTicks(state, index); + var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks; Response.OnCompleted(() => { - _logger.LogDebug("finished serving {0}", segmentPath); + _logger.LogDebug("Finished serving {SegmentPath}", segmentPath); if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); @@ -1972,30 +1944,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath), false, HttpContext); - } - - private long GetEndPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format( - CultureInfo.InvariantCulture, - "Invalid segment index requested: {0} - Segment count: {1}", - requestedIndex, - lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i <= requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - return TimeSpan.FromSeconds(startSeconds).Ticks; + return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)); } private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) @@ -2076,29 +2025,5 @@ namespace Jellyfin.Api.Controllers _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); } } - - private long GetStartPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format( - CultureInfo.InvariantCulture, - "Invalid segment index requested: {0} - Segment count: {1}", - requestedIndex, - lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i < requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - var position = TimeSpan.FromSeconds(startSeconds).Ticks; - return position; - } } } diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 7325dca0a..78634f0bf 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Api.Controllers return BadRequest("Invalid segment."); } - return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file), false, HttpContext); + return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)); } /// <summary> @@ -186,7 +186,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path), false, HttpContext); + return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)); } } } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index e72589cfa..5da1af42a 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -11,12 +12,14 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Branding; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -44,6 +47,8 @@ namespace Jellyfin.Api.Controllers private readonly IAuthorizationContext _authContext; private readonly ILogger<ImageController> _logger; private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IApplicationPaths _appPaths; + private readonly IImageEncoder _imageEncoder; /// <summary> /// Initializes a new instance of the <see cref="ImageController"/> class. @@ -56,6 +61,8 @@ namespace Jellyfin.Api.Controllers /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{ImageController}"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param> public ImageController( IUserManager userManager, ILibraryManager libraryManager, @@ -64,7 +71,9 @@ namespace Jellyfin.Api.Controllers IFileSystem fileSystem, IAuthorizationContext authContext, ILogger<ImageController> logger, - IServerConfigurationManager serverConfigurationManager) + IServerConfigurationManager serverConfigurationManager, + IApplicationPaths appPaths, + IImageEncoder imageEncoder) { _userManager = userManager; _libraryManager = libraryManager; @@ -74,6 +83,8 @@ namespace Jellyfin.Api.Controllers _authContext = authContext; _logger = logger; _serverConfigurationManager = serverConfigurationManager; + _appPaths = appPaths; + _imageEncoder = imageEncoder; } /// <summary> @@ -559,8 +570,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -643,8 +653,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -727,8 +736,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -811,8 +819,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -895,8 +902,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -979,8 +985,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1063,8 +1068,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1147,8 +1151,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1231,8 +1234,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1315,8 +1317,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1399,8 +1400,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1483,8 +1483,7 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + item) .ConfigureAwait(false); } @@ -1585,7 +1584,6 @@ namespace Jellyfin.Api.Controllers backgroundColor, foregroundLayer, null, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase), info) .ConfigureAwait(false); } @@ -1687,11 +1685,134 @@ namespace Jellyfin.Api.Controllers backgroundColor, foregroundLayer, null, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase), info) .ConfigureAwait(false); } + /// <summary> + /// Generates or gets the splashscreen. + /// </summary> + /// <param name="tag">Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Blur image.</param> + /// <param name="backgroundColor">Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Apply a foreground layer on top of the image.</param> + /// <param name="quality">Quality setting, from 0-100.</param> + /// <response code="200">Splashscreen returned successfully.</response> + /// <returns>The splashscreen.</returns> + [HttpGet("Branding/Splashscreen")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesImageFile] + public async Task<ActionResult> GetSplashscreen( + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery, Range(0, 100)] int quality = 90) + { + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + string splashscreenPath; + + if (!string.IsNullOrWhiteSpace(brandingOptions.SplashscreenLocation) + && System.IO.File.Exists(brandingOptions.SplashscreenLocation)) + { + splashscreenPath = brandingOptions.SplashscreenLocation; + } + else + { + splashscreenPath = Path.Combine(_appPaths.DataPath, "splashscreen.png"); + if (!System.IO.File.Exists(splashscreenPath)) + { + return NotFound(); + } + } + + var outputFormats = GetOutputFormats(format); + + TimeSpan? cacheDuration = null; + if (!string.IsNullOrEmpty(tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + var options = new ImageProcessingOptions + { + Image = new ItemImageInfo + { + Path = splashscreenPath + }, + Height = height, + MaxHeight = maxHeight, + MaxWidth = maxWidth, + FillHeight = fillHeight, + FillWidth = fillWidth, + Quality = quality, + Width = width, + Blur = blur, + BackgroundColor = backgroundColor, + ForegroundLayer = foregroundLayer, + SupportedOutputFormats = outputFormats + }; + + return await GetImageResult( + options, + cacheDuration, + ImmutableDictionary<string, string>.Empty, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// <summary> + /// Uploads a custom splashscreen. + /// </summary> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + /// <response code="204">Successfully uploaded new splashscreen.</response> + /// <response code="400">Error reading MimeType from uploaded image.</response> + /// <response code="403">User does not have permission to upload splashscreen..</response> + /// <exception cref="ArgumentException">Error reading the image format.</exception> + [HttpPost("Branding/Splashscreen")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [AcceptsImageFile] + public async Task<ActionResult> UploadCustomSplashscreen() + { + await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); + + var mimeType = MediaTypeHeaderValue.Parse(Request.ContentType).MediaType; + + if (!mimeType.HasValue) + { + return BadRequest("Error reading mimetype from uploaded image"); + } + + var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + MimeTypes.ToExtension(mimeType.Value)); + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + brandingOptions.SplashscreenLocation = filePath; + _serverConfigurationManager.SaveConfiguration("branding", brandingOptions); + + await using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + { + await memoryStream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false); + } + + return NoContent(); + } + private static async Task<MemoryStream> GetMemoryStream(Stream inputStream) { using var reader = new StreamReader(inputStream); @@ -1772,7 +1893,6 @@ namespace Jellyfin.Api.Controllers string? backgroundColor, string? foregroundLayer, BaseItem? item, - bool isHeadRequest, ItemImageInfo? imageInfo = null) { if (percentPlayed.HasValue) @@ -1823,28 +1943,37 @@ namespace Jellyfin.Api.Controllers { "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" } }; + if (!imageInfo.IsLocalFile && item != null) + { + imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, imageIndex ?? 0).ConfigureAwait(false); + } + + var options = new ImageProcessingOptions + { + Height = height, + ImageIndex = imageIndex ?? 0, + Image = imageInfo, + Item = item, + ItemId = itemId, + MaxHeight = maxHeight, + MaxWidth = maxWidth, + FillHeight = fillHeight, + FillWidth = fillWidth, + Quality = quality ?? 100, + Width = width, + AddPlayedIndicator = addPlayedIndicator ?? false, + PercentPlayed = percentPlayed ?? 0, + UnplayedCount = unplayedCount, + Blur = blur, + BackgroundColor = backgroundColor, + ForegroundLayer = foregroundLayer, + SupportedOutputFormats = outputFormats + }; + return await GetImageResult( - item, - itemId, - imageIndex, - width, - height, - maxWidth, - maxHeight, - fillWidth, - fillHeight, - quality, - addPlayedIndicator, - percentPlayed, - unplayedCount, - blur, - backgroundColor, - foregroundLayer, - imageInfo, - outputFormats, + options, cacheDuration, - responseHeaders, - isHeadRequest).ConfigureAwait(false); + responseHeaders).ConfigureAwait(false); } private ImageFormat[] GetOutputFormats(ImageFormat? format) @@ -1921,56 +2050,11 @@ namespace Jellyfin.Api.Controllers } private async Task<ActionResult> GetImageResult( - BaseItem? item, - Guid itemId, - int? index, - int? width, - int? height, - int? maxWidth, - int? maxHeight, - int? fillWidth, - int? fillHeight, - int? quality, - bool? addPlayedIndicator, - double? percentPlayed, - int? unplayedCount, - int? blur, - string? backgroundColor, - string? foregroundLayer, - ItemImageInfo imageInfo, - IReadOnlyCollection<ImageFormat> supportedFormats, + ImageProcessingOptions imageProcessingOptions, TimeSpan? cacheDuration, - IDictionary<string, string> headers, - bool isHeadRequest) + IDictionary<string, string> headers) { - if (!imageInfo.IsLocalFile && item != null) - { - imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, index ?? 0).ConfigureAwait(false); - } - - var options = new ImageProcessingOptions - { - Height = height, - ImageIndex = index ?? 0, - Image = imageInfo, - Item = item, - ItemId = itemId, - MaxHeight = maxHeight, - MaxWidth = maxWidth, - FillHeight = fillHeight, - FillWidth = fillWidth, - Quality = quality ?? 100, - Width = width, - AddPlayedIndicator = addPlayedIndicator ?? false, - PercentPlayed = percentPlayed ?? 0, - UnplayedCount = unplayedCount, - Blur = blur, - BackgroundColor = backgroundColor, - ForegroundLayer = foregroundLayer, - SupportedOutputFormats = supportedFormats - }; - - var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); + var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(imageProcessingOptions).ConfigureAwait(false); var disableCaching = Request.Headers[HeaderNames.CacheControl].Contains("no-cache"); var parsingSuccessful = DateTime.TryParse(Request.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); @@ -2019,12 +2103,6 @@ namespace Jellyfin.Api.Controllers } } - // if the request is a head request, return a NoContent result with the same headers as it would with a GET request - if (isHeadRequest) - { - return NoContent(); - } - return PhysicalFile(imagePath, imageContentType ?? MediaTypeNames.Text.Plain); } } diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index a6c2e07c9..e9d48b624 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -341,10 +341,7 @@ namespace Jellyfin.Api.Controllers { var list = items; - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = list.Count - }; + var totalCount = list.Count; if (limit.HasValue && limit < list.Count) { @@ -353,7 +350,10 @@ namespace Jellyfin.Api.Controllers var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); - result.Items = returnList; + var result = new QueryResult<BaseItemDto>( + 0, + totalCount, + returnList); return result; } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index f8192955e..dc7af0a20 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -491,10 +491,13 @@ namespace Jellyfin.Api.Controllers else { var itemsArray = folder.GetChildren(user, true); - result = new QueryResult<BaseItem> { Items = itemsArray, TotalRecordCount = itemsArray.Count, StartIndex = 0 }; + result = new QueryResult<BaseItem>(itemsArray); } - return new QueryResult<BaseItemDto> { StartIndex = startIndex.GetValueOrDefault(), TotalRecordCount = result.TotalRecordCount, Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user) }; + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user)); } /// <summary> @@ -836,12 +839,10 @@ namespace Jellyfin.Api.Controllers var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - StartIndex = startIndex.GetValueOrDefault(), - TotalRecordCount = itemsResult.TotalRecordCount, - Items = returnItems - }; + return new QueryResult<BaseItemDto>( + startIndex, + itemsResult.TotalRecordCount, + returnItems); } } } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index f1b9c2f67..c65462ab5 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -506,13 +506,8 @@ namespace Jellyfin.Api.Controllers } var dtoOptions = new DtoOptions().AddClientFields(Request); - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = items.Count, - Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() - }; - - return result; + var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions); + return new QueryResult<BaseItemDto>(resultArray); } /// <summary> @@ -759,11 +754,10 @@ namespace Jellyfin.Api.Controllers var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - Items = returnList, - TotalRecordCount = itemsResult.Count - }; + return new QueryResult<BaseItemDto>( + query.StartIndex, + itemsResult.Count, + returnList); } /// <summary> diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 9e2ef8c60..484b0a974 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -193,11 +193,10 @@ namespace Jellyfin.Api.Controllers dtoOptions.AddCurrentProgram = addCurrentProgram; var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = channelResult.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + startIndex, + channelResult.TotalRecordCount, + returnArray); } /// <summary> @@ -390,11 +389,7 @@ namespace Jellyfin.Api.Controllers var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user); - return new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = returnArray.Count - }; + return new QueryResult<BaseItemDto>(returnArray); } /// <summary> @@ -687,7 +682,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("Programs/Recommended")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<BaseItemDto>> GetRecommendedPrograms( + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms( [FromQuery] Guid? userId, [FromQuery] int? limit, [FromQuery] bool? isAiring, @@ -726,7 +721,7 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None); + return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false); } /// <summary> diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index cb4894d77..ffc748a6e 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -101,11 +101,7 @@ namespace Jellyfin.Api.Controllers Limit = limit ?? 0 }); - return new QueryResult<BaseItemDto> - { - Items = peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray(), - TotalRecordCount = peopleItems.Count - }; + return new QueryResult<BaseItemDto>(peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray()); } /// <summary> diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 1667d6ede..c18f1b427 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -208,11 +208,10 @@ namespace Jellyfin.Api.Controllers dtos[index].PlaylistItemId = items[index].Item1.Id; } - var result = new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = count - }; + var result = new QueryResult<BaseItemDto>( + startIndex, + count, + dtos); return result; } diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b41df1abb..b227dba2d 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; -using Jellyfin.Api.Models.PluginDtos; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -44,61 +43,6 @@ namespace Jellyfin.Api.Controllers } /// <summary> - /// Get plugin security info. - /// </summary> - /// <response code="200">Plugin security info returned.</response> - /// <returns>Plugin security info.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpGet("SecurityInfo")] - [ProducesResponseType(StatusCodes.Status200OK)] - public static ActionResult<PluginSecurityInfo> GetPluginSecurityInfo() - { - return new PluginSecurityInfo - { - IsMbSupporter = true, - SupporterKey = "IAmTotallyLegit" - }; - } - - /// <summary> - /// Gets registration status for a feature. - /// </summary> - /// <param name="name">Feature name.</param> - /// <response code="200">Registration status returned.</response> - /// <returns>Mb registration record.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpPost("RegistrationRecords/{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public static ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute, Required] string name) - { - return new MBRegistrationRecord - { - IsRegistered = true, - RegChecked = true, - TrialVersion = false, - IsValid = true, - RegError = false - }; - } - - /// <summary> - /// Gets registration status for a feature. - /// </summary> - /// <param name="name">Feature name.</param> - /// <response code="501">Not implemented.</response> - /// <returns>Not Implemented.</returns> - /// <exception cref="NotImplementedException">This endpoint is not implemented.</exception> - [Obsolete("Paid plugins are not supported")] - [HttpGet("Registrations/{name}")] - [ProducesResponseType(StatusCodes.Status501NotImplemented)] - public static ActionResult GetRegistration([FromRoute, Required] string name) - { - // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, - // delete all these registration endpoints. They are only kept for compatibility. - throw new NotImplementedException(); - } - - /// <summary> /// Gets a list of currently installed plugins. /// </summary> /// <response code="200">Installed plugins returned.</response> @@ -317,20 +261,5 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - - /// <summary> - /// Updates plugin security info. - /// </summary> - /// <param name="pluginSecurityInfo">Plugin security info.</param> - /// <response code="204">Plugin security info updated.</response> - /// <returns>An <see cref="NoContentResult"/>.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpPost("SecurityInfo")] - [Authorize(Policy = Policies.RequiresElevation)] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo) - { - return NoContent(); - } } } diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 26acb4cdc..6fcd2ae40 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -121,11 +121,7 @@ namespace Jellyfin.Api.Controllers IsSports = isSports }); - return new SearchHintResult - { - TotalRecordCount = result.TotalRecordCount, - SearchHints = result.Items.Select(GetSearchHintResult).ToArray() - }; + return new SearchHintResult(result.Items.Select(GetSearchHintResult).ToArray(), result.TotalRecordCount); } /// <summary> diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index af77c801f..73be26bb2 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -81,11 +81,10 @@ namespace Jellyfin.Api.Controllers var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - TotalRecordCount = result.TotalRecordCount, - Items = dtoList - }; + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + dtoList); } } } diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index e20bcd7a7..636130543 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -68,6 +68,7 @@ namespace Jellyfin.Api.Controllers /// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param> /// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param> /// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param> + /// <param name="enableRewatching">Whether to include watched episode in next up results.</param> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns> [HttpGet("NextUp")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -84,7 +85,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, - [FromQuery] bool disableFirstEpisode = false) + [FromQuery] bool disableFirstEpisode = false, + [FromQuery] bool enableRewatching = false) { var options = new DtoOptions { Fields = fields } .AddClientFields(Request) @@ -100,7 +102,8 @@ namespace Jellyfin.Api.Controllers UserId = userId ?? Guid.Empty, EnableTotalRecordCount = enableTotalRecordCount, DisableFirstEpisode = disableFirstEpisode, - NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue + NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, + EnableRewatching = enableRewatching }, options); @@ -110,11 +113,10 @@ namespace Jellyfin.Api.Controllers var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); - return new QueryResult<BaseItemDto> - { - TotalRecordCount = result.TotalRecordCount, - Items = returnItems - }; + return new QueryResult<BaseItemDto>( + startIndex, + result.TotalRecordCount, + returnItems); } /// <summary> @@ -169,11 +171,10 @@ namespace Jellyfin.Api.Controllers var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user); - return new QueryResult<BaseItemDto> - { - TotalRecordCount = itemsResult.Count, - Items = returnItems - }; + return new QueryResult<BaseItemDto>( + startIndex, + itemsResult.Count, + returnItems); } /// <summary> @@ -296,11 +297,10 @@ namespace Jellyfin.Api.Controllers var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - TotalRecordCount = episodes.Count, - Items = dtos - }; + return new QueryResult<BaseItemDto>( + startIndex, + episodes.Count, + dtos); } /// <summary> @@ -354,11 +354,7 @@ namespace Jellyfin.Api.Controllers var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user); - return new QueryResult<BaseItemDto> - { - TotalRecordCount = returnItems.Count, - Items = returnItems - }; + return new QueryResult<BaseItemDto>(returnItems); } /// <summary> diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 90cb4a74a..008d2f176 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -124,11 +124,7 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions().AddClientFields(Request); var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); - return new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = dtos.Length - }; + return new QueryResult<BaseItemDto>(dtos); } /// <summary> diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 3d27371f6..04171da8a 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -108,11 +108,7 @@ namespace Jellyfin.Api.Controllers var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)) .ToArray(); - return new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = dtos.Length - }; + return new QueryResult<BaseItemDto>(dtos); } /// <summary> diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 3c079a71d..44263fd98 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -134,12 +134,7 @@ namespace Jellyfin.Api.Controllers items = Array.Empty<BaseItemDto>(); } - var result = new QueryResult<BaseItemDto> - { - Items = items, - TotalRecordCount = items.Length - }; - + var result = new QueryResult<BaseItemDto>(items); return result; } @@ -470,7 +465,7 @@ namespace Jellyfin.Api.Controllers StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, state.Request.StartTimeTicks, Request, _dlnaManager); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, HttpContext).ConfigureAwait(false); } if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File) @@ -499,9 +494,7 @@ namespace Jellyfin.Api.Controllers return FileStreamResponseHelpers.GetStaticFileResult( state.MediaPath, - contentType, - isHeadRequest, - HttpContext); + contentType); } // Need to start ffmpeg (because media can't be returned directly) diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 8be6fd1b5..bac77d43b 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -136,8 +136,6 @@ namespace Jellyfin.Api.Controllers IEnumerable<BaseItem> ibnItems = ibnItemsArray; - var result = new QueryResult<BaseItemDto> { TotalRecordCount = ibnItemsArray.Count }; - if (startIndex.HasValue || limit.HasValue) { if (startIndex.HasValue) @@ -155,8 +153,10 @@ namespace Jellyfin.Api.Controllers var dtos = tuples.Select(i => _dtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user)); - result.Items = dtos.Where(i => i != null).ToArray(); - + var result = new QueryResult<BaseItemDto>( + startIndex, + ibnItemsArray.Count, + dtos.Where(i => i != null).ToArray()); return result; } diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index bec961dad..27497cd59 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -138,7 +138,7 @@ namespace Jellyfin.Api.Helpers StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); } if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File) @@ -167,9 +167,7 @@ namespace Jellyfin.Api.Helpers return FileStreamResponseHelpers.GetStaticFileResult( state.MediaPath, - contentType, - isHeadRequest, - _httpContextAccessor.HttpContext); + contentType); } // Need to start ffmpeg (because media can't be returned directly) diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6385b62c9..5bdd3fe2e 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -22,14 +22,12 @@ namespace Jellyfin.Api.Helpers /// Returns a static file from a remote source. /// </summary> /// <param name="state">The current <see cref="StreamState"/>.</param> - /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param> /// <param name="httpClient">The <see cref="HttpClient"/> making the remote request.</param> /// <param name="httpContext">The current http context.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param> /// <returns>A <see cref="Task{ActionResult}"/> containing the API response.</returns> public static async Task<ActionResult> GetStaticRemoteStreamResult( StreamState state, - bool isHeadRequest, HttpClient httpClient, HttpContext httpContext, CancellationToken cancellationToken = default) @@ -45,12 +43,6 @@ namespace Jellyfin.Api.Helpers httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none"; - if (isHeadRequest) - { - httpContext.Response.Headers[HeaderNames.ContentType] = contentType; - return new OkResult(); - } - return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType); } @@ -59,23 +51,11 @@ namespace Jellyfin.Api.Helpers /// </summary> /// <param name="path">The path to the file.</param> /// <param name="contentType">The content type of the file.</param> - /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param> - /// <param name="httpContext">The current http context.</param> /// <returns>An <see cref="ActionResult"/> the file.</returns> public static ActionResult GetStaticFileResult( string path, - string contentType, - bool isHeadRequest, - HttpContext httpContext) + string contentType) { - httpContext.Response.ContentType = contentType; - - // if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request - if (isHeadRequest) - { - return new OkResult(); - } - return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true }; } diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 3fa07720a..6f5b64ea8 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -83,10 +83,10 @@ namespace Jellyfin.Api.Helpers int totalBytesRead = 0; var stopwatch = Stopwatch.StartNew(); - while (KeepReading(stopwatch.ElapsedMilliseconds)) + while (true) { totalBytesRead += _stream.Read(buffer); - if (totalBytesRead > 0) + if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) { break; } @@ -109,10 +109,10 @@ namespace Jellyfin.Api.Helpers int totalBytesRead = 0; var stopwatch = Stopwatch.StartNew(); - while (KeepReading(stopwatch.ElapsedMilliseconds)) + while (true) { totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - if (totalBytesRead > 0) + if (StopReading(totalBytesRead, stopwatch.ElapsedMilliseconds)) { break; } @@ -172,10 +172,12 @@ namespace Jellyfin.Api.Helpers } } - private bool KeepReading(long elapsed) + private bool StopReading(int bytesRead, long elapsed) { - // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely - return !_job?.HasExited ?? elapsed < _timeoutMs; + // It should stop reading when anything has been successfully read or if the job has exited + // If the job is null, however, it's a live stream and will require user action to close, + // but don't keep it open indefinitely if it isn't reading anything + return bytesRead > 0 || (_job?.HasExited ?? elapsed >= _timeoutMs); } } } diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 2cfd36d00..20427d7fa 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -131,11 +131,10 @@ namespace Jellyfin.Api.Helpers return dto; }); - return new QueryResult<BaseItemDto> - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; + return new QueryResult<BaseItemDto>( + result.StartIndex, + result.TotalRecordCount, + dtos.ToArray()); } } } diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 3526d56c6..c8762b7c5 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -18,6 +18,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -42,6 +43,8 @@ namespace Jellyfin.Api.Helpers /// </summary> private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>(); + private readonly IAttachmentExtractor _attachmentExtractor; + private readonly IApplicationPaths _appPaths; private readonly IAuthorizationContext _authorizationContext; private readonly EncodingHelper _encodingHelper; private readonly IFileSystem _fileSystem; @@ -55,6 +58,8 @@ namespace Jellyfin.Api.Helpers /// <summary> /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class. /// </summary> + /// <param name="attachmentExtractor">Instance of the <see cref="IAttachmentExtractor"/> interface.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> @@ -65,6 +70,8 @@ namespace Jellyfin.Api.Helpers /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> public TranscodingJobHelper( + IAttachmentExtractor attachmentExtractor, + IApplicationPaths appPaths, ILogger<TranscodingJobHelper> logger, IMediaSourceManager mediaSourceManager, IFileSystem fileSystem, @@ -75,6 +82,8 @@ namespace Jellyfin.Api.Helpers EncodingHelper encodingHelper, ILoggerFactory loggerFactory) { + _attachmentExtractor = attachmentExtractor; + _appPaths = appPaths; _logger = logger; _mediaSourceManager = mediaSourceManager; _fileSystem = fileSystem; @@ -449,9 +458,12 @@ namespace Jellyfin.Api.Helpers var audioCodec = state.ActualOutputAudioCodec; var videoCodec = state.ActualOutputVideoCodec; var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType; - HardwareEncodingType? hardwareAccelerationType = string.IsNullOrEmpty(hardwareAccelerationTypeString) - ? null - : (HardwareEncodingType)Enum.Parse(typeof(HardwareEncodingType), hardwareAccelerationTypeString, true); + HardwareEncodingType? hardwareAccelerationType = null; + if (!string.IsNullOrEmpty(hardwareAccelerationTypeString) + && Enum.TryParse<HardwareEncodingType>(hardwareAccelerationTypeString, out var parsedHardwareAccelerationType)) + { + hardwareAccelerationType = parsedHardwareAccelerationType; + } _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo { @@ -513,6 +525,13 @@ namespace Jellyfin.Api.Helpers throw new ArgumentException("FFmpeg path not set."); } + // If subtitles get burned in fonts may need to be extracted from the media file + if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + { + var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); + await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, CancellationToken.None).ConfigureAwait(false); + } + var process = new Process { StartInfo = new ProcessStartInfo @@ -753,6 +772,8 @@ namespace Jellyfin.Api.Helpers job.HasExited = true; job.ExitCode = process.ExitCode; + ReportTranscodingProgress(job, state, null, null, null, null, null); + _logger.LogDebug("Disposing stream resources"); state.Dispose(); diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index b5af07408..c5b240e92 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -17,7 +17,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.2" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" /> @@ -26,10 +26,15 @@ <ItemGroup> <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + <ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> </ItemGroup> <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs b/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs deleted file mode 100644 index 7f1255f4b..000000000 --- a/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Jellyfin.Api.Models.PluginDtos -{ - /// <summary> - /// MB Registration Record. - /// </summary> - public class MBRegistrationRecord - { - /// <summary> - /// Gets or sets expiration date. - /// </summary> - public DateTime ExpirationDate { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is registered. - /// </summary> - public bool IsRegistered { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether reg checked. - /// </summary> - public bool RegChecked { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether reg error. - /// </summary> - public bool RegError { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether trial version. - /// </summary> - public bool TrialVersion { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is valid. - /// </summary> - public bool IsValid { get; set; } - } -} diff --git a/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs b/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs deleted file mode 100644 index a90398425..000000000 --- a/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Jellyfin.Api.Models.PluginDtos -{ - /// <summary> - /// Plugin security info. - /// </summary> - public class PluginSecurityInfo - { - /// <summary> - /// Gets or sets the supporter key. - /// </summary> - public string? SupporterKey { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is mb supporter. - /// </summary> - public bool IsMbSupporter { get; set; } - } -} diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs index 1791b0370..f8b0212b6 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs @@ -41,5 +41,15 @@ namespace Jellyfin.Api.Models.StreamingDtos /// Gets or sets the min segments. /// </summary> public int? MinSegments { get; set; } + + /// <summary> + /// Gets or sets the position of the requested segment in ticks. + /// </summary> + public long CurrentRuntimeTicks { get; set; } + + /// <summary> + /// Gets or sets the actual segment length in ticks. + /// </summary> + public long ActualSegmentLengthTicks { get; set; } } } diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index f2779d8f2..c35778065 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -29,6 +29,10 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 4cc215903..44631327e 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -30,6 +30,10 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 6d0a5ac2b..1fa8e570d 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -492,6 +492,14 @@ namespace Jellyfin.Drawing.Skia } } + /// <inheritdoc /> + public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops) + { + var splashBuilder = new SplashscreenBuilder(this); + var outputPath = Path.Combine(_appPaths.DataPath, "splashscreen.png"); + splashBuilder.GenerateSplash(posters, backdrops, outputPath); + } + private void DrawIndicator(SKCanvas canvas, int imageWidth, int imageHeight, ImageProcessingOptions options) { try diff --git a/Jellyfin.Drawing.Skia/SkiaHelper.cs b/Jellyfin.Drawing.Skia/SkiaHelper.cs index f9c79c855..35dcebdab 100644 --- a/Jellyfin.Drawing.Skia/SkiaHelper.cs +++ b/Jellyfin.Drawing.Skia/SkiaHelper.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using SkiaSharp; namespace Jellyfin.Drawing.Skia @@ -19,5 +20,41 @@ namespace Jellyfin.Drawing.Skia throw new SkiaCodecException(result); } } + + /// <summary> + /// Gets the next valid image as a bitmap. + /// </summary> + /// <param name="skiaEncoder">The current skia encoder.</param> + /// <param name="paths">The list of image paths.</param> + /// <param name="currentIndex">The current checked indes.</param> + /// <param name="newIndex">The new index.</param> + /// <returns>A valid bitmap, or null if no bitmap exists after <c>currentIndex</c>.</returns> + public static SKBitmap? GetNextValidImage(SkiaEncoder skiaEncoder, IReadOnlyList<string> paths, int currentIndex, out int newIndex) + { + var imagesTested = new Dictionary<int, int>(); + SKBitmap? bitmap = null; + + while (imagesTested.Count < paths.Count) + { + if (currentIndex >= paths.Count) + { + currentIndex = 0; + } + + bitmap = skiaEncoder.Decode(paths[currentIndex], false, null, out _); + + imagesTested[currentIndex] = 0; + + currentIndex++; + + if (bitmap != null) + { + break; + } + } + + newIndex = currentIndex; + return bitmap; + } } } diff --git a/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs new file mode 100644 index 000000000..e5fa6c2bd --- /dev/null +++ b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using SkiaSharp; + +namespace Jellyfin.Drawing.Skia +{ + /// <summary> + /// Used to build the splashscreen. + /// </summary> + public class SplashscreenBuilder + { + private const int FinalWidth = 1920; + private const int FinalHeight = 1080; + // generated collage resolution should be higher than the final resolution + private const int WallWidth = FinalWidth * 3; + private const int WallHeight = FinalHeight * 2; + private const int Rows = 6; + private const int Spacing = 20; + + private readonly SkiaEncoder _skiaEncoder; + + /// <summary> + /// Initializes a new instance of the <see cref="SplashscreenBuilder"/> class. + /// </summary> + /// <param name="skiaEncoder">The SkiaEncoder.</param> + public SplashscreenBuilder(SkiaEncoder skiaEncoder) + { + _skiaEncoder = skiaEncoder; + } + + /// <summary> + /// Generate a splashscreen. + /// </summary> + /// <param name="posters">The poster paths.</param> + /// <param name="backdrops">The landscape paths.</param> + /// <param name="outputPath">The output path.</param> + public void GenerateSplash(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops, string outputPath) + { + using var wall = GenerateCollage(posters, backdrops); + using var transformed = Transform3D(wall); + + using var outputStream = new SKFileWStream(outputPath); + using var pixmap = new SKPixmap(new SKImageInfo(FinalWidth, FinalHeight), transformed.GetPixels()); + pixmap.Encode(outputStream, StripCollageBuilder.GetEncodedFormat(outputPath), 90); + } + + /// <summary> + /// Generates a collage of posters and landscape pictures. + /// </summary> + /// <param name="posters">The poster paths.</param> + /// <param name="backdrops">The landscape paths.</param> + /// <returns>The created collage as a bitmap.</returns> + private SKBitmap GenerateCollage(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops) + { + var posterIndex = 0; + var backdropIndex = 0; + + var bitmap = new SKBitmap(WallWidth, WallHeight); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Black); + + int posterHeight = WallHeight / 6; + + for (int i = 0; i < Rows; i++) + { + int imageCounter = Random.Shared.Next(0, 5); + int currentWidthPos = i * 75; + int currentHeight = i * (posterHeight + Spacing); + + while (currentWidthPos < WallWidth) + { + SKBitmap? currentImage; + + switch (imageCounter) + { + case 0: + case 2: + case 3: + currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, posters, posterIndex, out int newPosterIndex); + posterIndex = newPosterIndex; + break; + default: + currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, backdrops, backdropIndex, out int newBackdropIndex); + backdropIndex = newBackdropIndex; + break; + } + + if (currentImage == null) + { + throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!"); + } + + // resize to the same aspect as the original + var imageWidth = Math.Abs(posterHeight * currentImage.Width / currentImage.Height); + using var resizedBitmap = new SKBitmap(imageWidth, posterHeight); + currentImage.ScalePixels(resizedBitmap, SKFilterQuality.High); + + // draw on canvas + canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight); + + currentWidthPos += imageWidth + Spacing; + + currentImage.Dispose(); + + if (imageCounter >= 4) + { + imageCounter = 0; + } + else + { + imageCounter++; + } + } + } + + return bitmap; + } + + /// <summary> + /// Transform the collage in 3D space. + /// </summary> + /// <param name="input">The bitmap to transform.</param> + /// <returns>The transformed image.</returns> + private SKBitmap Transform3D(SKBitmap input) + { + var bitmap = new SKBitmap(FinalWidth, FinalHeight); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Black); + var matrix = new SKMatrix + { + ScaleX = 0.324108899f, + ScaleY = 0.563934922f, + SkewX = -0.244337708f, + SkewY = 0.0377609022f, + TransX = 42.0407715f, + TransY = -198.104706f, + Persp0 = -9.08959337E-05f, + Persp1 = 6.85242048E-05f, + Persp2 = 0.988209724f + }; + + canvas.SetMatrix(matrix); + canvas.DrawBitmap(input, 0, 0); + + return bitmap; + } + } +} diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index d1cc2255d..6bece9db6 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -99,7 +99,7 @@ namespace Jellyfin.Drawing.Skia using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); - using var backdrop = GetNextValidImage(paths, 0, out _); + using var backdrop = SkiaHelper.GetNextValidImage(_skiaEncoder, paths, 0, out _); if (backdrop == null) { return bitmap; @@ -152,34 +152,6 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - private SKBitmap? GetNextValidImage(IReadOnlyList<string> paths, int currentIndex, out int newIndex) - { - var imagesTested = new Dictionary<int, int>(); - SKBitmap? bitmap = null; - - while (imagesTested.Count < paths.Count) - { - if (currentIndex >= paths.Count) - { - currentIndex = 0; - } - - bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out _); - - imagesTested[currentIndex] = 0; - - currentIndex++; - - if (bitmap != null) - { - break; - } - } - - newIndex = currentIndex; - return bitmap; - } - private SKBitmap BuildSquareCollageBitmap(IReadOnlyList<string> paths, int width, int height) { var bitmap = new SKBitmap(width, height); @@ -192,7 +164,7 @@ namespace Jellyfin.Drawing.Skia { for (var y = 0; y < 2; y++) { - using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex); + using var currentBitmap = SkiaHelper.GetNextValidImage(_skiaEncoder, paths, imageIndex, out int newIndex); imageIndex = newIndex; if (currentBitmap == null) diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index a6af8566c..ef8ef700f 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -11,6 +11,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 58b30ad2d..b16dc5390 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -628,7 +628,6 @@ namespace Jellyfin.Networking.Manager } TrustAllIP6Interfaces = config.TrustAllIP6Interfaces; - // UdpHelper.EnableMultiSocketBinding = config.EnableMultiSocketBinding; if (string.IsNullOrEmpty(MockNetworkSettings)) { @@ -750,7 +749,7 @@ namespace Jellyfin.Networking.Manager bool partial = token[^1] == '*'; if (partial) { - token = token[0..^1]; + token = token[..^1]; } foreach ((string interfc, int interfcIndex) in _interfaceNames) diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index ba2c8b54f..4447b212d 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -59,17 +59,16 @@ namespace Jellyfin.Server.Implementations.Activity entries = entries.Where(entry => entry.UserId != Guid.Empty == query.HasUserId.Value ); } - return new QueryResult<ActivityLogEntry> - { - Items = await entries + return new QueryResult<ActivityLogEntry>( + query.Skip, + await entries.CountAsync().ConfigureAwait(false), + await entries .Skip(query.Skip ?? 0) .Take(query.Limit ?? 100) .AsAsyncEnumerable() .Select(ConvertToOldModel) .ToListAsync() - .ConfigureAwait(false), - TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) - }; + .ConfigureAwait(false)); } /// <inheritdoc /> diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 6c77421c7..b5fc96079 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -145,12 +145,10 @@ namespace Jellyfin.Server.Implementations.Devices devices = devices.Take(query.Limit.Value); } - return new QueryResult<Device> - { - Items = await devices.ToListAsync().ConfigureAwait(false), - StartIndex = query.Skip ?? 0, - TotalRecordCount = count - }; + return new QueryResult<Device>( + query.Skip, + count, + await devices.ToListAsync().ConfigureAwait(false)); } /// <inheritdoc /> @@ -158,12 +156,10 @@ namespace Jellyfin.Server.Implementations.Devices { var devices = await GetDevices(query).ConfigureAwait(false); - return new QueryResult<DeviceInfo> - { - Items = devices.Items.Select(device => ToDeviceInfo(device)).ToList(), - StartIndex = devices.StartIndex, - TotalRecordCount = devices.TotalRecordCount - }; + return new QueryResult<DeviceInfo>( + devices.StartIndex, + devices.TotalRecordCount, + devices.Items.Select(device => ToDeviceInfo(device)).ToList()); } /// <inheritdoc /> diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 86aec1399..b7dab82af 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -12,6 +12,10 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> @@ -22,14 +26,14 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="System.Linq.Async" Version="5.1.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1"> + <PackageReference Include="System.Linq.Async" Version="6.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.2" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.2" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs index 73a619b8d..7e7e4ca95 100644 --- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs +++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs @@ -125,18 +125,20 @@ namespace Jellyfin.Server.Infrastructure // Copied from SendFileFallback.SendFileAsync const int BufferSize = 1024 * 16; - await using var fileStream = new FileStream( + var fileStream = new FileStream( filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: BufferSize, options: FileOptions.Asynchronous | FileOptions.SequentialScan); - - fileStream.Seek(offset, SeekOrigin.Begin); - await StreamCopyOperation - .CopyToAsync(fileStream, response.Body, count, BufferSize, CancellationToken.None) - .ConfigureAwait(true); + await using (fileStream.ConfigureAwait(false)) + { + fileStream.Seek(offset, SeekOrigin.Begin); + await StreamCopyOperation + .CopyToAsync(fileStream, response.Body, count, BufferSize, CancellationToken.None) + .ConfigureAwait(true); + } } private static bool IsSymLink(string path) => (File.GetAttributes(path) & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 12efa15dd..6743a24aa 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -14,10 +14,6 @@ <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> - <TreatWarningsAsErrors>false</TreatWarningsAsErrors> - </PropertyGroup> - <ItemGroup> <Compile Include="..\SharedVersion.cs" /> </ItemGroup> @@ -28,6 +24,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> @@ -35,10 +35,10 @@ <ItemGroup> <PackageReference Include="CommandLineParser" Version="2.8.0" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.1" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.1" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.2" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.2" /> <PackageReference Include="prometheus-net" Version="5.0.2" /> <PackageReference Include="prometheus-net.AspNetCore" Version="5.0.2" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> @@ -56,6 +56,7 @@ <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" /> <ProjectReference Include="..\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" /> <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" /> + <ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> </ItemGroup> <ItemGroup> diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs index 3e5982eed..e0c112d60 100644 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Jellyfin.Server.Middleware { @@ -65,7 +65,7 @@ namespace Jellyfin.Server.Middleware { // Always redirect back to the default path if the base prefix is invalid, missing, or is the full path. _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); - httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); + httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[DefaultRedirectKey]); return; } } @@ -74,7 +74,7 @@ namespace Jellyfin.Server.Middleware { // Always redirect back to the default path if root is requested. _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); - httpContext.Response.Redirect("/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); + httpContext.Response.Redirect("/" + _configuration[DefaultRedirectKey]); return; } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs index 21f153623..afd7aee5d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -58,13 +58,18 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var row in authenticatedDevices) { + var dateCreatedStr = row[9].ToString(); + _ = DateTime.TryParse(dateCreatedStr, out var dateCreated); + var dateLastActivityStr = row[10].ToString(); + _ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity); + if (row[6].IsDbNull()) { dbContext.ApiKeys.Add(new ApiKey(row[3].ToString()) { AccessToken = row[1].ToString(), - DateCreated = row[9].ToDateTime(), - DateLastActivity = row[10].ToDateTime() + DateCreated = dateCreated, + DateLastActivity = dateLastActivity }); } else @@ -78,8 +83,8 @@ namespace Jellyfin.Server.Migrations.Routines { AccessToken = row[1].ToString(), IsActive = row[8].ToBool(), - DateCreated = row[9].ToDateTime(), - DateLastActivity = row[10].ToDateTime() + DateCreated = dateCreated, + DateLastActivity = dateLastActivity }); } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f40526e22..fc871f064 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -13,7 +13,6 @@ using Emby.Server.Implementations; using Jellyfin.Server.Implementations; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -26,7 +25,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Serilog.Extensions.Logging; using SQLitePCL; -using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server @@ -168,7 +167,7 @@ namespace Jellyfin.Server "server, you may set the '--nowebclient' command line flag, or set" + "'{ConfigKey}=false' in your config settings.", webContentPath, - ConfigurationExtensions.HostWebClientKey); + HostWebClientKey); Environment.ExitCode = 1; return; } @@ -224,12 +223,16 @@ namespace Jellyfin.Server } finally { - _logger.LogInformation("Running query planner optimizations in the database... This might take a while"); - // Run before disposing the application - using var context = appHost.Resolve<JellyfinDbProvider>().CreateContext(); - if (context.Database.IsSqlite()) + // Don't throw additional exception if startup failed. + if (appHost.ServiceProvider != null) { - context.Database.ExecuteSqlRaw("PRAGMA optimize"); + _logger.LogInformation("Running query planner optimizations in the database... This might take a while"); + // Run before disposing the application + using var context = appHost.Resolve<JellyfinDbProvider>().CreateContext(); + if (context.Database.IsSqlite()) + { + context.Database.ExecuteSqlRaw("PRAGMA optimize"); + } } appHost.Dispose(); @@ -545,12 +548,15 @@ namespace Jellyfin.Server // Get a stream of the resource contents // NOTE: The .csproj name is used instead of the assembly name in the resource path const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; - await using Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath) + Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath) ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'"); - - // Copy the resource contents to the expected file path for the config file - await using Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await resource.CopyToAsync(dst).ConfigureAwait(false); + Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (resource.ConfigureAwait(false)) + await using (dst.ConfigureAwait(false)) + { + // Copy the resource contents to the expected file path for the config file + await resource.CopyToAsync(dst).ConfigureAwait(false); + } } /// <summary> @@ -576,7 +582,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (startupConfig != null && !startupConfig.HostWebClient()) { - inMemoryDefaultConfig[ConfigurationExtensions.DefaultRedirectKey] = "api-docs/swagger"; + inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger"; } return config diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 8085c2630..8eb5f2196 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Text; +using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.Configuration; using Jellyfin.Server.Extensions; using Jellyfin.Server.Implementations; @@ -104,6 +105,8 @@ namespace Jellyfin.Server services.AddHealthChecks() .AddDbContextCheck<JellyfinDb>(); + + services.AddHlsPlaylistGenerator(); } /// <summary> diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index a1cecc8c6..84ebde68c 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.Udp; -using MediaBrowser.Controller.Extensions; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Jellyfin.Server { @@ -86,17 +85,17 @@ namespace Jellyfin.Server if (NoWebClient) { - config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); + config.Add(HostWebClientKey, bool.FalseString); } if (PublishedServerUrl != null) { - config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl); + config.Add(AddressOverrideKey, PublishedServerUrl); } if (FFmpegPath != null) { - config.Add(ConfigurationExtensions.FfmpegPathKey, FFmpegPath); + config.Add(FfmpegPathKey, FFmpegPath); } return config; diff --git a/Jellyfin.sln b/Jellyfin.sln index 4626601c3..0514b9614 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -89,6 +89,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes", "src\Jellyfin.MediaEncoding.Keyframes\Jellyfin.MediaEncoding.Keyframes.csproj", "{06535CA1-4097-4360-85EB-5FB875D53239}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls", "src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj", "{DA9FD356-4894-4830-B208-D6BCE3E65B11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls.Tests", "tests\Jellyfin.MediaEncoding.Hls.Tests\Jellyfin.MediaEncoding.Hls.Tests.csproj", "{FE47334C-EFDE-4519-BD50-F24430FF360B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes.Tests", "tests\Jellyfin.MediaEncoding.Keyframes.Tests\Jellyfin.MediaEncoding.Keyframes.Tests.csproj", "{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -219,10 +227,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -243,6 +247,22 @@ Global {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.Build.0 = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE47334C-EFDE-4519-BD50-F24430FF360B}.Release|Any CPU.Build.0 = Release|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -263,6 +283,10 @@ Global {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} {332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {06535CA1-4097-4360-85EB-5FB875D53239} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {DA9FD356-4894-4830-B208-D6BCE3E65B11} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {FE47334C-EFDE-4519-BD50-F24430FF360B} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {24960660-DE6C-47BF-AEEF-CEE8F19FE6C2} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs index 08e01bfd6..c3a7cb394 100644 --- a/MediaBrowser.Common/Extensions/ProcessExtensions.cs +++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Common.Extensions } // Add an event handler for the process exit event - var tcs = new TaskCompletionSource<bool>(); + var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); process.Exited += (_, _) => tcs.TrySetResult(true); // Return immediately if the process has already exited diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 2a2fffce0..b61e104ce 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -49,6 +49,10 @@ <!-- Code analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index e6923b55c..85a99d62c 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Controller.Channels query.ChannelIds = new Guid[] { Id }; // Don't blow up here because it could cause parent screens with other content to fail - return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).Result; + return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).GetAwaiter().GetResult(); } catch { diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 4e67cfee4..e5c8ebfaf 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -74,5 +74,12 @@ namespace MediaBrowser.Controller.Drawing /// <param name="options">The options to use when creating the collage.</param> /// <param name="libraryName">Optional. </param> void CreateImageCollage(ImageCollageOptions options, string? libraryName); + + /// <summary> + /// Creates a new splashscreen image. + /// </summary> + /// <param name="posters">The list of poster paths.</param> + /// <param name="backdrops">The list of backdrop paths.</param> + void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 915971adc..c52732858 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Text.Json.Serialization; @@ -886,7 +887,7 @@ namespace MediaBrowser.Controller.Entities return Name; } - public string GetInternalMetadataPath() + public virtual string GetInternalMetadataPath() { var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; @@ -1286,7 +1287,7 @@ namespace MediaBrowser.Controller.Entities { if (IsFileProtocol) { - requiresSave = await RefreshedOwnedItems(options, GetFileSystemChildren(options.DirectoryService).ToList(), cancellationToken).ConfigureAwait(false); + requiresSave = await RefreshedOwnedItems(options, GetFileSystemChildren(options.DirectoryService), cancellationToken).ConfigureAwait(false); } await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh @@ -1363,7 +1364,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="fileSystemChildren">The list of filesystem children.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns> - protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) + protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder or UserRootFolder or AggregateFolder || this.GetType() == typeof(Folder)) { @@ -1380,7 +1381,7 @@ namespace MediaBrowser.Controller.Entities return directoryService.GetFileSystemEntries(path); } - private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) + private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { var extras = LibraryManager.FindExtras(item, fileSystemChildren, options.DirectoryService).ToArray(); var newExtraIds = extras.Select(i => i.Id).ToArray(); @@ -2041,27 +2042,32 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Validates that images within the item are still on the filesystem. /// </summary> - /// <param name="directoryService">The directory service to use.</param> /// <returns><c>true</c> if the images validate, <c>false</c> if not.</returns> - public bool ValidateImages(IDirectoryService directoryService) + public bool ValidateImages() { - var allFiles = ImageInfos - .Where(i => i.IsLocalFile) - .Select(i => System.IO.Path.GetDirectoryName(i.Path)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .SelectMany(path => directoryService.GetFilePaths(path)) - .ToList(); + List<ItemImageInfo> deletedImages = null; + foreach (var imageInfo in ImageInfos) + { + if (!imageInfo.IsLocalFile) + { + continue; + } - var deletedImages = ImageInfos - .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparison.OrdinalIgnoreCase)) - .ToList(); + if (File.Exists(imageInfo.Path)) + { + continue; + } + + (deletedImages ??= new List<ItemImageInfo>()).Add(imageInfo); + } - if (deletedImages.Count > 0) + var anyImagesRemoved = deletedImages?.Count > 0; + if (anyImagesRemoved) { RemoveImages(deletedImages); } - return deletedImages.Count > 0; + return anyImagesRemoved; } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 55551e70e..cb5ff6eec 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -783,11 +783,10 @@ namespace MediaBrowser.Controller.Entities returnItems = returnItems.Skip(startIndex.Value); } - return new QueryResult<BaseItem> - { - TotalRecordCount = totalCount, - Items = returnItems.ToArray() - }; + return new QueryResult<BaseItem>( + query.StartIndex, + totalCount, + returnItems.ToArray()); } private bool RequiresPostFiltering2(InternalItemsQuery query) @@ -849,6 +848,18 @@ namespace MediaBrowser.Controller.Entities return true; } + if (query.HasThemeSong.HasValue) + { + Logger.LogDebug("Query requires post-filtering due to HasThemeSong"); + return true; + } + + if (query.HasThemeVideo.HasValue) + { + Logger.LogDebug("Query requires post-filtering due to HasThemeVideo"); + return true; + } + // Filter by VideoType if (query.VideoTypes.Length > 0) { @@ -965,7 +976,7 @@ namespace MediaBrowser.Controller.Entities query.ChannelIds = new[] { ChannelId }; // Don't blow up here because it could cause parent screens with other content to fail - return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).Result; + return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress<double>(), CancellationToken.None).GetAwaiter().GetResult(); } catch { @@ -1586,7 +1597,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.Item2 != null); } - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index fe44f1169..279206da4 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -238,12 +238,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> ConvertToResult(List<BaseItem> items) { - var arr = items.ToArray(); - return new QueryResult<BaseItem> - { - Items = arr, - TotalRecordCount = arr.Length - }; + return new QueryResult<BaseItem>(items); } private QueryResult<BaseItem> GetMovieGenres(Folder parent, User user, InternalItemsQuery query) @@ -414,16 +409,6 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result) - where T : BaseItem - { - return new QueryResult<BaseItem> - { - Items = result.Items, // TODO Fix The co-variant conversion between T[] and BaseItem[], this can generate runtime issues if T is not BaseItem. - TotalRecordCount = result.TotalRecordCount - }; - } - private QueryResult<BaseItem> GetResult<T>( IEnumerable<T> items, InternalItemsQuery query) @@ -483,11 +468,10 @@ namespace MediaBrowser.Controller.Entities itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); } - return new QueryResult<BaseItem> - { - TotalRecordCount = totalCount, - Items = itemsArray - }; + return new QueryResult<BaseItem>( + query.StartIndex, + totalCount, + itemsArray); } public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 3e125602a..5ab7808c3 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -419,7 +419,7 @@ namespace MediaBrowser.Controller.Entities return updateType; } - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index f9285c768..957ce6744 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -15,6 +15,11 @@ namespace MediaBrowser.Controller.Extensions public const string DefaultRedirectKey = "DefaultRedirectPath"; /// <summary> + /// The key for the address override option. + /// </summary> + public const string AddressOverrideKey = "PublishedServerUrl"; + + /// <summary> /// The key for a setting that indicates whether the application should host web client content. /// </summary> public const string HostWebClientKey = "hostwebclient"; diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index a74d1b9f0..4a9721acb 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -24,11 +24,5 @@ namespace MediaBrowser.Controller.Library /// <param name="user">The user.</param> /// <returns>IEnumerable{System.String}.</returns> Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user); - - /// <summary> - /// Gets all intro files. - /// </summary> - /// <returns>IEnumerable{System.String}.</returns> - IEnumerable<string> GetAllIntroFiles(); } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 8db528330..313d27ce6 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -138,10 +138,10 @@ namespace MediaBrowser.Controller.Library /// Validate and refresh the People sub-set of the IBN. /// The items are stored in the db but not loaded into memory until actually requested by an operation. /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress); + Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken); /// <summary> /// Reloads the root media folder. @@ -151,11 +151,6 @@ namespace MediaBrowser.Controller.Library /// <returns>Task.</returns> Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken); - /// <summary> - /// Queues the library scan. - /// </summary> - void QueueLibraryScan(); - Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false); /// <summary> @@ -182,12 +177,6 @@ namespace MediaBrowser.Controller.Library Task<IEnumerable<Video>> GetIntros(BaseItem item, User user); /// <summary> - /// Gets all intro files. - /// </summary> - /// <returns>IEnumerable{System.String}.</returns> - IEnumerable<string> GetAllIntroFiles(); - - /// <summary> /// Adds the parts. /// </summary> /// <param name="rules">The rules.</param> @@ -434,7 +423,7 @@ namespace MediaBrowser.Controller.Library /// <param name="fileSystemChildren">The file system children.</param> /// <param name="directoryService">An instance of <see cref="IDirectoryService"/>.</param> /// <returns>IEnumerable<BaseItem>.</returns> - IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService); + IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService); /// <summary> /// Gets the collection folders. @@ -508,15 +497,6 @@ namespace MediaBrowser.Controller.Library string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem = null); /// <summary> - /// Substitutes the path. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="from">From.</param> - /// <param name="to">To.</param> - /// <returns>System.String.</returns> - string SubstitutePath(string path, string from, string to); - - /// <summary> /// Converts the image to local. /// </summary> /// <param name="item">The item.</param> @@ -587,15 +567,8 @@ namespace MediaBrowser.Controller.Library int GetCount(InternalItemsQuery query); - void AddExternalSubtitleStreams( - List<MediaStream> streams, - string videoPath, - string[] files); - Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason); - BaseItem GetParentItem(string parentId, Guid? userId); - BaseItem GetParentItem(Guid? parentId, Guid? userId); } } diff --git a/MediaBrowser.Controller/Library/IMetadataFileSaver.cs b/MediaBrowser.Controller/Library/IMetadataFileSaver.cs index 9c6f03a23..842c687d1 100644 --- a/MediaBrowser.Controller/Library/IMetadataFileSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataFileSaver.cs @@ -13,9 +13,4 @@ namespace MediaBrowser.Controller.Library /// <returns>System.String.</returns> string GetSavePath(BaseItem item); } - - public interface IConfigurableProvider - { - bool IsEnabled { get; } - } } diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index d963fd249..eed661345 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library @@ -29,6 +28,7 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> - void Save(BaseItem item, CancellationToken cancellationToken); + /// <returns>The task object representing the asynchronous operation.</returns> + Task SaveAsync(BaseItem item, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 6dc5665b2..46bdca302 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -188,7 +188,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="options">The options.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Recommended programs.</returns> - QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken); + Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken); /// <summary> /// Gets the recommended programs internal. diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index e63874f21..335222da9 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.Controller.LiveTv { Id = Id.ToString("N", CultureInfo.InvariantCulture), Protocol = PathProtocol ?? MediaProtocol.File, - MediaStreams = new List<MediaStream>(), + MediaStreams = Array.Empty<MediaStream>(), Name = Name, Path = Path, RunTimeTicks = RunTimeTicks, diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 432159d5d..e76a478a5 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -52,6 +52,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bde10dbbf..f7248acac 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using Jellyfin.Data.Enums; using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; @@ -28,7 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding private const string VideotoolboxAlias = "vt"; private const string OpenclAlias = "ocl"; private const string CudaAlias = "cu"; - + private readonly IApplicationPaths _appPaths; private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -51,9 +52,11 @@ namespace MediaBrowser.Controller.MediaEncoding }; public EncodingHelper( + IApplicationPaths appPaths, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) { + _appPaths = appPaths; _mediaEncoder = mediaEncoder; _subtitleEncoder = subtitleEncoder; } @@ -81,7 +84,6 @@ namespace MediaBrowser.Controller.MediaEncoding { "vaapi", hwEncoder + "_vaapi" }, { "videotoolbox", hwEncoder + "_videotoolbox" }, { "v4l2m2m", hwEncoder + "_v4l2m2m" }, - { "omx", hwEncoder + "_omx" }, }; if (!string.IsNullOrEmpty(hwType) @@ -578,13 +580,13 @@ namespace MediaBrowser.Controller.MediaEncoding options); } - private string GetVaapiDeviceArgs(string renderNodePath, string kernelDriver, string driver, string alias) + private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string alias) { alias ??= VaapiAlias; renderNodePath = renderNodePath ?? "/dev/dri/renderD128"; - var options = string.IsNullOrEmpty(kernelDriver) || string.IsNullOrEmpty(driver) + var options = string.IsNullOrEmpty(driver) ? renderNodePath - : ",kernel_driver=" + kernelDriver + ",driver=" + driver; + : ",driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver); return string.Format( CultureInfo.InvariantCulture, @@ -599,7 +601,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (OperatingSystem.IsLinux()) { // derive qsv from vaapi device - return GetVaapiDeviceArgs(null, "i915", "iHD", VaapiAlias) + arg + "@" + VaapiAlias; + return GetVaapiDeviceArgs(null, "iHD", "i915", VaapiAlias) + arg + "@" + VaapiAlias; } if (OperatingSystem.IsWindows()) @@ -688,7 +690,19 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias)); + if (_mediaEncoder.IsVaapiDeviceInteliHD) + { + args.Append(GetVaapiDeviceArgs(null, "iHD", null, VaapiAlias)); + } + else if (_mediaEncoder.IsVaapiDeviceInteli965) + { + args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias)); + } + else + { + args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias)); + } + var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias); if (isHwTonemapAvailable && IsOpenclFullSupported()) @@ -768,10 +782,6 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(GetCudaDeviceArgs(0, CudaAlias)) .Append(GetFilterHwDeviceArgs(CudaAlias)); - - // workaround for "No decoder surfaces left" error, - // but will increase vram usage. https://trac.ffmpeg.org/ticket/7562 - args.Append(" -extra_hw_frames 3"); } else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase)) { @@ -1080,6 +1090,12 @@ namespace MediaBrowser.Controller.MediaEncoding var alphaParam = enableAlpha ? ":alpha=1" : string.Empty; var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty; + var fontPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); + var fontParam = string.Format( + CultureInfo.InvariantCulture, + ":fontsdir='{0}'", + _mediaEncoder.EscapeSubtitleFilterPath(fontPath)); + // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); // string fallbackFontParam = string.Empty; @@ -1120,11 +1136,12 @@ namespace MediaBrowser.Controller.MediaEncoding // TODO: Perhaps also use original_size=1920x800 ?? return string.Format( CultureInfo.InvariantCulture, - "subtitles=f='{0}'{1}{2}{3}{4}", + "subtitles=f='{0}'{1}{2}{3}{4}{5}", _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), charsetParam, alphaParam, sub2videoParam, + fontParam, // fallbackFontParam, setPtsParam); } @@ -1133,11 +1150,12 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "subtitles='{0}:si={1}{2}{3}'{4}", + "subtitles=f='{0}':si={1}{2}{3}{4}{5}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), alphaParam, sub2videoParam, + fontParam, // fallbackFontParam, setPtsParam); } @@ -1281,11 +1299,6 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -low_power 1"; } - if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) - { - param += " -pix_fmt nv21"; - } - var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase); var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); @@ -1343,29 +1356,37 @@ namespace MediaBrowser.Controller.MediaEncoding switch (encodingOptions.EncoderPreset) { case "veryslow": - - param += " -preset slow"; // lossless is only supported on maxwell and newer(2014+) + param += " -preset p7"; break; case "slow": + param += " -preset p6"; + break; + case "slower": - param += " -preset slow"; + param += " -preset p5"; break; case "medium": - param += " -preset medium"; + param += " -preset p4"; break; case "fast": + param += " -preset p3"; + break; + case "faster": + param += " -preset p2"; + break; + case "veryfast": case "superfast": case "ultrafast": - param += " -preset fast"; + param += " -preset p1"; break; default: - param += " -preset default"; + param += " -preset p4"; break; } } @@ -1571,10 +1592,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(profile)) { - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { - // not supported by h264_omx param += " -profile:v:0 " + profile; } } @@ -1613,8 +1632,7 @@ namespace MediaBrowser.Controller.MediaEncoding // NVENC cannot adjust the given level, just throw an error. // level option may cause corrupted frames on AMD VAAPI. } - else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) - || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } @@ -2167,6 +2185,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && state.TranscodingType != TranscodingJobType.Progressive + && state.TranscodingType != TranscodingJobType.Hls && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) && (state.BaseRequest.StartTimeTicks ?? 0) > 0) { @@ -2695,6 +2714,7 @@ namespace MediaBrowser.Controller.MediaEncoding var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; var isSwDecoder = string.IsNullOrEmpty(vidDecoder); var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); @@ -2723,6 +2743,10 @@ namespace MediaBrowser.Controller.MediaEncoding { outFormat = "nv12"; } + else if (isV4l2Encoder) + { + outFormat = "yuv420p"; + } // sw scale mainFilters.Add(swScaleFilter); @@ -4268,11 +4292,6 @@ namespace MediaBrowser.Controller.MediaEncoding { return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth); } - - if (string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) - { - return GetOmxVidDecoder(state, options, videoStream, bitDepth); - } } var whichCodec = videoStream.Codec; @@ -4411,7 +4430,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (options.EnableEnhancedNvdecDecoder && isCudaSupported && isCodecAvailable) { - return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); + // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support. + return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty); } } @@ -4747,43 +4767,6 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - public string GetOmxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth) - { - if (!OperatingSystem.IsLinux() - || !string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - var is8bitSwFormatsOmx = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - - if (is8bitSwFormatsOmx) - { - if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) - || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - return GetHwDecoderName(options, "h264", "mmal", "h264", bitDepth); - } - - if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - return GetHwDecoderName(options, "mpeg2", "mmal", "mpeg2video", bitDepth); - } - - if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - return GetHwDecoderName(options, "mpeg4", "mmal", "mpeg4", bitDepth); - } - - if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - return GetHwDecoderName(options, "vc1", "mmal", "vc1", bitDepth); - } - } - - return null; - } - /// <summary> /// Gets the number of threads. /// </summary> diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index 4e7e26624..a2b6be1e6 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.MediaEncoding @@ -17,5 +18,10 @@ namespace MediaBrowser.Controller.MediaEncoding string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken); + Task ExtractAllAttachments( + string inputFile, + MediaSourceInfo mediaSource, + string outputPath, + CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index fd3eb8105..6bf3e7b46 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -26,6 +26,12 @@ namespace MediaBrowser.Controller.MediaEncoding string EncoderPath { get; } /// <summary> + /// Gets the probe path. + /// </summary> + /// <value>The probe path.</value> + string ProbePath { get; } + + /// <summary> /// Gets the version of encoder. /// </summary> /// <returns>The version of encoder.</returns> diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 837bf0bb2..24f7b5cd3 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -15,16 +15,9 @@ namespace MediaBrowser.Controller.Persistence /// <summary> /// Provides an interface to implement an Item repository. /// </summary> - public interface IItemRepository : IRepository + public interface IItemRepository : IDisposable { /// <summary> - /// Saves an item. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - void SaveItem(BaseItem item, CancellationToken cancellationToken); - - /// <summary> /// Deletes the item. /// </summary> /// <param name="id">The identifier.</param> @@ -81,7 +74,7 @@ namespace MediaBrowser.Controller.Persistence /// <param name="id">The identifier.</param> /// <param name="streams">The streams.</param> /// <param name="cancellationToken">The cancellation token.</param> - void SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken); + void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken); /// <summary> /// Gets the media attachments. @@ -99,13 +92,6 @@ namespace MediaBrowser.Controller.Persistence void SaveMediaAttachments(Guid id, IReadOnlyList<MediaAttachment> attachments, CancellationToken cancellationToken); /// <summary> - /// Gets the item ids. - /// </summary> - /// <param name="query">The query.</param> - /// <returns>IEnumerable<Guid>.</returns> - QueryResult<Guid> GetItemIds(InternalItemsQuery query); - - /// <summary> /// Gets the items. /// </summary> /// <param name="query">The query.</param> @@ -141,13 +127,6 @@ namespace MediaBrowser.Controller.Persistence List<string> GetPeopleNames(InternalPeopleQuery query); /// <summary> - /// Gets the item ids with path. - /// </summary> - /// <param name="query">The query.</param> - /// <returns>QueryResult<Tuple<Guid, System.String>>.</returns> - List<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query); - - /// <summary> /// Gets the item list. /// </summary> /// <param name="query">The query.</param> diff --git a/MediaBrowser.Controller/Persistence/IRepository.cs b/MediaBrowser.Controller/Persistence/IRepository.cs deleted file mode 100644 index 42f285076..000000000 --- a/MediaBrowser.Controller/Persistence/IRepository.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.Persistence -{ - /// <summary> - /// Provides a base interface for all the repository interfaces. - /// </summary> - public interface IRepository : IDisposable - { - /// <summary> - /// Gets the name of the repository. - /// </summary> - /// <value>The name.</value> - string Name { get; } - } -} diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index c43acfb6d..f2fb2826a 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -1,5 +1,6 @@ #nullable disable +using System; using System.Collections.Generic; using System.Threading; using MediaBrowser.Controller.Entities; @@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Persistence /// <summary> /// Provides an interface to implement a UserData repository. /// </summary> - public interface IUserDataRepository : IRepository + public interface IUserDataRepository : IDisposable { /// <summary> /// Saves the user data. diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 9f7a76be6..44bc4a50c 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -156,7 +156,8 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <param name="item">The item.</param> /// <param name="updateType">Type of the update.</param> - void SaveMetadata(BaseItem item, ItemUpdateType updateType); + /// <returns>The task object representing the asynchronous operation.</returns> + Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType); /// <summary> /// Saves the metadata. @@ -164,7 +165,8 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="updateType">Type of the update.</param> /// <param name="savers">The metadata savers.</param> - void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers); + /// <returns>The task object representing the asynchronous operation.</returns> + Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers); /// <summary> /// Gets the metadata options. diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6134c0cf3..c2ca23386 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Controller.Session AdditionalUsers = Array.Empty<SessionUserInfo>(); PlayState = new PlayerStateInfo(); SessionControllers = Array.Empty<ISessionController>(); + NowPlayingQueue = Array.Empty<QueueItem>(); + NowPlayingQueueFullItems = Array.Empty<BaseItemDto>(); } public PlayerStateInfo PlayState { get; set; } @@ -219,7 +221,9 @@ namespace MediaBrowser.Controller.Session } } - public QueueItem[] NowPlayingQueue { get; set; } + public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; } + + public IReadOnlyList<BaseItemDto> NowPlayingQueueFullItems { get; set; } public bool HasCustomDeviceName { get; set; } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 7dc6149f4..70fd68129 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -291,7 +291,7 @@ namespace MediaBrowser.LocalMetadata.Images foreach (var name in imageFileNames) { - if (AddImage(files, images, imagePrefix + name, ImageType.Primary)) + if (AddImage(files, images, name, ImageType.Primary, imagePrefix)) { return; } @@ -317,7 +317,7 @@ namespace MediaBrowser.LocalMetadata.Images if (!string.IsNullOrEmpty(name)) { - AddImage(files, images, imagePrefix + name + "-fanart", ImageType.Backdrop); + AddImage(files, images, name + "-fanart", ImageType.Backdrop, imagePrefix); // Support without the prefix if it's in it's own folder if (!isInMixedFolder) @@ -436,7 +436,7 @@ namespace MediaBrowser.LocalMetadata.Images private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, string imagePrefix, bool isInMixedFolder, ImageType type) { - var added = AddImage(files, images, imagePrefix + name, type); + var added = AddImage(files, images, name, type, imagePrefix); if (!isInMixedFolder) { @@ -449,32 +449,39 @@ namespace MediaBrowser.LocalMetadata.Images return added; } - private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type) + private static bool AddImage(IReadOnlyList<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type, string? prefix = null) { - var image = GetImage(files, name); + var image = GetImage(files, name, prefix); - if (image != null) + if (image == null) { - images.Add(new LocalImageInfo - { - FileInfo = image, - Type = type - }); - - return true; + return false; } - return false; + images.Add(new LocalImageInfo + { + FileInfo = image, + Type = type + }); + + return true; } - private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name) + private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name, string? prefix = null) { + var fileNameLength = name.Length + (prefix?.Length ?? 0); for (var i = 0; i < files.Count; i++) { var file = files[i]; - if (!file.IsDirectory - && file.Length > 0 - && Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase)) + if (file.IsDirectory || file.Length <= 0) + { + continue; + } + + var fileName = Path.GetFileNameWithoutExtension(file.FullName.AsSpan()); + if (fileName.Length == fileNameLength + && fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + && fileName.EndsWith(name, StringComparison.OrdinalIgnoreCase)) { return file; } diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 41c79651d..41ac7038a 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -22,6 +22,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 1a8b5bb4e..2d94c5de8 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -21,11 +22,6 @@ namespace MediaBrowser.LocalMetadata.Savers public abstract class BaseXmlSaver : IMetadataFileSaver { /// <summary> - /// Gets the date added format. - /// </summary> - public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - - /// <summary> /// Initializes a new instance of the <see cref="BaseXmlSaver"/> class. /// </summary> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> @@ -82,9 +78,7 @@ namespace MediaBrowser.LocalMetadata.Savers /// <param name="item">The item.</param> /// <returns>System.String.</returns> protected virtual string GetRootElementName(BaseItem item) - { - return "Item"; - } + => "Item"; /// <summary> /// Determines whether [is enabled for] [the specified item]. @@ -95,23 +89,10 @@ namespace MediaBrowser.LocalMetadata.Savers public abstract bool IsEnabledFor(BaseItem item, ItemUpdateType updateType); /// <inheritdoc /> - public void Save(BaseItem item, CancellationToken cancellationToken) + public async Task SaveAsync(BaseItem item, CancellationToken cancellationToken) { var path = GetSavePath(item); - - using var memoryStream = new MemoryStream(); - Save(item, memoryStream); - - memoryStream.Position = 0; - - cancellationToken.ThrowIfCancellationRequested(); - - SaveToFile(memoryStream, path); - } - - private void SaveToFile(Stream stream, string path) - { - var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); + var directory = Path.GetDirectoryName(path) ?? throw new InvalidDataException($"Provided path ({path}) is not valid."); Directory.CreateDirectory(directory); // On Windows, savint the file will fail if the file is hidden or readonly @@ -121,13 +102,41 @@ namespace MediaBrowser.LocalMetadata.Savers { Mode = FileMode.Create, Access = FileAccess.Write, - Share = FileShare.None, - PreallocationSize = stream.Length + Share = FileShare.None }; - using (var filestream = new FileStream(path, fileStreamOptions)) + var filestream = new FileStream(path, fileStreamOptions); + await using (filestream.ConfigureAwait(false)) { - stream.CopyTo(filestream); + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + Async = true + }; + + var writer = XmlWriter.Create(filestream, settings); + await using (writer.ConfigureAwait(false)) + { + var root = GetRootElementName(item); + + await writer.WriteStartDocumentAsync(true).ConfigureAwait(false); + + await writer.WriteStartElementAsync(null, root, null).ConfigureAwait(false); + + var baseItem = item; + + if (baseItem != null) + { + await AddCommonNodesAsync(baseItem, writer).ConfigureAwait(false); + } + + await WriteCustomElementsAsync(item, writer).ConfigureAwait(false); + + await writer.WriteEndElementAsync().ConfigureAwait(false); + + await writer.WriteEndDocumentAsync().ConfigureAwait(false); + } } if (ConfigurationManager.Configuration.SaveMetadataHidden) @@ -148,107 +157,76 @@ namespace MediaBrowser.LocalMetadata.Savers } } - private void Save(BaseItem item, Stream stream) - { - var settings = new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - CloseOutput = false - }; - - using (var writer = XmlWriter.Create(stream, settings)) - { - var root = GetRootElementName(item); - - writer.WriteStartDocument(true); - - writer.WriteStartElement(root); - - var baseItem = item; - - if (baseItem != null) - { - AddCommonNodes(baseItem, writer, LibraryManager); - } - - WriteCustomElements(item, writer); - - writer.WriteEndElement(); - - writer.WriteEndDocument(); - } - } - /// <summary> /// Write custom elements. /// </summary> /// <param name="item">The item.</param> /// <param name="writer">The xml writer.</param> - protected abstract void WriteCustomElements(BaseItem item, XmlWriter writer); + /// <returns>The task object representing the asynchronous operation.</returns> + protected abstract Task WriteCustomElementsAsync(BaseItem item, XmlWriter writer); /// <summary> /// Adds the common nodes. /// </summary> /// <param name="item">The item.</param> /// <param name="writer">The xml writer.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager) + /// <returns>The task object representing the asynchronous operation.</returns> + private async Task AddCommonNodesAsync(BaseItem item, XmlWriter writer) { if (!string.IsNullOrEmpty(item.OfficialRating)) { - writer.WriteElementString("ContentRating", item.OfficialRating); + await writer.WriteElementStringAsync(null, "ContentRating", null, item.OfficialRating).ConfigureAwait(false); } - writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G", CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "Added", null, item.DateCreated.ToLocalTime().ToString("G", CultureInfo.InvariantCulture)).ConfigureAwait(false); - writer.WriteElementString("LockData", item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); + await writer.WriteElementStringAsync(null, "LockData", null, item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false); if (item.LockedFields.Length > 0) { - writer.WriteElementString("LockedFields", string.Join('|', item.LockedFields)); + await writer.WriteElementStringAsync(null, "LockedFields", null, string.Join('|', item.LockedFields)).ConfigureAwait(false); } if (item.CriticRating.HasValue) { - writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "CriticRating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.Overview)) { - writer.WriteElementString("Overview", item.Overview); + await writer.WriteElementStringAsync(null, "Overview", null, item.Overview).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.OriginalTitle)) { - writer.WriteElementString("OriginalTitle", item.OriginalTitle); + await writer.WriteElementStringAsync(null, "OriginalTitle", null, item.OriginalTitle).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.CustomRating)) { - writer.WriteElementString("CustomRating", item.CustomRating); + await writer.WriteElementStringAsync(null, "CustomRating", null, item.CustomRating).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.Name) && item is not Episode) { - writer.WriteElementString("LocalTitle", item.Name); + await writer.WriteElementStringAsync(null, "LocalTitle", null, item.Name).ConfigureAwait(false); } var forcedSortName = item.ForcedSortName; if (!string.IsNullOrEmpty(forcedSortName)) { - writer.WriteElementString("SortTitle", forcedSortName); + await writer.WriteElementStringAsync(null, "SortTitle", null, forcedSortName).ConfigureAwait(false); } if (item.PremiereDate.HasValue) { if (item is Person) { - writer.WriteElementString("BirthDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "BirthDate", null, item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false); } else if (item is not Episode) { - writer.WriteElementString("PremiereDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "PremiereDate", null, item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false); } } @@ -256,69 +234,69 @@ namespace MediaBrowser.LocalMetadata.Savers { if (item is Person) { - writer.WriteElementString("DeathDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "DeathDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false); } else if (item is not Episode) { - writer.WriteElementString("EndDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "EndDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false); } } if (item.RemoteTrailers.Count > 0) { - writer.WriteStartElement("Trailers"); + await writer.WriteStartElementAsync(null, "Trailers", null).ConfigureAwait(false); foreach (var trailer in item.RemoteTrailers) { - writer.WriteElementString("Trailer", trailer.Url); + await writer.WriteElementStringAsync(null, "Trailer", null, trailer.Url).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item.ProductionLocations.Length > 0) { - writer.WriteStartElement("Countries"); + await writer.WriteStartElementAsync(null, "Countries", null).ConfigureAwait(false); foreach (var name in item.ProductionLocations) { - writer.WriteElementString("Country", name); + await writer.WriteElementStringAsync(null, "Country", null, name).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item is IHasDisplayOrder hasDisplayOrder && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) { - writer.WriteElementString("DisplayOrder", hasDisplayOrder.DisplayOrder); + await writer.WriteElementStringAsync(null, "DisplayOrder", null, hasDisplayOrder.DisplayOrder).ConfigureAwait(false); } if (item.CommunityRating.HasValue) { - writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "Rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (item.ProductionYear.HasValue && item is not Person) { - writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "ProductionYear", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (item is IHasAspectRatio hasAspectRatio) { if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) { - writer.WriteElementString("AspectRatio", hasAspectRatio.AspectRatio); + await writer.WriteElementStringAsync(null, "AspectRatio", null, hasAspectRatio.AspectRatio).ConfigureAwait(false); } } if (!string.IsNullOrEmpty(item.PreferredMetadataLanguage)) { - writer.WriteElementString("Language", item.PreferredMetadataLanguage); + await writer.WriteElementStringAsync(null, "Language", null, item.PreferredMetadataLanguage).ConfigureAwait(false); } if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode)) { - writer.WriteElementString("CountryCode", item.PreferredMetadataCountryCode); + await writer.WriteElementStringAsync(null, "CountryCode", null, item.PreferredMetadataCountryCode).ConfigureAwait(false); } // Use original runtime here, actual file runtime later in MediaInfo @@ -328,7 +306,7 @@ namespace MediaBrowser.LocalMetadata.Savers { var timespan = TimeSpan.FromTicks(runTimeTicks.Value); - writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "RunningTime", null, Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } if (item.ProviderIds != null) @@ -338,94 +316,94 @@ namespace MediaBrowser.LocalMetadata.Savers var providerId = item.ProviderIds[providerKey]; if (!string.IsNullOrEmpty(providerId)) { - writer.WriteElementString(providerKey + "Id", providerId); + await writer.WriteElementStringAsync(null, providerKey + "Id", null, providerId).ConfigureAwait(false); } } } if (!string.IsNullOrWhiteSpace(item.Tagline)) { - writer.WriteStartElement("Taglines"); - writer.WriteElementString("Tagline", item.Tagline); - writer.WriteEndElement(); + await writer.WriteStartElementAsync(null, "Taglines", null).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "Tagline", null, item.Tagline).ConfigureAwait(false); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item.Genres.Length > 0) { - writer.WriteStartElement("Genres"); + await writer.WriteStartElementAsync(null, "Genres", null).ConfigureAwait(false); foreach (var genre in item.Genres) { - writer.WriteElementString("Genre", genre); + await writer.WriteElementStringAsync(null, "Genre", null, genre).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item.Studios.Length > 0) { - writer.WriteStartElement("Studios"); + await writer.WriteStartElementAsync(null, "Studios", null).ConfigureAwait(false); foreach (var studio in item.Studios) { - writer.WriteElementString("Studio", studio); + await writer.WriteElementStringAsync(null, "Studio", null, studio).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item.Tags.Length > 0) { - writer.WriteStartElement("Tags"); + await writer.WriteStartElementAsync(null, "Tags", null).ConfigureAwait(false); foreach (var tag in item.Tags) { - writer.WriteElementString("Tag", tag); + await writer.WriteElementStringAsync(null, "Tag", null, tag).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } - var people = libraryManager.GetPeople(item); + var people = LibraryManager.GetPeople(item); if (people.Count > 0) { - writer.WriteStartElement("Persons"); + await writer.WriteStartElementAsync(null, "Persons", null).ConfigureAwait(false); foreach (var person in people) { - writer.WriteStartElement("Person"); - writer.WriteElementString("Name", person.Name); - writer.WriteElementString("Type", person.Type); - writer.WriteElementString("Role", person.Role); + await writer.WriteStartElementAsync(null, "Person", null).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "Name", null, person.Name).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "Type", null, person.Type).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "Role", null, person.Role).ConfigureAwait(false); if (person.SortOrder.HasValue) { - writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync(null, "SortOrder", null, person.SortOrder.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } if (item is BoxSet boxset) { - AddLinkedChildren(boxset, writer, "CollectionItems", "CollectionItem"); + await AddLinkedChildren(boxset, writer, "CollectionItems", "CollectionItem").ConfigureAwait(false); } if (item is Playlist playlist && !Playlist.IsPlaylistFile(playlist.Path)) { - AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem"); + await AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem").ConfigureAwait(false); } if (item is IHasShares hasShares) { - AddShares(hasShares, writer); + await AddSharesAsync(hasShares, writer).ConfigureAwait(false); } - AddMediaInfo(item, writer); + await AddMediaInfo(item, writer).ConfigureAwait(false); } /// <summary> @@ -433,23 +411,26 @@ namespace MediaBrowser.LocalMetadata.Savers /// </summary> /// <param name="item">The item.</param> /// <param name="writer">The xml writer.</param> - public static void AddShares(IHasShares item, XmlWriter writer) + /// <returns>The task object representing the asynchronous operation.</returns> + private static async Task AddSharesAsync(IHasShares item, XmlWriter writer) { - writer.WriteStartElement("Shares"); + await writer.WriteStartElementAsync(null, "Shares", null).ConfigureAwait(false); foreach (var share in item.Shares) { - writer.WriteStartElement("Share"); + await writer.WriteStartElementAsync(null, "Share", null).ConfigureAwait(false); - writer.WriteElementString("UserId", share.UserId); - writer.WriteElementString( + await writer.WriteElementStringAsync(null, "UserId", null, share.UserId).ConfigureAwait(false); + await writer.WriteElementStringAsync( + null, "CanEdit", - share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); + null, + share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false); - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } /// <summary> @@ -458,33 +439,29 @@ namespace MediaBrowser.LocalMetadata.Savers /// <param name="item">The item.</param> /// <param name="writer">The xml writer.</param> /// <typeparam name="T">Type of item.</typeparam> - public static void AddMediaInfo<T>(T item, XmlWriter writer) + /// <returns>The task object representing the asynchronous operation.</returns> + private static Task AddMediaInfo<T>(T item, XmlWriter writer) where T : BaseItem { - if (item is Video video) + if (item is Video video && video.Video3DFormat.HasValue) { - if (video.Video3DFormat.HasValue) + return video.Video3DFormat switch { - switch (video.Video3DFormat.Value) - { - case Video3DFormat.FullSideBySide: - writer.WriteElementString("Format3D", "FSBS"); - break; - case Video3DFormat.FullTopAndBottom: - writer.WriteElementString("Format3D", "FTAB"); - break; - case Video3DFormat.HalfSideBySide: - writer.WriteElementString("Format3D", "HSBS"); - break; - case Video3DFormat.HalfTopAndBottom: - writer.WriteElementString("Format3D", "HTAB"); - break; - case Video3DFormat.MVC: - writer.WriteElementString("Format3D", "MVC"); - break; - } - } - } + Video3DFormat.FullSideBySide => + writer.WriteElementStringAsync(null, "Format3D", null, "FSBS"), + Video3DFormat.FullTopAndBottom => + writer.WriteElementStringAsync(null, "Format3D", null, "FTAB"), + Video3DFormat.HalfSideBySide => + writer.WriteElementStringAsync(null, "Format3D", null, "HSBS"), + Video3DFormat.HalfTopAndBottom => + writer.WriteElementStringAsync(null, "Format3D", null, "HTAB"), + Video3DFormat.MVC => + writer.WriteElementStringAsync(null, "Format3D", null, "MVC"), + _ => Task.CompletedTask + }; + } + + return Task.CompletedTask; } /// <summary> @@ -494,7 +471,8 @@ namespace MediaBrowser.LocalMetadata.Savers /// <param name="writer">The xml writer.</param> /// <param name="pluralNodeName">The plural node name.</param> /// <param name="singularNodeName">The singular node name.</param> - public static void AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName) + /// <returns>The task object representing the asynchronous operation.</returns> + private static async Task AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName) { var items = item.LinkedChildren .Where(i => i.Type == LinkedChildType.Manual) @@ -505,28 +483,28 @@ namespace MediaBrowser.LocalMetadata.Savers return; } - writer.WriteStartElement(pluralNodeName); + await writer.WriteStartElementAsync(null, pluralNodeName, null).ConfigureAwait(false); foreach (var link in items) { if (!string.IsNullOrWhiteSpace(link.Path) || !string.IsNullOrWhiteSpace(link.LibraryItemId)) { - writer.WriteStartElement(singularNodeName); + await writer.WriteStartElementAsync(null, singularNodeName, null).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(link.Path)) { - writer.WriteElementString("Path", link.Path); + await writer.WriteElementStringAsync(null, "Path", null, link.Path).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(link.LibraryItemId)) { - writer.WriteElementString("ItemId", link.LibraryItemId); + await writer.WriteElementStringAsync(null, "ItemId", null, link.LibraryItemId).ConfigureAwait(false); } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } } - writer.WriteEndElement(); + await writer.WriteEndElementAsync().ConfigureAwait(false); } } } diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs index 8a5da95bf..940f51dac 100644 --- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; using System.Xml; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -38,9 +39,8 @@ namespace MediaBrowser.LocalMetadata.Savers } /// <inheritdoc /> - protected override void WriteCustomElements(BaseItem item, XmlWriter writer) - { - } + protected override Task WriteCustomElementsAsync(BaseItem item, XmlWriter writer) + => Task.CompletedTask; /// <inheritdoc /> protected override string GetLocalSavePath(BaseItem item) diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs index 76252bc09..847add07f 100644 --- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; using System.Xml; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -43,14 +44,16 @@ namespace MediaBrowser.LocalMetadata.Savers } /// <inheritdoc /> - protected override void WriteCustomElements(BaseItem item, XmlWriter writer) + protected override Task WriteCustomElementsAsync(BaseItem item, XmlWriter writer) { var game = (Playlist)item; - if (!string.IsNullOrEmpty(game.PlaylistMediaType)) + if (string.IsNullOrEmpty(game.PlaylistMediaType)) { - writer.WriteElementString("PlaylistMediaType", game.PlaylistMediaType); + return Task.CompletedTask; } + + return writer.WriteElementStringAsync(null, "PlaylistMediaType", null, game.PlaylistMediaType); } /// <inheritdoc /> diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 3fd4cd731..06d20d90e 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -83,6 +83,130 @@ namespace MediaBrowser.MediaEncoding.Attachments return (mediaAttachment, attachmentStream); } + public async Task ExtractAllAttachments( + string inputFile, + MediaSourceInfo mediaSource, + string outputPath, + CancellationToken cancellationToken) + { + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + if (!Directory.Exists(outputPath)) + { + await ExtractAllAttachmentsInternal( + _mediaEncoder.GetInputArgument(inputFile, mediaSource), + outputPath, + cancellationToken).ConfigureAwait(false); + } + } + finally + { + semaphore.Release(); + } + } + + private async Task ExtractAllAttachmentsInternal( + string inputPath, + string outputPath, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(inputPath)) + { + throw new ArgumentNullException(nameof(inputPath)); + } + + if (string.IsNullOrEmpty(outputPath)) + { + throw new ArgumentNullException(nameof(outputPath)); + } + + Directory.CreateDirectory(outputPath); + + var processArgs = string.Format( + CultureInfo.InvariantCulture, + "-dump_attachment:t \"\" -i {0} -t 0 -f null null", + inputPath); + + int exitCode; + + using (var process = new Process + { + StartInfo = new ProcessStartInfo + { + Arguments = processArgs, + FileName = _mediaEncoder.EncoderPath, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + WorkingDirectory = outputPath, + ErrorDialog = false + }, + EnableRaisingEvents = true + }) + { + _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + process.Start(); + + var ranToCompletion = await ProcessExtensions.WaitForExitAsync(process, cancellationToken).ConfigureAwait(false); + + if (!ranToCompletion) + { + try + { + _logger.LogWarning("Killing ffmpeg attachment extraction process"); + process.Kill(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error killing attachment extraction process"); + } + } + + exitCode = ranToCompletion ? process.ExitCode : -1; + } + + var failed = false; + + if (exitCode != 0) + { + failed = true; + + _logger.LogWarning("Deleting extracted attachments {Path} due to failure: {ExitCode}", outputPath, exitCode); + try + { + if (Directory.Exists(outputPath)) + { + Directory.Delete(outputPath); + } + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted attachments {Path}", outputPath); + } + } + else if (!Directory.Exists(outputPath)) + { + failed = true; + } + + if (failed) + { + _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); + + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); + } + else + { + _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath); + } + } + private async Task<Stream> GetAttachmentStream( MediaSourceInfo mediaSource, MediaAttachment mediaAttachment, diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index fe3069934..20d372d7a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -44,18 +44,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg4_cuvid", "vp8_cuvid", "vp9_cuvid", - "av1_cuvid", - "h264_mmal", - "mpeg2_mmal", - "mpeg4_mmal", - "vc1_mmal", - "h264_opencl", - "hevc_opencl", - "mpeg2_opencl", - "mpeg4_opencl", - "vp8_opencl", - "vp9_opencl", - "vc1_opencl" + "av1_cuvid" }; private static readonly string[] _requiredEncoders = new[] @@ -82,8 +71,6 @@ namespace MediaBrowser.MediaEncoding.Encoder "hevc_nvenc", "h264_vaapi", "hevc_vaapi", - "h264_omx", - "hevc_omx", "h264_v4l2m2m", "h264_videotoolbox", "hevc_videotoolbox" diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e1643ea43..c41ed20cd 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -92,6 +92,9 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public string EncoderPath => _ffmpegPath; + /// <inheritdoc /> + public string ProbePath => _ffprobePath; + public Version EncoderVersion => _ffmpegVersion; public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index b60ccd2ca..47de4edff 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -35,6 +35,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs index a1cef7a9f..c9e948780 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.MediaEncoding.Probing public class MediaChapter { [JsonPropertyName("id")] - public int Id { get; set; } + public long Id { get; set; } [JsonPropertyName("time_base")] public string TimeBase { get; set; } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 750fd44eb..8313ab5bc 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -44,9 +44,11 @@ namespace MediaBrowser.MediaEncoding.Probing private IReadOnlyList<string> SplitWhitelist => _splitWhiteList ??= new string[] { "AC/DC", + "As/Hi Soundworks", "Au/Ra", "Bremer/McCoy", "이달의 소녀 1/3", + "R!N / Gemie", "LOONA 1/3", "LOONA / yyxy", "LOONA / ODD EYE CIRCLE", @@ -689,9 +691,9 @@ namespace MediaBrowser.MediaEncoding.Probing if (string.IsNullOrEmpty(stream.Title)) { - // mp4 missing track title workaround: fall back to handler_name if populated + // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SoundHandler" string handlerName = GetDictionaryValue(streamInfo.Tags, "handler_name"); - if (!string.IsNullOrEmpty(handlerName)) + if (!string.IsNullOrEmpty(handlerName) && !string.Equals(handlerName, "SoundHandler", StringComparison.OrdinalIgnoreCase)) { stream.Title = handlerName; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 5b1ec8041..f4842d368 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -141,12 +141,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles var inputFormat = subtitle.Format; - // Return the original if we don't have any way of converting it - if (!TryGetWriter(outputFormat, out var writer)) - { - return subtitle.Stream; - } - // Return the original if the same format is being requested // Character encoding was already handled in GetSubtitleStream if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) @@ -440,7 +434,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw; } - var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false); + var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); if (!ranToCompletion) { @@ -677,8 +671,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!string.Equals(text, newText, StringComparison.Ordinal)) { - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) - using (var writer = new StreamWriter(fileStream, encoding)) + var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + var writer = new StreamWriter(fileStream, encoding); + await using (fileStream.ConfigureAwait(false)) + await using (writer.ConfigureAwait(false)) { await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index 7f19a5b85..cc42c1718 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -1,19 +1,32 @@ -#pragma warning disable CS1591 +using System.Text.Json.Serialization; +using System.Xml.Serialization; -namespace MediaBrowser.Model.Branding +namespace MediaBrowser.Model.Branding; + +/// <summary> +/// The branding options. +/// </summary> +public class BrandingOptions { - public class BrandingOptions - { - /// <summary> - /// Gets or sets the login disclaimer. - /// </summary> - /// <value>The login disclaimer.</value> - public string? LoginDisclaimer { get; set; } + /// <summary> + /// Gets or sets the login disclaimer. + /// </summary> + /// <value>The login disclaimer.</value> + public string? LoginDisclaimer { get; set; } + + /// <summary> + /// Gets or sets the custom CSS. + /// </summary> + /// <value>The custom CSS.</value> + public string? CustomCss { get; set; } - /// <summary> - /// Gets or sets the custom CSS. - /// </summary> - /// <value>The custom CSS.</value> - public string? CustomCss { get; set; } - } + /// <summary> + /// Gets or sets the splashscreen location on disk. + /// </summary> + /// <remarks> + /// Not served via the API. + /// Only used to save the custom uploaded user splashscreen in the configuration file. + /// </remarks> + [JsonIgnore] + public string? SplashscreenLocation { get; set; } } diff --git a/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs b/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs new file mode 100644 index 000000000..42f07dbff --- /dev/null +++ b/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs @@ -0,0 +1,30 @@ +namespace MediaBrowser.Model.Configuration +{ + /// <summary> + /// An enum representing the options to disable embedded subs. + /// </summary> + public enum EmbeddedSubtitleOptions + { + + /// <summary> + /// Allow all embedded subs. + /// </summary> + AllowAll = 0, + + /// <summary> + /// Allow only embedded subs that are text based. + /// </summary> + AllowText = 1, + + /// <summary> + /// Allow only embedded subs that are image based. + /// </summary> + AllowImage = 2, + + /// <summary> + /// Disable all embedded subs. + /// </summary> + AllowNone = 3, + } + +} diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index d0ded99ea..51917b50e 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,3 +1,5 @@ +using System; + #nullable disable #pragma warning disable CS1591 @@ -37,6 +39,7 @@ namespace MediaBrowser.Model.Configuration EnableHardwareEncoding = true; AllowHevcEncoding = false; EnableSubtitleExtraction = true; + AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = Array.Empty<string>(); HardwareDecodingCodecs = new string[] { "h264", "vc1" }; } @@ -115,5 +118,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } + + public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; } } } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index d3ce6aa7f..ad3bce86e 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Model.Configuration SkipSubtitlesIfAudioTrackMatches = true; RequirePerfectSubtitleMatch = true; + AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll; AutomaticallyAddToCollection = true; EnablePhotos = true; @@ -84,6 +85,8 @@ namespace MediaBrowser.Model.Configuration public bool AutomaticallyAddToCollection { get; set; } + public EmbeddedSubtitleOptions AllowEmbeddedSubtitles { get; set; } + public TypeOptions[] TypeOptions { get; set; } public TypeOptions? GetTypeOptions(string type) diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 58b06ca1d..6e129246b 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Model.Dlna return "DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags; } - public static List<string> BuildVideoHeader( + public static IEnumerable<string> BuildVideoHeader( DeviceProfile profile, string container, string videoCodec, diff --git a/MediaBrowser.Model/Dlna/DlnaProfileType.cs b/MediaBrowser.Model/Dlna/DlnaProfileType.cs index e30ed0f3c..c1a663bf1 100644 --- a/MediaBrowser.Model/Dlna/DlnaProfileType.cs +++ b/MediaBrowser.Model/Dlna/DlnaProfileType.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Dlna { Audio = 0, Video = 1, - Photo = 2 + Photo = 2, + Subtitle = 3 } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index cf8465067..a678c54e7 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -675,7 +675,7 @@ namespace MediaBrowser.Model.Dlna return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); } - private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken) + private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string accessToken) { var list = new List<NameValuePair>(); @@ -805,34 +805,12 @@ namespace MediaBrowser.Model.Dlna return list; } - public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) - { - return GetExternalSubtitles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); - } - - public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) - { - var list = GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); - var newList = new List<SubtitleStreamInfo>(); - - // First add the selected track - foreach (SubtitleStreamInfo stream in list) - { - if (stream.DeliveryMethod == SubtitleDeliveryMethod.External) - { - newList.Add(stream); - } - } - - return newList; - } - - public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) + public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) { return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); } - public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) + public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { var list = new List<SubtitleStreamInfo>(); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index a784025e3..094dc73b2 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -297,13 +297,13 @@ namespace MediaBrowser.Model.Dto /// Gets or sets wether the item has a logo, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent logo item id.</value> - public string ParentLogoItemId { get; set; } + public Guid? ParentLogoItemId { get; set; } /// <summary> /// Gets or sets wether the item has any backdrops, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent backdrop item id.</value> - public string ParentBackdropItemId { get; set; } + public Guid? ParentBackdropItemId { get; set; } /// <summary> /// Gets or sets the parent backdrop image tags. @@ -509,7 +509,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets wether the item has fan art, this will hold the Id of the Parent that has one. /// </summary> /// <value>The parent art item id.</value> - public string ParentArtItemId { get; set; } + public Guid? ParentArtItemId { get; set; } /// <summary> /// Gets or sets the parent art image tag. @@ -540,7 +540,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the parent thumb item id. /// </summary> /// <value>The parent thumb item id.</value> - public string ParentThumbItemId { get; set; } + public Guid? ParentThumbItemId { get; set; } /// <summary> /// Gets or sets the parent thumb image tag. diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index ddd7667ef..6b920b0ef 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -1,4 +1,5 @@ #nullable disable +using System; using System.Collections.Generic; using System.Text.Json.Serialization; using MediaBrowser.Model.Entities; @@ -20,7 +21,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the identifier. /// </summary> /// <value>The identifier.</value> - public string Id { get; set; } + public Guid Id { get; set; } /// <summary> /// Gets or sets the role. diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index ec3b37efa..049e14333 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Dto public MediaSourceInfo() { Formats = Array.Empty<string>(); - MediaStreams = new List<MediaStream>(); + MediaStreams = Array.Empty<MediaStream>(); MediaAttachments = Array.Empty<MediaAttachment>(); RequiredHttpHeaders = new Dictionary<string, string>(); SupportsTranscoding = true; @@ -88,7 +88,7 @@ namespace MediaBrowser.Model.Dto public Video3DFormat? Video3DFormat { get; set; } - public List<MediaStream> MediaStreams { get; set; } + public IReadOnlyList<MediaStream> MediaStreams { get; set; } public IReadOnlyList<MediaAttachment> MediaAttachments { get; set; } diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs index e0e889f7d..d098669ba 100644 --- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs +++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs @@ -1,7 +1,7 @@ -#nullable disable #pragma warning disable CS1591 using System; +using System.Collections.Generic; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Providers; @@ -19,16 +19,16 @@ namespace MediaBrowser.Model.Dto ContentTypeOptions = Array.Empty<NameValuePair>(); } - public ParentalRating[] ParentalRatingOptions { get; set; } + public IReadOnlyList<ParentalRating> ParentalRatingOptions { get; set; } - public CountryInfo[] Countries { get; set; } + public IReadOnlyList<CountryInfo> Countries { get; set; } - public CultureDto[] Cultures { get; set; } + public IReadOnlyList<CultureDto> Cultures { get; set; } - public ExternalIdInfo[] ExternalIdInfos { get; set; } + public IReadOnlyList<ExternalIdInfo> ExternalIdInfos { get; set; } - public string ContentType { get; set; } + public string? ContentType { get; set; } - public NameValuePair[] ContentTypeOptions { get; set; } + public IReadOnlyList<NameValuePair> ContentTypeOptions { get; set; } } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 38ac44794..341e4846e 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; +using Jellyfin.Extensions; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; @@ -17,6 +18,18 @@ namespace MediaBrowser.Model.Entities /// </summary> public class MediaStream { + private static readonly string[] _specialCodes = + { + // Uncoded languages. + "mis", + // Multiple languages. + "mul", + // Undetermined. + "und", + // No linguistic content; not applicable. + "zxx" + }; + /// <summary> /// Gets or sets the codec. /// </summary> @@ -137,7 +150,8 @@ namespace MediaBrowser.Model.Entities { var attributes = new List<string>(); - if (!string.IsNullOrEmpty(Language)) + // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). + if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) { // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes. string fullLanguage = CultureInfo diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs index 5246f87d9..d0cf2aad0 100644 --- a/MediaBrowser.Model/Globalization/CultureDto.cs +++ b/MediaBrowser.Model/Globalization/CultureDto.cs @@ -1,7 +1,6 @@ -#nullable disable #pragma warning disable CS1591 -using System; +using System.Collections.Generic; namespace MediaBrowser.Model.Globalization { @@ -10,39 +9,42 @@ namespace MediaBrowser.Model.Globalization /// </summary> public class CultureDto { - public CultureDto() + public CultureDto(string name, string displayName, string twoLetterISOLanguageName, IReadOnlyList<string> threeLetterISOLanguageNames) { - ThreeLetterISOLanguageNames = Array.Empty<string>(); + Name = name; + DisplayName = displayName; + TwoLetterISOLanguageName = twoLetterISOLanguageName; + ThreeLetterISOLanguageNames = threeLetterISOLanguageNames; } /// <summary> - /// Gets or sets the name. + /// Gets the name. /// </summary> /// <value>The name.</value> - public string Name { get; set; } + public string Name { get; } /// <summary> - /// Gets or sets the display name. + /// Gets the display name. /// </summary> /// <value>The display name.</value> - public string DisplayName { get; set; } + public string DisplayName { get; } /// <summary> - /// Gets or sets the name of the two letter ISO language. + /// Gets the name of the two letter ISO language. /// </summary> /// <value>The name of the two letter ISO language.</value> - public string TwoLetterISOLanguageName { get; set; } + public string TwoLetterISOLanguageName { get; } /// <summary> /// Gets the name of the three letter ISO language. /// </summary> /// <value>The name of the three letter ISO language.</value> - public string ThreeLetterISOLanguageName + public string? ThreeLetterISOLanguageName { get { var vals = ThreeLetterISOLanguageNames; - if (vals.Length > 0) + if (vals.Count > 0) { return vals[0]; } @@ -51,6 +53,6 @@ namespace MediaBrowser.Model.Globalization } } - public string[] ThreeLetterISOLanguageNames { get; set; } + public IReadOnlyList<string> ThreeLetterISOLanguageNames { get; } } } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 0f77d6b5b..786b20e9e 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -199,6 +199,20 @@ namespace MediaBrowser.Model.IO void SetAttributes(string path, bool isHidden, bool readOnly); - List<FileSystemMetadata> GetDrives(); + IEnumerable<FileSystemMetadata> GetDrives(); + + /// <summary> + /// Determines whether the directory exists. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>Whether the path exists.</returns> + bool DirectoryExists(string path); + + /// <summary> + /// Determines whether the file exists. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>Whether the path exists.</returns> + bool FileExists(string path); } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 63f7ada5c..4386f75af 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -35,12 +35,12 @@ <ItemGroup> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" /> - <PackageReference Include="MimeTypes" Version="2.2.1"> + <PackageReference Include="MimeTypes" Version="2.3.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="System.Globalization" Version="4.3.0" /> - <PackageReference Include="System.Text.Json" Version="6.0.1" /> + <PackageReference Include="System.Text.Json" Version="6.0.2" /> </ItemGroup> <ItemGroup> @@ -49,6 +49,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 0b846bb96..0a28acf37 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -102,5 +102,9 @@ namespace MediaBrowser.Model.Querying public const string DateLastContentAdded = "DateLastContentAdded"; public const string SeriesDatePlayed = "SeriesDatePlayed"; + + public const string ParentIndexNumber = "ParentIndexNumber"; + + public const string IndexNumber = "IndexNumber"; } } diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index fa8aa829d..133d6a916 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Querying EnableTotalRecordCount = true; DisableFirstEpisode = false; NextUpDateCutoff = DateTime.MinValue; + EnableRewatching = false; } /// <summary> @@ -81,5 +82,10 @@ namespace MediaBrowser.Model.Querying /// Gets or sets a value indicating the oldest date for a show to appear in Next Up. /// </summary> public DateTime NextUpDateCutoff { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether getting rewatching next up list. + /// </summary> + public bool EnableRewatching { get; set; } } } diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index 8ce794800..ea843f34c 100644 --- a/MediaBrowser.Model/Querying/QueryResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -19,6 +19,13 @@ namespace MediaBrowser.Model.Querying TotalRecordCount = items.Count; } + public QueryResult(int? startIndex, int? totalRecordCount, IReadOnlyList<T> items) + { + StartIndex = startIndex ?? 0; + TotalRecordCount = totalRecordCount ?? items.Count; + Items = items; + } + /// <summary> /// Gets or sets the items. /// </summary> diff --git a/MediaBrowser.Model/Search/SearchHintResult.cs b/MediaBrowser.Model/Search/SearchHintResult.cs index 92ba4139e..762a9a078 100644 --- a/MediaBrowser.Model/Search/SearchHintResult.cs +++ b/MediaBrowser.Model/Search/SearchHintResult.cs @@ -1,4 +1,5 @@ -#nullable disable +using System.Collections.Generic; + namespace MediaBrowser.Model.Search { /// <summary> @@ -7,15 +8,26 @@ namespace MediaBrowser.Model.Search public class SearchHintResult { /// <summary> - /// Gets or sets the search hints. + /// Initializes a new instance of the <see cref="SearchHintResult" /> class. + /// </summary> + /// <param name="searchHints">The search hints.</param> + /// <param name="totalRecordCount">The total record count.</param> + public SearchHintResult(IReadOnlyList<SearchHint> searchHints, int totalRecordCount) + { + SearchHints = searchHints; + TotalRecordCount = totalRecordCount; + } + + /// <summary> + /// Gets the search hints. /// </summary> /// <value>The search hints.</value> - public SearchHint[] SearchHints { get; set; } + public IReadOnlyList<SearchHint> SearchHints { get; } /// <summary> - /// Gets or sets the total record count. + /// Gets the total record count. /// </summary> /// <value>The total record count.</value> - public int TotalRecordCount { get; set; } + public int TotalRecordCount { get; } } } diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs index 29528c110..757b19b31 100644 --- a/MediaBrowser.Model/Session/GeneralCommand.cs +++ b/MediaBrowser.Model/Session/GeneralCommand.cs @@ -2,20 +2,26 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; -namespace MediaBrowser.Model.Session +namespace MediaBrowser.Model.Session; + +public class GeneralCommand { - public class GeneralCommand + public GeneralCommand() + : this(new Dictionary<string, string>()) + { + } + + [JsonConstructor] + public GeneralCommand(Dictionary<string, string> arguments) { - public GeneralCommand() - { - Arguments = new Dictionary<string, string>(); - } + Arguments = arguments; + } - public GeneralCommandType Name { get; set; } + public GeneralCommandType Name { get; set; } - public Guid ControllingUserId { get; set; } + public Guid ControllingUserId { get; set; } - public Dictionary<string, string> Arguments { get; } - } + public Dictionary<string, string> Arguments { get; } } diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs index 0db5697d3..f5753467a 100644 --- a/MediaBrowser.Model/Session/HardwareEncodingType.cs +++ b/MediaBrowser.Model/Session/HardwareEncodingType.cs @@ -21,28 +21,18 @@ NVENC = 2, /// <summary> - /// OpenMax OMX. + /// Video4Linux2 V4L2. /// </summary> - OMX = 3, - - /// <summary> - /// Exynos V4L2 MFC. - /// </summary> - V4L2M2M = 4, - - /// <summary> - /// MediaCodec Android. - /// </summary> - MediaCodec = 5, + V4L2M2M = 3, /// <summary> /// Video Acceleration API (VAAPI). /// </summary> - VAAPI = 6, + VAAPI = 4, /// <summary> /// Video ToolBox. /// </summary> - VideoToolBox = 7 + VideoToolBox = 5 } } diff --git a/MediaBrowser.Model/Tasks/IScheduledTask.cs b/MediaBrowser.Model/Tasks/IScheduledTask.cs index bf87088e4..123902d90 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTask.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTask.cs @@ -36,10 +36,10 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Executes the task. /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task Execute(CancellationToken cancellationToken, IProgress<double> progress); + Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken); /// <summary> /// Gets the default triggers that define when the task will run. diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 0f21ec7b2..bbbbfad54 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -388,7 +388,7 @@ namespace MediaBrowser.Providers.Manager /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns> public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images) { - var changed = item.ValidateImages(new DirectoryService(_fileSystem)); + var changed = item.ValidateImages(); for (var i = 0; i < _singularImages.Length; i++) { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 0385ce6a7..5281b3721 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -128,9 +128,7 @@ namespace MediaBrowser.Providers.Manager _metadataProviders = metadataProviders.ToArray(); _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); - _savers = metadataSavers - .Where(i => i is not IConfigurableProvider configurable || configurable.IsEnabled) - .ToArray(); + _savers = metadataSavers.ToArray(); } /// <inheritdoc/> @@ -653,16 +651,12 @@ namespace MediaBrowser.Providers.Manager } /// <inheritdoc/> - public void SaveMetadata(BaseItem item, ItemUpdateType updateType) - { - SaveMetadata(item, updateType, _savers); - } + public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType) + => SaveMetadataAsync(item, updateType, _savers); /// <inheritdoc/> - public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers) - { - SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIgnoreCase))); - } + public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers) + => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIgnoreCase))); /// <summary> /// Saves the metadata. @@ -670,7 +664,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="item">The item.</param> /// <param name="updateType">Type of the update.</param> /// <param name="savers">The savers.</param> - private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers) + private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers) { var libraryOptions = _libraryManager.GetLibraryOptions(item); @@ -695,7 +689,7 @@ namespace MediaBrowser.Providers.Manager try { _libraryMonitor.ReportFileSystemChangeBeginning(path); - saver.Save(item, CancellationToken.None); + await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -710,7 +704,7 @@ namespace MediaBrowser.Providers.Manager { try { - saver.Save(item, CancellationToken.None); + await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 43cf621cd..1851a9e4b 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -22,7 +22,7 @@ <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" /> <PackageReference Include="PlaylistsNET" Version="1.1.3" /> - <PackageReference Include="TMDbLib" Version="1.8.1" /> + <PackageReference Include="TMDbLib" Version="1.9.1" /> </ItemGroup> <PropertyGroup> @@ -38,6 +38,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 425913501..0bdf447ba 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,176 +1,36 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Emby.Naming.Audio; using Emby.Naming.Common; -using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.MediaInfo { /// <summary> - /// Resolves external audios for videos. + /// Resolves external audio files for <see cref="Video"/>. /// </summary> - public class AudioResolver + public class AudioResolver : MediaInfoResolver { - private readonly ILocalizationManager _localizationManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly NamingOptions _namingOptions; - /// <summary> - /// Initializes a new instance of the <see cref="AudioResolver"/> class. + /// Initializes a new instance of the <see cref="AudioResolver"/> class for external audio file processing. /// </summary> /// <param name="localizationManager">The localization manager.</param> /// <param name="mediaEncoder">The media encoder.</param> - /// <param name="namingOptions">The naming options.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param> public AudioResolver( ILocalizationManager localizationManager, IMediaEncoder mediaEncoder, + IFileSystem fileSystem, NamingOptions namingOptions) + : base( + localizationManager, + mediaEncoder, + fileSystem, + namingOptions, + DlnaProfileType.Audio) { - _localizationManager = localizationManager; - _mediaEncoder = mediaEncoder; - _namingOptions = namingOptions; - } - - /// <summary> - /// Returns the audio streams found in the external audio files for the given video. - /// </summary> - /// <param name="video">The video to get the external audio streams from.</param> - /// <param name="startIndex">The stream index to start adding audio streams at.</param> - /// <param name="directoryService">The directory service to search for files.</param> - /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> - /// <param name="cancellationToken">The cancellation token to cancel operation.</param> - /// <returns>A list of external audio streams.</returns> - public async IAsyncEnumerable<MediaStream> GetExternalAudioStreams( - Video video, - int startIndex, - IDirectoryService directoryService, - bool clearCache, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!video.IsFileProtocol) - { - yield break; - } - - IEnumerable<string> paths = GetExternalAudioFiles(video, directoryService, clearCache); - foreach (string path in paths) - { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken).ConfigureAwait(false); - - foreach (MediaStream mediaStream in mediaInfo.MediaStreams) - { - mediaStream.Index = startIndex++; - mediaStream.Type = MediaStreamType.Audio; - mediaStream.IsExternal = true; - mediaStream.Path = path; - mediaStream.IsDefault = false; - mediaStream.Title = null; - - if (string.IsNullOrEmpty(mediaStream.Language)) - { - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); - - if (language != fileNameWithoutExtension) - { - var culture = _localizationManager.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - mediaStream.Language = language; - } - } - - yield return mediaStream; - } - } - } - - /// <summary> - /// Returns the external audio file paths for the given video. - /// </summary> - /// <param name="video">The video to get the external audio file paths from.</param> - /// <param name="directoryService">The directory service to search for files.</param> - /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> - /// <returns>A list of external audio file paths.</returns> - public IEnumerable<string> GetExternalAudioFiles( - Video video, - IDirectoryService directoryService, - bool clearCache) - { - if (!video.IsFileProtocol) - { - yield break; - } - - // Check if video folder exists - string folder = video.ContainingFolderPath; - if (!Directory.Exists(folder)) - { - yield break; - } - - string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); - - var files = directoryService.GetFilePaths(folder, clearCache, true); - for (int i = 0; i < files.Count; i++) - { - string file = files[i]; - if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) - || !AudioFileParser.IsAudioFile(file, _namingOptions) - || Path.GetExtension(file.AsSpan()).Equals(".strm", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); - // The audio filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) - || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) - { - yield return file; - } - } - } - - /// <summary> - /// Returns the media info of the given audio file. - /// </summary> - /// <param name="path">The path to the audio file.</param> - /// <param name="cancellationToken">The cancellation token to cancel operation.</param> - /// <returns>The media info for the given audio file.</returns> - private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _mediaEncoder.GetMediaInfo( - new MediaInfoRequest - { - MediaType = DlnaProfileType.Audio, - MediaSource = new MediaSourceInfo - { - Path = path, - Protocol = MediaProtocol.File - } - }, - cancellationToken); } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 9eb79c39d..f22965436 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -84,8 +84,6 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="cancellationToken">The cancellation token.</param> protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken) { - var mediaStreams = mediaInfo.MediaStreams; - audio.Container = mediaInfo.Container; audio.TotalBitrate = mediaInfo.Bitrate; @@ -97,7 +95,7 @@ namespace MediaBrowser.Providers.MediaInfo FetchDataFromTags(audio, mediaInfo); - _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken); + _itemRepo.SaveMediaStreams(audio.Id, mediaInfo.MediaStreams, cancellationToken); } /// <summary> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 19a435196..fcd3f28d4 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -21,6 +21,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; @@ -39,11 +40,10 @@ namespace MediaBrowser.Providers.MediaInfo IHasItemChangeMonitor { private readonly ILogger<FFProbeProvider> _logger; - private readonly SubtitleResolver _subtitleResolver; private readonly AudioResolver _audioResolver; + private readonly SubtitleResolver _subtitleResolver; private readonly FFProbeVideoInfo _videoProber; private readonly FFProbeAudioInfo _audioProber; - private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None); public FFProbeProvider( @@ -58,11 +58,12 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, + IFileSystem fileSystem, NamingOptions namingOptions) { _logger = logger; - _audioResolver = new AudioResolver(localization, mediaEncoder, namingOptions); - _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(localization, mediaEncoder, fileSystem, namingOptions); + _subtitleResolver = new SubtitleResolver(localization, mediaEncoder, fileSystem, namingOptions); _videoProber = new FFProbeVideoInfo( _logger, mediaSourceManager, @@ -75,7 +76,8 @@ namespace MediaBrowser.Providers.MediaInfo subtitleManager, chapterManager, libraryManager, - _audioResolver); + _audioResolver, + _subtitleResolver); _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } @@ -104,7 +106,9 @@ namespace MediaBrowser.Providers.MediaInfo if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.SubtitleFiles.SequenceEqual( - _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) + _subtitleResolver.GetExternalFiles(video, directoryService, false) + .Select(info => info.Path).ToList(), + StringComparer.Ordinal)) { _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); return true; @@ -112,7 +116,9 @@ namespace MediaBrowser.Providers.MediaInfo if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + _audioResolver.GetExternalFiles(video, directoryService, false) + .Select(info => info.Path).ToList(), + StringComparer.Ordinal)) { _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); return true; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 77372e063..26ff0412b 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -45,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; private readonly AudioResolver _audioResolver; + private readonly SubtitleResolver _subtitleResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -61,9 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, - AudioResolver audioResolver) + AudioResolver audioResolver, + SubtitleResolver subtitleResolver) { _logger = logger; + _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; @@ -74,7 +77,7 @@ namespace MediaBrowser.Providers.MediaInfo _chapterManager = chapterManager; _libraryManager = libraryManager; _audioResolver = audioResolver; - _mediaSourceManager = mediaSourceManager; + _subtitleResolver = subtitleResolver; } public async Task<ItemUpdateType> ProbeVideo<T>( @@ -172,7 +175,7 @@ namespace MediaBrowser.Providers.MediaInfo if (mediaInfo != null) { - mediaStreams = mediaInfo.MediaStreams; + mediaStreams = mediaInfo.MediaStreams.ToList(); mediaAttachments = mediaInfo.MediaAttachments; video.TotalBitrate = mediaInfo.Bitrate; @@ -202,7 +205,7 @@ namespace MediaBrowser.Providers.MediaInfo video.Container = mediaInfo.Container; - chapters = mediaInfo.Chapters == null ? Array.Empty<ChapterInfo>() : mediaInfo.Chapters; + chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>(); if (blurayInfo != null) { FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo); @@ -215,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo chapters = Array.Empty<ChapterInfo>(); } - await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); @@ -229,12 +232,24 @@ namespace MediaBrowser.Providers.MediaInfo video.Video3DFormat ??= mediaInfo.Video3DFormat; } + if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone) + { + _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles setting", video.Path); + mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStream); + } + + if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone) + { + _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setting", video.Path); + mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStream); + } + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); video.Height = videoStream?.Height ?? 0; video.Width = videoStream?.Width ?? 0; - video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; + video.DefaultVideoStreamIndex = videoStream?.Index; video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); @@ -368,11 +383,11 @@ namespace MediaBrowser.Providers.MediaInfo private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOptions, LibraryOptions libraryOptions) { - var isFullRefresh = refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; + var replaceData = refreshOptions.ReplaceAllMetadata; if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating)) { - if (!string.IsNullOrWhiteSpace(data.OfficialRating) || isFullRefresh) + if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData) { video.OfficialRating = data.OfficialRating; } @@ -380,7 +395,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres)) { - if (video.Genres.Length == 0 || isFullRefresh) + if (video.Genres.Length == 0 || replaceData) { video.Genres = Array.Empty<string>(); @@ -393,21 +408,28 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios)) { - if (video.Studios.Length == 0 || isFullRefresh) + if (video.Studios.Length == 0 || replaceData) { video.SetStudios(data.Studios); } } - if (video is MusicVideo musicVideo) + if (!video.IsLocked && video is MusicVideo musicVideo) { - musicVideo.Album = data.Album; - musicVideo.Artists = data.Artists; + if (string.IsNullOrEmpty(musicVideo.Album) || replaceData) + { + musicVideo.Album = data.Album; + } + + if (musicVideo.Artists.Count == 0 || replaceData) + { + musicVideo.Artists = data.Artists; + } } if (data.ProductionYear.HasValue) { - if (!video.ProductionYear.HasValue || isFullRefresh) + if (!video.ProductionYear.HasValue || replaceData) { video.ProductionYear = data.ProductionYear; } @@ -415,7 +437,7 @@ namespace MediaBrowser.Providers.MediaInfo if (data.PremiereDate.HasValue) { - if (!video.PremiereDate.HasValue || isFullRefresh) + if (!video.PremiereDate.HasValue || replaceData) { video.PremiereDate = data.PremiereDate; } @@ -423,7 +445,7 @@ namespace MediaBrowser.Providers.MediaInfo if (data.IndexNumber.HasValue) { - if (!video.IndexNumber.HasValue || isFullRefresh) + if (!video.IndexNumber.HasValue || replaceData) { video.IndexNumber = data.IndexNumber; } @@ -431,7 +453,7 @@ namespace MediaBrowser.Providers.MediaInfo if (data.ParentIndexNumber.HasValue) { - if (!video.ParentIndexNumber.HasValue || isFullRefresh) + if (!video.ParentIndexNumber.HasValue || replaceData) { video.ParentIndexNumber = data.ParentIndexNumber; } @@ -462,7 +484,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview)) { - if (string.IsNullOrWhiteSpace(video.Overview) || isFullRefresh) + if (string.IsNullOrWhiteSpace(video.Overview) || replaceData) { video.Overview = data.Overview; } @@ -471,11 +493,11 @@ namespace MediaBrowser.Providers.MediaInfo private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options) { - var isFullRefresh = options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; + var replaceData = options.ReplaceAllMetadata; if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Cast)) { - if (isFullRefresh || _libraryManager.GetPeople(video).Count == 0) + if (replaceData || _libraryManager.GetPeople(video).Count == 0) { var people = new List<PersonInfo>(); @@ -507,16 +529,14 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="options">The refreshOptions.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task AddExternalSubtitles( + private async Task AddExternalSubtitlesAsync( Video video, List<MediaStream> currentStreams, MetadataRefreshOptions options, CancellationToken cancellationToken) { - var subtitleResolver = new SubtitleResolver(_localization); - var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); - var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false); + var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken); var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; @@ -570,7 +590,7 @@ namespace MediaBrowser.Providers.MediaInfo // Rescan if (downloadedLanguages.Count > 0) { - externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true); + externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, true, cancellationToken); } } @@ -593,12 +613,9 @@ namespace MediaBrowser.Providers.MediaInfo CancellationToken cancellationToken) { var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken); + var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken).ConfigureAwait(false); - await foreach (MediaStream externalAudioStream in externalAudioStreams) - { - currentStreams.Add(externalAudioStream); - } + currentStreams = currentStreams.Concat(externalAudioStreams).ToList(); // Select all external audio file paths video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs new file mode 100644 index 000000000..39be405ec --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using Emby.Naming.ExternalFiles; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Providers.MediaInfo +{ + /// <summary> + /// Resolves external files for <see cref="Video"/>. + /// </summary> + public abstract class MediaInfoResolver + { + /// <summary> + /// The <see cref="ExternalPathParser"/> instance. + /// </summary> + private readonly ExternalPathParser _externalPathParser; + + /// <summary> + /// The <see cref="IMediaEncoder"/> instance. + /// </summary> + private readonly IMediaEncoder _mediaEncoder; + + private readonly IFileSystem _fileSystem; + + /// <summary> + /// The <see cref="NamingOptions"/> instance. + /// </summary> + private readonly NamingOptions _namingOptions; + + /// <summary> + /// The <see cref="DlnaProfileType"/> of the files this resolver should resolve. + /// </summary> + private readonly DlnaProfileType _type; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaInfoResolver"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="mediaEncoder">The media encoder.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param> + /// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param> + protected MediaInfoResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + NamingOptions namingOptions, + DlnaProfileType type) + { + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _namingOptions = namingOptions; + _type = type; + _externalPathParser = new ExternalPathParser(namingOptions, localizationManager, _type); + } + + /// <summary> + /// Retrieves the external streams for the provided video. + /// </summary> + /// <param name="video">The <see cref="Video"/> object to search external streams for.</param> + /// <param name="startIndex">The stream index to start adding external streams at.</param> + /// <param name="directoryService">The directory service to search for files.</param> + /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The external streams located.</returns> + public async Task<IReadOnlyList<MediaStream>> GetExternalStreamsAsync( + Video video, + int startIndex, + IDirectoryService directoryService, + bool clearCache, + CancellationToken cancellationToken) + { + if (!video.IsFileProtocol) + { + return Array.Empty<MediaStream>(); + } + + var pathInfos = GetExternalFiles(video, directoryService, clearCache); + + if (!pathInfos.Any()) + { + return Array.Empty<MediaStream>(); + } + + var mediaStreams = new List<MediaStream>(); + + foreach (var pathInfo in pathInfos) + { + var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false); + + if (mediaInfo.MediaStreams.Count == 1) + { + MediaStream mediaStream = mediaInfo.MediaStreams[0]; + mediaStream.Index = startIndex++; + mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault; + mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced; + + mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); + } + else + { + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) + { + mediaStream.Index = startIndex++; + + mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); + } + } + } + + return mediaStreams.AsReadOnly(); + } + + /// <summary> + /// Returns the external file infos for the given video. + /// </summary> + /// <param name="video">The <see cref="Video"/> object to search external files for.</param> + /// <param name="directoryService">The directory service to search for files.</param> + /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> + /// <returns>The external file paths located.</returns> + public IReadOnlyList<ExternalPathParserResult> GetExternalFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + return Array.Empty<ExternalPathParserResult>(); + } + + // Check if video folder exists + string folder = video.ContainingFolderPath; + if (!_fileSystem.DirectoryExists(folder)) + { + return Array.Empty<ExternalPathParserResult>(); + } + + var files = directoryService.GetFilePaths(folder, clearCache).ToList(); + var internalMetadataPath = video.GetInternalMetadataPath(); + if (_fileSystem.DirectoryExists(internalMetadataPath)) + { + files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache)); + } + + if (!files.Any()) + { + return Array.Empty<ExternalPathParserResult>(); + } + + var externalPathInfos = new List<ExternalPathParserResult>(); + ReadOnlySpan<char> prefix = video.FileNameWithoutExtension; + foreach (var file in files) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.AsSpan()); + if (fileNameWithoutExtension.Length >= prefix.Length + && prefix.Equals(fileNameWithoutExtension[..prefix.Length], StringComparison.OrdinalIgnoreCase) + && (fileNameWithoutExtension.Length == prefix.Length || _namingOptions.MediaFlagDelimiters.Contains(fileNameWithoutExtension[prefix.Length]))) + { + var externalPathInfo = _externalPathParser.ParseFile(file, fileNameWithoutExtension[prefix.Length..].ToString()); + + if (externalPathInfo != null) + { + externalPathInfos.Add(externalPathInfo); + } + } + } + + return externalPathInfos; + } + + /// <summary> + /// Returns the media info of the given file. + /// </summary> + /// <param name="path">The path to the file.</param> + /// <param name="type">The <see cref="DlnaProfileType"/>.</param> + /// <param name="cancellationToken">The cancellation token to cancel operation.</param> + /// <returns>The media info for the given file.</returns> + private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, DlnaProfileType type, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaType = type, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File + } + }, + cancellationToken); + } + + /// <summary> + /// Merges path metadata into stream metadata. + /// </summary> + /// <param name="mediaStream">The <see cref="MediaStream"/> object.</param> + /// <param name="pathInfo">The <see cref="ExternalPathParserResult"/> object.</param> + /// <returns>The modified mediaStream.</returns> + private MediaStream MergeMetadata(MediaStream mediaStream, ExternalPathParserResult pathInfo) + { + mediaStream.Path = pathInfo.Path; + mediaStream.IsExternal = true; + mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title; + mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language; + + mediaStream.Type = _type switch + { + DlnaProfileType.Audio => MediaStreamType.Audio, + DlnaProfileType.Subtitle => MediaStreamType.Subtitle, + _ => mediaStream.Type + }; + + return mediaStream; + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index ba284187e..4b9ba944a 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -1,235 +1,36 @@ -using System; -using System.Collections.Generic; -using System.IO; +using Emby.Naming.Common; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.MediaInfo { /// <summary> - /// Resolves external subtitles for videos. + /// Resolves external subtitle files for <see cref="Video"/>. /// </summary> - public class SubtitleResolver + public class SubtitleResolver : MediaInfoResolver { - private readonly ILocalizationManager _localization; - - /// <summary> - /// Initializes a new instance of the <see cref="SubtitleResolver"/> class. - /// </summary> - /// <param name="localization">The localization manager.</param> - public SubtitleResolver(ILocalizationManager localization) - { - _localization = localization; - } - - /// <summary> - /// Retrieves the external subtitle streams for the provided video. - /// </summary> - /// <param name="video">The video to search from.</param> - /// <param name="startIndex">The stream index to start adding subtitle streams at.</param> - /// <param name="directoryService">The directory service to search for files.</param> - /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> - /// <returns>The external subtitle streams located.</returns> - public List<MediaStream> GetExternalSubtitleStreams( - Video video, - int startIndex, - IDirectoryService directoryService, - bool clearCache) - { - var streams = new List<MediaStream>(); - - if (!video.IsFileProtocol) - { - return streams; - } - - AddExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); - - startIndex += streams.Count; - - string folder = video.GetInternalMetadataPath(); - - if (!Directory.Exists(folder)) - { - return streams; - } - - try - { - AddExternalSubtitleStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); - } - catch (IOException) - { - } - - return streams; - } - - /// <summary> - /// Locates the external subtitle files for the provided video. - /// </summary> - /// <param name="video">The video to search from.</param> - /// <param name="directoryService">The directory service to search for files.</param> - /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> - /// <returns>The external subtitle file paths located.</returns> - public IEnumerable<string> GetExternalSubtitleFiles( - Video video, - IDirectoryService directoryService, - bool clearCache) - { - if (!video.IsFileProtocol) - { - yield break; - } - - var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache); - - foreach (var stream in streams) - { - yield return stream.Path; - } - } - /// <summary> - /// Extracts the subtitle files from the provided list and adds them to the list of streams. + /// Initializes a new instance of the <see cref="SubtitleResolver"/> class for external subtitle file processing. /// </summary> - /// <param name="streams">The list of streams to add external subtitles to.</param> - /// <param name="videoPath">The path to the video file.</param> - /// <param name="startIndex">The stream index to start adding subtitle streams at.</param> - /// <param name="files">The files to add if they are subtitles.</param> - public void AddExternalSubtitleStreams( - List<MediaStream> streams, - string videoPath, - int startIndex, - IReadOnlyList<string> files) - { - var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath); - - for (var i = 0; i < files.Count; i++) - { - var fullName = files[i]; - var extension = Path.GetExtension(fullName.AsSpan()); - if (!IsSubtitleExtension(extension)) - { - continue; - } - - var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName); - - MediaStream mediaStream; - - // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot - if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - mediaStream = new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName - }; - } - else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase) - || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase); - - var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase); - - // Support xbmc naming conventions - 300.spanish.srt - var languageSpan = fileNameWithoutExtension; - while (languageSpan.Length > 0) - { - var lastDot = languageSpan.LastIndexOf('.'); - if (lastDot < videoFileNameWithoutExtension.Length) - { - languageSpan = ReadOnlySpan<char>.Empty; - break; - } - - var currentSlice = languageSpan[lastDot..]; - if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase) - || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase) - || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase)) - { - languageSpan = languageSpan[..lastDot]; - continue; - } - - languageSpan = languageSpan[(lastDot + 1)..]; - break; - } - - var language = languageSpan.ToString(); - if (string.IsNullOrWhiteSpace(language)) - { - language = null; - } - else - { - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var culture = _localization.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - } - - mediaStream = new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Language = language, - IsForced = isForced, - IsDefault = isDefault - }; - } - else - { - continue; - } - - mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); - - streams.Add(mediaStream); - } - } - - private static bool IsSubtitleExtension(ReadOnlySpan<char> extension) - { - return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase); - } - - private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename) - { - // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); - filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); - return Path.GetFileNameWithoutExtension(filename.AsSpan()); - } - - private void AddExternalSubtitleStreams( - List<MediaStream> streams, - string folder, - string videoPath, - int startIndex, - IDirectoryService directoryService, - bool clearCache) + /// <param name="localizationManager">The localization manager.</param> + /// <param name="mediaEncoder">The media encoder.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param> + public SubtitleResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + NamingOptions namingOptions) + : base( + localizationManager, + mediaEncoder, + fileSystem, + namingOptions, + DlnaProfileType.Subtitle) { - var files = directoryService.GetFilePaths(folder, clearCache, true); - - AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 58651d42a..eb9071a52 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -63,7 +63,8 @@ namespace MediaBrowser.Providers.MediaInfo return _config.GetConfiguration<SubtitleOptions>("subtitles"); } - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var options = GetOptions(); @@ -210,6 +211,7 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + /// <inheritdoc /> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { return new[] diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index e0ab31b56..f4941565f 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -12,8 +12,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages { public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages { - // TODO change this for a Jellyfin-hosted repository. - public const string DefaultServer = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname"; + public const string DefaultServer = "https://raw.github.com/jellyfin/emby-artwork/master/studios"; public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs index 3a3048cec..e81324a6b 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -110,19 +110,19 @@ namespace MediaBrowser.Providers.Studios private string GetUrl(string image, string filename) { - return string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}.jpg", repositoryUrl, image, filename); + return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", repositoryUrl, image, filename); } private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken) { - string url = string.Format(CultureInfo.InvariantCulture, "{0}/studiothumbs.txt", repositoryUrl); + string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", repositoryUrl); return EnsureList(url, file, _fileSystem, cancellationToken); } private Task<string> EnsurePosterList(string file, CancellationToken cancellationToken) { - string url = string.Format(CultureInfo.InvariantCulture, "{0}/studioposters.txt", repositoryUrl); + string url = string.Format(CultureInfo.InvariantCulture, "{0}/posters.txt", repositoryUrl); return EnsureList(url, file, _fileSystem, cancellationToken); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 92f5306e5..03aaf380b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -43,6 +43,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public string? BackdropSize { get; set; } /// <summary> + /// Gets or sets a value indicating the logo image size to fetch. + /// </summary> + public string? LogoSize { get; set; } + + /// <summary> /// Gets or sets a value indicating the profile image size to fetch. /// </summary> public string? ProfileSize { get; set; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index 72bd38ffa..48ec0535c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -37,6 +37,9 @@ <select is="emby-select" id="selectBackdropSize" label="Backdrop"></select> </div> <div class="selectContainer"> + <select is="emby-select" id="selectLogoSize" label="Logo"></select> + </div> + <div class="selectContainer"> <select is="emby-select" id="selectProfileSize" label="Profile"></select> </div> <div class="selectContainer"> @@ -76,6 +79,10 @@ selBackdropSize.innerHTML = clientConfig.BackdropSizes.map(sizeOptionsGenerator); selBackdropSize.value = pluginConfig.BackdropSize; + var selLogoSize = document.querySelector('#selectLogoSize'); + selLogoSize.innerHTML = clientConfig.LogoSizes.map(sizeOptionsGenerator); + selLogoSize.value = pluginConfig.LogoSize; + var selProfileSize = document.querySelector('#selectProfileSize'); selProfileSize.innerHTML = clientConfig.ProfileSizes.map(sizeOptionsGenerator); selProfileSize.value = pluginConfig.ProfileSize; @@ -129,6 +136,7 @@ config.MaxCastMembers = document.querySelector('#maxCastMembers').value; config.PosterSize = document.querySelector('#selectPosterSize').value; config.BackdropSize = document.querySelector('#selectBackdropSize').value; + config.LogoSize = document.querySelector('#selectLogoSize').value; config.ProfileSize = document.querySelector('#selectProfileSize').value; config.StillSize = document.querySelector('#selectStillSize').value; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index f71f7bd10..16f0089f8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -44,7 +44,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return new List<ImageType> { ImageType.Primary, - ImageType.Backdrop + ImageType.Backdrop, + ImageType.Logo }; } @@ -85,10 +86,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var posters = movie.Images.Posters; var backdrops = movie.Images.Backdrops; - var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count); + var logos = movie.Images.Logos; + var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); + _tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 5ef3736c4..130d6ce44 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return new List<ImageType> { ImageType.Primary, - ImageType.Backdrop + ImageType.Backdrop, + ImageType.Logo }; } @@ -69,10 +70,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var posters = series.Images.Posters; var backdrops = series.Images.Backdrops; - var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count); + var logos = series.Images.Logos; + var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); + _tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 28d6f4d0c..d78652834 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -544,6 +544,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> + /// Converts logo <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. + /// </summary> + /// <param name="images">The input images.</param> + /// <param name="requestLanguage">The requested language.</param> + /// <param name="results">The collection to add the remote images into.</param> + public void ConvertLogosToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) + { + ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.LogoSize, ImageType.Logo, requestLanguage, results); + } + + /// <summary> /// Converts profile <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. /// </summary> /// <param name="images">The input images.</param> @@ -622,6 +633,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb pluginConfig.BackdropSize = imageConfig.BackdropSizes[^1]; } + if (!imageConfig.LogoSizes.Contains(pluginConfig.LogoSize)) + { + pluginConfig.LogoSize = imageConfig.LogoSizes[^1]; + } + if (!imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize)) { pluginConfig.ProfileSize = imageConfig.ProfileSizes[^1]; diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 34019e582..d0e229b23 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -248,8 +248,11 @@ namespace MediaBrowser.Providers.Subtitles var fileOptions = AsyncFile.WriteOptions; fileOptions.Mode = FileMode.CreateNew; fileOptions.PreallocationSize = stream.Length; - using var fs = new FileStream(savePath, fileOptions); - await stream.CopyToAsync(fs).ConfigureAwait(false); + var fs = new FileStream(savePath, fileOptions); + await using (fs.ConfigureAwait(false)) + { + await stream.CopyToAsync(fs).ConfigureAwait(false); + } return; } diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 935ff5f59..a6216ef30 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.XbmcMetadata { if (!string.IsNullOrWhiteSpace(_config.GetNfoConfiguration().UserId)) { - SaveMetadataForItem(e.Item, ItemUpdateType.MetadataDownload); + _ = SaveMetadataForItemAsync(e.Item, ItemUpdateType.MetadataDownload); } } } @@ -58,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata _userDataManager.UserDataSaved -= OnUserDataSaved; } - private void SaveMetadataForItem(BaseItem item, ItemUpdateType updateReason) + private async Task SaveMetadataForItemAsync(BaseItem item, ItemUpdateType updateReason) { if (!item.IsFileProtocol || !item.SupportsLocalMetadata) { @@ -67,7 +67,7 @@ namespace MediaBrowser.XbmcMetadata try { - _providerManager.SaveMetadata(item, updateReason, new[] { BaseNfoSaver.SaverName }); + await _providerManager.SaveMetadataAsync(item, updateReason, new[] { BaseNfoSaver.SaverName }).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index ad06688fb..4d0ba487b 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -22,6 +22,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 007101868..09ff84044 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -778,7 +778,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers case "thumb": { - FetchThumbNode(reader, itemResult); + FetchThumbNode(reader, itemResult, "thumb"); break; } @@ -796,7 +796,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } - FetchThumbNode(subtree, itemResult); + FetchThumbNode(subtree, itemResult, "fanart"); break; } @@ -819,17 +819,22 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - private void FetchThumbNode(XmlReader reader, MetadataResult<T> itemResult) + private void FetchThumbNode(XmlReader reader, MetadataResult<T> itemResult, string parentNode) { var artType = reader.GetAttribute("aspect"); var val = reader.ReadElementContentAsString(); // artType is null if the thumb node is a child of the fanart tag // -> set image type to fanart - if (string.IsNullOrWhiteSpace(artType)) + if (string.IsNullOrWhiteSpace(artType) && parentNode.Equals("fanart", StringComparison.Ordinal)) { artType = "fanart"; } + else if (string.IsNullOrWhiteSpace(artType)) + { + // Sonarr writes thumb tags for posters without aspect property + artType = "poster"; + } // skip: // - empty uri diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index d09981304..39bd87e96 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; using System.Xml; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; @@ -180,7 +181,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// <inheritdoc /> - public void Save(BaseItem item, CancellationToken cancellationToken) + public async Task SaveAsync(BaseItem item, CancellationToken cancellationToken) { var path = GetSavePath(item); @@ -192,11 +193,11 @@ namespace MediaBrowser.XbmcMetadata.Savers cancellationToken.ThrowIfCancellationRequested(); - SaveToFile(memoryStream, path); + await SaveToFileAsync(memoryStream, path).ConfigureAwait(false); } } - private void SaveToFile(Stream stream, string path) + private async Task SaveToFileAsync(Stream stream, string path) { var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); Directory.CreateDirectory(directory); @@ -209,12 +210,14 @@ namespace MediaBrowser.XbmcMetadata.Savers Mode = FileMode.Create, Access = FileAccess.Write, Share = FileShare.None, - PreallocationSize = stream.Length + PreallocationSize = stream.Length, + Options = FileOptions.Asynchronous }; - using (var filestream = new FileStream(path, fileStreamOptions)) + var filestream = new FileStream(path, fileStreamOptions); + await using (filestream.ConfigureAwait(false)) { - stream.CopyTo(filestream); + await stream.CopyToAsync(filestream).ConfigureAwait(false); } if (ConfigurationManager.Configuration.SaveMetadataHidden) diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs index 04b14c4dc..9d477ea9f 100644 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -4,7 +4,7 @@ using System.Net; namespace Rssdp { /// <summary> - /// Event arguments for the <see cref="Infrastructure.SsdpDeviceLocatorBase.DeviceAvailable"/> event. + /// Event arguments for the <see cref="Infrastructure.SsdpDeviceLocator.DeviceAvailable"/> event. /// </summary> public sealed class DeviceAvailableEventArgs : EventArgs { diff --git a/RSSDP/DeviceUnavailableEventArgs.cs b/RSSDP/DeviceUnavailableEventArgs.cs index 94248f39f..ca2515202 100644 --- a/RSSDP/DeviceUnavailableEventArgs.cs +++ b/RSSDP/DeviceUnavailableEventArgs.cs @@ -3,7 +3,7 @@ using System; namespace Rssdp { /// <summary> - /// Event arguments for the <see cref="Infrastructure.SsdpDeviceLocatorBase.DeviceUnavailable"/> event. + /// Event arguments for the <see cref="Infrastructure.SsdpDeviceLocator.DeviceUnavailable"/> event. /// </summary> public sealed class DeviceUnavailableEventArgs : EventArgs { diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index aa35811ef..3cbc991d6 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -42,10 +42,10 @@ namespace Rssdp.Infrastructure Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken); /// <summary> - /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances. + /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocator"/> and/or <see cref="ISsdpDevicePublisher"/> instances. /// </summary> /// <remarks> - /// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocatorBase"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para> + /// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocator"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para> /// </remarks> bool IsShared { get; set; } } diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs index 025c4d2e0..5cf74bd75 100644 --- a/RSSDP/RequestReceivedEventArgs.cs +++ b/RSSDP/RequestReceivedEventArgs.cs @@ -34,7 +34,7 @@ namespace Rssdp.Infrastructure } /// <summary> - /// The <see cref="UdpEndPoint"/> the request came from. + /// The <see cref="IPEndPoint"/> the request came from. /// </summary> public IPEndPoint ReceivedFrom { diff --git a/RSSDP/ResponseReceivedEventArgs.cs b/RSSDP/ResponseReceivedEventArgs.cs index 708113da1..93262a460 100644 --- a/RSSDP/ResponseReceivedEventArgs.cs +++ b/RSSDP/ResponseReceivedEventArgs.cs @@ -33,7 +33,7 @@ namespace Rssdp.Infrastructure } /// <summary> - /// The <see cref="UdpEndPoint"/> the response came from. + /// The <see cref="IPEndPoint"/> the response came from. /// </summary> public IPEndPoint ReceivedFrom { diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 58dabc628..a66b70ac1 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -295,10 +295,10 @@ namespace Rssdp.Infrastructure } /// <summary> - /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances. + /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocator"/> and/or <see cref="ISsdpDevicePublisher"/> instances. /// </summary> /// <remarks> - /// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocatorBase"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para> + /// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocator"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para> /// </remarks> public bool IsShared { diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 3a52b2a3e..681ef0a5c 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -42,7 +42,7 @@ namespace Rssdp.Infrastructure /// Raised for when /// <list type="bullet"> /// <item>An 'alive' notification is received that a device, regardless of whether or not that device is not already in the cache or has previously raised this event.</item> - /// <item>For each item found during a device <see cref="SearchAsync()"/> (cached or not), allowing clients to respond to found devices before the entire search is complete.</item> + /// <item>For each item found during a device <see cref="SearchAsync(System.Threading.CancellationToken)"/> (cached or not), allowing clients to respond to found devices before the entire search is complete.</item> /// <item>Only if the notification type matches the <see cref="NotificationFilter"/> property. By default the filter is null, meaning all notifications raise events (regardless of ant </item> /// </list> /// <para>This event may be raised from a background thread, if interacting with UI or other objects with specific thread affinity invoking to the relevant thread is required.</para> diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 708c706b5..350f0076a 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -13,7 +13,7 @@ RUN yum update -yq \ && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/e7acb87d-ab08-4620-9050-b3e80f688d36/e93bbadc19b12f81e3a6761719f28b47/dotnet-sdk-6.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 30615cd42..eeff9a96f 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -12,7 +12,7 @@ RUN dnf update -yq \ && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/e7acb87d-ab08-4620-9050-b3e80f688d36/e93bbadc19b12f81e3a6761719f28b47/dotnet-sdk-6.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index ccfaaa5f0..9d2deb1c6 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -17,7 +17,7 @@ RUN apt-get update -yqq \ libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/e7acb87d-ab08-4620-9050-b3e80f688d36/e93bbadc19b12f81e3a6761719f28b47/dotnet-sdk-6.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 988c8f16d..ec90dba83 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/e7acb87d-ab08-4620-9050-b3e80f688d36/e93bbadc19b12f81e3a6761719f28b47/dotnet-sdk-6.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 61a008d6a..3685e16c4 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/e7acb87d-ab08-4620-9050-b3e80f688d36/e93bbadc19b12f81e3a6761719f28b47/dotnet-sdk-6.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/jellyfin.ruleset b/jellyfin.ruleset index dea1a748b..1c834de82 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -57,6 +57,10 @@ </Rules> <Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.Design"> + <!-- error on CA1001: Types that own disposable fields should be disposable --> + <Rule Id="CA1001" Action="Error" /> + <!-- error on CA1012: Abstract types should not have public constructors --> + <Rule Id="CA1012" Action="Error" /> <!-- error on CA1063: Implement IDisposable correctly --> <Rule Id="CA1063" Action="Error" /> <!-- error on CA1305: Specify IFormatProvider --> @@ -80,6 +84,8 @@ <!-- error on CA2016: Forward the CancellationToken parameter to methods that take one or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token --> <Rule Id="CA2016" Action="Error" /> + <!-- error on CA2215: Dispose methods should call base class dispose --> + <Rule Id="CA2215" Action="Error" /> <!-- error on CA2254: Template should be a static expression --> <Rule Id="CA2254" Action="Error" /> @@ -136,4 +142,9 @@ <!-- disable warning CA2234: Pass System.Uri objects instead of strings --> <Rule Id="CA2234" Action="None" /> </Rules> + + <Rules AnalyzerId="Microsoft.CodeAnalysis.BannedApiAnalyzers" RuleNamespace="Microsoft.Design"> + <!-- error on RS0030: Do not used banned APIs --> + <Rule Id="RS0030" Action="Error" /> + </Rules> </RuleSet> diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 90d2a0da6..37baff5ae 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -29,6 +29,10 @@ <!-- Code Analyzers--> <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs b/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs new file mode 100644 index 000000000..09816c960 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs @@ -0,0 +1,96 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text.Json; +using Jellyfin.Extensions.Json; +using Jellyfin.MediaEncoding.Hls.Extractors; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.MediaEncoding.Hls.Cache; + +/// <inheritdoc /> +public class CacheDecorator : IKeyframeExtractor +{ + private readonly IKeyframeExtractor _keyframeExtractor; + private readonly ILogger<CacheDecorator> _logger; + private readonly string _keyframeExtractorName; + private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private readonly string _keyframeCachePath; + + /// <summary> + /// Initializes a new instance of the <see cref="CacheDecorator"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="keyframeExtractor">An instance of the <see cref="IKeyframeExtractor"/> interface.</param> + /// <param name="logger">An instance of the <see cref="ILogger{CacheDecorator}"/> interface.</param> + public CacheDecorator(IApplicationPaths applicationPaths, IKeyframeExtractor keyframeExtractor, ILogger<CacheDecorator> logger) + { + ArgumentNullException.ThrowIfNull(applicationPaths); + ArgumentNullException.ThrowIfNull(keyframeExtractor); + + _keyframeExtractor = keyframeExtractor; + _logger = logger; + _keyframeExtractorName = keyframeExtractor.GetType().Name; + // TODO make the dir configurable + _keyframeCachePath = Path.Combine(applicationPaths.DataPath, "keyframes"); + } + + /// <inheritdoc /> + public bool IsMetadataBased => _keyframeExtractor.IsMetadataBased; + + /// <inheritdoc /> + public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData) + { + keyframeData = null; + var cachePath = GetCachePath(_keyframeCachePath, filePath); + if (TryReadFromCache(cachePath, out var cachedResult)) + { + keyframeData = cachedResult; + return true; + } + + if (!_keyframeExtractor.TryExtractKeyframes(filePath, out var result)) + { + _logger.LogDebug("Failed to extract keyframes using {ExtractorName}", _keyframeExtractorName); + return false; + } + + _logger.LogDebug("Successfully extracted keyframes using {ExtractorName}", _keyframeExtractorName); + keyframeData = result; + SaveToCache(cachePath, keyframeData); + return true; + } + + private static void SaveToCache(string cachePath, KeyframeData keyframeData) + { + var json = JsonSerializer.Serialize(keyframeData, _jsonOptions); + Directory.CreateDirectory(Path.GetDirectoryName(cachePath) ?? throw new ArgumentException($"Provided path ({cachePath}) is not valid.", nameof(cachePath))); + File.WriteAllText(cachePath, json); + } + + private static string GetCachePath(string keyframeCachePath, string filePath) + { + var lastWriteTimeUtc = File.GetLastWriteTimeUtc(filePath); + ReadOnlySpan<char> filename = (filePath + "_" + lastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5() + ".json"; + var prefix = filename[..1]; + + return Path.Join(keyframeCachePath, prefix, filename); + } + + private static bool TryReadFromCache(string cachePath, [NotNullWhen(true)] out KeyframeData? cachedResult) + { + if (File.Exists(cachePath)) + { + var bytes = File.ReadAllBytes(cachePath); + cachedResult = JsonSerializer.Deserialize<KeyframeData>(bytes, _jsonOptions); + return cachedResult != null; + } + + cachedResult = null; + return false; + } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Extensions/MediaEncodingHlsServiceCollectionExtensions.cs b/src/Jellyfin.MediaEncoding.Hls/Extensions/MediaEncodingHlsServiceCollectionExtensions.cs new file mode 100644 index 000000000..8ed4edcea --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Extensions/MediaEncodingHlsServiceCollectionExtensions.cs @@ -0,0 +1,36 @@ +using System; +using Jellyfin.MediaEncoding.Hls.Cache; +using Jellyfin.MediaEncoding.Hls.Extractors; +using Jellyfin.MediaEncoding.Hls.Playlist; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.MediaEncoding.Hls.Extensions; + +/// <summary> +/// Extensions for the <see cref="IServiceCollection"/> interface. +/// </summary> +public static class MediaEncodingHlsServiceCollectionExtensions +{ + /// <summary> + /// Adds the hls playlist generators to the <see cref="IServiceCollection"/>. + /// </summary> + /// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddHlsPlaylistGenerator(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingletonWithDecorator(typeof(FfProbeKeyframeExtractor)); + serviceCollection.AddSingletonWithDecorator(typeof(MatroskaKeyframeExtractor)); + serviceCollection.AddSingleton<IDynamicHlsPlaylistGenerator, DynamicHlsPlaylistGenerator>(); + return serviceCollection; + } + + private static void AddSingletonWithDecorator(this IServiceCollection serviceCollection, Type type) + { + serviceCollection.AddSingleton<IKeyframeExtractor>(serviceProvider => + { + var extractor = ActivatorUtilities.CreateInstance(serviceProvider, type); + var decorator = ActivatorUtilities.CreateInstance<CacheDecorator>(serviceProvider, extractor); + return decorator; + }); + } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs new file mode 100644 index 000000000..f86599a23 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Emby.Naming.Common; +using Jellyfin.Extensions; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Controller.MediaEncoding; +using Microsoft.Extensions.Logging; +using Extractor = Jellyfin.MediaEncoding.Keyframes.FfProbe.FfProbeKeyframeExtractor; + +namespace Jellyfin.MediaEncoding.Hls.Extractors; + +/// <inheritdoc /> +public class FfProbeKeyframeExtractor : IKeyframeExtractor +{ + private readonly IMediaEncoder _mediaEncoder; + private readonly NamingOptions _namingOptions; + private readonly ILogger<FfProbeKeyframeExtractor> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="FfProbeKeyframeExtractor"/> class. + /// </summary> + /// <param name="mediaEncoder">An instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param> + /// <param name="logger">An instance of the <see cref="ILogger{FfprobeKeyframeExtractor}"/> interface.</param> + public FfProbeKeyframeExtractor(IMediaEncoder mediaEncoder, NamingOptions namingOptions, ILogger<FfProbeKeyframeExtractor> logger) + { + _mediaEncoder = mediaEncoder; + _namingOptions = namingOptions; + _logger = logger; + } + + /// <inheritdoc /> + public bool IsMetadataBased => false; + + /// <inheritdoc /> + public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData) + { + if (!_namingOptions.VideoFileExtensions.Contains(Path.GetExtension(filePath.AsSpan()), StringComparison.OrdinalIgnoreCase)) + { + keyframeData = null; + return false; + } + + try + { + keyframeData = Extractor.GetKeyframeData(_mediaEncoder.ProbePath, filePath); + return keyframeData.KeyframeTicks.Count > 0; + } + catch (Exception ex) + { + _logger.LogError(ex, "Extracting keyframes from {FilePath} using ffprobe failed", filePath); + } + + keyframeData = null; + return false; + } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs new file mode 100644 index 000000000..497210f41 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Jellyfin.MediaEncoding.Keyframes; + +namespace Jellyfin.MediaEncoding.Hls.Extractors; + +/// <summary> +/// Keyframe extractor. +/// </summary> +public interface IKeyframeExtractor +{ + /// <summary> + /// Gets a value indicating whether the extractor is based on container metadata. + /// </summary> + bool IsMetadataBased { get; } + + /// <summary> + /// Attempt to extract keyframes. + /// </summary> + /// <param name="filePath">The path to the file.</param> + /// <param name="keyframeData">The keyframes.</param> + /// <returns>A value indicating whether the keyframe extraction was successful.</returns> + bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData); +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs new file mode 100644 index 000000000..4bc537c0e --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs @@ -0,0 +1,48 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Jellyfin.MediaEncoding.Keyframes; +using Microsoft.Extensions.Logging; +using Extractor = Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor; + +namespace Jellyfin.MediaEncoding.Hls.Extractors; + +/// <inheritdoc /> +public class MatroskaKeyframeExtractor : IKeyframeExtractor +{ + private readonly ILogger<MatroskaKeyframeExtractor> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="MatroskaKeyframeExtractor"/> class. + /// </summary> + /// <param name="logger">An instance of the <see cref="ILogger{MatroskaKeyframeExtractor}"/> interface.</param> + public MatroskaKeyframeExtractor(ILogger<MatroskaKeyframeExtractor> logger) + { + _logger = logger; + } + + /// <inheritdoc /> + public bool IsMetadataBased => true; + + /// <inheritdoc /> + public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData) + { + if (!filePath.AsSpan().EndsWith(".mkv", StringComparison.OrdinalIgnoreCase)) + { + keyframeData = null; + return false; + } + + try + { + keyframeData = Extractor.GetKeyframeData(filePath); + return keyframeData.KeyframeTicks.Count > 0; + } + catch (Exception ex) + { + _logger.LogError(ex, "Extracting keyframes from {FilePath} using matroska metadata failed", filePath); + } + + keyframeData = null; + return false; + } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj new file mode 100644 index 000000000..56f973a21 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj @@ -0,0 +1,35 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <!-- Code Analyzers--> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" /> + <ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" /> + <ProjectReference Include="../Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> + </ItemGroup> + + <ItemGroup> + <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> + <_Parameter1>Jellyfin.MediaEncoding.Hls.Tests</_Parameter1> + </AssemblyAttribute> + </ItemGroup> + +</Project> diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs new file mode 100644 index 000000000..ac28ca26a --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs @@ -0,0 +1,56 @@ +namespace Jellyfin.MediaEncoding.Hls.Playlist; + +/// <summary> +/// Request class for the <see cref="IDynamicHlsPlaylistGenerator.CreateMainPlaylist(CreateMainPlaylistRequest)"/> method. +/// </summary> +public class CreateMainPlaylistRequest +{ + /// <summary> + /// Initializes a new instance of the <see cref="CreateMainPlaylistRequest"/> class. + /// </summary> + /// <param name="filePath">The absolute file path to the file.</param> + /// <param name="desiredSegmentLengthMs">The desired segment length in milliseconds.</param> + /// <param name="totalRuntimeTicks">The total duration of the file in ticks.</param> + /// <param name="segmentContainer">The desired segment container eg. "ts".</param> + /// <param name="endpointPrefix">The URI prefix for the relative URL in the playlist.</param> + /// <param name="queryString">The desired query string to append (must start with ?).</param> + public CreateMainPlaylistRequest(string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString) + { + FilePath = filePath; + DesiredSegmentLengthMs = desiredSegmentLengthMs; + TotalRuntimeTicks = totalRuntimeTicks; + SegmentContainer = segmentContainer; + EndpointPrefix = endpointPrefix; + QueryString = queryString; + } + + /// <summary> + /// Gets the file path. + /// </summary> + public string FilePath { get; } + + /// <summary> + /// Gets the desired segment length in milliseconds. + /// </summary> + public int DesiredSegmentLengthMs { get; } + + /// <summary> + /// Gets the total runtime in ticks. + /// </summary> + public long TotalRuntimeTicks { get; } + + /// <summary> + /// Gets the segment container. + /// </summary> + public string SegmentContainer { get; } + + /// <summary> + /// Gets the endpoint prefix for the URL. + /// </summary> + public string EndpointPrefix { get; } + + /// <summary> + /// Gets the query string. + /// </summary> + public string QueryString { get; } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs new file mode 100644 index 000000000..3382ba251 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Jellyfin.MediaEncoding.Hls.Extractors; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.MediaEncoding; + +namespace Jellyfin.MediaEncoding.Hls.Playlist; + +/// <inheritdoc /> +public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + private readonly IKeyframeExtractor[] _extractors; + + /// <summary> + /// Initializes a new instance of the <see cref="DynamicHlsPlaylistGenerator"/> class. + /// </summary> + /// <param name="serverConfigurationManager">An instance of the see <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="extractors">An instance of <see cref="IEnumerable{IKeyframeExtractor}"/>.</param> + public DynamicHlsPlaylistGenerator(IServerConfigurationManager serverConfigurationManager, IEnumerable<IKeyframeExtractor> extractors) + { + _serverConfigurationManager = serverConfigurationManager; + _extractors = extractors.Where(e => e.IsMetadataBased).ToArray(); + } + + /// <inheritdoc /> + public string CreateMainPlaylist(CreateMainPlaylistRequest request) + { + IReadOnlyList<double> segments; + if (TryExtractKeyframes(request.FilePath, out var keyframeData)) + { + segments = ComputeSegments(keyframeData, request.DesiredSegmentLengthMs); + } + else + { + segments = ComputeEqualLengthSegments(request.DesiredSegmentLengthMs, request.TotalRuntimeTicks); + } + + var segmentExtension = EncodingHelper.GetSegmentFileExtension(request.SegmentContainer); + + // http://ffmpeg.org/ffmpeg-all.html#toc-hls-2 + var isHlsInFmp4 = string.Equals(segmentExtension, ".mp4", StringComparison.OrdinalIgnoreCase); + var hlsVersion = isHlsInFmp4 ? "7" : "3"; + + var builder = new StringBuilder(128); + + builder.AppendLine("#EXTM3U") + .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD") + .Append("#EXT-X-VERSION:") + .Append(hlsVersion) + .AppendLine() + .Append("#EXT-X-TARGETDURATION:") + .Append(Math.Ceiling(segments.Count > 0 ? segments.Max() : request.DesiredSegmentLengthMs)) + .AppendLine() + .AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); + + var index = 0; + + if (isHlsInFmp4) + { + // Init file that only includes fMP4 headers + builder.Append("#EXT-X-MAP:URI=\"") + .Append(request.EndpointPrefix) + .Append("-1") + .Append(segmentExtension) + .Append(request.QueryString) + .Append("&runtimeTicks=0") + .Append("&actualSegmentLengthTicks=0") + .Append('"') + .AppendLine(); + } + + long currentRuntimeInSeconds = 0; + foreach (var length in segments) + { + // Manually convert to ticks to avoid precision loss when converting double + var lengthTicks = Convert.ToInt64(length * TimeSpan.TicksPerSecond); + builder.Append("#EXTINF:") + .Append(length.ToString("0.000000", CultureInfo.InvariantCulture)) + .AppendLine(", nodesc") + .Append(request.EndpointPrefix) + .Append(index++) + .Append(segmentExtension) + .Append(request.QueryString) + .Append("&runtimeTicks=") + .Append(currentRuntimeInSeconds) + .Append("&actualSegmentLengthTicks=") + .Append(lengthTicks) + .AppendLine(); + + currentRuntimeInSeconds += lengthTicks; + } + + builder.AppendLine("#EXT-X-ENDLIST"); + + return builder.ToString(); + } + + private bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData) + { + keyframeData = null; + if (!IsExtractionAllowedForFile(filePath, _serverConfigurationManager.GetEncodingOptions().AllowOnDemandMetadataBasedKeyframeExtractionForExtensions)) + { + return false; + } + + var len = _extractors.Length; + for (var i = 0; i < len; i++) + { + var extractor = _extractors[i]; + if (!extractor.TryExtractKeyframes(filePath, out var result)) + { + continue; + } + + keyframeData = result; + return true; + } + + return false; + } + + internal static bool IsExtractionAllowedForFile(ReadOnlySpan<char> filePath, string[] allowedExtensions) + { + var extension = Path.GetExtension(filePath); + if (extension.IsEmpty) + { + return false; + } + + // Remove the leading dot + var extensionWithoutDot = extension[1..]; + for (var i = 0; i < allowedExtensions.Length; i++) + { + var allowedExtension = allowedExtensions[i].AsSpan().TrimStart('.'); + if (extensionWithoutDot.Equals(allowedExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + internal static IReadOnlyList<double> ComputeSegments(KeyframeData keyframeData, int desiredSegmentLengthMs) + { + if (keyframeData.KeyframeTicks.Count > 0 && keyframeData.TotalDuration < keyframeData.KeyframeTicks[^1]) + { + throw new ArgumentException("Invalid duration in keyframe data", nameof(keyframeData)); + } + + long lastKeyframe = 0; + var result = new List<double>(); + // Scale the segment length to ticks to match the keyframes + var desiredSegmentLengthTicks = TimeSpan.FromMilliseconds(desiredSegmentLengthMs).Ticks; + var desiredCutTime = desiredSegmentLengthTicks; + for (var j = 0; j < keyframeData.KeyframeTicks.Count; j++) + { + var keyframe = keyframeData.KeyframeTicks[j]; + if (keyframe >= desiredCutTime) + { + var currentSegmentLength = keyframe - lastKeyframe; + result.Add(TimeSpan.FromTicks(currentSegmentLength).TotalSeconds); + lastKeyframe = keyframe; + desiredCutTime += desiredSegmentLengthTicks; + } + } + + result.Add(TimeSpan.FromTicks(keyframeData.TotalDuration - lastKeyframe).TotalSeconds); + return result; + } + + internal static double[] ComputeEqualLengthSegments(int desiredSegmentLengthMs, long totalRuntimeTicks) + { + if (desiredSegmentLengthMs == 0 || totalRuntimeTicks == 0) + { + throw new InvalidOperationException($"Invalid segment length ({desiredSegmentLengthMs}) or runtime ticks ({totalRuntimeTicks})"); + } + + var desiredSegmentLength = TimeSpan.FromMilliseconds(desiredSegmentLengthMs); + + var segmentLengthTicks = desiredSegmentLength.Ticks; + var wholeSegments = totalRuntimeTicks / segmentLengthTicks; + var remainingTicks = totalRuntimeTicks % segmentLengthTicks; + + var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1); + var segments = new double[segmentsLen]; + for (int i = 0; i < wholeSegments; i++) + { + segments[i] = desiredSegmentLength.TotalSeconds; + } + + if (remainingTicks != 0) + { + segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds; + } + + return segments; + } +} diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/IDynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/IDynamicHlsPlaylistGenerator.cs new file mode 100644 index 000000000..2626cb2dd --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/IDynamicHlsPlaylistGenerator.cs @@ -0,0 +1,14 @@ +namespace Jellyfin.MediaEncoding.Hls.Playlist; + +/// <summary> +/// Generator for dynamic HLS playlists where the segment lengths aren't known in advance. +/// </summary> +public interface IDynamicHlsPlaylistGenerator +{ + /// <summary> + /// Creates the main playlist containing the main video or audio stream. + /// </summary> + /// <param name="request">An instance of the <see cref="CreateMainPlaylistRequest"/> class.</param> + /// <returns>The playlist as a formatted string.</returns> + string CreateMainPlaylist(CreateMainPlaylistRequest request); +} diff --git a/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs new file mode 100644 index 000000000..a7d52184b --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.MediaEncoding.Hls.Extractors; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.MediaEncoding.Hls.ScheduledTasks; + +/// <inheritdoc /> +public class KeyframeExtractionScheduledTask : IScheduledTask +{ + private const int Pagesize = 1000; + + private readonly ILocalizationManager _localizationManager; + private readonly ILibraryManager _libraryManager; + private readonly IKeyframeExtractor[] _keyframeExtractors; + private static readonly BaseItemKind[] _itemTypes = { BaseItemKind.Episode, BaseItemKind.Movie }; + + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeExtractionScheduledTask"/> class. + /// </summary> + /// <param name="localizationManager">An instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="libraryManager">An instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="keyframeExtractors">The keyframe extractors.</param> + public KeyframeExtractionScheduledTask(ILocalizationManager localizationManager, ILibraryManager libraryManager, IEnumerable<IKeyframeExtractor> keyframeExtractors) + { + _localizationManager = localizationManager; + _libraryManager = libraryManager; + _keyframeExtractors = keyframeExtractors.OrderByDescending(e => e.IsMetadataBased).ToArray(); + } + + /// <inheritdoc /> + public string Name => _localizationManager.GetLocalizedString("TaskKeyframeExtractor"); + + /// <inheritdoc /> + public string Key => "KeyframeExtraction"; + + /// <inheritdoc /> + public string Description => _localizationManager.GetLocalizedString("TaskKeyframeExtractorDescription"); + + /// <inheritdoc /> + public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) + { + var query = new InternalItemsQuery + { + MediaTypes = new[] { MediaType.Video }, + IsVirtualItem = false, + IncludeItemTypes = _itemTypes, + DtoOptions = new DtoOptions(true), + SourceTypes = new[] { SourceType.Library }, + Recursive = true, + Limit = Pagesize + }; + + var numberOfVideos = _libraryManager.GetCount(query); + + var startIndex = 0; + var numComplete = 0; + + while (startIndex < numberOfVideos) + { + query.StartIndex = startIndex; + + var videos = _libraryManager.GetItemList(query); + var currentPageCount = videos.Count; + // TODO parallelize with Parallel.ForEach? + for (var i = 0; i < currentPageCount; i++) + { + var video = videos[i]; + // Only local files supported + if (video.IsFileProtocol && File.Exists(video.Path)) + { + for (var j = 0; j < _keyframeExtractors.Length; j++) + { + var extractor = _keyframeExtractors[j]; + // The cache decorator will make sure to save them in the data dir + if (extractor.TryExtractKeyframes(video.Path, out _)) + { + break; + } + } + } + + // Update progress + numComplete++; + double percent = (double)numComplete / numberOfVideos; + progress.Report(100 * percent); + } + + startIndex += Pagesize; + } + + progress.Report(100); + return Task.CompletedTask; + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>(); +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs new file mode 100644 index 000000000..320604e10 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; + +namespace Jellyfin.MediaEncoding.Keyframes.FfProbe; + +/// <summary> +/// FfProbe based keyframe extractor. +/// </summary> +public static class FfProbeKeyframeExtractor +{ + private const string DefaultArguments = "-v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\""; + + /// <summary> + /// Extracts the keyframes using the ffprobe executable at the specified path. + /// </summary> + /// <param name="ffProbePath">The path to the ffprobe executable.</param> + /// <param name="filePath">The file path.</param> + /// <returns>An instance of <see cref="KeyframeData"/>.</returns> + public static KeyframeData GetKeyframeData(string ffProbePath, string filePath) + { + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = ffProbePath, + Arguments = string.Format(CultureInfo.InvariantCulture, DefaultArguments, filePath), + + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + }, + EnableRaisingEvents = true + }; + + process.Start(); + + return ParseStream(process.StandardOutput); + } + + internal static KeyframeData ParseStream(StreamReader reader) + { + var keyframes = new List<long>(); + double streamDuration = 0; + double formatDuration = 0; + + while (!reader.EndOfStream) + { + var line = reader.ReadLine().AsSpan(); + if (line.IsEmpty) + { + continue; + } + + var firstComma = line.IndexOf(','); + var lineType = line[..firstComma]; + var rest = line[(firstComma + 1)..]; + if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase)) + { + if (rest.EndsWith(",K_")) + { + // Trim the flags from the packet line. Example line: packet,7169.079000,K_ + var keyframe = double.Parse(rest[..^3], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double. + keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond)); + } + } + else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase)) + { + if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult)) + { + streamDuration = streamDurationResult; + } + } + else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase)) + { + if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult)) + { + formatDuration = formatDurationResult; + } + } + } + + // Prefer the stream duration as it should be more accurate + var duration = streamDuration > 0 ? streamDuration : formatDuration; + + return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes); + } +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/FfTool/FfToolKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/FfTool/FfToolKeyframeExtractor.cs new file mode 100644 index 000000000..aaaca6fe1 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/FfTool/FfToolKeyframeExtractor.cs @@ -0,0 +1,17 @@ +using System; + +namespace Jellyfin.MediaEncoding.Keyframes.FfTool; + +/// <summary> +/// FfTool based keyframe extractor. +/// </summary> +public static class FfToolKeyframeExtractor +{ + /// <summary> + /// Extracts the keyframes using the fftool executable at the specified path. + /// </summary> + /// <param name="ffToolPath">The path to the fftool executable.</param> + /// <param name="filePath">The file path.</param> + /// <returns>An instance of <see cref="KeyframeData"/>.</returns> + public static KeyframeData GetKeyframeData(string ffToolPath, string filePath) => throw new NotImplementedException(); +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj new file mode 100644 index 000000000..31da11e24 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj @@ -0,0 +1,33 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="NEbml" Version="0.11.0" /> + </ItemGroup> + + <!-- Code Analyzers--> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" /> + </ItemGroup> + + <ItemGroup> + <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> + <_Parameter1>Jellyfin.MediaEncoding.Keyframes.Tests</_Parameter1> + </AssemblyAttribute> + </ItemGroup> + +</Project> diff --git a/src/Jellyfin.MediaEncoding.Keyframes/KeyframeData.cs b/src/Jellyfin.MediaEncoding.Keyframes/KeyframeData.cs new file mode 100644 index 000000000..06f9180e7 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/KeyframeData.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Jellyfin.MediaEncoding.Keyframes; + +/// <summary> +/// Keyframe information for a specific file. +/// </summary> +public class KeyframeData +{ + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeData"/> class. + /// </summary> + /// <param name="totalDuration">The total duration of the video stream in ticks.</param> + /// <param name="keyframeTicks">The video keyframes in ticks.</param> + public KeyframeData(long totalDuration, IReadOnlyList<long> keyframeTicks) + { + TotalDuration = totalDuration; + KeyframeTicks = keyframeTicks; + } + + /// <summary> + /// Gets the total duration of the stream in ticks. + /// </summary> + public long TotalDuration { get; } + + /// <summary> + /// Gets the keyframes in ticks. + /// </summary> + public IReadOnlyList<long> KeyframeTicks { get; } +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs new file mode 100644 index 000000000..fd170864b --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs @@ -0,0 +1,177 @@ +using System; +using System.Buffers.Binary; +using Jellyfin.MediaEncoding.Keyframes.Matroska.Models; +using NEbml.Core; + +namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions; + +/// <summary> +/// Extension methods for the <see cref="EbmlReader"/> class. +/// </summary> +internal static class EbmlReaderExtensions +{ + /// <summary> + /// Traverses the current container to find the element with <paramref name="identifier"/> identifier. + /// </summary> + /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param> + /// <param name="identifier">The element identifier.</param> + /// <returns>A value indicating whether the element was found.</returns> + internal static bool FindElement(this EbmlReader reader, ulong identifier) + { + while (reader.ReadNext()) + { + if (reader.ElementId.EncodedValue == identifier) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Reads the current position in the file as an unsigned integer converted from binary. + /// </summary> + /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param> + /// <returns>The unsigned integer.</returns> + internal static uint ReadUIntFromBinary(this EbmlReader reader) + { + var buffer = new byte[4]; + reader.ReadBinary(buffer, 0, 4); + return BinaryPrimitives.ReadUInt32BigEndian(buffer); + } + + /// <summary> + /// Reads from the start of the file to retrieve the SeekHead segment. + /// </summary> + /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param> + /// <returns>Instance of <see cref="SeekHead"/>.</returns> + internal static SeekHead ReadSeekHead(this EbmlReader reader) + { + reader = reader ?? throw new ArgumentNullException(nameof(reader)); + + if (reader.ElementPosition != 0) + { + throw new InvalidOperationException("File position must be at 0"); + } + + // Skip the header + if (!reader.FindElement(MatroskaConstants.SegmentContainer)) + { + throw new InvalidOperationException("Expected a segment container"); + } + + reader.EnterContainer(); + + long? tracksPosition = null; + long? cuesPosition = null; + long? infoPosition = null; + // The first element should be a SeekHead otherwise we'll have to search manually + if (!reader.FindElement(MatroskaConstants.SeekHead)) + { + throw new InvalidOperationException("Expected a SeekHead"); + } + + reader.EnterContainer(); + while (reader.FindElement(MatroskaConstants.Seek)) + { + reader.EnterContainer(); + reader.ReadNext(); + var type = (ulong)reader.ReadUIntFromBinary(); + switch (type) + { + case MatroskaConstants.Tracks: + reader.ReadNext(); + tracksPosition = (long)reader.ReadUInt(); + break; + case MatroskaConstants.Cues: + reader.ReadNext(); + cuesPosition = (long)reader.ReadUInt(); + break; + case MatroskaConstants.Info: + reader.ReadNext(); + infoPosition = (long)reader.ReadUInt(); + break; + } + + reader.LeaveContainer(); + + if (tracksPosition.HasValue && cuesPosition.HasValue && infoPosition.HasValue) + { + break; + } + } + + reader.LeaveContainer(); + + if (!tracksPosition.HasValue || !cuesPosition.HasValue || !infoPosition.HasValue) + { + throw new InvalidOperationException("SeekHead is missing or does not contain Info, Tracks and Cues positions. SeekHead referencing another SeekHead is not supported"); + } + + return new SeekHead(infoPosition.Value, tracksPosition.Value, cuesPosition.Value); + } + + /// <summary> + /// Reads from SegmentContainer to retrieve the Info segment. + /// </summary> + /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param> + /// <param name="position">The position of the info segment relative to the Segment container.</param> + /// <returns>Instance of <see cref="Info"/>.</returns> + internal static Info ReadInfo(this EbmlReader reader, long position) + { + reader.ReadAt(position); + + double? duration = null; + reader.EnterContainer(); + // Mandatory element + reader.FindElement(MatroskaConstants.TimestampScale); + var timestampScale = reader.ReadUInt(); + + if (reader.FindElement(MatroskaConstants.Duration)) + { + duration = reader.ReadFloat(); + } + + reader.LeaveContainer(); + + return new Info((long)timestampScale, duration); + } + + /// <summary> + /// Enters the Tracks segment and reads all tracks to find the specified type. + /// </summary> + /// <param name="reader">Instance of <see cref="EbmlReader"/>.</param> + /// <param name="tracksPosition">The relative position of the tracks segment.</param> + /// <param name="type">The track type identifier.</param> + /// <returns>The first track number with the specified type.</returns> + /// <exception cref="InvalidOperationException">Stream type is not found.</exception> + internal static ulong FindFirstTrackNumberByType(this EbmlReader reader, long tracksPosition, ulong type) + { + reader.ReadAt(tracksPosition); + + reader.EnterContainer(); + while (reader.FindElement(MatroskaConstants.TrackEntry)) + { + reader.EnterContainer(); + // Mandatory element + reader.FindElement(MatroskaConstants.TrackNumber); + var trackNumber = reader.ReadUInt(); + + // Mandatory element + reader.FindElement(MatroskaConstants.TrackType); + var trackType = reader.ReadUInt(); + + reader.LeaveContainer(); + if (trackType == MatroskaConstants.TrackTypeVideo) + { + reader.LeaveContainer(); + return trackNumber; + } + } + + reader.LeaveContainer(); + + throw new InvalidOperationException($"No stream with type {type} found"); + } +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs new file mode 100644 index 000000000..0d5c2f34f --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs @@ -0,0 +1,30 @@ +namespace Jellyfin.MediaEncoding.Keyframes.Matroska; + +/// <summary> +/// Constants for the Matroska identifiers. +/// </summary> +public static class MatroskaConstants +{ + internal const ulong SegmentContainer = 0x18538067; + + internal const ulong SeekHead = 0x114D9B74; + internal const ulong Seek = 0x4DBB; + + internal const ulong Info = 0x1549A966; + internal const ulong TimestampScale = 0x2AD7B1; + internal const ulong Duration = 0x4489; + + internal const ulong Tracks = 0x1654AE6B; + internal const ulong TrackEntry = 0xAE; + internal const ulong TrackNumber = 0xD7; + internal const ulong TrackType = 0x83; + + internal const ulong TrackTypeVideo = 0x1; + internal const ulong TrackTypeSubtitle = 0x11; + + internal const ulong Cues = 0x1C53BB6B; + internal const ulong CueTime = 0xB3; + internal const ulong CuePoint = 0xBB; + internal const ulong CueTrackPositions = 0xB7; + internal const ulong CuePointTrackNumber = 0xF7; +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs new file mode 100644 index 000000000..501b2bb17 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions; +using Jellyfin.MediaEncoding.Keyframes.Matroska.Models; +using NEbml.Core; + +namespace Jellyfin.MediaEncoding.Keyframes.Matroska; + +/// <summary> +/// The keyframe extractor for the matroska container. +/// </summary> +public static class MatroskaKeyframeExtractor +{ + /// <summary> + /// Extracts the keyframes in ticks (scaled using the container timestamp scale) from the matroska container. + /// </summary> + /// <param name="filePath">The file path.</param> + /// <returns>An instance of <see cref="KeyframeData"/>.</returns> + public static KeyframeData GetKeyframeData(string filePath) + { + using var stream = File.OpenRead(filePath); + using var reader = new EbmlReader(stream); + + var seekHead = reader.ReadSeekHead(); + // External lib does not support seeking backwards (yet) + Info info; + ulong videoTrackNumber; + if (seekHead.InfoPosition < seekHead.TracksPosition) + { + info = reader.ReadInfo(seekHead.InfoPosition); + videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo); + } + else + { + videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo); + info = reader.ReadInfo(seekHead.InfoPosition); + } + + var keyframes = new List<long>(); + reader.ReadAt(seekHead.CuesPosition); + reader.EnterContainer(); + + while (reader.FindElement(MatroskaConstants.CuePoint)) + { + reader.EnterContainer(); + ulong? trackNumber = null; + // Mandatory element + reader.FindElement(MatroskaConstants.CueTime); + var cueTime = reader.ReadUInt(); + + // Mandatory element + reader.FindElement(MatroskaConstants.CueTrackPositions); + reader.EnterContainer(); + if (reader.FindElement(MatroskaConstants.CuePointTrackNumber)) + { + trackNumber = reader.ReadUInt(); + } + + reader.LeaveContainer(); + + if (trackNumber == videoTrackNumber) + { + keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale)); + } + + reader.LeaveContainer(); + } + + reader.LeaveContainer(); + + var result = new KeyframeData(ScaleToTicks(info.Duration ?? 0, info.TimestampScale), keyframes); + return result; + } + + private static long ScaleToTicks(ulong unscaledValue, long timestampScale) + { + // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns + return (long)unscaledValue * timestampScale / 100; + } + + private static long ScaleToTicks(double unscaledValue, long timestampScale) + { + // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns + return Convert.ToInt64(unscaledValue * timestampScale / 100); + } +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs new file mode 100644 index 000000000..415d6da00 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs @@ -0,0 +1,28 @@ +namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models; + +/// <summary> +/// The matroska Info segment. +/// </summary> +internal class Info +{ + /// <summary> + /// Initializes a new instance of the <see cref="Info"/> class. + /// </summary> + /// <param name="timestampScale">The timestamp scale in nanoseconds.</param> + /// <param name="duration">The duration of the entire file.</param> + public Info(long timestampScale, double? duration) + { + TimestampScale = timestampScale; + Duration = duration; + } + + /// <summary> + /// Gets the timestamp scale in nanoseconds. + /// </summary> + public long TimestampScale { get; } + + /// <summary> + /// Gets the total duration of the file. + /// </summary> + public double? Duration { get; } +} diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs new file mode 100644 index 000000000..95e4fd882 --- /dev/null +++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs @@ -0,0 +1,35 @@ +namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models; + +/// <summary> +/// The matroska SeekHead segment. All positions are relative to the Segment container. +/// </summary> +internal class SeekHead +{ + /// <summary> + /// Initializes a new instance of the <see cref="SeekHead"/> class. + /// </summary> + /// <param name="infoPosition">The relative file position of the info segment.</param> + /// <param name="tracksPosition">The relative file position of the tracks segment.</param> + /// <param name="cuesPosition">The relative file position of the cues segment.</param> + public SeekHead(long infoPosition, long tracksPosition, long cuesPosition) + { + InfoPosition = infoPosition; + TracksPosition = tracksPosition; + CuesPosition = cuesPosition; + } + + /// <summary> + /// Gets relative file position of the info segment. + /// </summary> + public long InfoPosition { get; } + + /// <summary> + /// Gets the relative file position of the tracks segment. + /// </summary> + public long TracksPosition { get; } + + /// <summary> + /// Gets the relative file position of the cues segment. + /// </summary> + public long CuesPosition { get; } +} diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 6e0474dbf..e3237a8e3 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,17 +15,21 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index aaa6b5d90..1ad0f4e00 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -12,15 +12,19 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 981c7e9c9..abe7bee64 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -12,15 +12,19 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 6200a148b..cb1536274 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -7,15 +7,19 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 2a3918469..55125eb11 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -7,13 +7,13 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="coverlet.collector" Version="3.1.0"> + <PackageReference Include="coverlet.collector" Version="3.1.2"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> @@ -22,6 +22,10 @@ <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj new file mode 100644 index 000000000..c53dab6d8 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <IsPackable>false</IsPackable> + <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="coverlet.collector" Version="3.1.2"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> + <ProjectReference Include="..\..\src\Jellyfin.MediaEncoding.Keyframes\Jellyfin.MediaEncoding.Keyframes.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs new file mode 100644 index 000000000..79648c4f6 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs @@ -0,0 +1,101 @@ +using System; +using Jellyfin.MediaEncoding.Hls.Playlist; +using Jellyfin.MediaEncoding.Keyframes; +using Xunit; + +namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist +{ + public class DynamicHlsPlaylistGeneratorTests + { + [Theory] + [MemberData(nameof(ComputeSegments_Valid_Success_Data))] + public void ComputeSegments_Valid_Success(KeyframeData keyframeData, int desiredSegmentLengthMs, double[] segments) + { + Assert.Equal(segments, DynamicHlsPlaylistGenerator.ComputeSegments(keyframeData, desiredSegmentLengthMs)); + } + + [Fact] + public void ComputeSegments_InvalidDuration_ThrowsArgumentException() + { + var keyframeData = new KeyframeData(0, new[] { MsToTicks(10000) }); + Assert.Throws<ArgumentException>(() => DynamicHlsPlaylistGenerator.ComputeSegments(keyframeData, 6000)); + } + + [Theory] + [MemberData(nameof(ComputeEqualLengthSegments_Valid_Success_Data))] + public void ComputeEqualLengthSegments_Valid_Success(int desiredSegmentLengthMs, long totalRuntimeTicks, double[] segments) + { + Assert.Equal(segments, DynamicHlsPlaylistGenerator.ComputeEqualLengthSegments(desiredSegmentLengthMs, totalRuntimeTicks)); + } + + [Theory] + [InlineData(0, 1000000)] + [InlineData(1000, 0)] + public void ComputeEqualLengthSegments_Invalid_ThrowsInvalidOperationException(int desiredSegmentLengthMs, long totalRuntimeTicks) + { + Assert.Throws<InvalidOperationException>(() => DynamicHlsPlaylistGenerator.ComputeEqualLengthSegments(desiredSegmentLengthMs, totalRuntimeTicks)); + } + + [Theory] + [InlineData("testfile.mkv", new string[0], false)] + [InlineData("testfile.flv", new[] { ".mp4", ".mkv", ".ts" }, false)] + [InlineData("testfile.flv", new[] { ".mp4", ".mkv", ".ts", ".flv" }, true)] + [InlineData("/some/arbitrarily/long/path/testfile.mkv", new[] { "mkv" }, true)] + public void IsExtractionAllowedForFile_Valid_Success(string filePath, string[] allowedExtensions, bool isAllowed) + { + Assert.Equal(isAllowed, DynamicHlsPlaylistGenerator.IsExtractionAllowedForFile(filePath, allowedExtensions)); + } + + [Theory] + [InlineData("testfile", new[] { ".mp4" })] + public void IsExtractionAllowedForFile_Invalid_ReturnsFalse(string filePath, string[] allowedExtensions) + { + Assert.False(DynamicHlsPlaylistGenerator.IsExtractionAllowedForFile(filePath, allowedExtensions)); + } + + private static TheoryData<int, long, double[]> ComputeEqualLengthSegments_Valid_Success_Data() + { + var data = new TheoryData<int, long, double[]> + { + { 6000, MsToTicks(13000), new[] { 6.0, 6.0, 1.0 } }, + { 3000, MsToTicks(15000), new[] { 3.0, 3.0, 3.0, 3.0, 3.0 } }, + { 6000, MsToTicks(25000), new[] { 6.0, 6.0, 6.0, 6.0, 1.0 } }, + { 6000, MsToTicks(20123), new[] { 6.0, 6.0, 6.0, 2.123 } }, + { 6000, MsToTicks(1234), new[] { 1.234 } } + }; + + return data; + } + + private static TheoryData<KeyframeData, int, double[]> ComputeSegments_Valid_Success_Data() + { + var data = new TheoryData<KeyframeData, int, double[]> + { + { + new KeyframeData(MsToTicks(35000), new[] { 0, MsToTicks(10427), MsToTicks(20854), MsToTicks(31240) }), + 6000, + new[] { 10.427, 10.427, 10.386, 3.760 } + }, + { + new KeyframeData(MsToTicks(10000), new[] { 0, MsToTicks(1000), MsToTicks(2000), MsToTicks(3000), MsToTicks(4000), MsToTicks(5000) }), + 2000, + new[] { 2.0, 2.0, 6.0 } + }, + { + new KeyframeData(MsToTicks(10000), new[] { 0L }), + 6000, + new[] { 10.0 } + }, + { + new KeyframeData(MsToTicks(10000), Array.Empty<long>()), + 6000, + new[] { 10.0 } + } + }; + + return data; + } + + private static long MsToTicks(int value) => TimeSpan.FromMilliseconds(value).Ticks; + } +} diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/FfProbeKeyframeExtractorTests.cs b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/FfProbeKeyframeExtractorTests.cs new file mode 100644 index 000000000..e5debf8c0 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/FfProbeKeyframeExtractorTests.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Text.Json; +using Xunit; + +namespace Jellyfin.MediaEncoding.Keyframes.FfProbe +{ + public class FfProbeKeyframeExtractorTests + { + [Theory] + [InlineData("keyframes.txt", "keyframes_result.json")] + [InlineData("keyframes_streamduration.txt", "keyframes_streamduration_result.json")] + public void ParseStream_Valid_Success(string testDataFileName, string resultFileName) + { + var testDataPath = Path.Combine("FfProbe/Test Data", testDataFileName); + var resultPath = Path.Combine("FfProbe/Test Data", resultFileName); + using var resultFileStream = File.OpenRead(resultPath); + var expectedResult = JsonSerializer.Deserialize<KeyframeData>(resultFileStream)!; + + using var fileStream = File.OpenRead(testDataPath); + using var streamReader = new StreamReader(fileStream); + + var result = FfProbeKeyframeExtractor.ParseStream(streamReader); + + Assert.Equal(expectedResult.TotalDuration, result.TotalDuration); + Assert.Equal(expectedResult.KeyframeTicks, result.KeyframeTicks); + } + } +} diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes.txt b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes.txt new file mode 100644 index 000000000..a6d1b420c --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes.txt @@ -0,0 +1,16937 @@ +packet,0.000000,K_ +packet,0.375000,__ +packet,0.167000,__ +packet,0.042000,__ +packet,0.083000,__ +packet,0.125000,__ +packet,0.209000,__ +packet,0.250000,__ +packet,0.292000,__ +packet,0.334000,__ +packet,0.751000,__ +packet,0.542000,__ +packet,0.417000,__ +packet,0.459000,__ +packet,0.501000,__ +packet,0.584000,__ +packet,0.626000,__ +packet,0.667000,__ +packet,0.709000,__ +packet,0.959000,__ +packet,0.834000,__ +packet,0.792000,__ +packet,0.876000,__ +packet,0.918000,__ +packet,1.001000,__ +packet,1.168000,__ +packet,1.084000,__ +packet,1.043000,__ +packet,1.126000,__ +packet,1.335000,__ +packet,1.251000,__ +packet,1.210000,__ +packet,1.293000,__ +packet,1.460000,__ +packet,1.376000,__ +packet,1.418000,__ +packet,1.668000,__ +packet,1.543000,__ +packet,1.502000,__ +packet,1.585000,__ +packet,1.627000,__ +packet,1.793000,__ +packet,1.710000,__ +packet,1.752000,__ +packet,1.960000,__ +packet,1.877000,__ +packet,1.835000,__ +packet,1.919000,__ +packet,2.127000,__ +packet,2.044000,__ +packet,2.002000,__ +packet,2.085000,__ +packet,2.211000,__ +packet,2.169000,__ +packet,2.377000,__ +packet,2.294000,__ +packet,2.252000,__ +packet,2.336000,__ +packet,2.461000,__ +packet,2.419000,__ +packet,2.503000,__ +packet,2.544000,__ +packet,2.628000,__ +packet,2.586000,__ +packet,2.669000,__ +packet,2.711000,__ +packet,2.794000,__ +packet,2.753000,__ +packet,2.836000,__ +packet,2.878000,__ +packet,2.920000,__ +packet,2.961000,__ +packet,3.003000,__ +packet,3.045000,__ +packet,3.086000,__ +packet,3.128000,__ +packet,3.170000,__ +packet,3.212000,__ +packet,3.337000,__ +packet,3.253000,__ +packet,3.295000,__ +packet,3.420000,__ +packet,3.378000,__ +packet,3.462000,__ +packet,3.504000,__ +packet,3.545000,__ +packet,3.587000,__ +packet,3.629000,__ +packet,3.670000,__ +packet,3.712000,__ +packet,3.754000,__ +packet,3.837000,__ +packet,3.795000,__ +packet,3.879000,__ +packet,3.921000,__ +packet,3.962000,__ +packet,4.004000,__ +packet,4.046000,__ +packet,4.087000,__ +packet,4.129000,__ +packet,4.171000,__ +packet,4.213000,__ +packet,4.254000,__ +packet,4.338000,__ +packet,4.296000,__ +packet,4.421000,__ +packet,4.379000,__ +packet,4.505000,__ +packet,4.463000,__ +packet,4.588000,__ +packet,4.546000,__ +packet,4.630000,__ +packet,4.671000,__ +packet,4.713000,__ +packet,4.838000,__ +packet,4.755000,__ +packet,4.796000,__ +packet,4.880000,__ +packet,4.922000,__ +packet,4.963000,__ +packet,5.005000,__ +packet,5.047000,__ +packet,5.088000,__ +packet,5.130000,__ +packet,5.172000,__ +packet,5.214000,__ +packet,5.297000,__ +packet,5.255000,__ +packet,5.339000,__ +packet,5.380000,__ +packet,5.422000,__ +packet,5.464000,__ +packet,5.506000,__ +packet,5.547000,__ +packet,5.589000,__ +packet,5.631000,__ +packet,5.756000,__ +packet,5.672000,__ +packet,5.714000,__ +packet,5.923000,__ +packet,5.839000,__ +packet,5.797000,__ +packet,5.881000,__ +packet,6.089000,__ +packet,6.006000,__ +packet,5.964000,__ +packet,6.048000,__ +packet,6.256000,__ +packet,6.173000,__ +packet,6.131000,__ +packet,6.215000,__ +packet,6.381000,__ +packet,6.298000,__ +packet,6.340000,__ +packet,6.465000,__ +packet,6.423000,__ +packet,6.590000,__ +packet,6.507000,__ +packet,6.548000,__ +packet,6.632000,__ +packet,6.715000,__ +packet,6.673000,__ +packet,6.757000,__ +packet,6.882000,__ +packet,6.798000,__ +packet,6.840000,__ +packet,6.924000,__ +packet,6.965000,__ +packet,7.007000,__ +packet,7.049000,__ +packet,7.090000,__ +packet,7.132000,__ +packet,7.174000,__ +packet,7.216000,__ +packet,7.257000,__ +packet,7.299000,__ +packet,7.341000,__ +packet,7.382000,__ +packet,7.424000,__ +packet,7.466000,__ +packet,7.508000,__ +packet,7.549000,__ +packet,7.591000,__ +packet,7.633000,__ +packet,7.674000,__ +packet,7.716000,__ +packet,7.758000,__ +packet,7.799000,__ +packet,7.841000,__ +packet,7.966000,__ +packet,7.883000,__ +packet,7.925000,__ +packet,8.050000,__ +packet,8.008000,__ +packet,8.091000,__ +packet,8.133000,__ +packet,8.217000,__ +packet,8.175000,__ +packet,8.258000,__ +packet,8.300000,__ +packet,8.342000,__ +packet,8.383000,__ +packet,8.425000,__ +packet,8.467000,__ +packet,8.509000,__ +packet,8.550000,__ +packet,8.592000,__ +packet,8.634000,__ +packet,8.717000,__ +packet,8.675000,__ +packet,8.759000,__ +packet,8.800000,__ +packet,8.842000,__ +packet,8.884000,__ +packet,8.926000,__ +packet,8.967000,__ +packet,9.009000,__ +packet,9.051000,__ +packet,9.092000,__ +packet,9.134000,__ +packet,9.176000,__ +packet,9.218000,__ +packet,9.259000,__ +packet,9.301000,__ +packet,9.343000,__ +packet,9.384000,__ +packet,9.426000,__ +packet,9.468000,__ +packet,9.510000,__ +packet,9.551000,__ +packet,9.593000,__ +packet,9.635000,__ +packet,9.718000,__ +packet,9.676000,__ +packet,9.801000,__ +packet,9.760000,__ +packet,9.927000,__ +packet,9.843000,__ +packet,9.885000,__ +packet,10.010000,__ +packet,9.968000,__ +packet,10.052000,__ +packet,10.093000,__ +packet,10.177000,__ +packet,10.135000,__ +packet,10.260000,__ +packet,10.219000,__ +packet,10.302000,__ +packet,10.344000,__ +packet,10.385000,K_ +packet,10.427000,__ +packet,10.469000,__ +packet,10.511000,__ +packet,10.552000,__ +packet,10.594000,__ +packet,10.636000,__ +packet,10.677000,__ +packet,10.719000,__ +packet,10.761000,__ +packet,10.802000,__ +packet,10.844000,__ +packet,10.886000,__ +packet,10.928000,__ +packet,10.969000,__ +packet,11.011000,__ +packet,11.053000,__ +packet,11.094000,__ +packet,11.220000,__ +packet,11.136000,__ +packet,11.178000,__ +packet,11.345000,__ +packet,11.261000,__ +packet,11.303000,__ +packet,11.553000,__ +packet,11.428000,__ +packet,11.386000,__ +packet,11.470000,__ +packet,11.512000,__ +packet,11.637000,__ +packet,11.595000,__ +packet,11.762000,__ +packet,11.678000,__ +packet,11.720000,__ +packet,11.845000,__ +packet,11.803000,__ +packet,11.887000,__ +packet,11.929000,__ +packet,11.970000,__ +packet,12.012000,__ +packet,12.054000,__ +packet,12.095000,__ +packet,12.137000,__ +packet,12.179000,__ +packet,12.262000,__ +packet,12.221000,__ +packet,12.304000,__ +packet,12.346000,__ +packet,12.387000,__ +packet,12.429000,__ +packet,12.471000,__ +packet,12.513000,__ +packet,12.554000,__ +packet,12.596000,__ +packet,12.846000,__ +packet,12.721000,__ +packet,12.638000,__ +packet,12.679000,__ +packet,12.763000,__ +packet,12.804000,__ +packet,12.971000,__ +packet,12.888000,__ +packet,12.930000,__ +packet,13.180000,__ +packet,13.055000,__ +packet,13.013000,__ +packet,13.096000,__ +packet,13.138000,__ +packet,13.347000,__ +packet,13.263000,__ +packet,13.222000,__ +packet,13.305000,__ +packet,13.388000,K_ +packet,13.472000,__ +packet,13.430000,__ +packet,13.722000,__ +packet,13.597000,__ +packet,13.514000,__ +packet,13.555000,__ +packet,13.639000,__ +packet,13.680000,__ +packet,13.847000,__ +packet,13.764000,__ +packet,13.805000,__ +packet,13.889000,__ +packet,13.972000,__ +packet,13.931000,__ +packet,14.139000,__ +packet,14.056000,__ +packet,14.014000,__ +packet,14.097000,__ +packet,14.389000,__ +packet,14.264000,__ +packet,14.181000,__ +packet,14.223000,__ +packet,14.306000,__ +packet,14.348000,__ +packet,14.473000,__ +packet,14.431000,__ +packet,14.515000,K_ +packet,14.681000,__ +packet,14.598000,__ +packet,14.556000,__ +packet,14.640000,__ +packet,14.765000,__ +packet,14.723000,__ +packet,14.806000,__ +packet,14.848000,__ +packet,14.890000,__ +packet,14.932000,__ +packet,14.973000,__ +packet,15.015000,__ +packet,15.057000,__ +packet,15.098000,__ +packet,15.140000,__ +packet,15.224000,__ +packet,15.182000,__ +packet,15.265000,__ +packet,15.432000,__ +packet,15.349000,__ +packet,15.307000,__ +packet,15.390000,__ +packet,15.557000,__ +packet,15.474000,__ +packet,15.516000,__ +packet,15.599000,__ +packet,15.641000,__ +packet,15.724000,__ +packet,15.682000,__ +packet,15.766000,__ +packet,15.891000,__ +packet,15.807000,__ +packet,15.849000,__ +packet,15.974000,__ +packet,15.933000,__ +packet,16.016000,__ +packet,16.058000,__ +packet,16.099000,__ +packet,16.225000,__ +packet,16.141000,__ +packet,16.183000,__ +packet,16.266000,__ +packet,16.308000,__ +packet,16.350000,__ +packet,16.391000,__ +packet,16.433000,__ +packet,16.475000,__ +packet,16.517000,__ +packet,16.558000,K_ +packet,16.683000,__ +packet,16.600000,__ +packet,16.642000,__ +packet,16.808000,__ +packet,16.725000,__ +packet,16.767000,__ +packet,16.934000,__ +packet,16.850000,__ +packet,16.892000,__ +packet,17.017000,__ +packet,16.975000,__ +packet,17.100000,__ +packet,17.059000,__ +packet,17.267000,__ +packet,17.184000,__ +packet,17.142000,__ +packet,17.226000,__ +packet,17.434000,__ +packet,17.351000,__ +packet,17.309000,__ +packet,17.392000,__ +packet,17.518000,__ +packet,17.476000,__ +packet,17.684000,__ +packet,17.601000,__ +packet,17.559000,__ +packet,17.643000,__ +packet,17.851000,__ +packet,17.768000,__ +packet,17.726000,__ +packet,17.809000,__ +packet,18.018000,__ +packet,17.935000,__ +packet,17.893000,__ +packet,17.976000,__ +packet,18.101000,__ +packet,18.060000,__ +packet,18.185000,__ +packet,18.143000,__ +packet,18.352000,__ +packet,18.268000,__ +packet,18.227000,__ +packet,18.310000,__ +packet,18.519000,__ +packet,18.435000,__ +packet,18.393000,__ +packet,18.477000,__ +packet,18.602000,__ +packet,18.560000,__ +packet,18.644000,K_ +packet,18.769000,__ +packet,18.685000,__ +packet,18.727000,__ +packet,19.019000,__ +packet,18.894000,__ +packet,18.810000,__ +packet,18.852000,__ +packet,18.936000,__ +packet,18.977000,__ +packet,19.228000,__ +packet,19.102000,__ +packet,19.061000,__ +packet,19.144000,__ +packet,19.186000,__ +packet,19.603000,__ +packet,19.394000,__ +packet,19.269000,__ +packet,19.311000,__ +packet,19.353000,__ +packet,19.436000,__ +packet,19.478000,__ +packet,19.520000,__ +packet,19.561000,__ +packet,19.645000,K_ +packet,19.770000,__ +packet,19.686000,__ +packet,19.728000,__ +packet,19.937000,__ +packet,19.853000,__ +packet,19.811000,__ +packet,19.895000,__ +packet,20.187000,__ +packet,20.062000,__ +packet,19.978000,__ +packet,20.020000,__ +packet,20.103000,__ +packet,20.145000,__ +packet,20.354000,__ +packet,20.270000,__ +packet,20.229000,__ +packet,20.312000,__ +packet,20.646000,__ +packet,20.479000,__ +packet,20.395000,__ +packet,20.437000,__ +packet,20.521000,__ +packet,20.562000,__ +packet,20.604000,__ +packet,20.938000,__ +packet,20.771000,__ +packet,20.687000,__ +packet,20.729000,__ +packet,20.812000,__ +packet,20.854000,__ +packet,20.896000,__ +packet,20.979000,K_ +packet,21.146000,__ +packet,21.063000,__ +packet,21.021000,__ +packet,21.104000,__ +packet,21.271000,__ +packet,21.188000,__ +packet,21.230000,__ +packet,21.438000,__ +packet,21.355000,__ +packet,21.313000,__ +packet,21.396000,__ +packet,21.563000,__ +packet,21.480000,__ +packet,21.522000,__ +packet,21.688000,__ +packet,21.605000,__ +packet,21.647000,__ +packet,21.855000,__ +packet,21.772000,__ +packet,21.730000,__ +packet,21.813000,__ +packet,21.980000,__ +packet,21.897000,__ +packet,21.939000,__ +packet,22.189000,__ +packet,22.064000,__ +packet,22.022000,__ +packet,22.105000,__ +packet,22.147000,__ +packet,22.356000,__ +packet,22.272000,__ +packet,22.231000,__ +packet,22.314000,__ +packet,22.523000,__ +packet,22.439000,__ +packet,22.397000,__ +packet,22.481000,__ +packet,22.731000,__ +packet,22.606000,__ +packet,22.564000,__ +packet,22.648000,__ +packet,22.689000,__ +packet,22.981000,__ +packet,22.856000,__ +packet,22.773000,__ +packet,22.814000,__ +packet,22.898000,__ +packet,22.940000,__ +packet,23.232000,__ +packet,23.106000,__ +packet,23.023000,__ +packet,23.065000,__ +packet,23.148000,__ +packet,23.190000,__ +packet,23.315000,__ +packet,23.273000,__ +packet,23.398000,__ +packet,23.357000,__ +packet,23.565000,__ +packet,23.482000,__ +packet,23.440000,__ +packet,23.524000,__ +packet,23.607000,__ +packet,23.690000,__ +packet,23.649000,__ +packet,23.774000,__ +packet,23.732000,__ +packet,23.941000,__ +packet,23.857000,__ +packet,23.815000,__ +packet,23.899000,__ +packet,23.982000,__ +packet,24.024000,__ +packet,24.066000,__ +packet,24.107000,__ +packet,24.233000,__ +packet,24.149000,__ +packet,24.191000,__ +packet,24.358000,__ +packet,24.274000,__ +packet,24.316000,__ +packet,24.525000,__ +packet,24.441000,__ +packet,24.399000,__ +packet,24.483000,__ +packet,24.650000,__ +packet,24.566000,__ +packet,24.608000,__ +packet,24.775000,__ +packet,24.691000,__ +packet,24.733000,__ +packet,24.983000,__ +packet,24.858000,__ +packet,24.816000,__ +packet,24.900000,__ +packet,24.942000,__ +packet,25.359000,__ +packet,25.150000,__ +packet,25.025000,__ +packet,25.067000,__ +packet,25.108000,__ +packet,25.192000,__ +packet,25.234000,__ +packet,25.275000,__ +packet,25.317000,__ +packet,25.651000,__ +packet,25.484000,__ +packet,25.400000,__ +packet,25.442000,__ +packet,25.526000,__ +packet,25.567000,__ +packet,25.609000,__ +packet,25.734000,__ +packet,25.692000,__ +packet,25.776000,__ +packet,25.943000,__ +packet,25.859000,__ +packet,25.817000,__ +packet,25.901000,__ +packet,26.068000,__ +packet,25.984000,__ +packet,26.026000,__ +packet,26.443000,__ +packet,26.235000,__ +packet,26.109000,__ +packet,26.151000,__ +packet,26.193000,__ +packet,26.276000,__ +packet,26.318000,__ +packet,26.360000,__ +packet,26.401000,__ +packet,26.693000,__ +packet,26.568000,__ +packet,26.485000,__ +packet,26.527000,__ +packet,26.610000,__ +packet,26.652000,__ +packet,26.944000,__ +packet,26.818000,__ +packet,26.735000,__ +packet,26.777000,__ +packet,26.860000,__ +packet,26.902000,__ +packet,27.319000,__ +packet,27.110000,__ +packet,26.985000,__ +packet,27.027000,__ +packet,27.069000,__ +packet,27.152000,__ +packet,27.194000,__ +packet,27.236000,__ +packet,27.277000,__ +packet,27.694000,__ +packet,27.486000,__ +packet,27.361000,__ +packet,27.402000,__ +packet,27.444000,__ +packet,27.528000,__ +packet,27.569000,__ +packet,27.611000,__ +packet,27.653000,__ +packet,27.736000,__ +packet,28.111000,__ +packet,27.903000,__ +packet,27.778000,__ +packet,27.819000,__ +packet,27.861000,__ +packet,27.945000,__ +packet,27.986000,__ +packet,28.028000,__ +packet,28.070000,__ +packet,28.445000,__ +packet,28.278000,__ +packet,28.153000,__ +packet,28.195000,__ +packet,28.237000,__ +packet,28.320000,__ +packet,28.362000,__ +packet,28.403000,__ +packet,28.695000,__ +packet,28.570000,__ +packet,28.487000,__ +packet,28.529000,__ +packet,28.612000,__ +packet,28.654000,__ +packet,28.987000,__ +packet,28.820000,__ +packet,28.737000,__ +packet,28.779000,__ +packet,28.862000,__ +packet,28.904000,__ +packet,28.946000,__ +packet,29.363000,__ +packet,29.154000,__ +packet,29.029000,__ +packet,29.071000,__ +packet,29.112000,__ +packet,29.196000,__ +packet,29.238000,__ +packet,29.279000,__ +packet,29.321000,__ +packet,29.738000,__ +packet,29.530000,__ +packet,29.404000,__ +packet,29.446000,__ +packet,29.488000,__ +packet,29.571000,__ +packet,29.613000,__ +packet,29.655000,__ +packet,29.696000,__ +packet,29.863000,__ +packet,29.780000,__ +packet,29.821000,__ +packet,30.113000,__ +packet,29.988000,__ +packet,29.905000,__ +packet,29.947000,__ +packet,30.030000,__ +packet,30.072000,__ +packet,30.280000,__ +packet,30.197000,__ +packet,30.155000,__ +packet,30.239000,__ +packet,30.572000,__ +packet,30.405000,__ +packet,30.322000,__ +packet,30.364000,__ +packet,30.447000,__ +packet,30.489000,__ +packet,30.531000,__ +packet,30.656000,__ +packet,30.614000,__ +packet,30.989000,__ +packet,30.822000,__ +packet,30.697000,__ +packet,30.739000,__ +packet,30.781000,__ +packet,30.864000,__ +packet,30.906000,__ +packet,30.948000,__ +packet,31.073000,__ +packet,31.031000,__ +packet,31.365000,__ +packet,31.198000,__ +packet,31.114000,__ +packet,31.156000,__ +packet,31.240000,__ +packet,31.281000,__ +packet,31.323000,__ +packet,31.406000,K_ +packet,31.573000,__ +packet,31.490000,__ +packet,31.448000,__ +packet,31.532000,__ +packet,31.698000,__ +packet,31.615000,__ +packet,31.657000,__ +packet,32.074000,__ +packet,31.865000,__ +packet,31.740000,__ +packet,31.782000,__ +packet,31.823000,__ +packet,31.907000,__ +packet,31.949000,__ +packet,31.990000,__ +packet,32.032000,__ +packet,32.449000,__ +packet,32.241000,__ +packet,32.115000,__ +packet,32.157000,__ +packet,32.199000,__ +packet,32.282000,__ +packet,32.324000,__ +packet,32.366000,__ +packet,32.407000,__ +packet,32.658000,__ +packet,32.533000,__ +packet,32.491000,__ +packet,32.574000,__ +packet,32.616000,__ +packet,32.699000,K_ +packet,32.908000,__ +packet,32.783000,__ +packet,32.741000,__ +packet,32.824000,__ +packet,32.866000,__ +packet,33.116000,__ +packet,32.991000,__ +packet,32.950000,__ +packet,33.033000,__ +packet,33.075000,__ +packet,33.367000,__ +packet,33.242000,__ +packet,33.158000,__ +packet,33.200000,__ +packet,33.283000,__ +packet,33.325000,__ +packet,33.617000,__ +packet,33.492000,__ +packet,33.408000,__ +packet,33.450000,__ +packet,33.534000,__ +packet,33.575000,__ +packet,33.825000,__ +packet,33.700000,__ +packet,33.659000,__ +packet,33.742000,__ +packet,33.784000,__ +packet,34.117000,__ +packet,33.951000,__ +packet,33.867000,__ +packet,33.909000,__ +packet,33.992000,__ +packet,34.034000,__ +packet,34.076000,__ +packet,34.368000,__ +packet,34.243000,__ +packet,34.159000,__ +packet,34.201000,__ +packet,34.284000,__ +packet,34.326000,__ +packet,34.576000,__ +packet,34.451000,__ +packet,34.409000,__ +packet,34.493000,__ +packet,34.535000,__ +packet,34.701000,__ +packet,34.618000,__ +packet,34.660000,__ +packet,34.952000,__ +packet,34.826000,__ +packet,34.743000,__ +packet,34.785000,__ +packet,34.868000,__ +packet,34.910000,__ +packet,35.118000,__ +packet,35.035000,__ +packet,34.993000,__ +packet,35.077000,__ +packet,35.410000,__ +packet,35.244000,__ +packet,35.160000,__ +packet,35.202000,__ +packet,35.285000,__ +packet,35.327000,__ +packet,35.369000,__ +packet,35.494000,__ +packet,35.452000,__ +packet,35.744000,__ +packet,35.619000,__ +packet,35.536000,__ +packet,35.577000,__ +packet,35.661000,__ +packet,35.702000,__ +packet,35.911000,__ +packet,35.827000,__ +packet,35.786000,__ +packet,35.869000,__ +packet,36.078000,__ +packet,35.994000,__ +packet,35.953000,__ +packet,36.036000,__ +packet,36.245000,__ +packet,36.161000,__ +packet,36.119000,__ +packet,36.203000,__ +packet,36.370000,__ +packet,36.286000,__ +packet,36.328000,__ +packet,36.537000,__ +packet,36.453000,__ +packet,36.411000,__ +packet,36.495000,__ +packet,36.620000,__ +packet,36.578000,__ +packet,36.662000,__ +packet,36.787000,__ +packet,36.703000,__ +packet,36.745000,__ +packet,36.995000,__ +packet,36.870000,__ +packet,36.828000,__ +packet,36.912000,__ +packet,36.954000,__ +packet,37.162000,__ +packet,37.079000,__ +packet,37.037000,__ +packet,37.120000,__ +packet,37.329000,__ +packet,37.246000,__ +packet,37.204000,__ +packet,37.287000,__ +packet,37.454000,__ +packet,37.371000,__ +packet,37.412000,__ +packet,37.579000,__ +packet,37.496000,__ +packet,37.538000,__ +packet,37.704000,__ +packet,37.621000,__ +packet,37.663000,__ +packet,37.955000,__ +packet,37.829000,__ +packet,37.746000,__ +packet,37.788000,__ +packet,37.871000,__ +packet,37.913000,__ +packet,38.163000,__ +packet,38.038000,__ +packet,37.996000,__ +packet,38.080000,__ +packet,38.121000,__ +packet,38.330000,__ +packet,38.247000,__ +packet,38.205000,__ +packet,38.288000,__ +packet,38.455000,__ +packet,38.372000,__ +packet,38.413000,__ +packet,38.539000,__ +packet,38.497000,__ +packet,38.705000,__ +packet,38.622000,__ +packet,38.580000,__ +packet,38.664000,__ +packet,38.914000,__ +packet,38.789000,__ +packet,38.747000,__ +packet,38.830000,__ +packet,38.872000,__ +packet,39.081000,__ +packet,38.997000,__ +packet,38.956000,__ +packet,39.039000,__ +packet,39.414000,__ +packet,39.248000,__ +packet,39.122000,__ +packet,39.164000,__ +packet,39.206000,__ +packet,39.289000,__ +packet,39.331000,__ +packet,39.373000,__ +packet,39.540000,__ +packet,39.456000,__ +packet,39.498000,__ +packet,39.581000,__ +packet,39.623000,K_ +packet,39.831000,__ +packet,39.706000,__ +packet,39.665000,__ +packet,39.748000,__ +packet,39.790000,__ +packet,39.998000,__ +packet,39.915000,__ +packet,39.873000,__ +packet,39.957000,__ +packet,40.165000,__ +packet,40.082000,__ +packet,40.040000,__ +packet,40.123000,__ +packet,40.290000,__ +packet,40.207000,__ +packet,40.249000,__ +packet,40.499000,__ +packet,40.374000,__ +packet,40.332000,__ +packet,40.415000,__ +packet,40.457000,__ +packet,40.666000,__ +packet,40.582000,__ +packet,40.541000,__ +packet,40.624000,__ +packet,40.707000,K_ +packet,40.832000,__ +packet,40.749000,__ +packet,40.791000,__ +packet,40.999000,__ +packet,40.916000,__ +packet,40.874000,__ +packet,40.958000,__ +packet,41.166000,__ +packet,41.083000,__ +packet,41.041000,__ +packet,41.124000,__ +packet,41.333000,__ +packet,41.250000,__ +packet,41.208000,__ +packet,41.291000,__ +packet,41.500000,__ +packet,41.416000,__ +packet,41.375000,__ +packet,41.458000,__ +packet,41.542000,__ +packet,41.583000,__ +packet,41.792000,__ +packet,41.667000,__ +packet,41.625000,__ +packet,41.708000,__ +packet,41.750000,__ +packet,41.917000,__ +packet,41.833000,__ +packet,41.875000,__ +packet,42.125000,__ +packet,42.000000,__ +packet,41.959000,__ +packet,42.042000,__ +packet,42.084000,__ +packet,42.251000,__ +packet,42.167000,__ +packet,42.209000,__ +packet,42.459000,__ +packet,42.334000,__ +packet,42.292000,__ +packet,42.376000,__ +packet,42.417000,__ +packet,42.626000,__ +packet,42.543000,__ +packet,42.501000,__ +packet,42.584000,__ +packet,42.751000,__ +packet,42.668000,__ +packet,42.709000,__ +packet,42.918000,__ +packet,42.834000,__ +packet,42.793000,__ +packet,42.876000,__ +packet,43.126000,__ +packet,43.001000,__ +packet,42.960000,__ +packet,43.043000,__ +packet,43.085000,__ +packet,43.210000,__ +packet,43.168000,__ +packet,43.252000,K_ +packet,43.418000,__ +packet,43.335000,__ +packet,43.293000,__ +packet,43.377000,__ +packet,43.544000,__ +packet,43.460000,__ +packet,43.502000,__ +packet,43.710000,__ +packet,43.627000,__ +packet,43.585000,__ +packet,43.669000,__ +packet,43.835000,__ +packet,43.752000,__ +packet,43.794000,__ +packet,44.002000,__ +packet,43.919000,__ +packet,43.877000,__ +packet,43.961000,__ +packet,44.169000,__ +packet,44.086000,__ +packet,44.044000,__ +packet,44.127000,__ +packet,44.336000,__ +packet,44.253000,__ +packet,44.211000,__ +packet,44.294000,__ +packet,44.503000,__ +packet,44.419000,__ +packet,44.378000,__ +packet,44.461000,__ +packet,44.670000,__ +packet,44.586000,__ +packet,44.545000,__ +packet,44.628000,__ +packet,44.878000,__ +packet,44.753000,__ +packet,44.711000,__ +packet,44.795000,__ +packet,44.836000,__ +packet,44.962000,__ +packet,44.920000,__ +packet,45.128000,__ +packet,45.045000,__ +packet,45.003000,__ +packet,45.087000,__ +packet,45.212000,__ +packet,45.170000,__ +packet,45.295000,__ +packet,45.254000,__ +packet,45.379000,__ +packet,45.337000,__ +packet,45.546000,__ +packet,45.462000,__ +packet,45.420000,__ +packet,45.504000,__ +packet,45.671000,__ +packet,45.587000,__ +packet,45.629000,__ +packet,45.837000,__ +packet,45.754000,__ +packet,45.712000,__ +packet,45.796000,__ +packet,46.004000,__ +packet,45.921000,__ +packet,45.879000,__ +packet,45.963000,__ +packet,46.088000,__ +packet,46.046000,__ +packet,46.171000,__ +packet,46.129000,__ +packet,46.296000,__ +packet,46.213000,__ +packet,46.255000,__ +packet,46.380000,__ +packet,46.338000,__ +packet,46.421000,__ +packet,46.505000,__ +packet,46.463000,__ +packet,46.672000,__ +packet,46.588000,__ +packet,46.547000,__ +packet,46.630000,__ +packet,46.880000,__ +packet,46.755000,__ +packet,46.713000,__ +packet,46.797000,__ +packet,46.838000,__ +packet,47.005000,__ +packet,46.922000,__ +packet,46.964000,__ +packet,47.089000,__ +packet,47.047000,__ +packet,47.297000,__ +packet,47.172000,__ +packet,47.130000,__ +packet,47.214000,__ +packet,47.256000,__ +packet,47.464000,__ +packet,47.381000,__ +packet,47.339000,__ +packet,47.422000,__ +packet,47.589000,__ +packet,47.506000,__ +packet,47.548000,__ +packet,47.631000,K_ +packet,47.798000,__ +packet,47.714000,__ +packet,47.673000,__ +packet,47.756000,__ +packet,48.006000,__ +packet,47.881000,__ +packet,47.839000,__ +packet,47.923000,__ +packet,47.965000,__ +packet,48.131000,__ +packet,48.048000,__ +packet,48.090000,__ +packet,48.340000,__ +packet,48.215000,__ +packet,48.173000,__ +packet,48.257000,__ +packet,48.298000,__ +packet,48.507000,__ +packet,48.423000,__ +packet,48.382000,__ +packet,48.465000,__ +packet,48.632000,__ +packet,48.549000,__ +packet,48.590000,__ +packet,48.715000,__ +packet,48.674000,__ +packet,48.882000,__ +packet,48.799000,__ +packet,48.757000,__ +packet,48.840000,__ +packet,49.049000,__ +packet,48.966000,__ +packet,48.924000,__ +packet,49.007000,__ +packet,49.216000,__ +packet,49.132000,__ +packet,49.091000,__ +packet,49.174000,__ +packet,49.424000,__ +packet,49.299000,__ +packet,49.258000,__ +packet,49.341000,__ +packet,49.383000,__ +packet,49.591000,__ +packet,49.508000,__ +packet,49.466000,__ +packet,49.550000,__ +packet,49.675000,__ +packet,49.633000,__ +packet,49.841000,__ +packet,49.758000,__ +packet,49.716000,__ +packet,49.800000,__ +packet,50.008000,__ +packet,49.925000,__ +packet,49.883000,__ +packet,49.967000,__ +packet,50.133000,__ +packet,50.050000,__ +packet,50.092000,__ +packet,50.217000,__ +packet,50.175000,__ +packet,50.425000,__ +packet,50.300000,__ +packet,50.259000,__ +packet,50.342000,__ +packet,50.384000,__ +packet,50.592000,__ +packet,50.509000,__ +packet,50.467000,__ +packet,50.551000,__ +packet,50.717000,__ +packet,50.634000,__ +packet,50.676000,__ +packet,50.884000,__ +packet,50.801000,__ +packet,50.759000,__ +packet,50.842000,__ +packet,51.051000,__ +packet,50.968000,__ +packet,50.926000,__ +packet,51.009000,__ +packet,51.218000,__ +packet,51.134000,__ +packet,51.093000,__ +packet,51.176000,__ +packet,51.426000,__ +packet,51.301000,__ +packet,51.260000,__ +packet,51.343000,__ +packet,51.385000,__ +packet,51.593000,__ +packet,51.510000,__ +packet,51.468000,__ +packet,51.552000,__ +packet,51.760000,__ +packet,51.677000,__ +packet,51.635000,__ +packet,51.718000,__ +packet,51.885000,__ +packet,51.802000,__ +packet,51.843000,__ +packet,52.094000,__ +packet,51.969000,__ +packet,51.927000,__ +packet,52.010000,__ +packet,52.052000,__ +packet,52.261000,__ +packet,52.177000,__ +packet,52.135000,__ +packet,52.219000,__ +packet,52.302000,K_ +packet,52.469000,__ +packet,52.386000,__ +packet,52.344000,__ +packet,52.427000,__ +packet,52.594000,__ +packet,52.511000,__ +packet,52.553000,__ +packet,52.761000,__ +packet,52.678000,__ +packet,52.636000,__ +packet,52.719000,__ +packet,52.844000,__ +packet,52.803000,__ +packet,52.928000,__ +packet,52.886000,__ +packet,53.095000,__ +packet,53.011000,__ +packet,52.970000,__ +packet,53.053000,__ +packet,53.262000,__ +packet,53.178000,__ +packet,53.136000,__ +packet,53.220000,__ +packet,53.470000,__ +packet,53.345000,__ +packet,53.303000,__ +packet,53.387000,__ +packet,53.428000,__ +packet,53.512000,__ +packet,53.554000,K_ +packet,53.637000,__ +packet,53.595000,__ +packet,53.887000,__ +packet,53.762000,__ +packet,53.679000,__ +packet,53.720000,__ +packet,53.804000,__ +packet,53.845000,__ +packet,54.096000,__ +packet,53.971000,__ +packet,53.929000,__ +packet,54.012000,__ +packet,54.054000,__ +packet,54.346000,__ +packet,54.221000,__ +packet,54.137000,__ +packet,54.179000,__ +packet,54.263000,__ +packet,54.304000,__ +packet,54.513000,__ +packet,54.429000,__ +packet,54.388000,__ +packet,54.471000,__ +packet,54.763000,__ +packet,54.638000,__ +packet,54.555000,__ +packet,54.596000,__ +packet,54.680000,__ +packet,54.721000,__ +packet,54.930000,__ +packet,54.846000,__ +packet,54.805000,__ +packet,54.888000,__ +packet,55.013000,__ +packet,54.972000,__ +packet,55.055000,K_ +packet,55.180000,__ +packet,55.097000,__ +packet,55.138000,__ +packet,55.305000,__ +packet,55.222000,__ +packet,55.264000,__ +packet,55.430000,__ +packet,55.347000,__ +packet,55.389000,__ +packet,55.556000,__ +packet,55.472000,__ +packet,55.514000,__ +packet,55.722000,__ +packet,55.639000,__ +packet,55.597000,__ +packet,55.681000,__ +packet,56.098000,__ +packet,55.889000,__ +packet,55.764000,__ +packet,55.806000,__ +packet,55.847000,__ +packet,55.931000,__ +packet,55.973000,__ +packet,56.014000,__ +packet,56.056000,__ +packet,56.265000,__ +packet,56.181000,__ +packet,56.139000,__ +packet,56.223000,__ +packet,56.473000,__ +packet,56.348000,__ +packet,56.306000,__ +packet,56.390000,__ +packet,56.431000,__ +packet,56.640000,__ +packet,56.557000,__ +packet,56.515000,__ +packet,56.598000,__ +packet,56.682000,__ +packet,56.807000,__ +packet,56.723000,__ +packet,56.765000,__ +packet,56.848000,__ +packet,56.890000,__ +packet,56.932000,__ +packet,57.057000,__ +packet,56.974000,__ +packet,57.015000,__ +packet,57.099000,__ +packet,57.224000,__ +packet,57.140000,__ +packet,57.182000,__ +packet,57.266000,__ +packet,57.307000,__ +packet,57.474000,__ +packet,57.391000,__ +packet,57.349000,__ +packet,57.432000,__ +packet,57.516000,__ +packet,57.641000,__ +packet,57.558000,__ +packet,57.599000,__ +packet,57.683000,__ +packet,57.724000,__ +packet,57.933000,__ +packet,57.808000,__ +packet,57.766000,__ +packet,57.849000,__ +packet,57.891000,__ +packet,58.058000,__ +packet,57.975000,__ +packet,58.016000,__ +packet,58.267000,__ +packet,58.141000,__ +packet,58.100000,__ +packet,58.183000,__ +packet,58.225000,__ +packet,58.392000,__ +packet,58.308000,__ +packet,58.350000,__ +packet,58.559000,__ +packet,58.475000,__ +packet,58.433000,__ +packet,58.517000,__ +packet,58.725000,__ +packet,58.642000,__ +packet,58.600000,__ +packet,58.684000,__ +packet,58.809000,__ +packet,58.767000,__ +packet,59.017000,__ +packet,58.892000,__ +packet,58.850000,__ +packet,58.934000,__ +packet,58.976000,__ +packet,59.184000,__ +packet,59.101000,__ +packet,59.059000,__ +packet,59.142000,__ +packet,59.518000,__ +packet,59.351000,__ +packet,59.226000,__ +packet,59.268000,__ +packet,59.309000,__ +packet,59.393000,__ +packet,59.434000,__ +packet,59.476000,__ +packet,59.601000,__ +packet,59.560000,__ +packet,59.768000,__ +packet,59.685000,__ +packet,59.643000,__ +packet,59.726000,__ +packet,59.935000,__ +packet,59.851000,__ +packet,59.810000,__ +packet,59.893000,__ +packet,60.269000,__ +packet,60.102000,__ +packet,59.977000,__ +packet,60.018000,__ +packet,60.060000,__ +packet,60.143000,__ +packet,60.185000,__ +packet,60.227000,__ +packet,60.477000,__ +packet,60.352000,__ +packet,60.310000,__ +packet,60.394000,__ +packet,60.435000,__ +packet,60.644000,__ +packet,60.561000,__ +packet,60.519000,__ +packet,60.602000,__ +packet,60.811000,__ +packet,60.727000,__ +packet,60.686000,__ +packet,60.769000,__ +packet,61.019000,__ +packet,60.894000,__ +packet,60.852000,__ +packet,60.936000,__ +packet,60.978000,__ +packet,61.228000,__ +packet,61.103000,__ +packet,61.061000,__ +packet,61.144000,__ +packet,61.186000,__ +packet,61.603000,__ +packet,61.395000,__ +packet,61.270000,__ +packet,61.311000,__ +packet,61.353000,__ +packet,61.436000,__ +packet,61.478000,__ +packet,61.520000,__ +packet,61.562000,__ +packet,61.979000,__ +packet,61.770000,__ +packet,61.645000,__ +packet,61.687000,__ +packet,61.728000,__ +packet,61.812000,__ +packet,61.853000,__ +packet,61.895000,__ +packet,61.937000,__ +packet,62.271000,__ +packet,62.104000,__ +packet,62.020000,__ +packet,62.062000,__ +packet,62.145000,__ +packet,62.187000,__ +packet,62.229000,__ +packet,62.396000,__ +packet,62.312000,__ +packet,62.354000,__ +packet,62.604000,__ +packet,62.479000,__ +packet,62.437000,__ +packet,62.521000,__ +packet,62.563000,__ +packet,62.896000,__ +packet,62.729000,__ +packet,62.646000,__ +packet,62.688000,__ +packet,62.771000,__ +packet,62.813000,__ +packet,62.854000,__ +packet,63.063000,__ +packet,62.980000,__ +packet,62.938000,__ +packet,63.021000,__ +packet,63.105000,K_ +packet,63.355000,__ +packet,63.230000,__ +packet,63.146000,__ +packet,63.188000,__ +packet,63.272000,__ +packet,63.313000,__ +packet,63.605000,__ +packet,63.480000,__ +packet,63.397000,__ +packet,63.438000,__ +packet,63.522000,__ +packet,63.564000,__ +packet,63.855000,__ +packet,63.730000,__ +packet,63.647000,__ +packet,63.689000,__ +packet,63.772000,__ +packet,63.814000,__ +packet,64.106000,__ +packet,63.981000,__ +packet,63.897000,__ +packet,63.939000,__ +packet,64.022000,__ +packet,64.064000,__ +packet,64.231000,__ +packet,64.147000,__ +packet,64.189000,__ +packet,64.606000,__ +packet,64.398000,__ +packet,64.273000,__ +packet,64.314000,__ +packet,64.356000,__ +packet,64.439000,__ +packet,64.481000,__ +packet,64.523000,__ +packet,64.565000,__ +packet,64.648000,K_ +packet,64.773000,__ +packet,64.690000,__ +packet,64.731000,__ +packet,64.898000,__ +packet,64.815000,__ +packet,64.856000,__ +packet,65.107000,__ +packet,64.982000,__ +packet,64.940000,__ +packet,65.023000,__ +packet,65.065000,__ +packet,65.274000,__ +packet,65.190000,__ +packet,65.148000,__ +packet,65.232000,__ +packet,65.649000,__ +packet,65.440000,__ +packet,65.315000,__ +packet,65.357000,__ +packet,65.399000,__ +packet,65.482000,__ +packet,65.524000,__ +packet,65.566000,__ +packet,65.607000,__ +packet,65.816000,__ +packet,65.732000,__ +packet,65.691000,__ +packet,65.774000,__ +packet,66.024000,__ +packet,65.899000,__ +packet,65.857000,__ +packet,65.941000,__ +packet,65.983000,__ +packet,66.191000,__ +packet,66.108000,__ +packet,66.066000,__ +packet,66.149000,__ +packet,66.233000,__ +packet,66.400000,__ +packet,66.316000,__ +packet,66.275000,__ +packet,66.358000,__ +packet,66.525000,__ +packet,66.441000,__ +packet,66.483000,__ +packet,66.567000,K_ +packet,66.733000,__ +packet,66.650000,__ +packet,66.608000,__ +packet,66.692000,__ +packet,66.984000,__ +packet,66.858000,__ +packet,66.775000,__ +packet,66.817000,__ +packet,66.900000,__ +packet,66.942000,__ +packet,67.359000,__ +packet,67.150000,__ +packet,67.025000,__ +packet,67.067000,__ +packet,67.109000,__ +packet,67.192000,__ +packet,67.234000,__ +packet,67.276000,__ +packet,67.317000,__ +packet,67.734000,__ +packet,67.526000,__ +packet,67.401000,__ +packet,67.442000,__ +packet,67.484000,__ +packet,67.568000,__ +packet,67.609000,__ +packet,67.651000,__ +packet,67.693000,__ +packet,67.859000,__ +packet,67.776000,__ +packet,67.818000,__ +packet,68.151000,__ +packet,67.985000,__ +packet,67.901000,__ +packet,67.943000,__ +packet,68.026000,__ +packet,68.068000,__ +packet,68.110000,__ +packet,68.402000,__ +packet,68.277000,__ +packet,68.193000,__ +packet,68.235000,__ +packet,68.318000,__ +packet,68.360000,__ +packet,68.610000,__ +packet,68.485000,__ +packet,68.443000,__ +packet,68.527000,__ +packet,68.569000,__ +packet,68.652000,K_ +packet,68.819000,__ +packet,68.735000,__ +packet,68.694000,__ +packet,68.777000,__ +packet,68.986000,__ +packet,68.902000,__ +packet,68.860000,__ +packet,68.944000,__ +packet,69.361000,__ +packet,69.152000,__ +packet,69.027000,__ +packet,69.069000,__ +packet,69.111000,__ +packet,69.194000,__ +packet,69.236000,__ +packet,69.278000,__ +packet,69.319000,__ +packet,69.444000,__ +packet,69.403000,__ +packet,69.820000,__ +packet,69.611000,__ +packet,69.486000,__ +packet,69.528000,__ +packet,69.570000,__ +packet,69.653000,__ +packet,69.695000,__ +packet,69.736000,__ +packet,69.778000,__ +packet,70.028000,__ +packet,69.903000,__ +packet,69.861000,__ +packet,69.945000,__ +packet,69.987000,__ +packet,70.195000,__ +packet,70.112000,__ +packet,70.070000,__ +packet,70.153000,__ +packet,70.529000,__ +packet,70.362000,__ +packet,70.237000,__ +packet,70.279000,__ +packet,70.320000,__ +packet,70.404000,__ +packet,70.445000,__ +packet,70.487000,__ +packet,70.862000,__ +packet,70.696000,__ +packet,70.571000,__ +packet,70.612000,__ +packet,70.654000,__ +packet,70.737000,__ +packet,70.779000,__ +packet,70.821000,__ +packet,71.029000,__ +packet,70.946000,__ +packet,70.904000,__ +packet,70.988000,__ +packet,71.280000,__ +packet,71.154000,__ +packet,71.071000,__ +packet,71.113000,__ +packet,71.196000,__ +packet,71.238000,__ +packet,71.488000,__ +packet,71.363000,__ +packet,71.321000,__ +packet,71.405000,__ +packet,71.446000,__ +packet,71.863000,__ +packet,71.655000,__ +packet,71.530000,__ +packet,71.572000,__ +packet,71.613000,__ +packet,71.697000,__ +packet,71.738000,__ +packet,71.780000,__ +packet,71.822000,__ +packet,72.114000,__ +packet,71.989000,__ +packet,71.905000,__ +packet,71.947000,__ +packet,72.030000,__ +packet,72.072000,__ +packet,72.281000,__ +packet,72.197000,__ +packet,72.155000,__ +packet,72.239000,__ +packet,72.406000,__ +packet,72.322000,__ +packet,72.364000,__ +packet,72.614000,__ +packet,72.489000,__ +packet,72.447000,__ +packet,72.531000,__ +packet,72.573000,__ +packet,72.739000,__ +packet,72.656000,__ +packet,72.698000,__ +packet,73.031000,__ +packet,72.864000,__ +packet,72.781000,__ +packet,72.823000,__ +packet,72.906000,__ +packet,72.948000,__ +packet,72.990000,__ +packet,73.198000,__ +packet,73.115000,__ +packet,73.073000,__ +packet,73.156000,__ +packet,73.240000,K_ +packet,73.407000,__ +packet,73.323000,__ +packet,73.282000,__ +packet,73.365000,__ +packet,73.532000,__ +packet,73.448000,__ +packet,73.490000,__ +packet,73.699000,__ +packet,73.615000,__ +packet,73.574000,__ +packet,73.657000,__ +packet,73.865000,__ +packet,73.782000,__ +packet,73.740000,__ +packet,73.824000,__ +packet,74.032000,__ +packet,73.949000,__ +packet,73.907000,__ +packet,73.991000,__ +packet,74.199000,__ +packet,74.116000,__ +packet,74.074000,__ +packet,74.157000,__ +packet,74.283000,__ +packet,74.241000,__ +packet,74.408000,__ +packet,74.324000,__ +packet,74.366000,__ +packet,74.533000,__ +packet,74.449000,__ +packet,74.491000,__ +packet,74.700000,__ +packet,74.616000,__ +packet,74.575000,__ +packet,74.658000,__ +packet,74.866000,__ +packet,74.783000,__ +packet,74.741000,__ +packet,74.825000,__ +packet,75.033000,__ +packet,74.950000,__ +packet,74.908000,__ +packet,74.992000,__ +packet,75.200000,__ +packet,75.117000,__ +packet,75.075000,__ +packet,75.158000,__ +packet,75.409000,__ +packet,75.284000,__ +packet,75.242000,__ +packet,75.325000,__ +packet,75.367000,__ +packet,75.784000,__ +packet,75.576000,__ +packet,75.450000,__ +packet,75.492000,__ +packet,75.534000,__ +packet,75.617000,__ +packet,75.659000,__ +packet,75.701000,__ +packet,75.742000,__ +packet,75.826000,__ +packet,76.201000,__ +packet,75.993000,__ +packet,75.867000,__ +packet,75.909000,__ +packet,75.951000,__ +packet,76.034000,__ +packet,76.076000,__ +packet,76.118000,__ +packet,76.159000,__ +packet,76.368000,__ +packet,76.285000,__ +packet,76.243000,__ +packet,76.326000,__ +packet,76.451000,__ +packet,76.410000,__ +packet,76.618000,__ +packet,76.535000,__ +packet,76.493000,__ +packet,76.577000,__ +packet,76.827000,__ +packet,76.702000,__ +packet,76.660000,__ +packet,76.743000,__ +packet,76.785000,__ +packet,76.952000,__ +packet,76.868000,__ +packet,76.910000,__ +packet,77.160000,__ +packet,77.035000,__ +packet,76.994000,__ +packet,77.077000,__ +packet,77.119000,__ +packet,77.202000,K_ +packet,77.369000,__ +packet,77.286000,__ +packet,77.244000,__ +packet,77.327000,__ +packet,77.536000,__ +packet,77.452000,__ +packet,77.411000,__ +packet,77.494000,__ +packet,77.661000,__ +packet,77.578000,__ +packet,77.619000,__ +packet,77.828000,__ +packet,77.744000,__ +packet,77.703000,__ +packet,77.786000,__ +packet,77.953000,__ +packet,77.869000,__ +packet,77.911000,__ +packet,78.036000,__ +packet,77.995000,__ +packet,78.245000,__ +packet,78.120000,__ +packet,78.078000,__ +packet,78.161000,__ +packet,78.203000,__ +packet,78.328000,__ +packet,78.287000,__ +packet,78.495000,__ +packet,78.412000,__ +packet,78.370000,__ +packet,78.453000,__ +packet,78.620000,__ +packet,78.537000,__ +packet,78.579000,__ +packet,78.787000,__ +packet,78.704000,__ +packet,78.662000,__ +packet,78.745000,__ +packet,79.037000,__ +packet,78.912000,__ +packet,78.829000,__ +packet,78.870000,__ +packet,78.954000,__ +packet,78.996000,__ +packet,79.288000,__ +packet,79.162000,__ +packet,79.079000,__ +packet,79.121000,__ +packet,79.204000,__ +packet,79.246000,__ +packet,79.538000,__ +packet,79.413000,__ +packet,79.329000,__ +packet,79.371000,__ +packet,79.454000,__ +packet,79.496000,__ +packet,79.580000,__ +packet,79.621000,K_ +packet,79.663000,__ +packet,79.997000,__ +packet,79.830000,__ +packet,79.705000,__ +packet,79.746000,__ +packet,79.788000,__ +packet,79.871000,__ +packet,79.913000,__ +packet,79.955000,__ +packet,80.038000,__ +packet,80.247000,__ +packet,80.122000,__ +packet,80.080000,__ +packet,80.163000,__ +packet,80.205000,__ +packet,80.372000,__ +packet,80.289000,__ +packet,80.330000,__ +packet,80.539000,__ +packet,80.455000,__ +packet,80.414000,__ +packet,80.497000,__ +packet,80.706000,__ +packet,80.622000,__ +packet,80.581000,__ +packet,80.664000,__ +packet,80.872000,__ +packet,80.789000,__ +packet,80.747000,__ +packet,80.831000,__ +packet,81.039000,__ +packet,80.956000,__ +packet,80.914000,__ +packet,80.998000,__ +packet,81.248000,__ +packet,81.123000,__ +packet,81.081000,__ +packet,81.164000,__ +packet,81.206000,__ +packet,81.290000,__ +packet,81.456000,__ +packet,81.373000,__ +packet,81.331000,__ +packet,81.415000,__ +packet,81.623000,__ +packet,81.540000,__ +packet,81.498000,__ +packet,81.582000,__ +packet,81.999000,__ +packet,81.790000,__ +packet,81.665000,__ +packet,81.707000,__ +packet,81.748000,__ +packet,81.832000,__ +packet,81.873000,__ +packet,81.915000,__ +packet,81.957000,__ +packet,82.040000,__ +packet,82.249000,__ +packet,82.124000,__ +packet,82.082000,__ +packet,82.165000,__ +packet,82.207000,__ +packet,82.291000,__ +packet,82.541000,__ +packet,82.416000,__ +packet,82.332000,__ +packet,82.374000,__ +packet,82.457000,__ +packet,82.499000,__ +packet,82.916000,__ +packet,82.708000,__ +packet,82.583000,__ +packet,82.624000,__ +packet,82.666000,__ +packet,82.749000,__ +packet,82.791000,__ +packet,82.833000,__ +packet,82.874000,__ +packet,83.166000,__ +packet,83.041000,__ +packet,82.958000,__ +packet,83.000000,__ +packet,83.083000,__ +packet,83.125000,__ +packet,83.458000,__ +packet,83.292000,__ +packet,83.208000,__ +packet,83.250000,__ +packet,83.333000,__ +packet,83.375000,__ +packet,83.417000,__ +packet,83.584000,__ +packet,83.500000,__ +packet,83.542000,__ +packet,83.667000,__ +packet,83.625000,__ +packet,83.792000,__ +packet,83.709000,__ +packet,83.750000,__ +packet,83.959000,__ +packet,83.875000,__ +packet,83.834000,__ +packet,83.917000,__ +packet,84.167000,__ +packet,84.042000,__ +packet,84.001000,__ +packet,84.084000,__ +packet,84.126000,__ +packet,84.293000,__ +packet,84.209000,__ +packet,84.251000,__ +packet,84.459000,__ +packet,84.376000,__ +packet,84.334000,__ +packet,84.418000,__ +packet,84.751000,__ +packet,84.585000,__ +packet,84.501000,__ +packet,84.543000,__ +packet,84.626000,__ +packet,84.668000,__ +packet,84.710000,__ +packet,84.918000,__ +packet,84.835000,__ +packet,84.793000,__ +packet,84.876000,__ +packet,85.168000,__ +packet,85.043000,__ +packet,84.960000,__ +packet,85.002000,__ +packet,85.085000,__ +packet,85.127000,__ +packet,85.502000,__ +packet,85.335000,__ +packet,85.210000,__ +packet,85.252000,__ +packet,85.294000,__ +packet,85.377000,__ +packet,85.419000,__ +packet,85.460000,__ +packet,85.586000,__ +packet,85.544000,__ +packet,85.627000,__ +packet,85.669000,K_ +packet,85.752000,__ +packet,85.711000,__ +packet,85.919000,__ +packet,85.836000,__ +packet,85.794000,__ +packet,85.877000,__ +packet,86.044000,__ +packet,85.961000,__ +packet,86.003000,__ +packet,86.253000,__ +packet,86.128000,__ +packet,86.086000,__ +packet,86.169000,__ +packet,86.211000,__ +packet,86.461000,__ +packet,86.336000,__ +packet,86.295000,__ +packet,86.378000,__ +packet,86.420000,__ +packet,86.628000,__ +packet,86.545000,__ +packet,86.503000,__ +packet,86.587000,__ +packet,86.795000,__ +packet,86.712000,__ +packet,86.670000,__ +packet,86.753000,__ +packet,86.962000,__ +packet,86.878000,__ +packet,86.837000,__ +packet,86.920000,__ +packet,87.129000,__ +packet,87.045000,__ +packet,87.004000,__ +packet,87.087000,__ +packet,87.296000,__ +packet,87.212000,__ +packet,87.170000,__ +packet,87.254000,__ +packet,87.462000,__ +packet,87.379000,__ +packet,87.337000,__ +packet,87.421000,__ +packet,87.838000,__ +packet,87.629000,__ +packet,87.504000,__ +packet,87.546000,__ +packet,87.588000,__ +packet,87.671000,__ +packet,87.713000,__ +packet,87.754000,__ +packet,87.796000,__ +packet,87.921000,__ +packet,87.879000,__ +packet,88.046000,__ +packet,87.963000,__ +packet,88.005000,__ +packet,88.255000,__ +packet,88.130000,__ +packet,88.088000,__ +packet,88.171000,__ +packet,88.213000,__ +packet,88.297000,__ +packet,88.463000,__ +packet,88.380000,__ +packet,88.338000,__ +packet,88.422000,__ +packet,88.630000,__ +packet,88.547000,__ +packet,88.505000,__ +packet,88.589000,__ +packet,88.755000,__ +packet,88.672000,__ +packet,88.714000,__ +packet,88.797000,K_ +packet,88.880000,__ +packet,88.839000,__ +packet,88.922000,__ +packet,88.964000,__ +packet,89.131000,__ +packet,89.047000,__ +packet,89.006000,__ +packet,89.089000,__ +packet,89.506000,__ +packet,89.298000,__ +packet,89.172000,__ +packet,89.214000,__ +packet,89.256000,__ +packet,89.339000,__ +packet,89.381000,__ +packet,89.423000,__ +packet,89.464000,__ +packet,89.756000,__ +packet,89.631000,__ +packet,89.548000,__ +packet,89.590000,__ +packet,89.673000,__ +packet,89.715000,__ +packet,89.881000,__ +packet,89.798000,__ +packet,89.840000,__ +packet,90.090000,__ +packet,89.965000,__ +packet,89.923000,__ +packet,90.007000,__ +packet,90.048000,__ +packet,90.340000,__ +packet,90.215000,__ +packet,90.132000,__ +packet,90.173000,__ +packet,90.257000,__ +packet,90.299000,__ +packet,90.382000,K_ +packet,90.591000,__ +packet,90.465000,__ +packet,90.424000,__ +packet,90.507000,__ +packet,90.549000,__ +packet,90.841000,__ +packet,90.716000,__ +packet,90.632000,__ +packet,90.674000,__ +packet,90.757000,__ +packet,90.799000,__ +packet,90.882000,__ +packet,91.049000,__ +packet,90.966000,__ +packet,90.924000,__ +packet,91.008000,__ +packet,91.258000,__ +packet,91.133000,__ +packet,91.091000,__ +packet,91.174000,__ +packet,91.216000,__ +packet,91.550000,__ +packet,91.383000,__ +packet,91.300000,__ +packet,91.341000,__ +packet,91.425000,__ +packet,91.466000,__ +packet,91.508000,__ +packet,91.758000,__ +packet,91.633000,__ +packet,91.592000,__ +packet,91.675000,__ +packet,91.717000,__ +packet,92.009000,__ +packet,91.883000,__ +packet,91.800000,__ +packet,91.842000,__ +packet,91.925000,__ +packet,91.967000,__ +packet,92.259000,__ +packet,92.134000,__ +packet,92.050000,__ +packet,92.092000,__ +packet,92.175000,__ +packet,92.217000,__ +packet,92.634000,__ +packet,92.426000,__ +packet,92.301000,__ +packet,92.342000,__ +packet,92.384000,__ +packet,92.467000,__ +packet,92.509000,__ +packet,92.551000,__ +packet,92.593000,__ +packet,92.926000,__ +packet,92.759000,__ +packet,92.676000,__ +packet,92.718000,__ +packet,92.801000,__ +packet,92.843000,__ +packet,92.884000,__ +packet,93.176000,__ +packet,93.051000,__ +packet,92.968000,__ +packet,93.010000,__ +packet,93.093000,__ +packet,93.135000,__ +packet,93.385000,__ +packet,93.260000,__ +packet,93.218000,__ +packet,93.302000,__ +packet,93.343000,__ +packet,93.427000,K_ +packet,93.468000,__ +packet,93.552000,__ +packet,93.510000,__ +packet,93.719000,__ +packet,93.635000,__ +packet,93.594000,__ +packet,93.677000,__ +packet,93.969000,__ +packet,93.844000,__ +packet,93.760000,__ +packet,93.802000,__ +packet,93.885000,__ +packet,93.927000,__ +packet,94.094000,__ +packet,94.011000,__ +packet,94.052000,__ +packet,94.136000,__ +packet,94.303000,__ +packet,94.219000,__ +packet,94.177000,__ +packet,94.261000,__ +packet,94.511000,__ +packet,94.386000,__ +packet,94.344000,__ +packet,94.428000,__ +packet,94.469000,__ +packet,94.636000,__ +packet,94.553000,__ +packet,94.595000,__ +packet,94.803000,__ +packet,94.720000,__ +packet,94.678000,__ +packet,94.761000,__ +packet,94.886000,__ +packet,94.845000,__ +packet,95.012000,__ +packet,94.928000,__ +packet,94.970000,__ +packet,95.053000,__ +packet,95.220000,__ +packet,95.137000,__ +packet,95.095000,__ +packet,95.178000,__ +packet,95.387000,__ +packet,95.304000,__ +packet,95.262000,__ +packet,95.345000,__ +packet,95.679000,__ +packet,95.512000,__ +packet,95.429000,__ +packet,95.470000,__ +packet,95.554000,__ +packet,95.596000,__ +packet,95.637000,__ +packet,95.762000,__ +packet,95.721000,__ +packet,96.138000,__ +packet,95.929000,__ +packet,95.804000,__ +packet,95.846000,__ +packet,95.887000,__ +packet,95.971000,__ +packet,96.013000,__ +packet,96.054000,__ +packet,96.096000,__ +packet,96.430000,__ +packet,96.263000,__ +packet,96.179000,__ +packet,96.221000,__ +packet,96.305000,__ +packet,96.346000,__ +packet,96.388000,__ +packet,96.722000,__ +packet,96.555000,__ +packet,96.471000,__ +packet,96.513000,__ +packet,96.597000,__ +packet,96.638000,__ +packet,96.680000,__ +packet,96.930000,__ +packet,96.805000,__ +packet,96.763000,__ +packet,96.847000,__ +packet,96.888000,__ +packet,97.014000,__ +packet,96.972000,__ +packet,97.055000,__ +packet,97.264000,__ +packet,97.139000,__ +packet,97.097000,__ +packet,97.180000,__ +packet,97.222000,__ +packet,97.306000,__ +packet,97.431000,__ +packet,97.347000,__ +packet,97.389000,__ +packet,97.556000,__ +packet,97.472000,__ +packet,97.514000,__ +packet,97.806000,__ +packet,97.681000,__ +packet,97.598000,__ +packet,97.639000,__ +packet,97.723000,__ +packet,97.764000,__ +packet,97.931000,__ +packet,97.848000,__ +packet,97.889000,__ +packet,98.056000,__ +packet,97.973000,__ +packet,98.015000,__ +packet,98.181000,__ +packet,98.098000,__ +packet,98.140000,__ +packet,98.265000,__ +packet,98.223000,__ +packet,98.307000,K_ +packet,98.390000,__ +packet,98.348000,__ +packet,98.515000,__ +packet,98.432000,__ +packet,98.473000,__ +packet,98.599000,__ +packet,98.557000,__ +packet,98.765000,__ +packet,98.682000,__ +packet,98.640000,__ +packet,98.724000,__ +packet,98.974000,__ +packet,98.849000,__ +packet,98.807000,__ +packet,98.890000,__ +packet,98.932000,__ +packet,99.016000,__ +packet,99.182000,__ +packet,99.099000,__ +packet,99.057000,__ +packet,99.141000,__ +packet,99.391000,__ +packet,99.266000,__ +packet,99.224000,__ +packet,99.308000,__ +packet,99.349000,__ +packet,99.641000,__ +packet,99.516000,__ +packet,99.433000,__ +packet,99.474000,__ +packet,99.558000,__ +packet,99.600000,__ +packet,99.891000,__ +packet,99.766000,__ +packet,99.683000,__ +packet,99.725000,__ +packet,99.808000,__ +packet,99.850000,__ +packet,100.058000,__ +packet,99.975000,__ +packet,99.933000,__ +packet,100.017000,__ +packet,100.142000,__ +packet,100.100000,__ +packet,100.309000,__ +packet,100.225000,__ +packet,100.183000,__ +packet,100.267000,__ +packet,100.434000,__ +packet,100.350000,__ +packet,100.392000,__ +packet,100.642000,__ +packet,100.517000,__ +packet,100.475000,__ +packet,100.559000,__ +packet,100.601000,__ +packet,100.809000,__ +packet,100.726000,__ +packet,100.684000,__ +packet,100.767000,__ +packet,100.976000,__ +packet,100.892000,__ +packet,100.851000,__ +packet,100.934000,__ +packet,101.143000,__ +packet,101.059000,__ +packet,101.018000,__ +packet,101.101000,__ +packet,101.351000,__ +packet,101.226000,__ +packet,101.184000,__ +packet,101.268000,__ +packet,101.310000,__ +packet,101.518000,__ +packet,101.435000,__ +packet,101.393000,__ +packet,101.476000,__ +packet,101.685000,__ +packet,101.602000,__ +packet,101.560000,__ +packet,101.643000,__ +packet,101.935000,__ +packet,101.810000,__ +packet,101.727000,__ +packet,101.768000,__ +packet,101.852000,__ +packet,101.893000,__ +packet,102.102000,__ +packet,102.019000,__ +packet,101.977000,__ +packet,102.060000,__ +packet,102.269000,__ +packet,102.185000,__ +packet,102.144000,__ +packet,102.227000,__ +packet,102.519000,__ +packet,102.394000,__ +packet,102.311000,__ +packet,102.352000,__ +packet,102.436000,__ +packet,102.477000,__ +packet,102.686000,__ +packet,102.603000,__ +packet,102.561000,__ +packet,102.644000,__ +packet,102.853000,__ +packet,102.769000,__ +packet,102.728000,__ +packet,102.811000,__ +packet,103.061000,__ +packet,102.936000,__ +packet,102.894000,__ +packet,102.978000,__ +packet,103.020000,__ +packet,103.270000,__ +packet,103.145000,__ +packet,103.103000,__ +packet,103.186000,__ +packet,103.228000,__ +packet,103.353000,__ +packet,103.312000,__ +packet,103.562000,__ +packet,103.437000,__ +packet,103.395000,__ +packet,103.478000,__ +packet,103.520000,__ +packet,103.687000,__ +packet,103.604000,__ +packet,103.645000,__ +packet,103.979000,__ +packet,103.812000,__ +packet,103.729000,__ +packet,103.770000,__ +packet,103.854000,__ +packet,103.895000,__ +packet,103.937000,__ +packet,104.229000,__ +packet,104.104000,__ +packet,104.021000,__ +packet,104.062000,__ +packet,104.146000,__ +packet,104.187000,__ +packet,104.396000,__ +packet,104.313000,__ +packet,104.271000,__ +packet,104.354000,__ +packet,104.563000,__ +packet,104.479000,__ +packet,104.438000,__ +packet,104.521000,__ +packet,104.730000,__ +packet,104.646000,__ +packet,104.605000,__ +packet,104.688000,__ +packet,104.896000,__ +packet,104.813000,__ +packet,104.771000,__ +packet,104.855000,__ +packet,104.938000,__ +packet,105.063000,__ +packet,104.980000,__ +packet,105.022000,__ +packet,105.188000,__ +packet,105.105000,__ +packet,105.147000,__ +packet,105.397000,__ +packet,105.272000,__ +packet,105.230000,__ +packet,105.314000,__ +packet,105.355000,__ +packet,105.522000,__ +packet,105.439000,__ +packet,105.480000,__ +packet,105.564000,__ +packet,105.606000,K_ +packet,105.814000,__ +packet,105.689000,__ +packet,105.647000,__ +packet,105.731000,__ +packet,105.772000,__ +packet,105.981000,__ +packet,105.897000,__ +packet,105.856000,__ +packet,105.939000,__ +packet,106.148000,__ +packet,106.064000,__ +packet,106.023000,__ +packet,106.106000,__ +packet,106.273000,__ +packet,106.189000,__ +packet,106.231000,__ +packet,106.440000,__ +packet,106.356000,__ +packet,106.315000,__ +packet,106.398000,__ +packet,106.607000,__ +packet,106.523000,__ +packet,106.481000,__ +packet,106.565000,__ +packet,106.690000,__ +packet,106.648000,__ +packet,106.857000,__ +packet,106.773000,__ +packet,106.732000,__ +packet,106.815000,__ +packet,107.065000,__ +packet,106.940000,__ +packet,106.898000,__ +packet,106.982000,__ +packet,107.024000,__ +packet,107.232000,__ +packet,107.149000,__ +packet,107.107000,__ +packet,107.190000,__ +packet,107.357000,__ +packet,107.274000,__ +packet,107.316000,__ +packet,107.649000,__ +packet,107.482000,__ +packet,107.399000,__ +packet,107.441000,__ +packet,107.524000,__ +packet,107.566000,__ +packet,107.608000,__ +packet,107.816000,__ +packet,107.733000,__ +packet,107.691000,__ +packet,107.774000,__ +packet,108.025000,__ +packet,107.899000,__ +packet,107.858000,__ +packet,107.941000,__ +packet,107.983000,__ +packet,108.317000,__ +packet,108.150000,__ +packet,108.066000,__ +packet,108.108000,__ +packet,108.191000,__ +packet,108.233000,__ +packet,108.275000,__ +packet,108.567000,__ +packet,108.442000,__ +packet,108.358000,__ +packet,108.400000,__ +packet,108.483000,__ +packet,108.525000,__ +packet,108.734000,__ +packet,108.650000,__ +packet,108.609000,__ +packet,108.692000,__ +packet,108.775000,K_ +packet,108.859000,__ +packet,108.817000,__ +packet,109.192000,__ +packet,109.026000,__ +packet,108.900000,__ +packet,108.942000,__ +packet,108.984000,__ +packet,109.067000,__ +packet,109.109000,__ +packet,109.151000,__ +packet,109.443000,__ +packet,109.318000,__ +packet,109.234000,__ +packet,109.276000,__ +packet,109.359000,__ +packet,109.401000,__ +packet,109.568000,__ +packet,109.484000,__ +packet,109.526000,__ +packet,109.818000,__ +packet,109.693000,__ +packet,109.610000,__ +packet,109.651000,__ +packet,109.735000,__ +packet,109.776000,__ +packet,110.027000,__ +packet,109.901000,__ +packet,109.860000,__ +packet,109.943000,__ +packet,109.985000,__ +packet,110.402000,__ +packet,110.193000,__ +packet,110.068000,__ +packet,110.110000,__ +packet,110.152000,__ +packet,110.235000,__ +packet,110.277000,__ +packet,110.319000,__ +packet,110.360000,__ +packet,110.777000,__ +packet,110.569000,__ +packet,110.444000,__ +packet,110.485000,__ +packet,110.527000,__ +packet,110.611000,__ +packet,110.652000,__ +packet,110.694000,__ +packet,110.736000,__ +packet,110.944000,__ +packet,110.861000,__ +packet,110.819000,__ +packet,110.902000,__ +packet,111.069000,__ +packet,110.986000,__ +packet,111.028000,__ +packet,111.236000,__ +packet,111.153000,__ +packet,111.111000,__ +packet,111.194000,__ +packet,111.445000,__ +packet,111.320000,__ +packet,111.278000,__ +packet,111.361000,__ +packet,111.403000,__ +packet,111.612000,__ +packet,111.528000,__ +packet,111.486000,__ +packet,111.570000,__ +packet,111.778000,__ +packet,111.695000,__ +packet,111.653000,__ +packet,111.737000,__ +packet,112.029000,__ +packet,111.903000,__ +packet,111.820000,__ +packet,111.862000,__ +packet,111.945000,__ +packet,111.987000,__ +packet,112.195000,__ +packet,112.112000,__ +packet,112.070000,__ +packet,112.154000,__ +packet,112.571000,__ +packet,112.362000,__ +packet,112.237000,__ +packet,112.279000,__ +packet,112.321000,__ +packet,112.404000,__ +packet,112.446000,__ +packet,112.487000,__ +packet,112.529000,__ +packet,112.821000,__ +packet,112.696000,__ +packet,112.613000,__ +packet,112.654000,__ +packet,112.738000,__ +packet,112.779000,__ +packet,113.196000,__ +packet,112.988000,__ +packet,112.863000,__ +packet,112.904000,__ +packet,112.946000,__ +packet,113.030000,__ +packet,113.071000,__ +packet,113.113000,__ +packet,113.155000,__ +packet,113.363000,__ +packet,113.280000,__ +packet,113.238000,__ +packet,113.322000,__ +packet,113.447000,__ +packet,113.405000,__ +packet,113.614000,__ +packet,113.530000,__ +packet,113.488000,__ +packet,113.572000,__ +packet,113.739000,__ +packet,113.655000,__ +packet,113.697000,__ +packet,113.905000,__ +packet,113.822000,__ +packet,113.780000,__ +packet,113.864000,__ +packet,114.072000,__ +packet,113.989000,__ +packet,113.947000,__ +packet,114.031000,__ +packet,114.281000,__ +packet,114.156000,__ +packet,114.114000,__ +packet,114.197000,__ +packet,114.239000,__ +packet,114.656000,__ +packet,114.448000,__ +packet,114.323000,__ +packet,114.364000,__ +packet,114.406000,__ +packet,114.489000,__ +packet,114.531000,__ +packet,114.573000,__ +packet,114.615000,__ +packet,114.990000,__ +packet,114.823000,__ +packet,114.698000,__ +packet,114.740000,__ +packet,114.781000,__ +packet,114.865000,__ +packet,114.906000,__ +packet,114.948000,__ +packet,115.157000,__ +packet,115.073000,__ +packet,115.032000,__ +packet,115.115000,__ +packet,115.324000,__ +packet,115.240000,__ +packet,115.198000,__ +packet,115.282000,__ +packet,115.365000,__ +packet,115.407000,__ +packet,115.532000,__ +packet,115.449000,__ +packet,115.490000,__ +packet,115.657000,__ +packet,115.574000,__ +packet,115.616000,__ +packet,115.824000,__ +packet,115.741000,__ +packet,115.699000,__ +packet,115.782000,__ +packet,116.033000,__ +packet,115.907000,__ +packet,115.866000,__ +packet,115.949000,__ +packet,115.991000,__ +packet,116.158000,__ +packet,116.074000,__ +packet,116.116000,__ +packet,116.533000,__ +packet,116.325000,__ +packet,116.199000,__ +packet,116.241000,__ +packet,116.283000,__ +packet,116.366000,__ +packet,116.408000,__ +packet,116.450000,__ +packet,116.491000,__ +packet,116.575000,__ +packet,116.742000,__ +packet,116.658000,__ +packet,116.617000,__ +packet,116.700000,__ +packet,116.908000,__ +packet,116.825000,__ +packet,116.783000,__ +packet,116.867000,__ +packet,117.075000,__ +packet,116.992000,__ +packet,116.950000,__ +packet,117.034000,__ +packet,117.326000,__ +packet,117.200000,__ +packet,117.117000,__ +packet,117.159000,__ +packet,117.242000,__ +packet,117.284000,__ +packet,117.492000,__ +packet,117.409000,__ +packet,117.367000,__ +packet,117.451000,__ +packet,117.701000,__ +packet,117.576000,__ +packet,117.534000,__ +packet,117.618000,__ +packet,117.659000,__ +packet,118.035000,__ +packet,117.868000,__ +packet,117.743000,__ +packet,117.784000,__ +packet,117.826000,__ +packet,117.909000,__ +packet,117.951000,__ +packet,117.993000,__ +packet,118.327000,__ +packet,118.160000,__ +packet,118.076000,__ +packet,118.118000,__ +packet,118.201000,__ +packet,118.243000,__ +packet,118.285000,__ +packet,118.535000,__ +packet,118.410000,__ +packet,118.368000,__ +packet,118.452000,__ +packet,118.493000,__ +packet,118.744000,__ +packet,118.619000,__ +packet,118.577000,__ +packet,118.660000,__ +packet,118.702000,__ +packet,118.785000,K_ +packet,118.910000,__ +packet,118.827000,__ +packet,118.869000,__ +packet,119.077000,__ +packet,118.994000,__ +packet,118.952000,__ +packet,119.036000,__ +packet,119.244000,__ +packet,119.161000,__ +packet,119.119000,__ +packet,119.202000,__ +packet,119.620000,__ +packet,119.411000,__ +packet,119.286000,__ +packet,119.328000,__ +packet,119.369000,__ +packet,119.453000,__ +packet,119.494000,__ +packet,119.536000,__ +packet,119.578000,__ +packet,119.786000,__ +packet,119.703000,__ +packet,119.661000,__ +packet,119.745000,__ +packet,120.037000,__ +packet,119.911000,__ +packet,119.828000,__ +packet,119.870000,__ +packet,119.953000,__ +packet,119.995000,__ +packet,120.287000,__ +packet,120.162000,__ +packet,120.078000,__ +packet,120.120000,__ +packet,120.203000,__ +packet,120.245000,__ +packet,120.621000,__ +packet,120.454000,__ +packet,120.329000,__ +packet,120.370000,__ +packet,120.412000,__ +packet,120.495000,__ +packet,120.537000,__ +packet,120.579000,__ +packet,120.746000,__ +packet,120.662000,__ +packet,120.704000,__ +packet,120.912000,__ +packet,120.829000,__ +packet,120.787000,__ +packet,120.871000,__ +packet,121.121000,__ +packet,120.996000,__ +packet,120.954000,__ +packet,121.038000,__ +packet,121.079000,__ +packet,121.330000,__ +packet,121.204000,__ +packet,121.163000,__ +packet,121.246000,__ +packet,121.288000,__ +packet,121.580000,__ +packet,121.455000,__ +packet,121.371000,__ +packet,121.413000,__ +packet,121.496000,__ +packet,121.538000,__ +packet,121.622000,__ +packet,121.788000,__ +packet,121.705000,__ +packet,121.663000,__ +packet,121.747000,__ +packet,122.080000,__ +packet,121.913000,__ +packet,121.830000,__ +packet,121.872000,__ +packet,121.955000,__ +packet,121.997000,__ +packet,122.039000,__ +packet,122.164000,__ +packet,122.122000,__ +packet,122.205000,K_ +packet,122.331000,__ +packet,122.247000,__ +packet,122.289000,__ +packet,122.664000,__ +packet,122.497000,__ +packet,122.372000,__ +packet,122.414000,__ +packet,122.456000,__ +packet,122.539000,__ +packet,122.581000,__ +packet,122.623000,__ +packet,122.998000,__ +packet,122.831000,__ +packet,122.706000,__ +packet,122.748000,__ +packet,122.789000,__ +packet,122.873000,__ +packet,122.914000,__ +packet,122.956000,__ +packet,123.165000,__ +packet,123.081000,__ +packet,123.040000,__ +packet,123.123000,__ +packet,123.290000,__ +packet,123.206000,__ +packet,123.248000,__ +packet,123.624000,__ +packet,123.457000,__ +packet,123.332000,__ +packet,123.373000,__ +packet,123.415000,__ +packet,123.498000,__ +packet,123.540000,__ +packet,123.582000,__ +packet,123.999000,__ +packet,123.790000,__ +packet,123.665000,__ +packet,123.707000,__ +packet,123.749000,__ +packet,123.832000,__ +packet,123.874000,__ +packet,123.915000,__ +packet,123.957000,__ +packet,124.249000,__ +packet,124.124000,__ +packet,124.041000,__ +packet,124.082000,__ +packet,124.166000,__ +packet,124.207000,__ +packet,124.541000,__ +packet,124.374000,__ +packet,124.291000,__ +packet,124.333000,__ +packet,124.416000,__ +packet,124.458000,__ +packet,124.499000,__ +packet,124.708000,__ +packet,124.625000,__ +packet,124.583000,__ +packet,124.666000,__ +packet,124.875000,__ +packet,124.791000,__ +packet,124.750000,__ +packet,124.833000,__ +packet,125.083000,__ +packet,124.958000,__ +packet,124.916000,__ +packet,125.000000,__ +packet,125.042000,__ +packet,125.125000,K_ +packet,125.417000,__ +packet,125.250000,__ +packet,125.167000,__ +packet,125.208000,__ +packet,125.292000,__ +packet,125.334000,__ +packet,125.375000,__ +packet,125.751000,__ +packet,125.584000,__ +packet,125.459000,__ +packet,125.500000,__ +packet,125.542000,__ +packet,125.626000,__ +packet,125.667000,__ +packet,125.709000,__ +packet,126.126000,__ +packet,125.917000,__ +packet,125.792000,__ +packet,125.834000,__ +packet,125.876000,__ +packet,125.959000,__ +packet,126.001000,__ +packet,126.043000,__ +packet,126.084000,__ +packet,126.251000,__ +packet,126.168000,__ +packet,126.209000,__ +packet,126.501000,__ +packet,126.376000,__ +packet,126.293000,__ +packet,126.335000,__ +packet,126.418000,__ +packet,126.460000,__ +packet,126.543000,K_ +packet,126.585000,__ +packet,126.627000,__ +packet,126.835000,__ +packet,126.710000,__ +packet,126.668000,__ +packet,126.752000,__ +packet,126.793000,__ +packet,126.960000,__ +packet,126.877000,__ +packet,126.918000,__ +packet,127.127000,__ +packet,127.044000,__ +packet,127.002000,__ +packet,127.085000,__ +packet,127.169000,__ +packet,127.336000,__ +packet,127.252000,__ +packet,127.210000,__ +packet,127.294000,__ +packet,127.461000,__ +packet,127.377000,__ +packet,127.419000,__ +packet,127.628000,__ +packet,127.544000,__ +packet,127.502000,__ +packet,127.586000,__ +packet,127.753000,__ +packet,127.669000,__ +packet,127.711000,__ +packet,127.878000,__ +packet,127.794000,__ +packet,127.836000,__ +packet,128.045000,__ +packet,127.961000,__ +packet,127.919000,__ +packet,128.003000,__ +packet,128.253000,__ +packet,128.128000,__ +packet,128.086000,__ +packet,128.170000,__ +packet,128.211000,__ +packet,128.295000,__ +packet,128.503000,__ +packet,128.378000,__ +packet,128.337000,__ +packet,128.420000,__ +packet,128.462000,__ +packet,128.545000,__ +packet,128.670000,__ +packet,128.587000,__ +packet,128.629000,__ +packet,128.795000,__ +packet,128.712000,__ +packet,128.754000,__ +packet,129.004000,__ +packet,128.879000,__ +packet,128.837000,__ +packet,128.920000,__ +packet,128.962000,__ +packet,129.171000,__ +packet,129.087000,__ +packet,129.046000,__ +packet,129.129000,__ +packet,129.212000,__ +packet,129.296000,__ +packet,129.254000,__ +packet,129.379000,__ +packet,129.338000,__ +packet,129.504000,__ +packet,129.421000,__ +packet,129.463000,__ +packet,129.671000,__ +packet,129.588000,__ +packet,129.546000,__ +packet,129.630000,__ +packet,129.838000,__ +packet,129.755000,__ +packet,129.713000,__ +packet,129.796000,__ +packet,129.921000,__ +packet,129.880000,__ +packet,129.963000,__ +packet,130.172000,__ +packet,130.047000,__ +packet,130.005000,__ +packet,130.088000,__ +packet,130.130000,__ +packet,130.255000,__ +packet,130.213000,__ +packet,130.422000,__ +packet,130.339000,__ +packet,130.297000,__ +packet,130.380000,__ +packet,130.505000,__ +packet,130.464000,__ +packet,130.547000,K_ +packet,130.714000,__ +packet,130.631000,__ +packet,130.589000,__ +packet,130.672000,__ +packet,131.048000,__ +packet,130.881000,__ +packet,130.756000,__ +packet,130.797000,__ +packet,130.839000,__ +packet,130.922000,__ +packet,130.964000,__ +packet,131.006000,__ +packet,131.089000,__ +packet,131.173000,__ +packet,131.131000,__ +packet,131.298000,__ +packet,131.214000,__ +packet,131.256000,__ +packet,131.340000,__ +packet,131.381000,__ +packet,131.548000,__ +packet,131.465000,__ +packet,131.423000,__ +packet,131.506000,__ +packet,131.923000,__ +packet,131.715000,__ +packet,131.590000,__ +packet,131.632000,__ +packet,131.673000,__ +packet,131.757000,__ +packet,131.798000,__ +packet,131.840000,__ +packet,131.882000,__ +packet,132.132000,__ +packet,132.007000,__ +packet,131.965000,__ +packet,132.049000,__ +packet,132.090000,__ +packet,132.341000,__ +packet,132.215000,__ +packet,132.174000,__ +packet,132.257000,__ +packet,132.299000,__ +packet,132.382000,__ +packet,132.549000,__ +packet,132.466000,__ +packet,132.424000,__ +packet,132.507000,__ +packet,132.716000,__ +packet,132.633000,__ +packet,132.591000,__ +packet,132.674000,__ +packet,132.883000,__ +packet,132.799000,__ +packet,132.758000,__ +packet,132.841000,__ +packet,133.216000,__ +packet,133.050000,__ +packet,132.924000,__ +packet,132.966000,__ +packet,133.008000,__ +packet,133.091000,__ +packet,133.133000,__ +packet,133.175000,__ +packet,133.342000,__ +packet,133.258000,__ +packet,133.300000,__ +packet,133.383000,K_ +packet,133.759000,__ +packet,133.550000,__ +packet,133.425000,__ +packet,133.467000,__ +packet,133.508000,__ +packet,133.592000,__ +packet,133.634000,__ +packet,133.675000,__ +packet,133.717000,__ +packet,134.092000,__ +packet,133.925000,__ +packet,133.800000,__ +packet,133.842000,__ +packet,133.884000,__ +packet,133.967000,__ +packet,134.009000,__ +packet,134.051000,__ +packet,134.468000,__ +packet,134.259000,__ +packet,134.134000,__ +packet,134.176000,__ +packet,134.217000,__ +packet,134.301000,__ +packet,134.343000,__ +packet,134.384000,__ +packet,134.426000,__ +packet,134.509000,__ +packet,134.551000,K_ +packet,134.718000,__ +packet,134.635000,__ +packet,134.593000,__ +packet,134.676000,__ +packet,134.885000,__ +packet,134.801000,__ +packet,134.760000,__ +packet,134.843000,__ +packet,135.052000,__ +packet,134.968000,__ +packet,134.926000,__ +packet,135.010000,__ +packet,135.218000,__ +packet,135.135000,__ +packet,135.093000,__ +packet,135.177000,__ +packet,135.385000,__ +packet,135.302000,__ +packet,135.260000,__ +packet,135.344000,__ +packet,135.469000,__ +packet,135.427000,__ +packet,135.636000,__ +packet,135.552000,__ +packet,135.510000,__ +packet,135.594000,__ +packet,135.677000,K_ +packet,136.053000,__ +packet,135.844000,__ +packet,135.719000,__ +packet,135.761000,__ +packet,135.802000,__ +packet,135.886000,__ +packet,135.927000,__ +packet,135.969000,__ +packet,136.011000,__ +packet,136.303000,__ +packet,136.178000,__ +packet,136.094000,__ +packet,136.136000,__ +packet,136.219000,__ +packet,136.261000,__ +packet,136.678000,__ +packet,136.470000,__ +packet,136.345000,__ +packet,136.386000,__ +packet,136.428000,__ +packet,136.511000,__ +packet,136.553000,__ +packet,136.595000,__ +packet,136.637000,__ +packet,136.803000,__ +packet,136.720000,__ +packet,136.762000,__ +packet,136.845000,K_ +packet,136.928000,__ +packet,136.887000,__ +packet,137.012000,__ +packet,136.970000,__ +packet,137.137000,__ +packet,137.054000,__ +packet,137.095000,__ +packet,137.387000,__ +packet,137.262000,__ +packet,137.179000,__ +packet,137.220000,__ +packet,137.304000,__ +packet,137.346000,__ +packet,137.596000,__ +packet,137.471000,__ +packet,137.429000,__ +packet,137.512000,__ +packet,137.554000,__ +packet,137.721000,__ +packet,137.638000,__ +packet,137.679000,__ +packet,138.013000,__ +packet,137.846000,__ +packet,137.763000,__ +packet,137.804000,__ +packet,137.888000,__ +packet,137.929000,__ +packet,137.971000,__ +packet,138.138000,__ +packet,138.055000,__ +packet,138.096000,__ +packet,138.430000,__ +packet,138.263000,__ +packet,138.180000,__ +packet,138.221000,__ +packet,138.305000,__ +packet,138.347000,__ +packet,138.388000,__ +packet,138.680000,__ +packet,138.555000,__ +packet,138.472000,__ +packet,138.513000,__ +packet,138.597000,__ +packet,138.639000,__ +packet,138.972000,__ +packet,138.805000,__ +packet,138.722000,__ +packet,138.764000,__ +packet,138.847000,__ +packet,138.889000,__ +packet,138.930000,__ +packet,139.056000,__ +packet,139.014000,__ +packet,139.431000,__ +packet,139.222000,__ +packet,139.097000,__ +packet,139.139000,__ +packet,139.181000,__ +packet,139.264000,__ +packet,139.306000,__ +packet,139.348000,__ +packet,139.389000,__ +packet,139.723000,__ +packet,139.556000,__ +packet,139.473000,__ +packet,139.514000,__ +packet,139.598000,__ +packet,139.640000,__ +packet,139.681000,__ +packet,139.806000,__ +packet,139.765000,__ +packet,139.973000,__ +packet,139.890000,__ +packet,139.848000,__ +packet,139.931000,__ +packet,140.223000,__ +packet,140.098000,__ +packet,140.015000,__ +packet,140.057000,__ +packet,140.140000,__ +packet,140.182000,__ +packet,140.307000,__ +packet,140.265000,__ +packet,140.474000,__ +packet,140.390000,__ +packet,140.349000,__ +packet,140.432000,__ +packet,140.849000,__ +packet,140.641000,__ +packet,140.515000,__ +packet,140.557000,__ +packet,140.599000,__ +packet,140.682000,__ +packet,140.724000,__ +packet,140.766000,__ +packet,140.807000,__ +packet,141.224000,__ +packet,141.016000,__ +packet,140.891000,__ +packet,140.932000,__ +packet,140.974000,__ +packet,141.058000,__ +packet,141.099000,__ +packet,141.141000,__ +packet,141.183000,__ +packet,141.600000,__ +packet,141.391000,__ +packet,141.266000,__ +packet,141.308000,__ +packet,141.350000,__ +packet,141.433000,__ +packet,141.475000,__ +packet,141.516000,__ +packet,141.558000,__ +packet,141.975000,__ +packet,141.767000,__ +packet,141.642000,__ +packet,141.683000,__ +packet,141.725000,__ +packet,141.808000,__ +packet,141.850000,__ +packet,141.892000,__ +packet,141.933000,__ +packet,142.351000,__ +packet,142.142000,__ +packet,142.017000,__ +packet,142.059000,__ +packet,142.100000,__ +packet,142.184000,__ +packet,142.225000,__ +packet,142.267000,__ +packet,142.309000,__ +packet,142.643000,__ +packet,142.476000,__ +packet,142.392000,__ +packet,142.434000,__ +packet,142.517000,__ +packet,142.559000,__ +packet,142.601000,__ +packet,142.684000,__ +packet,142.726000,K_ +packet,142.893000,__ +packet,142.809000,__ +packet,142.768000,__ +packet,142.851000,__ +packet,143.060000,__ +packet,142.976000,__ +packet,142.934000,__ +packet,143.018000,__ +packet,143.435000,__ +packet,143.226000,__ +packet,143.101000,__ +packet,143.143000,__ +packet,143.185000,__ +packet,143.268000,__ +packet,143.310000,__ +packet,143.352000,__ +packet,143.393000,__ +packet,143.477000,__ +packet,143.560000,__ +packet,143.518000,__ +packet,143.727000,__ +packet,143.644000,__ +packet,143.602000,__ +packet,143.685000,__ +packet,143.935000,__ +packet,143.810000,__ +packet,143.769000,__ +packet,143.852000,__ +packet,143.894000,__ +packet,144.102000,__ +packet,144.019000,__ +packet,143.977000,__ +packet,144.061000,__ +packet,144.144000,__ +packet,144.353000,__ +packet,144.227000,__ +packet,144.186000,__ +packet,144.269000,__ +packet,144.311000,__ +packet,144.478000,__ +packet,144.394000,__ +packet,144.436000,__ +packet,144.561000,__ +packet,144.519000,__ +packet,144.770000,__ +packet,144.645000,__ +packet,144.603000,__ +packet,144.686000,__ +packet,144.728000,__ +packet,144.936000,__ +packet,144.853000,__ +packet,144.811000,__ +packet,144.895000,__ +packet,145.020000,__ +packet,144.978000,__ +packet,145.187000,__ +packet,145.103000,__ +packet,145.062000,__ +packet,145.145000,__ +packet,145.562000,__ +packet,145.354000,__ +packet,145.228000,__ +packet,145.270000,__ +packet,145.312000,__ +packet,145.395000,__ +packet,145.437000,__ +packet,145.479000,__ +packet,145.520000,__ +packet,145.729000,__ +packet,145.646000,__ +packet,145.604000,__ +packet,145.687000,__ +packet,145.937000,__ +packet,145.812000,__ +packet,145.771000,__ +packet,145.854000,__ +packet,145.896000,__ +packet,146.021000,__ +packet,145.979000,__ +packet,146.063000,K_ +packet,146.313000,__ +packet,146.188000,__ +packet,146.104000,__ +packet,146.146000,__ +packet,146.229000,__ +packet,146.271000,__ +packet,146.438000,__ +packet,146.355000,__ +packet,146.396000,__ +packet,146.521000,__ +packet,146.480000,__ +packet,146.688000,__ +packet,146.605000,__ +packet,146.563000,__ +packet,146.647000,__ +packet,146.855000,__ +packet,146.772000,__ +packet,146.730000,__ +packet,146.813000,__ +packet,147.064000,__ +packet,146.938000,__ +packet,146.897000,__ +packet,146.980000,__ +packet,147.022000,__ +packet,147.189000,__ +packet,147.105000,__ +packet,147.147000,__ +packet,147.314000,__ +packet,147.230000,__ +packet,147.272000,__ +packet,147.481000,__ +packet,147.397000,__ +packet,147.356000,__ +packet,147.439000,__ +packet,147.522000,__ +packet,147.731000,__ +packet,147.606000,__ +packet,147.564000,__ +packet,147.648000,__ +packet,147.689000,__ +packet,147.898000,__ +packet,147.814000,__ +packet,147.773000,__ +packet,147.856000,__ +packet,148.065000,__ +packet,147.981000,__ +packet,147.939000,__ +packet,148.023000,__ +packet,148.106000,__ +packet,148.315000,__ +packet,148.190000,__ +packet,148.148000,__ +packet,148.231000,__ +packet,148.273000,__ +packet,148.482000,__ +packet,148.398000,__ +packet,148.357000,__ +packet,148.440000,__ +packet,148.649000,__ +packet,148.565000,__ +packet,148.523000,__ +packet,148.607000,__ +packet,148.815000,__ +packet,148.732000,__ +packet,148.690000,__ +packet,148.774000,__ +packet,148.982000,__ +packet,148.899000,__ +packet,148.857000,__ +packet,148.940000,__ +packet,149.107000,__ +packet,149.024000,__ +packet,149.066000,__ +packet,149.358000,__ +packet,149.232000,__ +packet,149.149000,__ +packet,149.191000,__ +packet,149.274000,__ +packet,149.316000,__ +packet,149.608000,__ +packet,149.483000,__ +packet,149.399000,__ +packet,149.441000,__ +packet,149.524000,__ +packet,149.566000,__ +packet,149.733000,__ +packet,149.650000,__ +packet,149.691000,__ +packet,149.941000,__ +packet,149.816000,__ +packet,149.775000,__ +packet,149.858000,__ +packet,149.900000,__ +packet,150.025000,__ +packet,149.983000,__ +packet,150.067000,K_ +packet,150.108000,__ +packet,150.275000,__ +packet,150.192000,__ +packet,150.150000,__ +packet,150.233000,__ +packet,150.400000,__ +packet,150.317000,__ +packet,150.359000,__ +packet,150.484000,__ +packet,150.442000,__ +packet,150.609000,__ +packet,150.525000,__ +packet,150.567000,__ +packet,150.984000,__ +packet,150.776000,__ +packet,150.651000,__ +packet,150.692000,__ +packet,150.734000,__ +packet,150.817000,__ +packet,150.859000,__ +packet,150.901000,__ +packet,150.942000,__ +packet,151.109000,__ +packet,151.026000,__ +packet,151.068000,__ +packet,151.485000,__ +packet,151.276000,__ +packet,151.151000,__ +packet,151.193000,__ +packet,151.234000,__ +packet,151.318000,__ +packet,151.360000,__ +packet,151.401000,__ +packet,151.443000,__ +packet,151.693000,__ +packet,151.568000,__ +packet,151.526000,__ +packet,151.610000,__ +packet,151.652000,__ +packet,151.860000,__ +packet,151.777000,__ +packet,151.735000,__ +packet,151.818000,__ +packet,152.235000,__ +packet,152.027000,__ +packet,151.902000,__ +packet,151.943000,__ +packet,151.985000,__ +packet,152.069000,__ +packet,152.110000,__ +packet,152.152000,__ +packet,152.194000,__ +packet,152.361000,__ +packet,152.277000,__ +packet,152.319000,__ +packet,152.486000,__ +packet,152.402000,__ +packet,152.444000,__ +packet,152.694000,__ +packet,152.569000,__ +packet,152.527000,__ +packet,152.611000,__ +packet,152.653000,__ +packet,152.944000,__ +packet,152.819000,__ +packet,152.736000,__ +packet,152.778000,__ +packet,152.861000,__ +packet,152.903000,__ +packet,153.111000,__ +packet,153.028000,__ +packet,152.986000,__ +packet,153.070000,__ +packet,153.487000,__ +packet,153.278000,__ +packet,153.153000,__ +packet,153.195000,__ +packet,153.236000,__ +packet,153.320000,__ +packet,153.362000,__ +packet,153.403000,__ +packet,153.445000,__ +packet,153.654000,__ +packet,153.570000,__ +packet,153.528000,__ +packet,153.612000,__ +packet,153.862000,__ +packet,153.737000,__ +packet,153.695000,__ +packet,153.779000,__ +packet,153.820000,__ +packet,154.029000,__ +packet,153.945000,__ +packet,153.904000,__ +packet,153.987000,__ +packet,154.071000,K_ +packet,154.237000,__ +packet,154.154000,__ +packet,154.112000,__ +packet,154.196000,__ +packet,154.404000,__ +packet,154.321000,__ +packet,154.279000,__ +packet,154.363000,__ +packet,154.571000,__ +packet,154.488000,__ +packet,154.446000,__ +packet,154.529000,__ +packet,154.946000,__ +packet,154.738000,__ +packet,154.613000,__ +packet,154.655000,__ +packet,154.696000,__ +packet,154.780000,__ +packet,154.821000,__ +packet,154.863000,__ +packet,154.905000,__ +packet,154.988000,__ +packet,155.113000,__ +packet,155.030000,__ +packet,155.072000,__ +packet,155.238000,__ +packet,155.155000,__ +packet,155.197000,__ +packet,155.364000,__ +packet,155.280000,__ +packet,155.322000,__ +packet,155.489000,__ +packet,155.405000,__ +packet,155.447000,__ +packet,155.614000,__ +packet,155.530000,__ +packet,155.572000,__ +packet,155.781000,__ +packet,155.697000,__ +packet,155.656000,__ +packet,155.739000,__ +packet,156.114000,__ +packet,155.947000,__ +packet,155.822000,__ +packet,155.864000,__ +packet,155.906000,__ +packet,155.989000,__ +packet,156.031000,__ +packet,156.073000,__ +packet,156.281000,__ +packet,156.198000,__ +packet,156.156000,__ +packet,156.239000,__ +packet,156.448000,__ +packet,156.365000,__ +packet,156.323000,__ +packet,156.406000,__ +packet,156.615000,__ +packet,156.531000,__ +packet,156.490000,__ +packet,156.573000,__ +packet,156.740000,__ +packet,156.657000,__ +packet,156.698000,__ +packet,156.907000,__ +packet,156.823000,__ +packet,156.782000,__ +packet,156.865000,__ +packet,157.115000,__ +packet,156.990000,__ +packet,156.948000,__ +packet,157.032000,__ +packet,157.074000,__ +packet,157.366000,__ +packet,157.240000,__ +packet,157.157000,__ +packet,157.199000,__ +packet,157.282000,__ +packet,157.324000,__ +packet,157.532000,__ +packet,157.449000,__ +packet,157.407000,__ +packet,157.491000,__ +packet,157.866000,__ +packet,157.699000,__ +packet,157.574000,__ +packet,157.616000,__ +packet,157.658000,__ +packet,157.741000,__ +packet,157.783000,__ +packet,157.824000,__ +packet,158.075000,__ +packet,157.949000,__ +packet,157.908000,__ +packet,157.991000,__ +packet,158.033000,__ +packet,158.367000,__ +packet,158.200000,__ +packet,158.116000,__ +packet,158.158000,__ +packet,158.241000,__ +packet,158.283000,__ +packet,158.325000,__ +packet,158.408000,__ +packet,158.450000,K_ +packet,158.492000,__ +packet,158.533000,__ +packet,158.575000,__ +packet,158.784000,__ +packet,158.659000,__ +packet,158.617000,__ +packet,158.700000,__ +packet,158.742000,__ +packet,158.909000,__ +packet,158.825000,__ +packet,158.867000,__ +packet,159.034000,__ +packet,158.950000,__ +packet,158.992000,__ +packet,159.117000,__ +packet,159.076000,__ +packet,159.368000,__ +packet,159.242000,__ +packet,159.159000,__ +packet,159.201000,__ +packet,159.284000,__ +packet,159.326000,__ +packet,159.743000,__ +packet,159.534000,__ +packet,159.409000,__ +packet,159.451000,__ +packet,159.493000,__ +packet,159.576000,__ +packet,159.618000,__ +packet,159.660000,__ +packet,159.701000,__ +packet,159.785000,__ +packet,159.993000,__ +packet,159.868000,__ +packet,159.826000,__ +packet,159.910000,__ +packet,159.951000,__ +packet,160.118000,__ +packet,160.035000,__ +packet,160.077000,__ +packet,160.410000,__ +packet,160.243000,__ +packet,160.160000,__ +packet,160.202000,__ +packet,160.285000,__ +packet,160.327000,__ +packet,160.369000,__ +packet,160.619000,__ +packet,160.494000,__ +packet,160.452000,__ +packet,160.535000,__ +packet,160.577000,__ +packet,160.661000,__ +packet,160.702000,K_ +packet,160.869000,__ +packet,160.786000,__ +packet,160.744000,__ +packet,160.827000,__ +packet,161.119000,__ +packet,160.994000,__ +packet,160.911000,__ +packet,160.952000,__ +packet,161.036000,__ +packet,161.078000,__ +packet,161.328000,__ +packet,161.203000,__ +packet,161.161000,__ +packet,161.244000,__ +packet,161.286000,__ +packet,161.578000,__ +packet,161.453000,__ +packet,161.370000,__ +packet,161.411000,__ +packet,161.495000,__ +packet,161.536000,__ +packet,161.828000,__ +packet,161.703000,__ +packet,161.620000,__ +packet,161.662000,__ +packet,161.745000,__ +packet,161.787000,__ +packet,162.079000,__ +packet,161.953000,__ +packet,161.870000,__ +packet,161.912000,__ +packet,161.995000,__ +packet,162.037000,__ +packet,162.162000,__ +packet,162.120000,__ +packet,162.245000,__ +packet,162.204000,__ +packet,162.287000,__ +packet,162.412000,__ +packet,162.329000,__ +packet,162.371000,__ +packet,162.579000,__ +packet,162.496000,__ +packet,162.454000,__ +packet,162.537000,__ +packet,162.746000,__ +packet,162.663000,__ +packet,162.621000,__ +packet,162.704000,__ +packet,162.788000,K_ +packet,162.829000,__ +packet,163.205000,__ +packet,162.996000,__ +packet,162.871000,__ +packet,162.913000,__ +packet,162.954000,__ +packet,163.038000,__ +packet,163.080000,__ +packet,163.121000,__ +packet,163.163000,__ +packet,163.455000,__ +packet,163.330000,__ +packet,163.246000,__ +packet,163.288000,__ +packet,163.372000,__ +packet,163.413000,__ +packet,163.622000,__ +packet,163.538000,__ +packet,163.497000,__ +packet,163.580000,__ +packet,163.872000,__ +packet,163.747000,__ +packet,163.664000,__ +packet,163.705000,__ +packet,163.789000,__ +packet,163.830000,__ +packet,163.914000,__ +packet,163.955000,K_ +packet,164.164000,__ +packet,164.039000,__ +packet,163.997000,__ +packet,164.081000,__ +packet,164.122000,__ +packet,164.206000,__ +packet,164.373000,__ +packet,164.289000,__ +packet,164.247000,__ +packet,164.331000,__ +packet,164.581000,__ +packet,164.456000,__ +packet,164.414000,__ +packet,164.498000,__ +packet,164.539000,__ +packet,164.748000,__ +packet,164.665000,__ +packet,164.623000,__ +packet,164.706000,__ +packet,164.915000,__ +packet,164.831000,__ +packet,164.790000,__ +packet,164.873000,__ +packet,165.040000,__ +packet,164.956000,__ +packet,164.998000,__ +packet,165.248000,__ +packet,165.123000,__ +packet,165.082000,__ +packet,165.165000,__ +packet,165.207000,__ +packet,165.415000,__ +packet,165.332000,__ +packet,165.290000,__ +packet,165.374000,__ +packet,165.457000,__ +packet,165.666000,__ +packet,165.540000,__ +packet,165.499000,__ +packet,165.582000,__ +packet,165.624000,__ +packet,165.749000,__ +packet,165.707000,__ +packet,165.916000,__ +packet,165.832000,__ +packet,165.791000,__ +packet,165.874000,__ +packet,165.957000,__ +packet,166.124000,__ +packet,166.041000,__ +packet,165.999000,__ +packet,166.083000,__ +packet,166.375000,__ +packet,166.249000,__ +packet,166.166000,__ +packet,166.208000,__ +packet,166.291000,__ +packet,166.333000,__ +packet,166.416000,__ +packet,166.458000,__ +packet,166.625000,__ +packet,166.541000,__ +packet,166.500000,__ +packet,166.583000,__ +packet,166.792000,__ +packet,166.708000,__ +packet,166.667000,__ +packet,166.750000,__ +packet,166.958000,__ +packet,166.875000,__ +packet,166.833000,__ +packet,166.917000,__ +packet,167.125000,__ +packet,167.042000,__ +packet,167.000000,__ +packet,167.084000,__ +packet,167.167000,__ +packet,167.209000,K_ +packet,167.250000,__ +packet,167.376000,__ +packet,167.292000,__ +packet,167.334000,__ +packet,167.417000,__ +packet,167.501000,__ +packet,167.459000,__ +packet,167.584000,__ +packet,167.542000,__ +packet,167.751000,__ +packet,167.668000,__ +packet,167.626000,__ +packet,167.709000,__ +packet,167.959000,__ +packet,167.834000,__ +packet,167.793000,__ +packet,167.876000,__ +packet,167.918000,__ +packet,168.085000,__ +packet,168.001000,__ +packet,168.043000,__ +packet,168.460000,__ +packet,168.251000,__ +packet,168.126000,__ +packet,168.168000,__ +packet,168.210000,__ +packet,168.293000,__ +packet,168.335000,__ +packet,168.377000,__ +packet,168.418000,__ +packet,168.502000,K_ +packet,168.627000,__ +packet,168.543000,__ +packet,168.585000,__ +packet,168.835000,__ +packet,168.710000,__ +packet,168.669000,__ +packet,168.752000,__ +packet,168.794000,__ +packet,169.127000,__ +packet,168.960000,__ +packet,168.877000,__ +packet,168.919000,__ +packet,169.002000,__ +packet,169.044000,__ +packet,169.086000,__ +packet,169.294000,__ +packet,169.211000,__ +packet,169.169000,__ +packet,169.252000,__ +packet,169.419000,__ +packet,169.336000,__ +packet,169.378000,__ +packet,169.544000,__ +packet,169.461000,__ +packet,169.503000,__ +packet,169.586000,__ +packet,169.878000,__ +packet,169.711000,__ +packet,169.628000,__ +packet,169.670000,__ +packet,169.753000,__ +packet,169.795000,__ +packet,169.836000,__ +packet,170.128000,__ +packet,170.003000,__ +packet,169.920000,__ +packet,169.961000,__ +packet,170.045000,__ +packet,170.087000,__ +packet,170.379000,__ +packet,170.253000,__ +packet,170.170000,__ +packet,170.212000,__ +packet,170.295000,__ +packet,170.337000,__ +packet,170.545000,__ +packet,170.462000,__ +packet,170.420000,__ +packet,170.504000,__ +packet,170.671000,__ +packet,170.587000,__ +packet,170.629000,__ +packet,170.837000,__ +packet,170.754000,__ +packet,170.712000,__ +packet,170.796000,__ +packet,171.046000,__ +packet,170.921000,__ +packet,170.879000,__ +packet,170.962000,__ +packet,171.004000,__ +packet,171.380000,__ +packet,171.213000,__ +packet,171.088000,__ +packet,171.129000,__ +packet,171.171000,__ +packet,171.254000,__ +packet,171.296000,__ +packet,171.338000,__ +packet,171.463000,__ +packet,171.421000,__ +packet,171.505000,__ +packet,171.588000,__ +packet,171.546000,__ +packet,171.672000,__ +packet,171.630000,__ +packet,171.797000,__ +packet,171.713000,__ +packet,171.755000,__ +packet,171.880000,__ +packet,171.838000,__ +packet,172.005000,__ +packet,171.922000,__ +packet,171.963000,__ +packet,172.130000,__ +packet,172.047000,__ +packet,172.089000,__ +packet,172.297000,__ +packet,172.214000,__ +packet,172.172000,__ +packet,172.255000,__ +packet,172.464000,__ +packet,172.381000,__ +packet,172.339000,__ +packet,172.422000,__ +packet,172.756000,__ +packet,172.589000,__ +packet,172.506000,__ +packet,172.547000,__ +packet,172.631000,__ +packet,172.673000,__ +packet,172.714000,__ +packet,172.923000,__ +packet,172.839000,__ +packet,172.798000,__ +packet,172.881000,__ +packet,172.964000,__ +packet,173.006000,__ +packet,173.131000,__ +packet,173.048000,__ +packet,173.090000,__ +packet,173.215000,__ +packet,173.173000,__ +packet,173.256000,__ +packet,173.298000,__ +packet,173.382000,__ +packet,173.340000,__ +packet,173.465000,__ +packet,173.423000,__ +packet,173.507000,__ +packet,173.715000,__ +packet,173.590000,__ +packet,173.548000,__ +packet,173.632000,__ +packet,173.674000,__ +packet,173.882000,__ +packet,173.799000,__ +packet,173.757000,__ +packet,173.840000,__ +packet,174.132000,__ +packet,174.007000,__ +packet,173.924000,__ +packet,173.965000,__ +packet,174.049000,__ +packet,174.091000,__ +packet,174.341000,__ +packet,174.216000,__ +packet,174.174000,__ +packet,174.257000,__ +packet,174.299000,__ +packet,174.508000,__ +packet,174.424000,__ +packet,174.383000,__ +packet,174.466000,__ +packet,174.633000,__ +packet,174.549000,__ +packet,174.591000,__ +packet,174.883000,__ +packet,174.758000,__ +packet,174.675000,__ +packet,174.716000,__ +packet,174.800000,__ +packet,174.841000,__ +packet,175.092000,__ +packet,174.966000,__ +packet,174.925000,__ +packet,175.008000,__ +packet,175.050000,__ +packet,175.384000,__ +packet,175.217000,__ +packet,175.133000,__ +packet,175.175000,__ +packet,175.258000,__ +packet,175.300000,__ +packet,175.342000,__ +packet,175.592000,__ +packet,175.467000,__ +packet,175.425000,__ +packet,175.509000,__ +packet,175.550000,__ +packet,175.717000,__ +packet,175.634000,__ +packet,175.676000,__ +packet,175.884000,__ +packet,175.801000,__ +packet,175.759000,__ +packet,175.842000,__ +packet,176.093000,__ +packet,175.967000,__ +packet,175.926000,__ +packet,176.009000,__ +packet,176.051000,__ +packet,176.259000,__ +packet,176.176000,__ +packet,176.134000,__ +packet,176.218000,__ +packet,176.510000,__ +packet,176.385000,__ +packet,176.301000,__ +packet,176.343000,__ +packet,176.426000,__ +packet,176.468000,__ +packet,176.635000,__ +packet,176.551000,__ +packet,176.593000,__ +packet,176.718000,__ +packet,176.677000,__ +packet,177.010000,__ +packet,176.843000,__ +packet,176.760000,__ +packet,176.802000,__ +packet,176.885000,__ +packet,176.927000,__ +packet,176.968000,__ +packet,177.260000,__ +packet,177.135000,__ +packet,177.052000,__ +packet,177.094000,__ +packet,177.177000,__ +packet,177.219000,__ +packet,177.427000,__ +packet,177.344000,__ +packet,177.302000,__ +packet,177.386000,__ +packet,177.803000,__ +packet,177.594000,__ +packet,177.469000,__ +packet,177.511000,__ +packet,177.552000,__ +packet,177.636000,__ +packet,177.678000,__ +packet,177.719000,__ +packet,177.761000,__ +packet,177.844000,__ +packet,178.095000,__ +packet,177.969000,__ +packet,177.886000,__ +packet,177.928000,__ +packet,178.011000,__ +packet,178.053000,__ +packet,178.261000,__ +packet,178.178000,__ +packet,178.136000,__ +packet,178.220000,__ +packet,178.428000,__ +packet,178.345000,__ +packet,178.303000,__ +packet,178.387000,__ +packet,178.762000,__ +packet,178.595000,__ +packet,178.470000,__ +packet,178.512000,__ +packet,178.553000,__ +packet,178.637000,__ +packet,178.679000,__ +packet,178.720000,__ +packet,178.887000,__ +packet,178.804000,__ +packet,178.845000,__ +packet,178.929000,K_ +packet,179.012000,__ +packet,178.970000,__ +packet,179.221000,__ +packet,179.096000,__ +packet,179.054000,__ +packet,179.137000,__ +packet,179.179000,__ +packet,179.388000,__ +packet,179.304000,__ +packet,179.262000,__ +packet,179.346000,__ +packet,179.638000,__ +packet,179.513000,__ +packet,179.429000,__ +packet,179.471000,__ +packet,179.554000,__ +packet,179.596000,__ +packet,179.888000,__ +packet,179.763000,__ +packet,179.680000,__ +packet,179.721000,__ +packet,179.805000,__ +packet,179.846000,__ +packet,180.097000,__ +packet,179.971000,__ +packet,179.930000,__ +packet,180.013000,__ +packet,180.055000,__ +packet,180.138000,__ +packet,180.263000,__ +packet,180.180000,__ +packet,180.222000,__ +packet,180.389000,__ +packet,180.305000,__ +packet,180.347000,__ +packet,180.555000,__ +packet,180.472000,__ +packet,180.430000,__ +packet,180.514000,__ +packet,180.847000,__ +packet,180.681000,__ +packet,180.597000,__ +packet,180.639000,__ +packet,180.722000,__ +packet,180.764000,__ +packet,180.806000,__ +packet,181.014000,__ +packet,180.931000,__ +packet,180.889000,__ +packet,180.972000,__ +packet,181.139000,__ +packet,181.056000,__ +packet,181.098000,__ +packet,181.348000,__ +packet,181.223000,__ +packet,181.181000,__ +packet,181.264000,__ +packet,181.306000,__ +packet,181.473000,__ +packet,181.390000,__ +packet,181.431000,__ +packet,181.640000,__ +packet,181.556000,__ +packet,181.515000,__ +packet,181.598000,__ +packet,181.765000,__ +packet,181.682000,__ +packet,181.723000,__ +packet,182.057000,__ +packet,181.890000,__ +packet,181.807000,__ +packet,181.848000,__ +packet,181.932000,__ +packet,181.973000,__ +packet,182.015000,__ +packet,182.224000,__ +packet,182.140000,__ +packet,182.099000,__ +packet,182.182000,__ +packet,182.391000,__ +packet,182.307000,__ +packet,182.265000,__ +packet,182.349000,__ +packet,182.474000,__ +packet,182.432000,__ +packet,182.641000,__ +packet,182.557000,__ +packet,182.516000,__ +packet,182.599000,__ +packet,183.016000,__ +packet,182.808000,__ +packet,182.683000,__ +packet,182.724000,__ +packet,182.766000,__ +packet,182.849000,__ +packet,182.891000,__ +packet,182.933000,__ +packet,182.974000,__ +packet,183.266000,__ +packet,183.141000,__ +packet,183.058000,__ +packet,183.100000,__ +packet,183.183000,__ +packet,183.225000,__ +packet,183.475000,__ +packet,183.350000,__ +packet,183.308000,__ +packet,183.392000,__ +packet,183.433000,__ +packet,183.725000,__ +packet,183.600000,__ +packet,183.517000,__ +packet,183.558000,__ +packet,183.642000,__ +packet,183.684000,__ +packet,183.850000,__ +packet,183.767000,__ +packet,183.809000,__ +packet,184.142000,__ +packet,183.975000,__ +packet,183.892000,__ +packet,183.934000,__ +packet,184.017000,__ +packet,184.059000,__ +packet,184.101000,__ +packet,184.351000,__ +packet,184.226000,__ +packet,184.184000,__ +packet,184.267000,__ +packet,184.309000,__ +packet,184.476000,__ +packet,184.393000,__ +packet,184.434000,__ +packet,184.559000,__ +packet,184.518000,__ +packet,184.810000,__ +packet,184.685000,__ +packet,184.601000,__ +packet,184.643000,__ +packet,184.726000,__ +packet,184.768000,__ +packet,184.976000,__ +packet,184.893000,__ +packet,184.851000,__ +packet,184.935000,__ +packet,185.060000,__ +packet,185.018000,__ +packet,185.227000,__ +packet,185.143000,__ +packet,185.102000,__ +packet,185.185000,__ +packet,185.394000,__ +packet,185.310000,__ +packet,185.268000,__ +packet,185.352000,__ +packet,185.602000,__ +packet,185.477000,__ +packet,185.435000,__ +packet,185.519000,__ +packet,185.560000,__ +packet,185.644000,__ +packet,185.894000,__ +packet,185.769000,__ +packet,185.686000,__ +packet,185.727000,__ +packet,185.811000,__ +packet,185.852000,__ +packet,186.144000,__ +packet,186.019000,__ +packet,185.936000,__ +packet,185.977000,__ +packet,186.061000,__ +packet,186.103000,__ +packet,186.353000,__ +packet,186.228000,__ +packet,186.186000,__ +packet,186.269000,__ +packet,186.311000,__ +packet,186.436000,__ +packet,186.395000,__ +packet,186.603000,__ +packet,186.520000,__ +packet,186.478000,__ +packet,186.561000,__ +packet,186.645000,__ +packet,187.020000,__ +packet,186.812000,__ +packet,186.687000,__ +packet,186.728000,__ +packet,186.770000,__ +packet,186.853000,__ +packet,186.895000,__ +packet,186.937000,__ +packet,186.978000,__ +packet,187.062000,__ +packet,187.396000,__ +packet,187.229000,__ +packet,187.104000,__ +packet,187.145000,__ +packet,187.187000,__ +packet,187.270000,__ +packet,187.312000,__ +packet,187.354000,__ +packet,187.604000,__ +packet,187.479000,__ +packet,187.437000,__ +packet,187.521000,__ +packet,187.562000,__ +packet,187.646000,__ +packet,187.896000,__ +packet,187.771000,__ +packet,187.688000,__ +packet,187.729000,__ +packet,187.813000,__ +packet,187.854000,__ +packet,188.230000,__ +packet,188.063000,__ +packet,187.938000,__ +packet,187.979000,__ +packet,188.021000,__ +packet,188.105000,__ +packet,188.146000,__ +packet,188.188000,__ +packet,188.271000,__ +packet,188.313000,K_ +packet,188.480000,__ +packet,188.397000,__ +packet,188.355000,__ +packet,188.438000,__ +packet,188.730000,__ +packet,188.605000,__ +packet,188.522000,__ +packet,188.563000,__ +packet,188.647000,__ +packet,188.689000,__ +packet,188.897000,__ +packet,188.814000,__ +packet,188.772000,__ +packet,188.855000,__ +packet,189.106000,__ +packet,188.980000,__ +packet,188.939000,__ +packet,189.022000,__ +packet,189.064000,__ +packet,189.481000,__ +packet,189.272000,__ +packet,189.147000,__ +packet,189.189000,__ +packet,189.231000,__ +packet,189.314000,__ +packet,189.356000,__ +packet,189.398000,__ +packet,189.439000,__ +packet,189.856000,__ +packet,189.648000,__ +packet,189.523000,__ +packet,189.564000,__ +packet,189.606000,__ +packet,189.690000,__ +packet,189.731000,__ +packet,189.773000,__ +packet,189.815000,__ +packet,190.023000,__ +packet,189.940000,__ +packet,189.898000,__ +packet,189.981000,__ +packet,190.148000,__ +packet,190.065000,__ +packet,190.107000,__ +packet,190.357000,__ +packet,190.232000,__ +packet,190.190000,__ +packet,190.273000,__ +packet,190.315000,__ +packet,190.732000,__ +packet,190.524000,__ +packet,190.399000,__ +packet,190.440000,__ +packet,190.482000,__ +packet,190.565000,__ +packet,190.607000,__ +packet,190.649000,__ +packet,190.691000,__ +packet,190.941000,__ +packet,190.816000,__ +packet,190.774000,__ +packet,190.857000,__ +packet,190.899000,__ +packet,190.982000,K_ +packet,191.066000,__ +packet,191.024000,__ +packet,191.233000,__ +packet,191.149000,__ +packet,191.108000,__ +packet,191.191000,__ +packet,191.400000,__ +packet,191.316000,__ +packet,191.274000,__ +packet,191.358000,__ +packet,191.525000,__ +packet,191.441000,__ +packet,191.483000,__ +packet,191.692000,__ +packet,191.608000,__ +packet,191.566000,__ +packet,191.650000,__ +packet,191.858000,__ +packet,191.775000,__ +packet,191.733000,__ +packet,191.817000,__ +packet,191.900000,__ +packet,191.983000,__ +packet,191.942000,__ +packet,192.109000,__ +packet,192.025000,__ +packet,192.067000,__ +packet,192.150000,__ +packet,192.234000,__ +packet,192.192000,__ +packet,192.317000,__ +packet,192.275000,__ +packet,192.401000,__ +packet,192.359000,__ +packet,192.526000,__ +packet,192.442000,__ +packet,192.484000,__ +packet,192.609000,__ +packet,192.567000,__ +packet,192.651000,__ +packet,192.734000,__ +packet,192.693000,__ +packet,192.818000,__ +packet,192.776000,__ +packet,192.901000,__ +packet,192.859000,__ +packet,193.068000,__ +packet,192.984000,__ +packet,192.943000,__ +packet,193.026000,__ +packet,193.110000,__ +packet,193.151000,K_ +packet,193.318000,__ +packet,193.235000,__ +packet,193.193000,__ +packet,193.276000,__ +packet,193.443000,__ +packet,193.360000,__ +packet,193.402000,__ +packet,193.527000,__ +packet,193.485000,__ +packet,193.694000,__ +packet,193.610000,__ +packet,193.568000,__ +packet,193.652000,__ +packet,193.777000,__ +packet,193.735000,__ +packet,193.944000,__ +packet,193.860000,__ +packet,193.819000,__ +packet,193.902000,__ +packet,194.027000,__ +packet,193.985000,__ +packet,194.152000,__ +packet,194.069000,__ +packet,194.111000,__ +packet,194.361000,__ +packet,194.236000,__ +packet,194.194000,__ +packet,194.277000,__ +packet,194.319000,__ +packet,194.528000,__ +packet,194.444000,__ +packet,194.403000,__ +packet,194.486000,__ +packet,194.778000,__ +packet,194.653000,__ +packet,194.569000,__ +packet,194.611000,__ +packet,194.695000,__ +packet,194.736000,__ +packet,195.112000,__ +packet,194.945000,__ +packet,194.820000,__ +packet,194.861000,__ +packet,194.903000,__ +packet,194.986000,__ +packet,195.028000,__ +packet,195.070000,__ +packet,195.362000,__ +packet,195.237000,__ +packet,195.153000,__ +packet,195.195000,__ +packet,195.278000,__ +packet,195.320000,__ +packet,195.487000,__ +packet,195.404000,__ +packet,195.445000,__ +packet,195.654000,__ +packet,195.570000,__ +packet,195.529000,__ +packet,195.612000,__ +packet,195.862000,__ +packet,195.737000,__ +packet,195.696000,__ +packet,195.779000,__ +packet,195.821000,__ +packet,195.946000,__ +packet,195.904000,__ +packet,196.029000,__ +packet,195.987000,__ +packet,196.154000,__ +packet,196.071000,__ +packet,196.113000,__ +packet,196.363000,__ +packet,196.238000,__ +packet,196.196000,__ +packet,196.279000,__ +packet,196.321000,__ +packet,196.488000,__ +packet,196.405000,__ +packet,196.446000,__ +packet,196.655000,__ +packet,196.571000,__ +packet,196.530000,__ +packet,196.613000,__ +packet,196.822000,__ +packet,196.738000,__ +packet,196.697000,__ +packet,196.780000,__ +packet,196.988000,__ +packet,196.905000,__ +packet,196.863000,__ +packet,196.947000,__ +packet,197.155000,__ +packet,197.072000,__ +packet,197.030000,__ +packet,197.114000,__ +packet,197.239000,__ +packet,197.197000,__ +packet,197.364000,__ +packet,197.280000,__ +packet,197.322000,__ +packet,197.489000,__ +packet,197.406000,__ +packet,197.447000,__ +packet,197.614000,__ +packet,197.531000,__ +packet,197.572000,__ +packet,197.656000,__ +packet,197.906000,__ +packet,197.781000,__ +packet,197.698000,__ +packet,197.739000,__ +packet,197.823000,__ +packet,197.864000,__ +packet,198.115000,__ +packet,197.989000,__ +packet,197.948000,__ +packet,198.031000,__ +packet,198.073000,__ +packet,198.281000,__ +packet,198.198000,__ +packet,198.156000,__ +packet,198.240000,__ +packet,198.448000,__ +packet,198.365000,__ +packet,198.323000,__ +packet,198.407000,__ +packet,198.615000,__ +packet,198.532000,__ +packet,198.490000,__ +packet,198.573000,__ +packet,198.740000,__ +packet,198.657000,__ +packet,198.699000,__ +packet,198.949000,__ +packet,198.824000,__ +packet,198.782000,__ +packet,198.865000,__ +packet,198.907000,__ +packet,199.074000,__ +packet,198.990000,__ +packet,199.032000,__ +packet,199.157000,__ +packet,199.116000,__ +packet,199.408000,__ +packet,199.282000,__ +packet,199.199000,__ +packet,199.241000,__ +packet,199.324000,__ +packet,199.366000,__ +packet,199.616000,__ +packet,199.491000,__ +packet,199.449000,__ +packet,199.533000,__ +packet,199.574000,__ +packet,199.658000,K_ +packet,199.825000,__ +packet,199.741000,__ +packet,199.700000,__ +packet,199.783000,__ +packet,199.991000,__ +packet,199.908000,__ +packet,199.866000,__ +packet,199.950000,__ +packet,200.158000,__ +packet,200.075000,__ +packet,200.033000,__ +packet,200.117000,__ +packet,200.325000,__ +packet,200.242000,__ +packet,200.200000,__ +packet,200.283000,__ +packet,200.492000,__ +packet,200.409000,__ +packet,200.367000,__ +packet,200.450000,__ +packet,200.659000,__ +packet,200.575000,__ +packet,200.534000,__ +packet,200.617000,__ +packet,200.826000,__ +packet,200.742000,__ +packet,200.701000,__ +packet,200.784000,__ +packet,200.867000,__ +packet,201.118000,__ +packet,200.992000,__ +packet,200.909000,__ +packet,200.951000,__ +packet,201.034000,__ +packet,201.076000,__ +packet,201.493000,__ +packet,201.284000,__ +packet,201.159000,__ +packet,201.201000,__ +packet,201.243000,__ +packet,201.326000,__ +packet,201.368000,__ +packet,201.410000,__ +packet,201.451000,__ +packet,201.660000,__ +packet,201.576000,__ +packet,201.535000,__ +packet,201.618000,__ +packet,201.702000,K_ +packet,201.868000,__ +packet,201.785000,__ +packet,201.743000,__ +packet,201.827000,__ +packet,202.035000,__ +packet,201.952000,__ +packet,201.910000,__ +packet,201.993000,__ +packet,202.077000,__ +packet,202.285000,__ +packet,202.160000,__ +packet,202.119000,__ +packet,202.202000,__ +packet,202.244000,__ +packet,202.452000,__ +packet,202.369000,__ +packet,202.327000,__ +packet,202.411000,__ +packet,202.661000,__ +packet,202.536000,__ +packet,202.494000,__ +packet,202.577000,__ +packet,202.619000,__ +packet,202.911000,__ +packet,202.786000,__ +packet,202.703000,__ +packet,202.744000,__ +packet,202.828000,__ +packet,202.869000,__ +packet,203.078000,__ +packet,202.994000,__ +packet,202.953000,__ +packet,203.036000,__ +packet,203.245000,__ +packet,203.161000,__ +packet,203.120000,__ +packet,203.203000,__ +packet,203.412000,__ +packet,203.328000,__ +packet,203.286000,__ +packet,203.370000,__ +packet,203.495000,__ +packet,203.453000,__ +packet,203.537000,K_ +packet,203.704000,__ +packet,203.620000,__ +packet,203.578000,__ +packet,203.662000,__ +packet,203.870000,__ +packet,203.787000,__ +packet,203.745000,__ +packet,203.829000,__ +packet,203.954000,__ +packet,203.912000,__ +packet,204.121000,__ +packet,204.037000,__ +packet,203.995000,__ +packet,204.079000,__ +packet,204.287000,__ +packet,204.204000,__ +packet,204.162000,__ +packet,204.246000,__ +packet,204.413000,__ +packet,204.329000,__ +packet,204.371000,__ +packet,204.538000,__ +packet,204.454000,__ +packet,204.496000,__ +packet,204.621000,__ +packet,204.579000,__ +packet,204.788000,__ +packet,204.705000,__ +packet,204.663000,__ +packet,204.746000,__ +packet,204.996000,__ +packet,204.871000,__ +packet,204.830000,__ +packet,204.913000,__ +packet,204.955000,__ +packet,205.080000,__ +packet,205.038000,__ +packet,205.122000,K_ +packet,205.372000,__ +packet,205.247000,__ +packet,205.163000,__ +packet,205.205000,__ +packet,205.288000,__ +packet,205.330000,__ +packet,205.664000,__ +packet,205.497000,__ +packet,205.414000,__ +packet,205.455000,__ +packet,205.539000,__ +packet,205.580000,__ +packet,205.622000,__ +packet,205.831000,__ +packet,205.747000,__ +packet,205.706000,__ +packet,205.789000,__ +packet,206.164000,__ +packet,205.997000,__ +packet,205.872000,__ +packet,205.914000,__ +packet,205.956000,__ +packet,206.039000,__ +packet,206.081000,__ +packet,206.123000,__ +packet,206.373000,__ +packet,206.248000,__ +packet,206.206000,__ +packet,206.289000,__ +packet,206.331000,__ +packet,206.498000,__ +packet,206.415000,__ +packet,206.456000,__ +packet,206.540000,K_ +packet,206.665000,__ +packet,206.581000,__ +packet,206.623000,__ +packet,206.873000,__ +packet,206.748000,__ +packet,206.707000,__ +packet,206.790000,__ +packet,206.832000,__ +packet,207.040000,__ +packet,206.957000,__ +packet,206.915000,__ +packet,206.998000,__ +packet,207.165000,__ +packet,207.082000,__ +packet,207.124000,__ +packet,207.332000,__ +packet,207.249000,__ +packet,207.207000,__ +packet,207.290000,__ +packet,207.541000,__ +packet,207.416000,__ +packet,207.374000,__ +packet,207.457000,__ +packet,207.499000,__ +packet,207.708000,__ +packet,207.624000,__ +packet,207.582000,__ +packet,207.666000,__ +packet,207.833000,__ +packet,207.749000,__ +packet,207.791000,__ +packet,208.041000,__ +packet,207.916000,__ +packet,207.874000,__ +packet,207.958000,__ +packet,207.999000,__ +packet,208.166000,__ +packet,208.083000,__ +packet,208.125000,__ +packet,208.375000,__ +packet,208.250000,__ +packet,208.208000,__ +packet,208.291000,__ +packet,208.333000,__ +packet,208.417000,__ +packet,208.458000,__ +packet,208.500000,K_ +packet,208.625000,__ +packet,208.542000,__ +packet,208.583000,__ +packet,208.792000,__ +packet,208.709000,__ +packet,208.667000,__ +packet,208.750000,__ +packet,208.917000,__ +packet,208.834000,__ +packet,208.875000,__ +packet,209.084000,__ +packet,209.000000,__ +packet,208.959000,__ +packet,209.042000,__ +packet,209.292000,__ +packet,209.167000,__ +packet,209.126000,__ +packet,209.209000,__ +packet,209.251000,__ +packet,209.334000,__ +packet,209.459000,__ +packet,209.376000,__ +packet,209.418000,__ +packet,209.584000,__ +packet,209.501000,__ +packet,209.543000,__ +packet,209.793000,__ +packet,209.668000,__ +packet,209.626000,__ +packet,209.710000,__ +packet,209.751000,__ +packet,209.918000,__ +packet,209.835000,__ +packet,209.876000,__ +packet,210.085000,__ +packet,210.001000,__ +packet,209.960000,__ +packet,210.043000,__ +packet,210.252000,__ +packet,210.168000,__ +packet,210.127000,__ +packet,210.210000,__ +packet,210.419000,__ +packet,210.335000,__ +packet,210.293000,__ +packet,210.377000,__ +packet,210.627000,__ +packet,210.502000,__ +packet,210.460000,__ +packet,210.544000,__ +packet,210.585000,__ +packet,210.836000,__ +packet,210.711000,__ +packet,210.669000,__ +packet,210.752000,__ +packet,210.794000,__ +packet,210.877000,__ +packet,210.919000,K_ +packet,211.044000,__ +packet,210.961000,__ +packet,211.002000,__ +packet,211.128000,__ +packet,211.086000,__ +packet,211.169000,__ +packet,211.294000,__ +packet,211.211000,__ +packet,211.253000,__ +packet,211.420000,__ +packet,211.336000,__ +packet,211.378000,__ +packet,211.670000,__ +packet,211.545000,__ +packet,211.461000,__ +packet,211.503000,__ +packet,211.586000,__ +packet,211.628000,__ +packet,212.003000,__ +packet,211.837000,__ +packet,211.712000,__ +packet,211.753000,__ +packet,211.795000,__ +packet,211.878000,__ +packet,211.920000,__ +packet,211.962000,__ +packet,212.045000,__ +packet,212.087000,K_ +packet,212.212000,__ +packet,212.129000,__ +packet,212.170000,__ +packet,212.379000,__ +packet,212.295000,__ +packet,212.254000,__ +packet,212.337000,__ +packet,212.462000,__ +packet,212.421000,__ +packet,212.838000,__ +packet,212.629000,__ +packet,212.504000,__ +packet,212.546000,__ +packet,212.587000,__ +packet,212.671000,__ +packet,212.713000,__ +packet,212.754000,__ +packet,212.796000,__ +packet,212.963000,__ +packet,212.879000,__ +packet,212.921000,__ +packet,213.171000,__ +packet,213.046000,__ +packet,213.004000,__ +packet,213.088000,__ +packet,213.130000,__ +packet,213.547000,__ +packet,213.338000,__ +packet,213.213000,__ +packet,213.255000,__ +packet,213.296000,__ +packet,213.380000,__ +packet,213.422000,__ +packet,213.463000,__ +packet,213.505000,__ +packet,213.630000,__ +packet,213.588000,__ +packet,213.797000,__ +packet,213.714000,__ +packet,213.672000,__ +packet,213.755000,__ +packet,214.172000,__ +packet,213.964000,__ +packet,213.839000,__ +packet,213.880000,__ +packet,213.922000,__ +packet,214.005000,__ +packet,214.047000,__ +packet,214.089000,__ +packet,214.131000,__ +packet,214.381000,__ +packet,214.256000,__ +packet,214.214000,__ +packet,214.297000,__ +packet,214.339000,__ +packet,214.548000,__ +packet,214.464000,__ +packet,214.423000,__ +packet,214.506000,__ +packet,214.798000,__ +packet,214.673000,__ +packet,214.589000,__ +packet,214.631000,__ +packet,214.715000,__ +packet,214.756000,__ +packet,214.965000,__ +packet,214.881000,__ +packet,214.840000,__ +packet,214.923000,__ +packet,215.132000,__ +packet,215.048000,__ +packet,215.006000,__ +packet,215.090000,__ +packet,215.298000,__ +packet,215.215000,__ +packet,215.173000,__ +packet,215.257000,__ +packet,215.507000,__ +packet,215.382000,__ +packet,215.340000,__ +packet,215.424000,__ +packet,215.465000,__ +packet,215.674000,__ +packet,215.590000,__ +packet,215.549000,__ +packet,215.632000,__ +packet,215.882000,__ +packet,215.757000,__ +packet,215.716000,__ +packet,215.799000,__ +packet,215.841000,__ +packet,216.258000,__ +packet,216.049000,__ +packet,215.924000,__ +packet,215.966000,__ +packet,216.007000,__ +packet,216.091000,__ +packet,216.133000,__ +packet,216.174000,__ +packet,216.216000,__ +packet,216.550000,__ +packet,216.383000,__ +packet,216.299000,__ +packet,216.341000,__ +packet,216.425000,__ +packet,216.466000,__ +packet,216.508000,__ +packet,216.800000,__ +packet,216.675000,__ +packet,216.591000,__ +packet,216.633000,__ +packet,216.717000,__ +packet,216.758000,__ +packet,216.842000,K_ +packet,216.883000,__ +packet,217.050000,__ +packet,216.967000,__ +packet,216.925000,__ +packet,217.008000,__ +packet,217.259000,__ +packet,217.134000,__ +packet,217.092000,__ +packet,217.175000,__ +packet,217.217000,__ +packet,217.509000,__ +packet,217.384000,__ +packet,217.300000,__ +packet,217.342000,__ +packet,217.426000,__ +packet,217.467000,__ +packet,217.634000,__ +packet,217.551000,__ +packet,217.592000,__ +packet,217.843000,__ +packet,217.718000,__ +packet,217.676000,__ +packet,217.759000,__ +packet,217.801000,__ +packet,217.968000,__ +packet,217.884000,__ +packet,217.926000,__ +packet,218.176000,__ +packet,218.051000,__ +packet,218.009000,__ +packet,218.093000,__ +packet,218.135000,__ +packet,218.343000,__ +packet,218.260000,__ +packet,218.218000,__ +packet,218.301000,__ +packet,218.552000,__ +packet,218.427000,__ +packet,218.385000,__ +packet,218.468000,__ +packet,218.510000,__ +packet,218.885000,__ +packet,218.719000,__ +packet,218.593000,__ +packet,218.635000,__ +packet,218.677000,__ +packet,218.760000,__ +packet,218.802000,__ +packet,218.844000,__ +packet,219.177000,__ +packet,219.010000,__ +packet,218.927000,__ +packet,218.969000,__ +packet,219.052000,__ +packet,219.094000,__ +packet,219.136000,__ +packet,219.428000,__ +packet,219.302000,__ +packet,219.219000,__ +packet,219.261000,__ +packet,219.344000,__ +packet,219.386000,__ +packet,219.553000,__ +packet,219.469000,__ +packet,219.511000,__ +packet,219.845000,__ +packet,219.678000,__ +packet,219.594000,__ +packet,219.636000,__ +packet,219.720000,__ +packet,219.761000,__ +packet,219.803000,__ +packet,220.178000,__ +packet,220.011000,__ +packet,219.886000,__ +packet,219.928000,__ +packet,219.970000,__ +packet,220.053000,__ +packet,220.095000,__ +packet,220.137000,__ +packet,220.429000,__ +packet,220.303000,__ +packet,220.220000,__ +packet,220.262000,__ +packet,220.345000,__ +packet,220.387000,__ +packet,220.679000,__ +packet,220.554000,__ +packet,220.470000,__ +packet,220.512000,__ +packet,220.595000,__ +packet,220.637000,__ +packet,220.929000,__ +packet,220.804000,__ +packet,220.721000,__ +packet,220.762000,__ +packet,220.846000,__ +packet,220.887000,__ +packet,221.263000,__ +packet,221.096000,__ +packet,220.971000,__ +packet,221.012000,__ +packet,221.054000,__ +packet,221.138000,__ +packet,221.179000,__ +packet,221.221000,__ +packet,221.388000,__ +packet,221.304000,__ +packet,221.346000,__ +packet,221.555000,__ +packet,221.471000,__ +packet,221.430000,__ +packet,221.513000,__ +packet,221.638000,__ +packet,221.596000,__ +packet,221.722000,__ +packet,221.680000,__ +packet,221.805000,__ +packet,221.763000,__ +packet,221.930000,__ +packet,221.847000,__ +packet,221.888000,__ +packet,222.055000,__ +packet,221.972000,__ +packet,222.013000,__ +packet,222.139000,__ +packet,222.097000,__ +packet,222.389000,__ +packet,222.264000,__ +packet,222.180000,__ +packet,222.222000,__ +packet,222.305000,__ +packet,222.347000,__ +packet,222.431000,__ +packet,222.597000,__ +packet,222.514000,__ +packet,222.472000,__ +packet,222.556000,__ +packet,222.639000,__ +packet,222.681000,__ +packet,222.764000,__ +packet,222.723000,__ +packet,222.931000,__ +packet,222.848000,__ +packet,222.806000,__ +packet,222.889000,__ +packet,223.181000,__ +packet,223.056000,__ +packet,222.973000,__ +packet,223.014000,__ +packet,223.098000,__ +packet,223.140000,__ +packet,223.390000,__ +packet,223.265000,__ +packet,223.223000,__ +packet,223.306000,__ +packet,223.348000,__ +packet,223.765000,__ +packet,223.557000,__ +packet,223.432000,__ +packet,223.473000,__ +packet,223.515000,__ +packet,223.598000,__ +packet,223.640000,__ +packet,223.682000,__ +packet,223.724000,__ +packet,224.141000,__ +packet,223.932000,__ +packet,223.807000,__ +packet,223.849000,__ +packet,223.890000,__ +packet,223.974000,__ +packet,224.015000,__ +packet,224.057000,__ +packet,224.099000,__ +packet,224.433000,__ +packet,224.266000,__ +packet,224.182000,__ +packet,224.224000,__ +packet,224.307000,__ +packet,224.349000,__ +packet,224.391000,__ +packet,224.683000,__ +packet,224.558000,__ +packet,224.474000,__ +packet,224.516000,__ +packet,224.599000,__ +packet,224.641000,__ +packet,224.975000,__ +packet,224.808000,__ +packet,224.725000,__ +packet,224.766000,__ +packet,224.850000,__ +packet,224.891000,__ +packet,224.933000,__ +packet,225.183000,__ +packet,225.058000,__ +packet,225.016000,__ +packet,225.100000,__ +packet,225.142000,__ +packet,225.350000,__ +packet,225.267000,__ +packet,225.225000,__ +packet,225.308000,__ +packet,225.392000,K_ +packet,225.559000,__ +packet,225.475000,__ +packet,225.434000,__ +packet,225.517000,__ +packet,225.934000,__ +packet,225.726000,__ +packet,225.600000,__ +packet,225.642000,__ +packet,225.684000,__ +packet,225.767000,__ +packet,225.809000,__ +packet,225.851000,__ +packet,225.892000,__ +packet,226.143000,__ +packet,226.017000,__ +packet,225.976000,__ +packet,226.059000,__ +packet,226.101000,__ +packet,226.268000,__ +packet,226.184000,__ +packet,226.226000,__ +packet,226.351000,__ +packet,226.309000,__ +packet,226.435000,__ +packet,226.393000,__ +packet,226.685000,__ +packet,226.560000,__ +packet,226.476000,__ +packet,226.518000,__ +packet,226.601000,__ +packet,226.643000,__ +packet,226.810000,__ +packet,226.727000,__ +packet,226.768000,__ +packet,227.144000,__ +packet,226.977000,__ +packet,226.852000,__ +packet,226.893000,__ +packet,226.935000,__ +packet,227.018000,__ +packet,227.060000,__ +packet,227.102000,__ +packet,227.519000,__ +packet,227.310000,__ +packet,227.185000,__ +packet,227.227000,__ +packet,227.269000,__ +packet,227.352000,__ +packet,227.394000,__ +packet,227.436000,__ +packet,227.477000,__ +packet,227.769000,__ +packet,227.644000,__ +packet,227.561000,__ +packet,227.602000,__ +packet,227.686000,__ +packet,227.728000,__ +packet,227.936000,__ +packet,227.853000,__ +packet,227.811000,__ +packet,227.894000,__ +packet,228.186000,__ +packet,228.061000,__ +packet,227.978000,__ +packet,228.019000,__ +packet,228.103000,__ +packet,228.145000,__ +packet,228.311000,__ +packet,228.228000,__ +packet,228.270000,__ +packet,228.478000,__ +packet,228.395000,__ +packet,228.353000,__ +packet,228.437000,__ +packet,228.812000,__ +packet,228.645000,__ +packet,228.520000,__ +packet,228.562000,__ +packet,228.603000,__ +packet,228.687000,__ +packet,228.729000,__ +packet,228.770000,__ +packet,229.062000,__ +packet,228.937000,__ +packet,228.854000,__ +packet,228.895000,__ +packet,228.979000,__ +packet,229.020000,__ +packet,229.312000,__ +packet,229.187000,__ +packet,229.104000,__ +packet,229.146000,__ +packet,229.229000,__ +packet,229.271000,__ +packet,229.479000,__ +packet,229.396000,__ +packet,229.354000,__ +packet,229.438000,__ +packet,229.521000,K_ +packet,229.646000,__ +packet,229.563000,__ +packet,229.604000,__ +packet,229.855000,__ +packet,229.730000,__ +packet,229.688000,__ +packet,229.771000,__ +packet,229.813000,__ +packet,230.063000,__ +packet,229.938000,__ +packet,229.896000,__ +packet,229.980000,__ +packet,230.021000,__ +packet,230.147000,__ +packet,230.105000,__ +packet,230.313000,__ +packet,230.230000,__ +packet,230.188000,__ +packet,230.272000,__ +packet,230.480000,__ +packet,230.397000,__ +packet,230.355000,__ +packet,230.439000,__ +packet,230.647000,__ +packet,230.564000,__ +packet,230.522000,__ +packet,230.605000,__ +packet,230.897000,__ +packet,230.772000,__ +packet,230.689000,__ +packet,230.731000,__ +packet,230.814000,__ +packet,230.856000,__ +packet,231.064000,__ +packet,230.981000,__ +packet,230.939000,__ +packet,231.022000,__ +packet,231.231000,__ +packet,231.148000,__ +packet,231.106000,__ +packet,231.189000,__ +packet,231.523000,__ +packet,231.356000,__ +packet,231.273000,__ +packet,231.314000,__ +packet,231.398000,__ +packet,231.440000,__ +packet,231.481000,__ +packet,231.690000,__ +packet,231.606000,__ +packet,231.565000,__ +packet,231.648000,__ +packet,231.898000,__ +packet,231.773000,__ +packet,231.732000,__ +packet,231.815000,__ +packet,231.857000,__ +packet,232.065000,__ +packet,231.982000,__ +packet,231.940000,__ +packet,232.023000,__ +packet,232.190000,__ +packet,232.107000,__ +packet,232.149000,__ +packet,232.399000,__ +packet,232.274000,__ +packet,232.232000,__ +packet,232.315000,__ +packet,232.357000,__ +packet,232.524000,__ +packet,232.441000,__ +packet,232.482000,__ +packet,232.691000,__ +packet,232.607000,__ +packet,232.566000,__ +packet,232.649000,__ +packet,232.899000,__ +packet,232.774000,__ +packet,232.733000,__ +packet,232.816000,__ +packet,232.858000,__ +packet,233.191000,__ +packet,233.024000,__ +packet,232.941000,__ +packet,232.983000,__ +packet,233.066000,__ +packet,233.108000,__ +packet,233.150000,__ +packet,233.275000,__ +packet,233.233000,__ +packet,233.316000,__ +packet,233.483000,__ +packet,233.400000,__ +packet,233.358000,__ +packet,233.442000,__ +packet,233.859000,__ +packet,233.650000,__ +packet,233.525000,__ +packet,233.567000,__ +packet,233.608000,__ +packet,233.692000,__ +packet,233.734000,__ +packet,233.775000,__ +packet,233.817000,__ +packet,234.025000,__ +packet,233.942000,__ +packet,233.900000,__ +packet,233.984000,__ +packet,234.192000,__ +packet,234.109000,__ +packet,234.067000,__ +packet,234.151000,__ +packet,234.401000,__ +packet,234.276000,__ +packet,234.234000,__ +packet,234.317000,__ +packet,234.359000,__ +packet,234.568000,__ +packet,234.484000,__ +packet,234.443000,__ +packet,234.526000,__ +packet,234.943000,__ +packet,234.735000,__ +packet,234.609000,__ +packet,234.651000,__ +packet,234.693000,__ +packet,234.776000,__ +packet,234.818000,__ +packet,234.860000,__ +packet,234.901000,__ +packet,235.193000,__ +packet,235.068000,__ +packet,234.985000,__ +packet,235.026000,__ +packet,235.110000,__ +packet,235.152000,__ +packet,235.360000,__ +packet,235.277000,__ +packet,235.235000,__ +packet,235.318000,__ +packet,235.652000,__ +packet,235.485000,__ +packet,235.402000,__ +packet,235.444000,__ +packet,235.527000,__ +packet,235.569000,__ +packet,235.610000,__ +packet,235.986000,__ +packet,235.819000,__ +packet,235.694000,__ +packet,235.736000,__ +packet,235.777000,__ +packet,235.861000,__ +packet,235.902000,__ +packet,235.944000,__ +packet,236.236000,__ +packet,236.111000,__ +packet,236.027000,__ +packet,236.069000,__ +packet,236.153000,__ +packet,236.194000,__ +packet,236.403000,__ +packet,236.319000,__ +packet,236.278000,__ +packet,236.361000,__ +packet,236.528000,__ +packet,236.445000,__ +packet,236.486000,__ +packet,236.737000,__ +packet,236.611000,__ +packet,236.570000,__ +packet,236.653000,__ +packet,236.695000,__ +packet,236.987000,__ +packet,236.862000,__ +packet,236.778000,__ +packet,236.820000,__ +packet,236.903000,__ +packet,236.945000,__ +packet,237.279000,__ +packet,237.112000,__ +packet,237.028000,__ +packet,237.070000,__ +packet,237.154000,__ +packet,237.195000,__ +packet,237.237000,__ +packet,237.404000,__ +packet,237.320000,__ +packet,237.362000,__ +packet,237.446000,K_ +packet,237.696000,__ +packet,237.571000,__ +packet,237.487000,__ +packet,237.529000,__ +packet,237.612000,__ +packet,237.654000,__ +packet,237.863000,__ +packet,237.779000,__ +packet,237.738000,__ +packet,237.821000,__ +packet,238.071000,__ +packet,237.946000,__ +packet,237.904000,__ +packet,237.988000,__ +packet,238.029000,__ +packet,238.238000,__ +packet,238.155000,__ +packet,238.113000,__ +packet,238.196000,__ +packet,238.363000,__ +packet,238.280000,__ +packet,238.321000,__ +packet,238.530000,__ +packet,238.447000,__ +packet,238.405000,__ +packet,238.488000,__ +packet,238.739000,__ +packet,238.613000,__ +packet,238.572000,__ +packet,238.655000,__ +packet,238.697000,__ +packet,238.864000,__ +packet,238.780000,__ +packet,238.822000,__ +packet,239.030000,__ +packet,238.947000,__ +packet,238.905000,__ +packet,238.989000,__ +packet,239.239000,__ +packet,239.114000,__ +packet,239.072000,__ +packet,239.156000,__ +packet,239.197000,__ +packet,239.489000,__ +packet,239.364000,__ +packet,239.281000,__ +packet,239.322000,__ +packet,239.406000,__ +packet,239.448000,__ +packet,239.740000,__ +packet,239.614000,__ +packet,239.531000,__ +packet,239.573000,__ +packet,239.656000,__ +packet,239.698000,__ +packet,239.990000,__ +packet,239.865000,__ +packet,239.781000,__ +packet,239.823000,__ +packet,239.906000,__ +packet,239.948000,__ +packet,240.240000,__ +packet,240.115000,__ +packet,240.031000,__ +packet,240.073000,__ +packet,240.157000,__ +packet,240.198000,__ +packet,240.490000,__ +packet,240.365000,__ +packet,240.282000,__ +packet,240.323000,__ +packet,240.407000,__ +packet,240.449000,__ +packet,240.741000,__ +packet,240.615000,__ +packet,240.532000,__ +packet,240.574000,__ +packet,240.657000,__ +packet,240.699000,__ +packet,240.907000,__ +packet,240.824000,__ +packet,240.782000,__ +packet,240.866000,__ +packet,241.074000,__ +packet,240.991000,__ +packet,240.949000,__ +packet,241.032000,__ +packet,241.199000,__ +packet,241.116000,__ +packet,241.158000,__ +packet,241.366000,__ +packet,241.283000,__ +packet,241.241000,__ +packet,241.324000,__ +packet,241.616000,__ +packet,241.491000,__ +packet,241.408000,__ +packet,241.450000,__ +packet,241.533000,__ +packet,241.575000,__ +packet,241.783000,__ +packet,241.700000,__ +packet,241.658000,__ +packet,241.742000,__ +packet,241.992000,__ +packet,241.867000,__ +packet,241.825000,__ +packet,241.908000,__ +packet,241.950000,__ +packet,242.200000,__ +packet,242.075000,__ +packet,242.033000,__ +packet,242.117000,__ +packet,242.159000,__ +packet,242.367000,__ +packet,242.284000,__ +packet,242.242000,__ +packet,242.325000,__ +packet,242.617000,__ +packet,242.492000,__ +packet,242.409000,__ +packet,242.451000,__ +packet,242.534000,__ +packet,242.576000,__ +packet,242.868000,__ +packet,242.743000,__ +packet,242.659000,__ +packet,242.701000,__ +packet,242.784000,__ +packet,242.826000,__ +packet,243.118000,__ +packet,242.993000,__ +packet,242.909000,__ +packet,242.951000,__ +packet,243.034000,__ +packet,243.076000,__ +packet,243.243000,__ +packet,243.160000,__ +packet,243.201000,__ +packet,243.493000,__ +packet,243.368000,__ +packet,243.285000,__ +packet,243.326000,__ +packet,243.410000,__ +packet,243.452000,__ +packet,243.744000,__ +packet,243.618000,__ +packet,243.535000,__ +packet,243.577000,__ +packet,243.660000,__ +packet,243.702000,__ +packet,243.869000,__ +packet,243.785000,__ +packet,243.827000,__ +packet,244.119000,__ +packet,243.994000,__ +packet,243.910000,__ +packet,243.952000,__ +packet,244.035000,__ +packet,244.077000,__ +packet,244.286000,__ +packet,244.202000,__ +packet,244.161000,__ +packet,244.244000,__ +packet,244.494000,__ +packet,244.369000,__ +packet,244.327000,__ +packet,244.411000,__ +packet,244.453000,__ +packet,244.745000,__ +packet,244.619000,__ +packet,244.536000,__ +packet,244.578000,__ +packet,244.661000,__ +packet,244.703000,__ +packet,244.995000,__ +packet,244.870000,__ +packet,244.786000,__ +packet,244.828000,__ +packet,244.911000,__ +packet,244.953000,__ +packet,245.162000,__ +packet,245.078000,__ +packet,245.036000,__ +packet,245.120000,__ +packet,245.245000,__ +packet,245.203000,__ +packet,245.370000,__ +packet,245.287000,__ +packet,245.328000,__ +packet,245.537000,__ +packet,245.454000,__ +packet,245.412000,__ +packet,245.495000,__ +packet,245.746000,__ +packet,245.620000,__ +packet,245.579000,__ +packet,245.662000,__ +packet,245.704000,__ +packet,245.912000,__ +packet,245.829000,__ +packet,245.787000,__ +packet,245.871000,__ +packet,246.079000,__ +packet,245.996000,__ +packet,245.954000,__ +packet,246.037000,__ +packet,246.204000,__ +packet,246.121000,__ +packet,246.163000,__ +packet,246.371000,__ +packet,246.288000,__ +packet,246.246000,__ +packet,246.329000,__ +packet,246.538000,__ +packet,246.455000,__ +packet,246.413000,__ +packet,246.496000,__ +packet,246.747000,__ +packet,246.621000,__ +packet,246.580000,__ +packet,246.663000,__ +packet,246.705000,__ +packet,246.872000,__ +packet,246.788000,__ +packet,246.830000,__ +packet,246.997000,__ +packet,246.913000,__ +packet,246.955000,__ +packet,247.122000,__ +packet,247.038000,__ +packet,247.080000,__ +packet,247.289000,__ +packet,247.205000,__ +packet,247.164000,__ +packet,247.247000,__ +packet,247.497000,__ +packet,247.372000,__ +packet,247.330000,__ +packet,247.414000,__ +packet,247.456000,__ +packet,247.622000,__ +packet,247.539000,__ +packet,247.581000,__ +packet,247.831000,__ +packet,247.706000,__ +packet,247.664000,__ +packet,247.748000,__ +packet,247.789000,__ +packet,247.873000,K_ +packet,248.081000,__ +packet,247.956000,__ +packet,247.914000,__ +packet,247.998000,__ +packet,248.039000,__ +packet,248.206000,__ +packet,248.123000,__ +packet,248.165000,__ +packet,248.373000,__ +packet,248.290000,__ +packet,248.248000,__ +packet,248.331000,__ +packet,248.540000,__ +packet,248.457000,__ +packet,248.415000,__ +packet,248.498000,__ +packet,248.749000,__ +packet,248.623000,__ +packet,248.582000,__ +packet,248.665000,__ +packet,248.707000,__ +packet,248.874000,__ +packet,248.790000,__ +packet,248.832000,__ +packet,249.040000,__ +packet,248.957000,__ +packet,248.915000,__ +packet,248.999000,__ +packet,249.249000,__ +packet,249.124000,__ +packet,249.082000,__ +packet,249.166000,__ +packet,249.207000,__ +packet,249.374000,__ +packet,249.291000,__ +packet,249.332000,__ +packet,249.541000,__ +packet,249.458000,__ +packet,249.416000,__ +packet,249.499000,__ +packet,249.750000,__ +packet,249.624000,__ +packet,249.583000,__ +packet,249.666000,__ +packet,249.708000,__ +packet,249.875000,__ +packet,249.791000,__ +packet,249.833000,__ +packet,250.041000,__ +packet,249.958000,__ +packet,249.916000,__ +packet,250.000000,__ +packet,250.208000,__ +packet,250.125000,__ +packet,250.083000,__ +packet,250.167000,__ +packet,250.375000,__ +packet,250.292000,__ +packet,250.250000,__ +packet,250.333000,__ +packet,250.542000,__ +packet,250.459000,__ +packet,250.417000,__ +packet,250.500000,__ +packet,250.751000,__ +packet,250.625000,__ +packet,250.584000,__ +packet,250.667000,__ +packet,250.709000,__ +packet,250.876000,__ +packet,250.792000,__ +packet,250.834000,__ +packet,251.042000,__ +packet,250.959000,__ +packet,250.917000,__ +packet,251.001000,__ +packet,251.251000,__ +packet,251.126000,__ +packet,251.084000,__ +packet,251.168000,__ +packet,251.209000,__ +packet,251.376000,__ +packet,251.293000,__ +packet,251.334000,__ +packet,251.543000,__ +packet,251.460000,__ +packet,251.418000,__ +packet,251.501000,__ +packet,251.710000,__ +packet,251.626000,__ +packet,251.585000,__ +packet,251.668000,__ +packet,251.793000,__ +packet,251.752000,__ +packet,251.960000,__ +packet,251.877000,__ +packet,251.835000,__ +packet,251.918000,__ +packet,252.127000,__ +packet,252.043000,__ +packet,252.002000,__ +packet,252.085000,__ +packet,252.294000,__ +packet,252.210000,__ +packet,252.169000,__ +packet,252.252000,__ +packet,252.419000,__ +packet,252.335000,__ +packet,252.377000,__ +packet,252.586000,__ +packet,252.502000,__ +packet,252.461000,__ +packet,252.544000,__ +packet,252.711000,__ +packet,252.627000,__ +packet,252.669000,__ +packet,252.878000,__ +packet,252.794000,__ +packet,252.753000,__ +packet,252.836000,__ +packet,253.044000,__ +packet,252.961000,__ +packet,252.919000,__ +packet,253.003000,__ +packet,253.211000,__ +packet,253.128000,__ +packet,253.086000,__ +packet,253.170000,__ +packet,253.378000,__ +packet,253.295000,__ +packet,253.253000,__ +packet,253.336000,__ +packet,253.545000,__ +packet,253.462000,__ +packet,253.420000,__ +packet,253.503000,__ +packet,253.754000,__ +packet,253.628000,__ +packet,253.587000,__ +packet,253.670000,__ +packet,253.712000,__ +packet,253.920000,__ +packet,253.837000,__ +packet,253.795000,__ +packet,253.879000,__ +packet,254.087000,__ +packet,254.004000,__ +packet,253.962000,__ +packet,254.045000,__ +packet,254.254000,__ +packet,254.171000,__ +packet,254.129000,__ +packet,254.212000,__ +packet,254.421000,__ +packet,254.337000,__ +packet,254.296000,__ +packet,254.379000,__ +packet,254.588000,__ +packet,254.504000,__ +packet,254.463000,__ +packet,254.546000,__ +packet,254.755000,__ +packet,254.671000,__ +packet,254.629000,__ +packet,254.713000,__ +packet,254.921000,__ +packet,254.838000,__ +packet,254.796000,__ +packet,254.880000,__ +packet,255.088000,__ +packet,255.005000,__ +packet,254.963000,__ +packet,255.046000,__ +packet,255.255000,__ +packet,255.172000,__ +packet,255.130000,__ +packet,255.213000,__ +packet,255.422000,__ +packet,255.338000,__ +packet,255.297000,__ +packet,255.380000,__ +packet,255.589000,__ +packet,255.505000,__ +packet,255.464000,__ +packet,255.547000,__ +packet,255.756000,__ +packet,255.672000,__ +packet,255.630000,__ +packet,255.714000,__ +packet,255.922000,__ +packet,255.839000,__ +packet,255.797000,__ +packet,255.881000,__ +packet,256.089000,__ +packet,256.006000,__ +packet,255.964000,__ +packet,256.047000,__ +packet,256.256000,__ +packet,256.173000,__ +packet,256.131000,__ +packet,256.214000,__ +packet,256.423000,__ +packet,256.339000,__ +packet,256.298000,__ +packet,256.381000,__ +packet,256.590000,__ +packet,256.506000,__ +packet,256.465000,__ +packet,256.548000,__ +packet,256.757000,__ +packet,256.673000,__ +packet,256.631000,__ +packet,256.715000,__ +packet,256.882000,__ +packet,256.798000,__ +packet,256.840000,__ +packet,257.048000,__ +packet,256.965000,__ +packet,256.923000,__ +packet,257.007000,__ +packet,257.215000,__ +packet,257.132000,__ +packet,257.090000,__ +packet,257.174000,__ +packet,257.382000,__ +packet,257.299000,__ +packet,257.257000,__ +packet,257.340000,__ +packet,257.549000,__ +packet,257.466000,__ +packet,257.424000,__ +packet,257.507000,__ +packet,257.758000,__ +packet,257.632000,__ +packet,257.591000,__ +packet,257.674000,__ +packet,257.716000,__ +packet,257.924000,__ +packet,257.841000,__ +packet,257.799000,__ +packet,257.883000,__ +packet,258.091000,__ +packet,258.008000,__ +packet,257.966000,__ +packet,258.049000,__ +packet,258.175000,__ +packet,258.133000,__ +packet,258.216000,K_ +packet,258.383000,__ +packet,258.300000,__ +packet,258.258000,__ +packet,258.341000,__ +packet,258.550000,__ +packet,258.467000,__ +packet,258.425000,__ +packet,258.508000,__ +packet,258.717000,__ +packet,258.633000,__ +packet,258.592000,__ +packet,258.675000,__ +packet,258.884000,__ +packet,258.800000,__ +packet,258.759000,__ +packet,258.842000,__ +packet,259.050000,__ +packet,258.967000,__ +packet,258.925000,__ +packet,259.009000,__ +packet,259.217000,__ +packet,259.134000,__ +packet,259.092000,__ +packet,259.176000,__ +packet,259.384000,__ +packet,259.301000,__ +packet,259.259000,__ +packet,259.342000,__ +packet,259.551000,__ +packet,259.468000,__ +packet,259.426000,__ +packet,259.509000,__ +packet,259.718000,__ +packet,259.634000,__ +packet,259.593000,__ +packet,259.676000,__ +packet,259.885000,__ +packet,259.801000,__ +packet,259.760000,__ +packet,259.843000,__ +packet,260.051000,__ +packet,259.968000,__ +packet,259.926000,__ +packet,260.010000,__ +packet,260.218000,__ +packet,260.135000,__ +packet,260.093000,__ +packet,260.177000,__ +packet,260.385000,__ +packet,260.302000,__ +packet,260.260000,__ +packet,260.343000,__ +packet,260.552000,__ +packet,260.469000,__ +packet,260.427000,__ +packet,260.510000,__ +packet,260.677000,__ +packet,260.594000,__ +packet,260.635000,__ +packet,260.719000,K_ +packet,260.761000,__ +packet,261.011000,__ +packet,260.886000,__ +packet,260.802000,__ +packet,260.844000,__ +packet,260.927000,__ +packet,260.969000,__ +packet,261.178000,__ +packet,261.094000,__ +packet,261.052000,__ +packet,261.136000,__ +packet,261.303000,__ +packet,261.219000,__ +packet,261.261000,__ +packet,261.636000,__ +packet,261.470000,__ +packet,261.344000,__ +packet,261.386000,__ +packet,261.428000,__ +packet,261.511000,__ +packet,261.553000,__ +packet,261.595000,__ +packet,261.845000,__ +packet,261.720000,__ +packet,261.678000,__ +packet,261.762000,__ +packet,261.803000,__ +packet,262.179000,__ +packet,262.012000,__ +packet,261.887000,__ +packet,261.928000,__ +packet,261.970000,__ +packet,262.053000,__ +packet,262.095000,__ +packet,262.137000,__ +packet,262.429000,__ +packet,262.304000,__ +packet,262.220000,__ +packet,262.262000,__ +packet,262.345000,__ +packet,262.387000,__ +packet,262.554000,__ +packet,262.471000,__ +packet,262.512000,__ +packet,262.846000,__ +packet,262.679000,__ +packet,262.596000,__ +packet,262.637000,__ +packet,262.721000,__ +packet,262.763000,__ +packet,262.804000,__ +packet,263.054000,__ +packet,262.929000,__ +packet,262.888000,__ +packet,262.971000,__ +packet,263.013000,__ +packet,263.305000,__ +packet,263.180000,__ +packet,263.096000,__ +packet,263.138000,__ +packet,263.221000,__ +packet,263.263000,__ +packet,263.638000,__ +packet,263.472000,__ +packet,263.346000,__ +packet,263.388000,__ +packet,263.430000,__ +packet,263.513000,__ +packet,263.555000,__ +packet,263.597000,__ +packet,263.972000,__ +packet,263.805000,__ +packet,263.680000,__ +packet,263.722000,__ +packet,263.764000,__ +packet,263.847000,__ +packet,263.889000,__ +packet,263.930000,__ +packet,264.181000,__ +packet,264.055000,__ +packet,264.014000,__ +packet,264.097000,__ +packet,264.139000,__ +packet,264.556000,__ +packet,264.347000,__ +packet,264.222000,__ +packet,264.264000,__ +packet,264.306000,__ +packet,264.389000,__ +packet,264.431000,__ +packet,264.473000,__ +packet,264.514000,__ +packet,264.765000,__ +packet,264.639000,__ +packet,264.598000,__ +packet,264.681000,__ +packet,264.723000,__ +packet,265.140000,__ +packet,264.931000,__ +packet,264.806000,__ +packet,264.848000,__ +packet,264.890000,__ +packet,264.973000,__ +packet,265.015000,__ +packet,265.056000,__ +packet,265.098000,__ +packet,265.474000,__ +packet,265.307000,__ +packet,265.182000,__ +packet,265.223000,__ +packet,265.265000,__ +packet,265.348000,__ +packet,265.390000,__ +packet,265.432000,__ +packet,265.557000,__ +packet,265.515000,__ +packet,265.932000,__ +packet,265.724000,__ +packet,265.599000,__ +packet,265.640000,__ +packet,265.682000,__ +packet,265.766000,__ +packet,265.807000,__ +packet,265.849000,__ +packet,265.891000,__ +packet,266.183000,__ +packet,266.057000,__ +packet,265.974000,__ +packet,266.016000,__ +packet,266.099000,__ +packet,266.141000,__ +packet,266.558000,__ +packet,266.349000,__ +packet,266.224000,__ +packet,266.266000,__ +packet,266.308000,__ +packet,266.391000,__ +packet,266.433000,__ +packet,266.475000,__ +packet,266.516000,__ +packet,266.725000,__ +packet,266.641000,__ +packet,266.600000,__ +packet,266.683000,__ +packet,266.933000,__ +packet,266.808000,__ +packet,266.767000,__ +packet,266.850000,__ +packet,266.892000,__ +packet,267.058000,__ +packet,266.975000,__ +packet,267.017000,__ +packet,267.225000,__ +packet,267.142000,__ +packet,267.100000,__ +packet,267.184000,__ +packet,267.434000,__ +packet,267.309000,__ +packet,267.267000,__ +packet,267.350000,__ +packet,267.392000,__ +packet,267.559000,__ +packet,267.476000,__ +packet,267.517000,__ +packet,267.851000,__ +packet,267.684000,__ +packet,267.601000,__ +packet,267.642000,__ +packet,267.726000,__ +packet,267.768000,__ +packet,267.809000,__ +packet,267.934000,__ +packet,267.893000,__ +packet,268.101000,__ +packet,268.018000,__ +packet,267.976000,__ +packet,268.059000,__ +packet,268.185000,__ +packet,268.143000,__ +packet,268.435000,__ +packet,268.310000,__ +packet,268.226000,__ +packet,268.268000,__ +packet,268.351000,__ +packet,268.393000,__ +packet,268.602000,__ +packet,268.518000,__ +packet,268.477000,__ +packet,268.560000,__ +packet,268.852000,__ +packet,268.727000,__ +packet,268.643000,__ +packet,268.685000,__ +packet,268.769000,__ +packet,268.810000,__ +packet,269.102000,__ +packet,268.977000,__ +packet,268.894000,__ +packet,268.935000,__ +packet,269.019000,__ +packet,269.060000,__ +packet,269.186000,__ +packet,269.144000,__ +packet,269.478000,__ +packet,269.311000,__ +packet,269.227000,__ +packet,269.269000,__ +packet,269.352000,__ +packet,269.394000,__ +packet,269.436000,__ +packet,269.686000,__ +packet,269.561000,__ +packet,269.519000,__ +packet,269.603000,__ +packet,269.644000,__ +packet,269.728000,K_ +packet,269.853000,__ +packet,269.770000,__ +packet,269.811000,__ +packet,270.020000,__ +packet,269.936000,__ +packet,269.895000,__ +packet,269.978000,__ +packet,270.228000,__ +packet,270.103000,__ +packet,270.061000,__ +packet,270.145000,__ +packet,270.187000,__ +packet,270.520000,__ +packet,270.353000,__ +packet,270.270000,__ +packet,270.312000,__ +packet,270.395000,__ +packet,270.437000,__ +packet,270.479000,__ +packet,270.771000,__ +packet,270.645000,__ +packet,270.562000,__ +packet,270.604000,__ +packet,270.687000,__ +packet,270.729000,__ +packet,271.021000,__ +packet,270.896000,__ +packet,270.812000,__ +packet,270.854000,__ +packet,270.937000,__ +packet,270.979000,__ +packet,271.313000,__ +packet,271.146000,__ +packet,271.062000,__ +packet,271.104000,__ +packet,271.188000,__ +packet,271.229000,__ +packet,271.271000,__ +packet,271.521000,__ +packet,271.396000,__ +packet,271.354000,__ +packet,271.438000,__ +packet,271.480000,__ +packet,271.772000,__ +packet,271.646000,__ +packet,271.563000,__ +packet,271.605000,__ +packet,271.688000,__ +packet,271.730000,__ +packet,272.022000,__ +packet,271.897000,__ +packet,271.813000,__ +packet,271.855000,__ +packet,271.938000,__ +packet,271.980000,__ +packet,272.272000,__ +packet,272.147000,__ +packet,272.063000,__ +packet,272.105000,__ +packet,272.189000,__ +packet,272.230000,__ +packet,272.439000,__ +packet,272.355000,__ +packet,272.314000,__ +packet,272.397000,__ +packet,272.647000,__ +packet,272.522000,__ +packet,272.481000,__ +packet,272.564000,__ +packet,272.606000,__ +packet,272.898000,__ +packet,272.773000,__ +packet,272.689000,__ +packet,272.731000,__ +packet,272.814000,__ +packet,272.856000,__ +packet,273.148000,__ +packet,273.023000,__ +packet,272.939000,__ +packet,272.981000,__ +packet,273.064000,__ +packet,273.106000,__ +packet,273.523000,__ +packet,273.315000,__ +packet,273.190000,__ +packet,273.231000,__ +packet,273.273000,__ +packet,273.356000,__ +packet,273.398000,__ +packet,273.440000,__ +packet,273.482000,__ +packet,273.774000,__ +packet,273.648000,__ +packet,273.565000,__ +packet,273.607000,__ +packet,273.690000,__ +packet,273.732000,__ +packet,274.024000,__ +packet,273.899000,__ +packet,273.815000,__ +packet,273.857000,__ +packet,273.940000,__ +packet,273.982000,__ +packet,274.274000,__ +packet,274.149000,__ +packet,274.065000,__ +packet,274.107000,__ +packet,274.191000,__ +packet,274.232000,__ +packet,274.524000,__ +packet,274.399000,__ +packet,274.316000,__ +packet,274.357000,__ +packet,274.441000,__ +packet,274.483000,__ +packet,274.775000,__ +packet,274.649000,__ +packet,274.566000,__ +packet,274.608000,__ +packet,274.691000,__ +packet,274.733000,__ +packet,275.025000,__ +packet,274.900000,__ +packet,274.816000,__ +packet,274.858000,__ +packet,274.941000,__ +packet,274.983000,__ +packet,275.275000,__ +packet,275.150000,__ +packet,275.066000,__ +packet,275.108000,__ +packet,275.192000,__ +packet,275.233000,__ +packet,275.525000,__ +packet,275.400000,__ +packet,275.317000,__ +packet,275.358000,__ +packet,275.442000,__ +packet,275.484000,__ +packet,275.776000,__ +packet,275.650000,__ +packet,275.567000,__ +packet,275.609000,__ +packet,275.692000,__ +packet,275.734000,__ +packet,276.026000,__ +packet,275.901000,__ +packet,275.817000,__ +packet,275.859000,__ +packet,275.942000,__ +packet,275.984000,__ +packet,276.276000,__ +packet,276.151000,__ +packet,276.067000,__ +packet,276.109000,__ +packet,276.193000,__ +packet,276.234000,__ +packet,276.526000,__ +packet,276.401000,__ +packet,276.318000,__ +packet,276.359000,__ +packet,276.443000,__ +packet,276.485000,__ +packet,276.777000,__ +packet,276.651000,__ +packet,276.568000,__ +packet,276.610000,__ +packet,276.693000,__ +packet,276.735000,__ +packet,277.027000,__ +packet,276.902000,__ +packet,276.818000,__ +packet,276.860000,__ +packet,276.943000,__ +packet,276.985000,__ +packet,277.277000,__ +packet,277.152000,__ +packet,277.068000,__ +packet,277.110000,__ +packet,277.194000,__ +packet,277.235000,__ +packet,277.527000,__ +packet,277.402000,__ +packet,277.319000,__ +packet,277.360000,__ +packet,277.444000,__ +packet,277.486000,__ +packet,277.778000,__ +packet,277.652000,__ +packet,277.569000,__ +packet,277.611000,__ +packet,277.694000,__ +packet,277.736000,__ +packet,278.028000,__ +packet,277.903000,__ +packet,277.819000,__ +packet,277.861000,__ +packet,277.944000,__ +packet,277.986000,__ +packet,278.278000,__ +packet,278.153000,__ +packet,278.069000,__ +packet,278.111000,__ +packet,278.195000,__ +packet,278.236000,__ +packet,278.320000,__ +packet,278.361000,K_ +packet,278.737000,__ +packet,278.528000,__ +packet,278.403000,__ +packet,278.445000,__ +packet,278.487000,__ +packet,278.570000,__ +packet,278.612000,__ +packet,278.653000,__ +packet,278.695000,__ +packet,279.112000,__ +packet,278.904000,__ +packet,278.779000,__ +packet,278.820000,__ +packet,278.862000,__ +packet,278.945000,__ +packet,278.987000,__ +packet,279.029000,__ +packet,279.070000,__ +packet,279.321000,__ +packet,279.196000,__ +packet,279.154000,__ +packet,279.237000,__ +packet,279.279000,__ +packet,279.446000,__ +packet,279.362000,__ +packet,279.404000,__ +packet,279.488000,__ +packet,279.529000,__ +packet,279.571000,__ +packet,279.738000,__ +packet,279.654000,__ +packet,279.613000,__ +packet,279.696000,__ +packet,279.821000,__ +packet,279.780000,__ +packet,279.863000,__ +packet,279.905000,__ +packet,279.946000,__ +packet,279.988000,__ +packet,280.071000,__ +packet,280.030000,__ +packet,280.238000,__ +packet,280.155000,__ +packet,280.113000,__ +packet,280.197000,__ +packet,280.405000,__ +packet,280.322000,__ +packet,280.280000,__ +packet,280.363000,__ +packet,280.572000,__ +packet,280.489000,__ +packet,280.447000,__ +packet,280.530000,__ +packet,280.739000,__ +packet,280.655000,__ +packet,280.614000,__ +packet,280.697000,__ +packet,280.906000,__ +packet,280.822000,__ +packet,280.781000,__ +packet,280.864000,__ +packet,280.989000,__ +packet,280.947000,__ +packet,281.156000,__ +packet,281.072000,__ +packet,281.031000,__ +packet,281.114000,__ +packet,281.531000,__ +packet,281.323000,__ +packet,281.198000,__ +packet,281.239000,__ +packet,281.281000,__ +packet,281.364000,__ +packet,281.406000,__ +packet,281.448000,__ +packet,281.490000,__ +packet,281.907000,__ +packet,281.698000,__ +packet,281.573000,__ +packet,281.615000,__ +packet,281.656000,__ +packet,281.740000,__ +packet,281.782000,__ +packet,281.823000,__ +packet,281.865000,__ +packet,282.282000,__ +packet,282.073000,__ +packet,281.948000,__ +packet,281.990000,__ +packet,282.032000,__ +packet,282.115000,__ +packet,282.157000,__ +packet,282.199000,__ +packet,282.240000,__ +packet,282.324000,__ +packet,282.491000,__ +packet,282.407000,__ +packet,282.365000,__ +packet,282.449000,__ +packet,282.532000,K_ +packet,282.699000,__ +packet,282.616000,__ +packet,282.574000,__ +packet,282.657000,__ +packet,282.908000,__ +packet,282.783000,__ +packet,282.741000,__ +packet,282.824000,__ +packet,282.866000,__ +packet,283.074000,__ +packet,282.991000,__ +packet,282.949000,__ +packet,283.033000,__ +packet,283.241000,__ +packet,283.158000,__ +packet,283.116000,__ +packet,283.200000,__ +packet,283.408000,__ +packet,283.325000,__ +packet,283.283000,__ +packet,283.366000,__ +packet,283.533000,__ +packet,283.450000,__ +packet,283.492000,__ +packet,283.700000,__ +packet,283.617000,__ +packet,283.575000,__ +packet,283.658000,__ +packet,283.992000,__ +packet,283.825000,__ +packet,283.742000,__ +packet,283.784000,__ +packet,283.867000,__ +packet,283.909000,__ +packet,283.950000,__ +packet,284.242000,__ +packet,284.117000,__ +packet,284.034000,__ +packet,284.075000,__ +packet,284.159000,__ +packet,284.201000,__ +packet,284.409000,__ +packet,284.326000,__ +packet,284.284000,__ +packet,284.367000,__ +packet,284.534000,__ +packet,284.451000,__ +packet,284.493000,__ +packet,284.701000,__ +packet,284.618000,__ +packet,284.576000,__ +packet,284.659000,__ +packet,284.910000,__ +packet,284.785000,__ +packet,284.743000,__ +packet,284.826000,__ +packet,284.868000,__ +packet,285.076000,__ +packet,284.993000,__ +packet,284.951000,__ +packet,285.035000,__ +packet,285.243000,__ +packet,285.160000,__ +packet,285.118000,__ +packet,285.202000,__ +packet,285.410000,__ +packet,285.327000,__ +packet,285.285000,__ +packet,285.368000,__ +packet,285.577000,__ +packet,285.494000,__ +packet,285.452000,__ +packet,285.535000,__ +packet,285.744000,__ +packet,285.660000,__ +packet,285.619000,__ +packet,285.702000,__ +packet,285.911000,__ +packet,285.827000,__ +packet,285.786000,__ +packet,285.869000,__ +packet,286.077000,__ +packet,285.994000,__ +packet,285.952000,__ +packet,286.036000,__ +packet,286.244000,__ +packet,286.161000,__ +packet,286.119000,__ +packet,286.203000,__ +packet,286.411000,__ +packet,286.328000,__ +packet,286.286000,__ +packet,286.369000,__ +packet,286.578000,__ +packet,286.495000,__ +packet,286.453000,__ +packet,286.536000,__ +packet,286.745000,__ +packet,286.661000,__ +packet,286.620000,__ +packet,286.703000,__ +packet,286.912000,__ +packet,286.828000,__ +packet,286.787000,__ +packet,286.870000,__ +packet,287.078000,__ +packet,286.995000,__ +packet,286.953000,__ +packet,287.037000,__ +packet,287.245000,__ +packet,287.162000,__ +packet,287.120000,__ +packet,287.204000,__ +packet,287.412000,__ +packet,287.329000,__ +packet,287.287000,__ +packet,287.370000,__ +packet,287.579000,__ +packet,287.496000,__ +packet,287.454000,__ +packet,287.537000,__ +packet,287.746000,__ +packet,287.662000,__ +packet,287.621000,__ +packet,287.704000,__ +packet,287.913000,__ +packet,287.829000,__ +packet,287.788000,__ +packet,287.871000,__ +packet,288.079000,__ +packet,287.996000,__ +packet,287.954000,__ +packet,288.038000,__ +packet,288.246000,__ +packet,288.163000,__ +packet,288.121000,__ +packet,288.205000,__ +packet,288.413000,__ +packet,288.330000,__ +packet,288.288000,__ +packet,288.371000,__ +packet,288.580000,__ +packet,288.497000,__ +packet,288.455000,__ +packet,288.538000,__ +packet,288.747000,__ +packet,288.663000,__ +packet,288.622000,__ +packet,288.705000,__ +packet,288.914000,__ +packet,288.830000,__ +packet,288.789000,__ +packet,288.872000,__ +packet,289.080000,__ +packet,288.997000,__ +packet,288.955000,__ +packet,289.039000,__ +packet,289.247000,__ +packet,289.164000,__ +packet,289.122000,__ +packet,289.206000,__ +packet,289.414000,__ +packet,289.331000,__ +packet,289.289000,__ +packet,289.372000,__ +packet,289.581000,__ +packet,289.498000,__ +packet,289.456000,__ +packet,289.539000,__ +packet,289.748000,__ +packet,289.664000,__ +packet,289.623000,__ +packet,289.706000,__ +packet,289.915000,__ +packet,289.831000,__ +packet,289.790000,__ +packet,289.873000,__ +packet,289.956000,K_ +packet,290.207000,__ +packet,290.081000,__ +packet,289.998000,__ +packet,290.040000,__ +packet,290.123000,__ +packet,290.165000,__ +packet,290.415000,__ +packet,290.290000,__ +packet,290.248000,__ +packet,290.332000,__ +packet,290.373000,__ +packet,290.540000,__ +packet,290.457000,__ +packet,290.499000,__ +packet,290.665000,__ +packet,290.582000,__ +packet,290.624000,__ +packet,290.874000,__ +packet,290.749000,__ +packet,290.707000,__ +packet,290.791000,__ +packet,290.832000,__ +packet,291.041000,__ +packet,290.957000,__ +packet,290.916000,__ +packet,290.999000,__ +packet,291.291000,__ +packet,291.166000,__ +packet,291.082000,__ +packet,291.124000,__ +packet,291.208000,__ +packet,291.249000,__ +packet,291.458000,__ +packet,291.374000,__ +packet,291.333000,__ +packet,291.416000,__ +packet,291.666000,__ +packet,291.541000,__ +packet,291.500000,__ +packet,291.583000,__ +packet,291.625000,__ +packet,291.917000,__ +packet,291.792000,__ +packet,291.708000,__ +packet,291.750000,__ +packet,291.833000,__ +packet,291.875000,__ +packet,292.042000,__ +packet,291.958000,__ +packet,292.000000,__ +packet,292.250000,__ +packet,292.125000,__ +packet,292.083000,__ +packet,292.167000,__ +packet,292.209000,__ +packet,292.417000,__ +packet,292.334000,__ +packet,292.292000,__ +packet,292.375000,__ +packet,292.542000,__ +packet,292.459000,__ +packet,292.501000,__ +packet,292.793000,__ +packet,292.667000,__ +packet,292.584000,__ +packet,292.626000,__ +packet,292.709000,__ +packet,292.751000,__ +packet,292.918000,__ +packet,292.834000,__ +packet,292.876000,__ +packet,292.959000,K_ +packet,293.084000,__ +packet,293.001000,__ +packet,293.043000,__ +packet,293.251000,__ +packet,293.168000,__ +packet,293.126000,__ +packet,293.210000,__ +packet,293.418000,__ +packet,293.335000,__ +packet,293.293000,__ +packet,293.376000,__ +packet,293.460000,__ +packet,293.502000,__ +packet,293.627000,__ +packet,293.543000,__ +packet,293.585000,__ +packet,293.835000,__ +packet,293.710000,__ +packet,293.668000,__ +packet,293.752000,__ +packet,293.794000,__ +packet,294.085000,__ +packet,293.960000,__ +packet,293.877000,__ +packet,293.919000,__ +packet,294.002000,__ +packet,294.044000,__ +packet,294.211000,__ +packet,294.127000,__ +packet,294.169000,__ +packet,294.377000,__ +packet,294.294000,__ +packet,294.252000,__ +packet,294.336000,__ +packet,294.628000,__ +packet,294.503000,__ +packet,294.419000,__ +packet,294.461000,__ +packet,294.544000,__ +packet,294.586000,__ +packet,294.753000,__ +packet,294.669000,__ +packet,294.711000,__ +packet,294.961000,__ +packet,294.836000,__ +packet,294.795000,__ +packet,294.878000,__ +packet,294.920000,__ +packet,295.086000,__ +packet,295.003000,__ +packet,295.045000,__ +packet,295.253000,__ +packet,295.170000,__ +packet,295.128000,__ +packet,295.212000,__ +packet,295.504000,__ +packet,295.378000,__ +packet,295.295000,__ +packet,295.337000,__ +packet,295.420000,__ +packet,295.462000,__ +packet,295.712000,__ +packet,295.587000,__ +packet,295.545000,__ +packet,295.629000,__ +packet,295.670000,__ +packet,295.879000,__ +packet,295.796000,__ +packet,295.754000,__ +packet,295.837000,__ +packet,295.962000,__ +packet,295.921000,__ +packet,296.213000,__ +packet,296.087000,__ +packet,296.004000,__ +packet,296.046000,__ +packet,296.129000,__ +packet,296.171000,__ +packet,296.338000,__ +packet,296.254000,__ +packet,296.296000,__ +packet,296.505000,__ +packet,296.421000,__ +packet,296.379000,__ +packet,296.463000,__ +packet,296.755000,__ +packet,296.630000,__ +packet,296.546000,__ +packet,296.588000,__ +packet,296.671000,__ +packet,296.713000,__ +packet,296.963000,__ +packet,296.838000,__ +packet,296.797000,__ +packet,296.880000,__ +packet,296.922000,__ +packet,297.130000,__ +packet,297.047000,__ +packet,297.005000,__ +packet,297.088000,__ +packet,297.255000,__ +packet,297.172000,__ +packet,297.214000,__ +packet,297.464000,__ +packet,297.339000,__ +packet,297.297000,__ +packet,297.380000,__ +packet,297.422000,__ +packet,297.506000,__ +packet,297.714000,__ +packet,297.589000,__ +packet,297.547000,__ +packet,297.631000,__ +packet,297.672000,__ +packet,297.881000,__ +packet,297.798000,__ +packet,297.756000,__ +packet,297.839000,__ +packet,297.923000,K_ +packet,298.089000,__ +packet,298.006000,__ +packet,297.964000,__ +packet,298.048000,__ +packet,298.215000,__ +packet,298.131000,__ +packet,298.173000,__ +packet,298.256000,__ +packet,298.423000,__ +packet,298.340000,__ +packet,298.298000,__ +packet,298.381000,__ +packet,298.632000,__ +packet,298.507000,__ +packet,298.465000,__ +packet,298.548000,__ +packet,298.590000,__ +packet,298.840000,__ +packet,298.715000,__ +packet,298.673000,__ +packet,298.757000,__ +packet,298.799000,__ +packet,298.882000,__ +packet,299.049000,__ +packet,298.965000,__ +packet,298.924000,__ +packet,299.007000,__ +packet,299.174000,__ +packet,299.090000,__ +packet,299.132000,__ +packet,299.341000,__ +packet,299.257000,__ +packet,299.216000,__ +packet,299.299000,__ +packet,299.382000,__ +packet,299.633000,__ +packet,299.508000,__ +packet,299.424000,__ +packet,299.466000,__ +packet,299.549000,__ +packet,299.591000,__ +packet,299.800000,__ +packet,299.716000,__ +packet,299.674000,__ +packet,299.758000,__ +packet,299.841000,__ +packet,300.008000,__ +packet,299.925000,__ +packet,299.883000,__ +packet,299.966000,__ +packet,300.175000,__ +packet,300.091000,__ +packet,300.050000,__ +packet,300.133000,__ +packet,300.258000,__ +packet,300.217000,__ +packet,300.467000,__ +packet,300.342000,__ +packet,300.300000,__ +packet,300.383000,__ +packet,300.425000,__ +packet,300.592000,__ +packet,300.509000,__ +packet,300.550000,__ +packet,300.759000,__ +packet,300.675000,__ +packet,300.634000,__ +packet,300.717000,__ +packet,300.884000,__ +packet,300.801000,__ +packet,300.842000,__ +packet,301.092000,__ +packet,300.967000,__ +packet,300.926000,__ +packet,301.009000,__ +packet,301.051000,__ +packet,301.259000,__ +packet,301.176000,__ +packet,301.134000,__ +packet,301.218000,__ +packet,301.468000,__ +packet,301.343000,__ +packet,301.301000,__ +packet,301.384000,__ +packet,301.426000,__ +packet,301.718000,__ +packet,301.593000,__ +packet,301.510000,__ +packet,301.551000,__ +packet,301.635000,__ +packet,301.676000,__ +packet,301.760000,K_ +packet,301.927000,__ +packet,301.843000,__ +packet,301.802000,__ +packet,301.885000,__ +packet,302.177000,__ +packet,302.052000,__ +packet,301.968000,__ +packet,302.010000,__ +packet,302.093000,__ +packet,302.135000,__ +packet,302.427000,__ +packet,302.302000,__ +packet,302.219000,__ +packet,302.260000,__ +packet,302.344000,__ +packet,302.385000,__ +packet,302.677000,__ +packet,302.552000,__ +packet,302.469000,__ +packet,302.511000,__ +packet,302.594000,__ +packet,302.636000,__ +packet,303.011000,__ +packet,302.844000,__ +packet,302.719000,__ +packet,302.761000,__ +packet,302.803000,__ +packet,302.886000,__ +packet,302.928000,__ +packet,302.969000,__ +packet,303.386000,__ +packet,303.178000,__ +packet,303.053000,__ +packet,303.094000,__ +packet,303.136000,__ +packet,303.220000,__ +packet,303.261000,__ +packet,303.303000,__ +packet,303.345000,__ +packet,303.595000,__ +packet,303.470000,__ +packet,303.428000,__ +packet,303.512000,__ +packet,303.553000,__ +packet,303.845000,__ +packet,303.720000,__ +packet,303.637000,__ +packet,303.678000,__ +packet,303.762000,__ +packet,303.804000,__ +packet,304.137000,__ +packet,303.970000,__ +packet,303.887000,__ +packet,303.929000,__ +packet,304.012000,__ +packet,304.054000,__ +packet,304.095000,__ +packet,304.179000,__ +packet,304.554000,__ +packet,304.346000,__ +packet,304.221000,__ +packet,304.262000,__ +packet,304.304000,__ +packet,304.387000,__ +packet,304.429000,__ +packet,304.471000,__ +packet,304.513000,__ +packet,304.846000,__ +packet,304.679000,__ +packet,304.596000,__ +packet,304.638000,__ +packet,304.721000,__ +packet,304.763000,__ +packet,304.805000,__ +packet,304.888000,K_ +packet,305.013000,__ +packet,304.930000,__ +packet,304.971000,__ +packet,305.180000,__ +packet,305.096000,__ +packet,305.055000,__ +packet,305.138000,__ +packet,305.388000,__ +packet,305.263000,__ +packet,305.222000,__ +packet,305.305000,__ +packet,305.347000,__ +packet,305.764000,__ +packet,305.555000,__ +packet,305.430000,__ +packet,305.472000,__ +packet,305.514000,__ +packet,305.597000,__ +packet,305.639000,__ +packet,305.680000,__ +packet,305.722000,__ +packet,305.889000,__ +packet,305.806000,__ +packet,305.847000,__ +packet,306.014000,__ +packet,305.931000,__ +packet,305.972000,__ +packet,306.181000,__ +packet,306.097000,__ +packet,306.056000,__ +packet,306.139000,__ +packet,306.389000,__ +packet,306.264000,__ +packet,306.223000,__ +packet,306.306000,__ +packet,306.348000,__ +packet,306.556000,__ +packet,306.473000,__ +packet,306.431000,__ +packet,306.515000,__ +packet,306.765000,__ +packet,306.640000,__ +packet,306.598000,__ +packet,306.681000,__ +packet,306.723000,__ +packet,306.890000,__ +packet,306.807000,__ +packet,306.848000,__ +packet,307.140000,__ +packet,307.015000,__ +packet,306.932000,__ +packet,306.973000,__ +packet,307.057000,__ +packet,307.098000,__ +packet,307.265000,__ +packet,307.182000,__ +packet,307.224000,__ +packet,307.307000,__ +packet,307.349000,K_ +packet,307.390000,__ +packet,307.557000,__ +packet,307.474000,__ +packet,307.432000,__ +packet,307.516000,__ +packet,307.724000,__ +packet,307.641000,__ +packet,307.599000,__ +packet,307.682000,__ +packet,307.849000,__ +packet,307.766000,__ +packet,307.808000,__ +packet,308.016000,__ +packet,307.933000,__ +packet,307.891000,__ +packet,307.974000,__ +packet,308.225000,__ +packet,308.099000,__ +packet,308.058000,__ +packet,308.141000,__ +packet,308.183000,__ +packet,308.391000,__ +packet,308.308000,__ +packet,308.266000,__ +packet,308.350000,__ +packet,308.517000,__ +packet,308.433000,__ +packet,308.475000,__ +packet,308.767000,__ +packet,308.642000,__ +packet,308.558000,__ +packet,308.600000,__ +packet,308.683000,__ +packet,308.725000,__ +packet,309.142000,__ +packet,308.934000,__ +packet,308.809000,__ +packet,308.850000,__ +packet,308.892000,__ +packet,308.975000,__ +packet,309.017000,__ +packet,309.059000,__ +packet,309.100000,__ +packet,309.267000,__ +packet,309.184000,__ +packet,309.226000,__ +packet,309.476000,__ +packet,309.351000,__ +packet,309.309000,__ +packet,309.392000,__ +packet,309.434000,__ +packet,309.601000,__ +packet,309.518000,__ +packet,309.559000,__ +packet,309.768000,__ +packet,309.684000,__ +packet,309.643000,__ +packet,309.726000,__ +packet,309.976000,__ +packet,309.851000,__ +packet,309.810000,__ +packet,309.893000,__ +packet,309.935000,__ +packet,310.101000,__ +packet,310.018000,__ +packet,310.060000,__ +packet,310.268000,__ +packet,310.185000,__ +packet,310.143000,__ +packet,310.227000,__ +packet,310.477000,__ +packet,310.352000,__ +packet,310.310000,__ +packet,310.393000,__ +packet,310.435000,__ +packet,310.519000,__ +packet,310.727000,__ +packet,310.602000,__ +packet,310.560000,__ +packet,310.644000,__ +packet,310.685000,__ +packet,310.852000,__ +packet,310.769000,__ +packet,310.811000,__ +packet,310.977000,__ +packet,310.894000,__ +packet,310.936000,__ +packet,311.102000,__ +packet,311.019000,__ +packet,311.061000,__ +packet,311.269000,__ +packet,311.186000,__ +packet,311.144000,__ +packet,311.228000,__ +packet,311.478000,__ +packet,311.353000,__ +packet,311.311000,__ +packet,311.394000,__ +packet,311.436000,__ +packet,311.520000,__ +packet,311.645000,__ +packet,311.561000,__ +packet,311.603000,__ +packet,311.728000,__ +packet,311.686000,__ +packet,311.770000,K_ +packet,311.812000,__ +packet,311.978000,__ +packet,311.895000,__ +packet,311.853000,__ +packet,311.937000,__ +packet,312.062000,__ +packet,312.020000,__ +packet,312.187000,__ +packet,312.103000,__ +packet,312.145000,__ +packet,312.354000,__ +packet,312.270000,__ +packet,312.229000,__ +packet,312.312000,__ +packet,312.646000,__ +packet,312.479000,__ +packet,312.395000,__ +packet,312.437000,__ +packet,312.521000,__ +packet,312.562000,__ +packet,312.604000,__ +packet,312.979000,__ +packet,312.813000,__ +packet,312.687000,__ +packet,312.729000,__ +packet,312.771000,__ +packet,312.854000,__ +packet,312.896000,__ +packet,312.938000,__ +packet,313.063000,__ +packet,313.021000,__ +packet,313.230000,__ +packet,313.146000,__ +packet,313.104000,__ +packet,313.188000,__ +packet,313.313000,__ +packet,313.271000,__ +packet,313.480000,__ +packet,313.396000,__ +packet,313.355000,__ +packet,313.438000,__ +packet,313.605000,__ +packet,313.522000,__ +packet,313.563000,__ +packet,313.772000,__ +packet,313.688000,__ +packet,313.647000,__ +packet,313.730000,__ +packet,313.855000,__ +packet,313.814000,__ +packet,314.064000,__ +packet,313.939000,__ +packet,313.897000,__ +packet,313.980000,__ +packet,314.022000,__ +packet,314.105000,K_ +packet,314.272000,__ +packet,314.189000,__ +packet,314.147000,__ +packet,314.231000,__ +packet,314.439000,__ +packet,314.356000,__ +packet,314.314000,__ +packet,314.397000,__ +packet,314.689000,__ +packet,314.564000,__ +packet,314.481000,__ +packet,314.523000,__ +packet,314.606000,__ +packet,314.648000,__ +packet,315.023000,__ +packet,314.856000,__ +packet,314.731000,__ +packet,314.773000,__ +packet,314.815000,__ +packet,314.898000,__ +packet,314.940000,__ +packet,314.981000,__ +packet,315.190000,__ +packet,315.106000,__ +packet,315.065000,__ +packet,315.148000,__ +packet,315.398000,__ +packet,315.273000,__ +packet,315.232000,__ +packet,315.315000,__ +packet,315.357000,__ +packet,315.774000,__ +packet,315.565000,__ +packet,315.440000,__ +packet,315.482000,__ +packet,315.524000,__ +packet,315.607000,__ +packet,315.649000,__ +packet,315.690000,__ +packet,315.732000,__ +packet,315.816000,K_ +packet,315.982000,__ +packet,315.899000,__ +packet,315.857000,__ +packet,315.941000,__ +packet,316.191000,__ +packet,316.066000,__ +packet,316.024000,__ +packet,316.107000,__ +packet,316.149000,__ +packet,316.483000,__ +packet,316.316000,__ +packet,316.233000,__ +packet,316.274000,__ +packet,316.358000,__ +packet,316.399000,__ +packet,316.441000,__ +packet,316.650000,__ +packet,316.566000,__ +packet,316.525000,__ +packet,316.608000,__ +packet,317.025000,__ +packet,316.817000,__ +packet,316.691000,__ +packet,316.733000,__ +packet,316.775000,__ +packet,316.858000,__ +packet,316.900000,__ +packet,316.942000,__ +packet,316.983000,__ +packet,317.275000,__ +packet,317.150000,__ +packet,317.067000,__ +packet,317.108000,__ +packet,317.192000,__ +packet,317.234000,__ +packet,317.400000,__ +packet,317.317000,__ +packet,317.359000,__ +packet,317.567000,__ +packet,317.484000,__ +packet,317.442000,__ +packet,317.526000,__ +packet,317.651000,__ +packet,317.609000,__ +packet,317.734000,__ +packet,317.692000,__ +packet,317.818000,__ +packet,317.776000,__ +packet,317.859000,__ +packet,317.901000,__ +packet,317.943000,__ +packet,317.984000,__ +packet,318.068000,__ +packet,318.026000,__ +packet,318.193000,__ +packet,318.109000,__ +packet,318.151000,__ +packet,318.318000,__ +packet,318.235000,__ +packet,318.276000,__ +packet,318.485000,__ +packet,318.401000,__ +packet,318.360000,__ +packet,318.443000,__ +packet,318.652000,__ +packet,318.568000,__ +packet,318.527000,__ +packet,318.610000,__ +packet,318.819000,__ +packet,318.735000,__ +packet,318.693000,__ +packet,318.777000,__ +packet,319.027000,__ +packet,318.902000,__ +packet,318.860000,__ +packet,318.944000,__ +packet,318.985000,__ +packet,319.194000,__ +packet,319.110000,__ +packet,319.069000,__ +packet,319.152000,__ +packet,319.319000,__ +packet,319.236000,__ +packet,319.277000,__ +packet,319.486000,__ +packet,319.402000,__ +packet,319.361000,__ +packet,319.444000,__ +packet,319.528000,__ +packet,319.611000,__ +packet,319.569000,__ +packet,319.694000,__ +packet,319.653000,__ +packet,319.736000,__ +packet,319.903000,__ +packet,319.820000,__ +packet,319.778000,__ +packet,319.861000,__ +packet,320.028000,__ +packet,319.945000,__ +packet,319.986000,__ +packet,320.070000,K_ +packet,320.111000,__ +packet,320.195000,__ +packet,320.153000,__ +packet,320.237000,__ +packet,320.529000,__ +packet,320.362000,__ +packet,320.278000,__ +packet,320.320000,__ +packet,320.403000,__ +packet,320.445000,__ +packet,320.487000,__ +packet,320.779000,__ +packet,320.654000,__ +packet,320.570000,__ +packet,320.612000,__ +packet,320.695000,__ +packet,320.737000,__ +packet,321.029000,__ +packet,320.904000,__ +packet,320.821000,__ +packet,320.862000,__ +packet,320.946000,__ +packet,320.987000,__ +packet,321.279000,__ +packet,321.154000,__ +packet,321.071000,__ +packet,321.112000,__ +packet,321.196000,__ +packet,321.238000,__ +packet,321.530000,__ +packet,321.404000,__ +packet,321.321000,__ +packet,321.363000,__ +packet,321.446000,__ +packet,321.488000,__ +packet,321.780000,__ +packet,321.655000,__ +packet,321.571000,__ +packet,321.613000,__ +packet,321.696000,__ +packet,321.738000,__ +packet,321.863000,__ +packet,321.822000,__ +packet,322.113000,__ +packet,321.988000,__ +packet,321.905000,__ +packet,321.947000,__ +packet,322.030000,__ +packet,322.072000,__ +packet,322.364000,__ +packet,322.239000,__ +packet,322.155000,__ +packet,322.197000,__ +packet,322.280000,__ +packet,322.322000,__ +packet,322.614000,__ +packet,322.489000,__ +packet,322.405000,__ +packet,322.447000,__ +packet,322.531000,__ +packet,322.572000,__ +packet,322.864000,__ +packet,322.739000,__ +packet,322.656000,__ +packet,322.697000,__ +packet,322.781000,__ +packet,322.823000,__ +packet,323.114000,__ +packet,322.989000,__ +packet,322.906000,__ +packet,322.948000,__ +packet,323.031000,__ +packet,323.073000,__ +packet,323.198000,__ +packet,323.156000,__ +packet,323.448000,__ +packet,323.323000,__ +packet,323.240000,__ +packet,323.281000,__ +packet,323.365000,__ +packet,323.406000,__ +packet,323.698000,__ +packet,323.573000,__ +packet,323.490000,__ +packet,323.532000,__ +packet,323.615000,__ +packet,323.657000,__ +packet,323.949000,__ +packet,323.824000,__ +packet,323.740000,__ +packet,323.782000,__ +packet,323.865000,__ +packet,323.907000,__ +packet,324.199000,__ +packet,324.074000,__ +packet,323.990000,__ +packet,324.032000,__ +packet,324.115000,__ +packet,324.157000,__ +packet,324.449000,__ +packet,324.324000,__ +packet,324.241000,__ +packet,324.282000,__ +packet,324.366000,__ +packet,324.407000,__ +packet,324.699000,__ +packet,324.574000,__ +packet,324.491000,__ +packet,324.533000,__ +packet,324.616000,__ +packet,324.658000,__ +packet,324.783000,__ +packet,324.741000,__ +packet,324.866000,__ +packet,324.825000,__ +packet,325.116000,__ +packet,324.991000,__ +packet,324.908000,__ +packet,324.950000,__ +packet,325.033000,__ +packet,325.075000,__ +packet,325.367000,__ +packet,325.242000,__ +packet,325.158000,__ +packet,325.200000,__ +packet,325.283000,__ +packet,325.325000,__ +packet,325.617000,__ +packet,325.492000,__ +packet,325.408000,__ +packet,325.450000,__ +packet,325.534000,__ +packet,325.575000,__ +packet,325.867000,__ +packet,325.742000,__ +packet,325.659000,__ +packet,325.700000,__ +packet,325.784000,__ +packet,325.826000,__ +packet,326.117000,__ +packet,325.992000,__ +packet,325.909000,__ +packet,325.951000,__ +packet,326.034000,__ +packet,326.076000,__ +packet,326.409000,__ +packet,326.243000,__ +packet,326.159000,__ +packet,326.201000,__ +packet,326.284000,__ +packet,326.326000,__ +packet,326.368000,__ +packet,326.451000,__ +packet,326.576000,__ +packet,326.493000,__ +packet,326.535000,__ +packet,326.618000,__ +packet,326.743000,__ +packet,326.660000,__ +packet,326.701000,__ +packet,326.910000,__ +packet,326.827000,__ +packet,326.785000,__ +packet,326.868000,__ +packet,326.952000,__ +packet,327.077000,__ +packet,326.993000,__ +packet,327.035000,__ +packet,327.202000,__ +packet,327.118000,__ +packet,327.160000,__ +packet,327.285000,__ +packet,327.244000,__ +packet,327.619000,__ +packet,327.452000,__ +packet,327.327000,__ +packet,327.369000,__ +packet,327.410000,__ +packet,327.494000,__ +packet,327.536000,__ +packet,327.577000,__ +packet,327.911000,__ +packet,327.744000,__ +packet,327.661000,__ +packet,327.702000,__ +packet,327.786000,__ +packet,327.828000,__ +packet,327.869000,__ +packet,327.953000,K_ +packet,328.036000,__ +packet,327.994000,__ +packet,328.286000,__ +packet,328.161000,__ +packet,328.078000,__ +packet,328.119000,__ +packet,328.203000,__ +packet,328.245000,__ +packet,328.578000,__ +packet,328.411000,__ +packet,328.328000,__ +packet,328.370000,__ +packet,328.453000,__ +packet,328.495000,__ +packet,328.537000,__ +packet,328.703000,__ +packet,328.620000,__ +packet,328.662000,__ +packet,328.912000,__ +packet,328.787000,__ +packet,328.745000,__ +packet,328.829000,__ +packet,328.870000,__ +packet,328.954000,__ +packet,329.329000,__ +packet,329.120000,__ +packet,328.995000,__ +packet,329.037000,__ +packet,329.079000,__ +packet,329.162000,__ +packet,329.204000,__ +packet,329.246000,__ +packet,329.287000,__ +packet,329.412000,__ +packet,329.371000,__ +packet,329.579000,__ +packet,329.496000,__ +packet,329.454000,__ +packet,329.538000,__ +packet,329.704000,__ +packet,329.621000,__ +packet,329.663000,__ +packet,329.788000,__ +packet,329.746000,__ +packet,329.955000,__ +packet,329.871000,__ +packet,329.830000,__ +packet,329.913000,__ +packet,329.996000,K_ +packet,330.205000,__ +packet,330.080000,__ +packet,330.038000,__ +packet,330.121000,__ +packet,330.163000,__ +packet,330.539000,__ +packet,330.372000,__ +packet,330.247000,__ +packet,330.288000,__ +packet,330.330000,__ +packet,330.413000,__ +packet,330.455000,__ +packet,330.497000,__ +packet,330.622000,__ +packet,330.580000,__ +packet,330.789000,__ +packet,330.705000,__ +packet,330.664000,__ +packet,330.747000,__ +packet,330.997000,__ +packet,330.872000,__ +packet,330.831000,__ +packet,330.914000,__ +packet,330.956000,__ +packet,331.039000,__ +packet,331.206000,__ +packet,331.122000,__ +packet,331.081000,__ +packet,331.164000,__ +packet,331.248000,__ +packet,331.289000,K_ +packet,331.331000,__ +packet,331.498000,__ +packet,331.414000,__ +packet,331.373000,__ +packet,331.456000,__ +packet,331.665000,__ +packet,331.581000,__ +packet,331.540000,__ +packet,331.623000,__ +packet,331.790000,__ +packet,331.706000,__ +packet,331.748000,__ +packet,331.998000,__ +packet,331.873000,__ +packet,331.832000,__ +packet,331.915000,__ +packet,331.957000,__ +packet,332.123000,__ +packet,332.040000,__ +packet,332.082000,__ +packet,332.290000,__ +packet,332.207000,__ +packet,332.165000,__ +packet,332.249000,__ +packet,332.457000,__ +packet,332.374000,__ +packet,332.332000,__ +packet,332.415000,__ +packet,332.541000,__ +packet,332.499000,__ +packet,332.666000,__ +packet,332.582000,__ +packet,332.624000,__ +packet,332.833000,__ +packet,332.749000,__ +packet,332.707000,__ +packet,332.791000,__ +packet,332.958000,__ +packet,332.874000,__ +packet,332.916000,__ +packet,333.083000,__ +packet,332.999000,__ +packet,333.041000,__ +packet,333.250000,__ +packet,333.166000,__ +packet,333.124000,__ +packet,333.208000,__ +packet,333.291000,K_ +packet,333.542000,__ +packet,333.416000,__ +packet,333.333000,__ +packet,333.375000,__ +packet,333.458000,__ +packet,333.500000,__ +packet,333.875000,__ +packet,333.708000,__ +packet,333.583000,__ +packet,333.625000,__ +packet,333.667000,__ +packet,333.750000,__ +packet,333.792000,__ +packet,333.834000,__ +packet,334.084000,__ +packet,333.959000,__ +packet,333.917000,__ +packet,334.000000,__ +packet,334.042000,__ +packet,334.209000,__ +packet,334.125000,__ +packet,334.167000,__ +packet,334.417000,__ +packet,334.292000,__ +packet,334.251000,__ +packet,334.334000,__ +packet,334.376000,__ +packet,334.584000,__ +packet,334.501000,__ +packet,334.459000,__ +packet,334.543000,__ +packet,334.668000,__ +packet,334.626000,__ +packet,334.751000,__ +packet,334.709000,__ +packet,334.835000,__ +packet,334.793000,__ +packet,335.001000,__ +packet,334.918000,__ +packet,334.876000,__ +packet,334.960000,__ +packet,335.168000,__ +packet,335.085000,__ +packet,335.043000,__ +packet,335.126000,__ +packet,335.293000,__ +packet,335.210000,__ +packet,335.252000,__ +packet,335.460000,__ +packet,335.377000,__ +packet,335.335000,__ +packet,335.418000,__ +packet,335.627000,__ +packet,335.544000,__ +packet,335.502000,__ +packet,335.585000,__ +packet,335.919000,__ +packet,335.752000,__ +packet,335.669000,__ +packet,335.710000,__ +packet,335.794000,__ +packet,335.836000,__ +packet,335.877000,__ +packet,336.002000,__ +packet,335.961000,__ +packet,336.127000,__ +packet,336.044000,__ +packet,336.086000,__ +packet,336.253000,__ +packet,336.169000,__ +packet,336.211000,__ +packet,336.294000,__ +packet,336.419000,__ +packet,336.336000,__ +packet,336.378000,__ +packet,336.503000,__ +packet,336.461000,__ +packet,336.545000,__ +packet,336.670000,__ +packet,336.586000,__ +packet,336.628000,__ +packet,336.878000,__ +packet,336.753000,__ +packet,336.711000,__ +packet,336.795000,__ +packet,336.837000,__ +packet,336.920000,K_ +packet,337.128000,__ +packet,337.003000,__ +packet,336.962000,__ +packet,337.045000,__ +packet,337.087000,__ +packet,337.295000,__ +packet,337.212000,__ +packet,337.170000,__ +packet,337.254000,__ +packet,337.379000,__ +packet,337.337000,__ +packet,337.546000,__ +packet,337.462000,__ +packet,337.420000,__ +packet,337.504000,__ +packet,337.712000,__ +packet,337.629000,__ +packet,337.587000,__ +packet,337.671000,__ +packet,337.921000,__ +packet,337.796000,__ +packet,337.754000,__ +packet,337.838000,__ +packet,337.879000,__ +packet,337.963000,K_ +packet,338.129000,__ +packet,338.046000,__ +packet,338.004000,__ +packet,338.088000,__ +packet,338.296000,__ +packet,338.213000,__ +packet,338.171000,__ +packet,338.255000,__ +packet,338.463000,__ +packet,338.380000,__ +packet,338.338000,__ +packet,338.421000,__ +packet,338.630000,__ +packet,338.547000,__ +packet,338.505000,__ +packet,338.588000,__ +packet,338.880000,__ +packet,338.755000,__ +packet,338.672000,__ +packet,338.713000,__ +packet,338.797000,__ +packet,338.839000,__ +packet,339.047000,__ +packet,338.964000,__ +packet,338.922000,__ +packet,339.005000,__ +packet,339.214000,__ +packet,339.130000,__ +packet,339.089000,__ +packet,339.172000,__ +packet,339.297000,__ +packet,339.256000,__ +packet,339.464000,__ +packet,339.381000,__ +packet,339.339000,__ +packet,339.422000,__ +packet,339.506000,__ +packet,339.673000,__ +packet,339.589000,__ +packet,339.548000,__ +packet,339.631000,__ +packet,339.881000,__ +packet,339.756000,__ +packet,339.714000,__ +packet,339.798000,__ +packet,339.840000,__ +packet,340.048000,__ +packet,339.965000,__ +packet,339.923000,__ +packet,340.006000,__ +packet,340.215000,__ +packet,340.131000,__ +packet,340.090000,__ +packet,340.173000,__ +packet,340.382000,__ +packet,340.298000,__ +packet,340.257000,__ +packet,340.340000,__ +packet,340.715000,__ +packet,340.549000,__ +packet,340.423000,__ +packet,340.465000,__ +packet,340.507000,__ +packet,340.590000,__ +packet,340.632000,__ +packet,340.674000,__ +packet,341.091000,__ +packet,340.882000,__ +packet,340.757000,__ +packet,340.799000,__ +packet,340.841000,__ +packet,340.924000,__ +packet,340.966000,__ +packet,341.007000,__ +packet,341.049000,__ +packet,341.258000,__ +packet,341.174000,__ +packet,341.132000,__ +packet,341.216000,__ +packet,341.508000,__ +packet,341.383000,__ +packet,341.299000,__ +packet,341.341000,__ +packet,341.424000,__ +packet,341.466000,__ +packet,341.758000,__ +packet,341.633000,__ +packet,341.550000,__ +packet,341.591000,__ +packet,341.675000,__ +packet,341.716000,__ +packet,341.925000,__ +packet,341.842000,__ +packet,341.800000,__ +packet,341.883000,__ +packet,342.175000,__ +packet,342.050000,__ +packet,341.967000,__ +packet,342.008000,__ +packet,342.092000,__ +packet,342.133000,__ +packet,342.551000,__ +packet,342.342000,__ +packet,342.217000,__ +packet,342.259000,__ +packet,342.300000,__ +packet,342.384000,__ +packet,342.425000,__ +packet,342.467000,__ +packet,342.509000,__ +packet,342.926000,__ +packet,342.717000,__ +packet,342.592000,__ +packet,342.634000,__ +packet,342.676000,__ +packet,342.759000,__ +packet,342.801000,__ +packet,342.843000,__ +packet,342.884000,__ +packet,343.093000,__ +packet,343.009000,__ +packet,342.968000,__ +packet,343.051000,__ +packet,343.301000,__ +packet,343.176000,__ +packet,343.134000,__ +packet,343.218000,__ +packet,343.260000,__ +packet,343.510000,__ +packet,343.385000,__ +packet,343.343000,__ +packet,343.426000,__ +packet,343.468000,__ +packet,343.802000,__ +packet,343.635000,__ +packet,343.552000,__ +packet,343.593000,__ +packet,343.677000,__ +packet,343.718000,__ +packet,343.760000,__ +packet,343.844000,K_ +packet,344.010000,__ +packet,343.927000,__ +packet,343.885000,__ +packet,343.969000,__ +packet,344.177000,__ +packet,344.094000,__ +packet,344.052000,__ +packet,344.135000,__ +packet,344.261000,__ +packet,344.219000,__ +packet,344.553000,__ +packet,344.386000,__ +packet,344.302000,__ +packet,344.344000,__ +packet,344.427000,__ +packet,344.469000,__ +packet,344.511000,__ +packet,344.719000,__ +packet,344.636000,__ +packet,344.594000,__ +packet,344.678000,__ +packet,344.803000,__ +packet,344.761000,__ +packet,345.053000,__ +packet,344.928000,__ +packet,344.845000,__ +packet,344.886000,__ +packet,344.970000,__ +packet,345.011000,__ +packet,345.303000,__ +packet,345.178000,__ +packet,345.095000,__ +packet,345.136000,__ +packet,345.220000,__ +packet,345.262000,__ +packet,345.637000,__ +packet,345.470000,__ +packet,345.345000,__ +packet,345.387000,__ +packet,345.428000,__ +packet,345.512000,__ +packet,345.554000,__ +packet,345.595000,__ +packet,345.887000,__ +packet,345.762000,__ +packet,345.679000,__ +packet,345.720000,__ +packet,345.804000,__ +packet,345.846000,__ +packet,345.929000,K_ +packet,346.096000,__ +packet,346.012000,__ +packet,345.971000,__ +packet,346.054000,__ +packet,346.263000,__ +packet,346.179000,__ +packet,346.137000,__ +packet,346.221000,__ +packet,346.638000,__ +packet,346.429000,__ +packet,346.304000,__ +packet,346.346000,__ +packet,346.388000,__ +packet,346.471000,__ +packet,346.513000,__ +packet,346.555000,__ +packet,346.596000,__ +packet,346.930000,__ +packet,346.763000,__ +packet,346.680000,__ +packet,346.721000,__ +packet,346.805000,__ +packet,346.847000,__ +packet,346.888000,__ +packet,347.264000,__ +packet,347.097000,__ +packet,346.972000,__ +packet,347.013000,__ +packet,347.055000,__ +packet,347.138000,__ +packet,347.180000,__ +packet,347.222000,__ +packet,347.639000,__ +packet,347.430000,__ +packet,347.305000,__ +packet,347.347000,__ +packet,347.389000,__ +packet,347.472000,__ +packet,347.514000,__ +packet,347.556000,__ +packet,347.597000,__ +packet,347.931000,__ +packet,347.764000,__ +packet,347.681000,__ +packet,347.722000,__ +packet,347.806000,__ +packet,347.848000,__ +packet,347.889000,__ +packet,348.265000,__ +packet,348.098000,__ +packet,347.973000,__ +packet,348.014000,__ +packet,348.056000,__ +packet,348.139000,__ +packet,348.181000,__ +packet,348.223000,__ +packet,348.640000,__ +packet,348.431000,__ +packet,348.306000,__ +packet,348.348000,__ +packet,348.390000,__ +packet,348.473000,__ +packet,348.515000,__ +packet,348.557000,__ +packet,348.598000,__ +packet,348.932000,__ +packet,348.765000,__ +packet,348.682000,__ +packet,348.723000,__ +packet,348.807000,__ +packet,348.849000,__ +packet,348.890000,__ +packet,349.057000,__ +packet,348.974000,__ +packet,349.015000,__ +packet,349.099000,K_ +packet,349.266000,__ +packet,349.182000,__ +packet,349.140000,__ +packet,349.224000,__ +packet,349.558000,__ +packet,349.391000,__ +packet,349.307000,__ +packet,349.349000,__ +packet,349.432000,__ +packet,349.474000,__ +packet,349.516000,__ +packet,349.808000,__ +packet,349.683000,__ +packet,349.599000,__ +packet,349.641000,__ +packet,349.724000,__ +packet,349.766000,__ +packet,349.975000,__ +packet,349.891000,__ +packet,349.850000,__ +packet,349.933000,__ +packet,350.308000,__ +packet,350.141000,__ +packet,350.016000,__ +packet,350.058000,__ +packet,350.100000,__ +packet,350.183000,__ +packet,350.225000,__ +packet,350.267000,__ +packet,350.517000,__ +packet,350.392000,__ +packet,350.350000,__ +packet,350.433000,__ +packet,350.475000,__ +packet,350.851000,__ +packet,350.684000,__ +packet,350.559000,__ +packet,350.600000,__ +packet,350.642000,__ +packet,350.725000,__ +packet,350.767000,__ +packet,350.809000,__ +packet,350.934000,__ +packet,350.892000,__ +packet,351.309000,__ +packet,351.101000,__ +packet,350.976000,__ +packet,351.017000,__ +packet,351.059000,__ +packet,351.142000,__ +packet,351.184000,__ +packet,351.226000,__ +packet,351.268000,__ +packet,351.560000,__ +packet,351.434000,__ +packet,351.351000,__ +packet,351.393000,__ +packet,351.476000,__ +packet,351.518000,__ +packet,351.810000,__ +packet,351.685000,__ +packet,351.601000,__ +packet,351.643000,__ +packet,351.726000,__ +packet,351.768000,__ +packet,352.060000,__ +packet,351.935000,__ +packet,351.852000,__ +packet,351.893000,__ +packet,351.977000,__ +packet,352.018000,__ +packet,352.435000,__ +packet,352.227000,__ +packet,352.102000,__ +packet,352.143000,__ +packet,352.185000,__ +packet,352.269000,__ +packet,352.310000,__ +packet,352.352000,__ +packet,352.394000,__ +packet,352.686000,__ +packet,352.561000,__ +packet,352.477000,__ +packet,352.519000,__ +packet,352.602000,__ +packet,352.644000,__ +packet,353.061000,__ +packet,352.853000,__ +packet,352.727000,__ +packet,352.769000,__ +packet,352.811000,__ +packet,352.894000,__ +packet,352.936000,__ +packet,352.978000,__ +packet,353.019000,__ +packet,353.270000,__ +packet,353.144000,__ +packet,353.103000,__ +packet,353.186000,__ +packet,353.228000,__ +packet,353.311000,K_ +packet,353.562000,__ +packet,353.436000,__ +packet,353.353000,__ +packet,353.395000,__ +packet,353.478000,__ +packet,353.520000,__ +packet,353.937000,__ +packet,353.728000,__ +packet,353.603000,__ +packet,353.645000,__ +packet,353.687000,__ +packet,353.770000,__ +packet,353.812000,__ +packet,353.854000,__ +packet,353.895000,__ +packet,354.271000,__ +packet,354.104000,__ +packet,353.979000,__ +packet,354.020000,__ +packet,354.062000,__ +packet,354.145000,__ +packet,354.187000,__ +packet,354.229000,__ +packet,354.646000,__ +packet,354.437000,__ +packet,354.312000,__ +packet,354.354000,__ +packet,354.396000,__ +packet,354.479000,__ +packet,354.521000,__ +packet,354.563000,__ +packet,354.604000,__ +packet,354.938000,__ +packet,354.771000,__ +packet,354.688000,__ +packet,354.729000,__ +packet,354.813000,__ +packet,354.855000,__ +packet,354.896000,__ +packet,355.272000,__ +packet,355.105000,__ +packet,354.980000,__ +packet,355.021000,__ +packet,355.063000,__ +packet,355.146000,__ +packet,355.188000,__ +packet,355.230000,__ +packet,355.564000,__ +packet,355.397000,__ +packet,355.313000,__ +packet,355.355000,__ +packet,355.438000,__ +packet,355.480000,__ +packet,355.522000,__ +packet,355.814000,__ +packet,355.689000,__ +packet,355.605000,__ +packet,355.647000,__ +packet,355.730000,__ +packet,355.772000,__ +packet,356.147000,__ +packet,355.981000,__ +packet,355.856000,__ +packet,355.897000,__ +packet,355.939000,__ +packet,356.022000,__ +packet,356.064000,__ +packet,356.106000,__ +packet,356.231000,__ +packet,356.189000,__ +packet,356.273000,K_ +packet,356.398000,__ +packet,356.314000,__ +packet,356.356000,__ +packet,356.690000,__ +packet,356.523000,__ +packet,356.439000,__ +packet,356.481000,__ +packet,356.565000,__ +packet,356.606000,__ +packet,356.648000,__ +packet,356.940000,__ +packet,356.815000,__ +packet,356.731000,__ +packet,356.773000,__ +packet,356.857000,__ +packet,356.898000,__ +packet,356.982000,__ +packet,357.148000,__ +packet,357.065000,__ +packet,357.023000,__ +packet,357.107000,__ +packet,357.274000,__ +packet,357.190000,__ +packet,357.232000,__ +packet,357.649000,__ +packet,357.440000,__ +packet,357.315000,__ +packet,357.357000,__ +packet,357.399000,__ +packet,357.482000,__ +packet,357.524000,__ +packet,357.566000,__ +packet,357.607000,__ +packet,357.941000,__ +packet,357.774000,__ +packet,357.691000,__ +packet,357.732000,__ +packet,357.816000,__ +packet,357.858000,__ +packet,357.899000,__ +packet,358.275000,__ +packet,358.108000,__ +packet,357.983000,__ +packet,358.024000,__ +packet,358.066000,__ +packet,358.149000,__ +packet,358.191000,__ +packet,358.233000,__ +packet,358.650000,__ +packet,358.441000,__ +packet,358.316000,__ +packet,358.358000,__ +packet,358.400000,__ +packet,358.483000,__ +packet,358.525000,__ +packet,358.567000,__ +packet,358.608000,__ +packet,358.942000,__ +packet,358.775000,__ +packet,358.692000,__ +packet,358.733000,__ +packet,358.817000,__ +packet,358.859000,__ +packet,358.900000,__ +packet,359.276000,__ +packet,359.109000,__ +packet,358.984000,__ +packet,359.025000,__ +packet,359.067000,__ +packet,359.150000,__ +packet,359.192000,__ +packet,359.234000,__ +packet,359.651000,__ +packet,359.442000,__ +packet,359.317000,__ +packet,359.359000,__ +packet,359.401000,__ +packet,359.484000,__ +packet,359.526000,__ +packet,359.568000,__ +packet,359.609000,__ +packet,359.985000,__ +packet,359.818000,__ +packet,359.693000,__ +packet,359.734000,__ +packet,359.776000,__ +packet,359.860000,__ +packet,359.901000,__ +packet,359.943000,__ +packet,360.026000,K_ +packet,360.235000,__ +packet,360.110000,__ +packet,360.068000,__ +packet,360.151000,__ +packet,360.193000,__ +packet,360.610000,__ +packet,360.402000,__ +packet,360.277000,__ +packet,360.318000,__ +packet,360.360000,__ +packet,360.443000,__ +packet,360.485000,__ +packet,360.527000,__ +packet,360.569000,__ +packet,360.694000,__ +packet,360.652000,__ +packet,360.986000,__ +packet,360.819000,__ +packet,360.735000,__ +packet,360.777000,__ +packet,360.861000,__ +packet,360.902000,__ +packet,360.944000,__ +packet,361.236000,__ +packet,361.111000,__ +packet,361.027000,__ +packet,361.069000,__ +packet,361.152000,__ +packet,361.194000,__ +packet,361.444000,__ +packet,361.319000,__ +packet,361.278000,__ +packet,361.361000,__ +packet,361.403000,__ +packet,361.820000,__ +packet,361.611000,__ +packet,361.486000,__ +packet,361.528000,__ +packet,361.570000,__ +packet,361.653000,__ +packet,361.695000,__ +packet,361.736000,__ +packet,361.778000,__ +packet,362.070000,__ +packet,361.945000,__ +packet,361.862000,__ +packet,361.903000,__ +packet,361.987000,__ +packet,362.028000,__ +packet,362.362000,__ +packet,362.195000,__ +packet,362.112000,__ +packet,362.153000,__ +packet,362.237000,__ +packet,362.279000,__ +packet,362.320000,__ +packet,362.404000,K_ +packet,362.696000,__ +packet,362.529000,__ +packet,362.445000,__ +packet,362.487000,__ +packet,362.571000,__ +packet,362.612000,__ +packet,362.654000,__ +packet,362.946000,__ +packet,362.821000,__ +packet,362.737000,__ +packet,362.779000,__ +packet,362.863000,__ +packet,362.904000,__ +packet,363.280000,__ +packet,363.113000,__ +packet,362.988000,__ +packet,363.029000,__ +packet,363.071000,__ +packet,363.154000,__ +packet,363.196000,__ +packet,363.238000,__ +packet,363.655000,__ +packet,363.446000,__ +packet,363.321000,__ +packet,363.363000,__ +packet,363.405000,__ +packet,363.488000,__ +packet,363.530000,__ +packet,363.572000,__ +packet,363.613000,__ +packet,363.947000,__ +packet,363.780000,__ +packet,363.697000,__ +packet,363.738000,__ +packet,363.822000,__ +packet,363.864000,__ +packet,363.905000,__ +packet,364.281000,__ +packet,364.114000,__ +packet,363.989000,__ +packet,364.030000,__ +packet,364.072000,__ +packet,364.155000,__ +packet,364.197000,__ +packet,364.239000,__ +packet,364.656000,__ +packet,364.447000,__ +packet,364.322000,__ +packet,364.364000,__ +packet,364.406000,__ +packet,364.489000,__ +packet,364.531000,__ +packet,364.573000,__ +packet,364.614000,__ +packet,364.948000,__ +packet,364.781000,__ +packet,364.698000,__ +packet,364.739000,__ +packet,364.823000,__ +packet,364.865000,__ +packet,364.906000,__ +packet,365.282000,__ +packet,365.115000,__ +packet,364.990000,__ +packet,365.031000,__ +packet,365.073000,__ +packet,365.156000,__ +packet,365.198000,__ +packet,365.240000,__ +packet,365.657000,__ +packet,365.448000,__ +packet,365.323000,__ +packet,365.365000,__ +packet,365.407000,__ +packet,365.490000,__ +packet,365.532000,__ +packet,365.574000,__ +packet,365.615000,__ +packet,365.949000,__ +packet,365.782000,__ +packet,365.699000,__ +packet,365.740000,__ +packet,365.824000,__ +packet,365.866000,__ +packet,365.907000,__ +packet,366.283000,__ +packet,366.116000,__ +packet,365.991000,__ +packet,366.032000,__ +packet,366.074000,__ +packet,366.157000,__ +packet,366.199000,__ +packet,366.241000,__ +packet,366.658000,__ +packet,366.449000,__ +packet,366.324000,__ +packet,366.366000,__ +packet,366.408000,__ +packet,366.491000,__ +packet,366.533000,__ +packet,366.575000,__ +packet,366.616000,__ +packet,367.033000,__ +packet,366.825000,__ +packet,366.700000,__ +packet,366.741000,__ +packet,366.783000,__ +packet,366.867000,__ +packet,366.908000,__ +packet,366.950000,__ +packet,366.992000,__ +packet,367.158000,__ +packet,367.075000,__ +packet,367.117000,__ +packet,367.200000,K_ +packet,367.367000,__ +packet,367.284000,__ +packet,367.242000,__ +packet,367.325000,__ +packet,367.534000,__ +packet,367.450000,__ +packet,367.409000,__ +packet,367.492000,__ +packet,367.909000,__ +packet,367.701000,__ +packet,367.576000,__ +packet,367.617000,__ +packet,367.659000,__ +packet,367.742000,__ +packet,367.784000,__ +packet,367.826000,__ +packet,367.868000,__ +packet,368.285000,__ +packet,368.076000,__ +packet,367.951000,__ +packet,367.993000,__ +packet,368.034000,__ +packet,368.118000,__ +packet,368.159000,__ +packet,368.201000,__ +packet,368.243000,__ +packet,368.577000,__ +packet,368.410000,__ +packet,368.326000,__ +packet,368.368000,__ +packet,368.451000,__ +packet,368.493000,__ +packet,368.535000,__ +packet,368.785000,__ +packet,368.660000,__ +packet,368.618000,__ +packet,368.702000,__ +packet,368.743000,__ +packet,368.952000,__ +packet,368.869000,__ +packet,368.827000,__ +packet,368.910000,__ +packet,369.160000,__ +packet,369.035000,__ +packet,368.994000,__ +packet,369.077000,__ +packet,369.119000,__ +packet,369.327000,__ +packet,369.244000,__ +packet,369.202000,__ +packet,369.286000,__ +packet,369.578000,__ +packet,369.452000,__ +packet,369.369000,__ +packet,369.411000,__ +packet,369.494000,__ +packet,369.536000,__ +packet,369.786000,__ +packet,369.661000,__ +packet,369.619000,__ +packet,369.703000,__ +packet,369.744000,__ +packet,369.953000,__ +packet,369.870000,__ +packet,369.828000,__ +packet,369.911000,__ +packet,370.161000,__ +packet,370.036000,__ +packet,369.995000,__ +packet,370.078000,__ +packet,370.120000,__ +packet,370.287000,__ +packet,370.203000,__ +packet,370.245000,__ +packet,370.579000,__ +packet,370.412000,__ +packet,370.328000,__ +packet,370.370000,__ +packet,370.453000,__ +packet,370.495000,__ +packet,370.537000,__ +packet,370.829000,__ +packet,370.704000,__ +packet,370.620000,__ +packet,370.662000,__ +packet,370.745000,__ +packet,370.787000,__ +packet,371.079000,__ +packet,370.954000,__ +packet,370.871000,__ +packet,370.912000,__ +packet,370.996000,__ +packet,371.037000,__ +packet,371.329000,__ +packet,371.204000,__ +packet,371.121000,__ +packet,371.162000,__ +packet,371.246000,__ +packet,371.288000,__ +packet,371.580000,__ +packet,371.454000,__ +packet,371.371000,__ +packet,371.413000,__ +packet,371.496000,__ +packet,371.538000,__ +packet,371.830000,__ +packet,371.705000,__ +packet,371.621000,__ +packet,371.663000,__ +packet,371.746000,__ +packet,371.788000,__ +packet,371.997000,__ +packet,371.913000,__ +packet,371.872000,__ +packet,371.955000,__ +packet,372.163000,__ +packet,372.080000,__ +packet,372.038000,__ +packet,372.122000,__ +packet,372.205000,K_ +packet,372.330000,__ +packet,372.247000,__ +packet,372.289000,__ +packet,372.497000,__ +packet,372.414000,__ +packet,372.372000,__ +packet,372.455000,__ +packet,372.664000,__ +packet,372.581000,__ +packet,372.539000,__ +packet,372.622000,__ +packet,372.789000,__ +packet,372.706000,__ +packet,372.747000,__ +packet,372.956000,__ +packet,372.873000,__ +packet,372.831000,__ +packet,372.914000,__ +packet,373.123000,__ +packet,373.039000,__ +packet,372.998000,__ +packet,373.081000,__ +packet,373.331000,__ +packet,373.206000,__ +packet,373.164000,__ +packet,373.248000,__ +packet,373.290000,__ +packet,373.498000,__ +packet,373.415000,__ +packet,373.373000,__ +packet,373.456000,__ +packet,373.665000,__ +packet,373.582000,__ +packet,373.540000,__ +packet,373.623000,__ +packet,373.832000,__ +packet,373.748000,__ +packet,373.707000,__ +packet,373.790000,__ +packet,373.999000,__ +packet,373.915000,__ +packet,373.874000,__ +packet,373.957000,__ +packet,374.165000,__ +packet,374.082000,__ +packet,374.040000,__ +packet,374.124000,__ +packet,374.291000,__ +packet,374.207000,__ +packet,374.249000,__ +packet,374.666000,__ +packet,374.457000,__ +packet,374.332000,__ +packet,374.374000,__ +packet,374.416000,__ +packet,374.499000,__ +packet,374.541000,__ +packet,374.583000,__ +packet,374.624000,__ +packet,374.833000,__ +packet,374.749000,__ +packet,374.708000,__ +packet,374.791000,__ +packet,375.166000,__ +packet,375.000000,__ +packet,374.875000,__ +packet,374.916000,__ +packet,374.958000,__ +packet,375.041000,__ +packet,375.083000,__ +packet,375.125000,__ +packet,375.292000,__ +packet,375.208000,__ +packet,375.250000,__ +packet,375.333000,K_ +packet,375.500000,__ +packet,375.417000,__ +packet,375.375000,__ +packet,375.458000,__ +packet,375.709000,__ +packet,375.584000,__ +packet,375.542000,__ +packet,375.625000,__ +packet,375.667000,__ +packet,375.876000,__ +packet,375.792000,__ +packet,375.750000,__ +packet,375.834000,__ +packet,376.209000,__ +packet,376.042000,__ +packet,375.917000,__ +packet,375.959000,__ +packet,376.001000,__ +packet,376.084000,__ +packet,376.126000,__ +packet,376.167000,__ +packet,376.334000,__ +packet,376.251000,__ +packet,376.293000,__ +packet,376.501000,__ +packet,376.418000,__ +packet,376.376000,__ +packet,376.459000,__ +packet,376.668000,__ +packet,376.585000,__ +packet,376.543000,__ +packet,376.626000,__ +packet,377.043000,__ +packet,376.835000,__ +packet,376.710000,__ +packet,376.751000,__ +packet,376.793000,__ +packet,376.877000,__ +packet,376.918000,__ +packet,376.960000,__ +packet,377.002000,__ +packet,377.085000,__ +packet,377.127000,K_ +packet,377.252000,__ +packet,377.168000,__ +packet,377.210000,__ +packet,377.335000,__ +packet,377.294000,__ +packet,377.419000,__ +packet,377.377000,__ +packet,377.544000,__ +packet,377.460000,__ +packet,377.502000,__ +packet,377.711000,__ +packet,377.627000,__ +packet,377.586000,__ +packet,377.669000,__ +packet,377.919000,__ +packet,377.794000,__ +packet,377.752000,__ +packet,377.836000,__ +packet,377.878000,__ +packet,378.003000,__ +packet,377.961000,__ +packet,378.044000,__ +packet,378.211000,__ +packet,378.128000,__ +packet,378.086000,__ +packet,378.169000,__ +packet,378.420000,__ +packet,378.295000,__ +packet,378.253000,__ +packet,378.336000,__ +packet,378.378000,__ +packet,378.587000,__ +packet,378.503000,__ +packet,378.461000,__ +packet,378.545000,__ +packet,378.712000,__ +packet,378.628000,__ +packet,378.670000,__ +packet,378.879000,__ +packet,378.795000,__ +packet,378.753000,__ +packet,378.837000,__ +packet,379.045000,__ +packet,378.962000,__ +packet,378.920000,__ +packet,379.004000,__ +packet,379.129000,__ +packet,379.087000,__ +packet,379.296000,__ +packet,379.212000,__ +packet,379.170000,__ +packet,379.254000,__ +packet,379.421000,__ +packet,379.337000,__ +packet,379.379000,__ +packet,379.713000,__ +packet,379.546000,__ +packet,379.462000,__ +packet,379.504000,__ +packet,379.588000,__ +packet,379.629000,__ +packet,379.671000,__ +packet,379.838000,__ +packet,379.754000,__ +packet,379.796000,__ +packet,379.963000,__ +packet,379.880000,__ +packet,379.921000,__ +packet,380.130000,__ +packet,380.046000,__ +packet,380.005000,__ +packet,380.088000,__ +packet,380.297000,__ +packet,380.213000,__ +packet,380.171000,__ +packet,380.255000,__ +packet,380.547000,__ +packet,380.422000,__ +packet,380.338000,__ +packet,380.380000,__ +packet,380.463000,__ +packet,380.505000,__ +packet,380.714000,__ +packet,380.630000,__ +packet,380.589000,__ +packet,380.672000,__ +packet,380.755000,__ +packet,380.922000,__ +packet,380.839000,__ +packet,380.797000,__ +packet,380.881000,__ +packet,381.172000,__ +packet,381.047000,__ +packet,380.964000,__ +packet,381.006000,__ +packet,381.089000,__ +packet,381.131000,__ +packet,381.298000,__ +packet,381.214000,__ +packet,381.256000,__ +packet,381.464000,__ +packet,381.381000,__ +packet,381.339000,__ +packet,381.423000,__ +packet,381.631000,__ +packet,381.548000,__ +packet,381.506000,__ +packet,381.590000,__ +packet,381.756000,__ +packet,381.673000,__ +packet,381.715000,__ +packet,381.965000,__ +packet,381.840000,__ +packet,381.798000,__ +packet,381.882000,__ +packet,381.923000,__ +packet,382.007000,__ +packet,382.215000,__ +packet,382.090000,__ +packet,382.048000,__ +packet,382.132000,__ +packet,382.173000,__ +packet,382.340000,__ +packet,382.257000,__ +packet,382.299000,__ +packet,382.549000,__ +packet,382.424000,__ +packet,382.382000,__ +packet,382.465000,__ +packet,382.507000,__ +packet,382.674000,__ +packet,382.591000,__ +packet,382.632000,__ +packet,382.757000,__ +packet,382.716000,__ +packet,382.924000,__ +packet,382.841000,__ +packet,382.799000,__ +packet,382.883000,__ +packet,383.091000,__ +packet,383.008000,__ +packet,382.966000,__ +packet,383.049000,__ +packet,383.258000,__ +packet,383.174000,__ +packet,383.133000,__ +packet,383.216000,__ +packet,383.592000,__ +packet,383.425000,__ +packet,383.300000,__ +packet,383.341000,__ +packet,383.383000,__ +packet,383.466000,__ +packet,383.508000,__ +packet,383.550000,__ +packet,383.842000,__ +packet,383.717000,__ +packet,383.633000,__ +packet,383.675000,__ +packet,383.758000,__ +packet,383.800000,__ +packet,384.050000,__ +packet,383.925000,__ +packet,383.884000,__ +packet,383.967000,__ +packet,384.009000,__ +packet,384.301000,__ +packet,384.175000,__ +packet,384.092000,__ +packet,384.134000,__ +packet,384.217000,__ +packet,384.259000,__ +packet,384.551000,__ +packet,384.426000,__ +packet,384.342000,__ +packet,384.384000,__ +packet,384.467000,__ +packet,384.509000,__ +packet,384.676000,__ +packet,384.593000,__ +packet,384.634000,__ +packet,384.759000,__ +packet,384.718000,__ +packet,384.968000,__ +packet,384.843000,__ +packet,384.801000,__ +packet,384.885000,__ +packet,384.926000,__ +packet,385.093000,__ +packet,385.010000,__ +packet,385.051000,__ +packet,385.302000,__ +packet,385.176000,__ +packet,385.135000,__ +packet,385.218000,__ +packet,385.260000,__ +packet,385.427000,__ +packet,385.343000,__ +packet,385.385000,__ +packet,385.719000,__ +packet,385.552000,__ +packet,385.468000,__ +packet,385.510000,__ +packet,385.594000,__ +packet,385.635000,__ +packet,385.677000,__ +packet,385.969000,__ +packet,385.844000,__ +packet,385.760000,__ +packet,385.802000,__ +packet,385.886000,__ +packet,385.927000,__ +packet,386.219000,__ +packet,386.094000,__ +packet,386.011000,__ +packet,386.052000,__ +packet,386.136000,__ +packet,386.177000,__ +packet,386.344000,__ +packet,386.261000,__ +packet,386.303000,__ +packet,386.595000,__ +packet,386.469000,__ +packet,386.386000,__ +packet,386.428000,__ +packet,386.511000,__ +packet,386.553000,__ +packet,386.803000,__ +packet,386.678000,__ +packet,386.636000,__ +packet,386.720000,__ +packet,386.761000,__ +packet,386.970000,__ +packet,386.887000,__ +packet,386.845000,__ +packet,386.928000,__ +packet,387.304000,__ +packet,387.137000,__ +packet,387.012000,__ +packet,387.053000,__ +packet,387.095000,__ +packet,387.178000,__ +packet,387.220000,__ +packet,387.262000,__ +packet,387.345000,__ +packet,387.512000,__ +packet,387.429000,__ +packet,387.387000,__ +packet,387.470000,__ +packet,387.554000,K_ +packet,387.721000,__ +packet,387.637000,__ +packet,387.596000,__ +packet,387.679000,__ +packet,387.804000,__ +packet,387.762000,__ +packet,388.013000,__ +packet,387.888000,__ +packet,387.846000,__ +packet,387.929000,__ +packet,387.971000,__ +packet,388.305000,__ +packet,388.138000,__ +packet,388.054000,__ +packet,388.096000,__ +packet,388.179000,__ +packet,388.221000,__ +packet,388.263000,__ +packet,388.430000,__ +packet,388.346000,__ +packet,388.388000,__ +packet,388.597000,__ +packet,388.513000,__ +packet,388.471000,__ +packet,388.555000,__ +packet,388.722000,__ +packet,388.638000,__ +packet,388.680000,__ +packet,388.847000,__ +packet,388.763000,__ +packet,388.805000,__ +packet,389.055000,__ +packet,388.930000,__ +packet,388.889000,__ +packet,388.972000,__ +packet,389.014000,__ +packet,389.180000,__ +packet,389.097000,__ +packet,389.139000,__ +packet,389.306000,__ +packet,389.222000,__ +packet,389.264000,__ +packet,389.681000,__ +packet,389.472000,__ +packet,389.347000,__ +packet,389.389000,__ +packet,389.431000,__ +packet,389.514000,__ +packet,389.556000,__ +packet,389.598000,__ +packet,389.639000,__ +packet,389.848000,__ +packet,389.764000,__ +packet,389.723000,__ +packet,389.806000,__ +packet,390.015000,__ +packet,389.931000,__ +packet,389.890000,__ +packet,389.973000,__ +packet,390.348000,__ +packet,390.181000,__ +packet,390.056000,__ +packet,390.098000,__ +packet,390.140000,__ +packet,390.223000,__ +packet,390.265000,__ +packet,390.307000,__ +packet,390.557000,__ +packet,390.432000,__ +packet,390.390000,__ +packet,390.473000,__ +packet,390.515000,__ +packet,390.724000,__ +packet,390.640000,__ +packet,390.599000,__ +packet,390.682000,__ +packet,390.849000,__ +packet,390.765000,__ +packet,390.807000,__ +packet,391.224000,__ +packet,391.016000,__ +packet,390.891000,__ +packet,390.932000,__ +packet,390.974000,__ +packet,391.057000,__ +packet,391.099000,__ +packet,391.141000,__ +packet,391.182000,__ +packet,391.474000,__ +packet,391.349000,__ +packet,391.266000,__ +packet,391.308000,__ +packet,391.391000,__ +packet,391.433000,__ +packet,391.683000,__ +packet,391.558000,__ +packet,391.516000,__ +packet,391.600000,__ +packet,391.641000,__ +packet,391.850000,__ +packet,391.766000,__ +packet,391.725000,__ +packet,391.808000,__ +packet,392.017000,__ +packet,391.933000,__ +packet,391.892000,__ +packet,391.975000,__ +packet,392.142000,__ +packet,392.058000,__ +packet,392.100000,__ +packet,392.350000,__ +packet,392.225000,__ +packet,392.183000,__ +packet,392.267000,__ +packet,392.309000,__ +packet,392.559000,__ +packet,392.434000,__ +packet,392.392000,__ +packet,392.475000,__ +packet,392.517000,__ +packet,392.726000,__ +packet,392.642000,__ +packet,392.601000,__ +packet,392.684000,__ +packet,392.934000,__ +packet,392.809000,__ +packet,392.767000,__ +packet,392.851000,__ +packet,392.893000,__ +packet,393.101000,__ +packet,393.018000,__ +packet,392.976000,__ +packet,393.059000,__ +packet,393.310000,__ +packet,393.184000,__ +packet,393.143000,__ +packet,393.226000,__ +packet,393.268000,__ +packet,393.560000,__ +packet,393.435000,__ +packet,393.351000,__ +packet,393.393000,__ +packet,393.476000,__ +packet,393.518000,__ +packet,393.727000,__ +packet,393.643000,__ +packet,393.602000,__ +packet,393.685000,__ +packet,394.019000,__ +packet,393.852000,__ +packet,393.768000,__ +packet,393.810000,__ +packet,393.894000,__ +packet,393.935000,__ +packet,393.977000,__ +packet,394.060000,__ +packet,394.227000,__ +packet,394.144000,__ +packet,394.102000,__ +packet,394.185000,__ +packet,394.311000,__ +packet,394.269000,__ +packet,394.561000,__ +packet,394.436000,__ +packet,394.352000,__ +packet,394.394000,__ +packet,394.477000,__ +packet,394.519000,__ +packet,394.686000,__ +packet,394.603000,__ +packet,394.644000,__ +packet,394.728000,__ +packet,394.895000,__ +packet,394.811000,__ +packet,394.769000,__ +packet,394.853000,__ +packet,394.936000,__ +packet,395.103000,__ +packet,395.020000,__ +packet,394.978000,__ +packet,395.061000,__ +packet,395.270000,__ +packet,395.186000,__ +packet,395.145000,__ +packet,395.228000,__ +packet,395.645000,__ +packet,395.437000,__ +packet,395.312000,__ +packet,395.353000,__ +packet,395.395000,__ +packet,395.478000,__ +packet,395.520000,__ +packet,395.562000,__ +packet,395.604000,__ +packet,395.687000,__ +packet,395.729000,K_ +packet,395.896000,__ +packet,395.812000,__ +packet,395.770000,__ +packet,395.854000,__ +packet,395.937000,__ +packet,396.062000,__ +packet,395.979000,__ +packet,396.021000,__ +packet,396.146000,__ +packet,396.104000,__ +packet,396.187000,__ +packet,396.271000,__ +packet,396.229000,__ +packet,396.354000,__ +packet,396.313000,__ +packet,396.479000,__ +packet,396.396000,__ +packet,396.438000,__ +packet,396.521000,__ +packet,396.605000,__ +packet,396.563000,__ +packet,396.771000,__ +packet,396.688000,__ +packet,396.646000,__ +packet,396.730000,__ +packet,396.938000,__ +packet,396.855000,__ +packet,396.813000,__ +packet,396.897000,__ +packet,397.105000,__ +packet,397.022000,__ +packet,396.980000,__ +packet,397.063000,__ +packet,397.314000,__ +packet,397.188000,__ +packet,397.147000,__ +packet,397.230000,__ +packet,397.272000,__ +packet,397.564000,__ +packet,397.439000,__ +packet,397.355000,__ +packet,397.397000,__ +packet,397.480000,__ +packet,397.522000,__ +packet,397.606000,__ +packet,397.772000,__ +packet,397.689000,__ +packet,397.647000,__ +packet,397.731000,__ +packet,397.939000,__ +packet,397.856000,__ +packet,397.814000,__ +packet,397.898000,__ +packet,398.106000,__ +packet,398.023000,__ +packet,397.981000,__ +packet,398.064000,__ +packet,398.189000,__ +packet,398.148000,__ +packet,398.356000,__ +packet,398.273000,__ +packet,398.231000,__ +packet,398.315000,__ +packet,398.523000,__ +packet,398.440000,__ +packet,398.398000,__ +packet,398.481000,__ +packet,398.690000,__ +packet,398.607000,__ +packet,398.565000,__ +packet,398.648000,__ +packet,398.899000,__ +packet,398.773000,__ +packet,398.732000,__ +packet,398.815000,__ +packet,398.857000,__ +packet,399.065000,__ +packet,398.982000,__ +packet,398.940000,__ +packet,399.024000,__ +packet,399.232000,__ +packet,399.149000,__ +packet,399.107000,__ +packet,399.190000,__ +packet,399.274000,__ +packet,399.441000,__ +packet,399.357000,__ +packet,399.316000,__ +packet,399.399000,__ +packet,399.691000,__ +packet,399.566000,__ +packet,399.482000,__ +packet,399.524000,__ +packet,399.608000,__ +packet,399.649000,__ +packet,399.900000,__ +packet,399.774000,__ +packet,399.733000,__ +packet,399.816000,__ +packet,399.858000,__ +packet,400.025000,__ +packet,399.941000,__ +packet,399.983000,__ +packet,400.066000,__ +packet,400.233000,__ +packet,400.150000,__ +packet,400.108000,__ +packet,400.191000,__ +packet,400.400000,__ +packet,400.317000,__ +packet,400.275000,__ +packet,400.358000,__ +packet,400.567000,__ +packet,400.483000,__ +packet,400.442000,__ +packet,400.525000,__ +packet,400.650000,__ +packet,400.609000,__ +packet,401.026000,__ +packet,400.817000,__ +packet,400.692000,__ +packet,400.734000,__ +packet,400.775000,__ +packet,400.859000,__ +packet,400.901000,__ +packet,400.942000,__ +packet,400.984000,__ +packet,401.359000,__ +packet,401.192000,__ +packet,401.067000,__ +packet,401.109000,__ +packet,401.151000,__ +packet,401.234000,__ +packet,401.276000,__ +packet,401.318000,__ +packet,401.568000,__ +packet,401.443000,__ +packet,401.401000,__ +packet,401.484000,__ +packet,401.526000,__ +packet,401.610000,K_ +packet,401.776000,__ +packet,401.693000,__ +packet,401.651000,__ +packet,401.735000,__ +packet,401.902000,__ +packet,401.818000,__ +packet,401.860000,__ +packet,402.027000,__ +packet,401.943000,__ +packet,401.985000,__ +packet,402.193000,__ +packet,402.110000,__ +packet,402.068000,__ +packet,402.152000,__ +packet,402.277000,__ +packet,402.235000,__ +packet,402.485000,__ +packet,402.360000,__ +packet,402.319000,__ +packet,402.402000,__ +packet,402.444000,__ +packet,402.527000,__ +packet,402.611000,__ +packet,402.569000,__ +packet,402.777000,__ +packet,402.694000,__ +packet,402.652000,__ +packet,402.736000,__ +packet,402.986000,__ +packet,402.861000,__ +packet,402.819000,__ +packet,402.903000,__ +packet,402.944000,__ +packet,403.153000,__ +packet,403.069000,__ +packet,403.028000,__ +packet,403.111000,__ +packet,403.320000,__ +packet,403.236000,__ +packet,403.194000,__ +packet,403.278000,__ +packet,403.361000,__ +packet,403.528000,__ +packet,403.445000,__ +packet,403.403000,__ +packet,403.486000,__ +packet,403.570000,__ +packet,403.612000,__ +packet,403.778000,__ +packet,403.695000,__ +packet,403.653000,__ +packet,403.737000,__ +packet,403.987000,__ +packet,403.862000,__ +packet,403.820000,__ +packet,403.904000,__ +packet,403.945000,__ +packet,404.112000,__ +packet,404.029000,__ +packet,404.070000,__ +packet,404.279000,__ +packet,404.195000,__ +packet,404.154000,__ +packet,404.237000,__ +packet,404.487000,__ +packet,404.362000,__ +packet,404.321000,__ +packet,404.404000,__ +packet,404.446000,__ +packet,404.654000,__ +packet,404.571000,__ +packet,404.529000,__ +packet,404.613000,__ +packet,404.738000,__ +packet,404.696000,__ +packet,404.863000,__ +packet,404.779000,__ +packet,404.821000,__ +packet,405.071000,__ +packet,404.946000,__ +packet,404.905000,__ +packet,404.988000,__ +packet,405.030000,__ +packet,405.196000,__ +packet,405.113000,__ +packet,405.155000,__ +packet,405.363000,__ +packet,405.280000,__ +packet,405.238000,__ +packet,405.322000,__ +packet,405.447000,__ +packet,405.405000,__ +packet,405.614000,__ +packet,405.530000,__ +packet,405.488000,__ +packet,405.572000,__ +packet,405.822000,__ +packet,405.697000,__ +packet,405.655000,__ +packet,405.739000,__ +packet,405.780000,__ +packet,405.947000,__ +packet,405.864000,__ +packet,405.906000,__ +packet,406.114000,__ +packet,406.031000,__ +packet,405.989000,__ +packet,406.072000,__ +packet,406.323000,__ +packet,406.197000,__ +packet,406.156000,__ +packet,406.239000,__ +packet,406.281000,__ +packet,406.448000,__ +packet,406.364000,__ +packet,406.406000,__ +packet,406.615000,__ +packet,406.531000,__ +packet,406.489000,__ +packet,406.573000,__ +packet,406.823000,__ +packet,406.698000,__ +packet,406.656000,__ +packet,406.740000,__ +packet,406.781000,__ +packet,406.990000,__ +packet,406.907000,__ +packet,406.865000,__ +packet,406.948000,__ +packet,407.073000,__ +packet,407.032000,__ +packet,407.198000,__ +packet,407.115000,__ +packet,407.157000,__ +packet,407.365000,__ +packet,407.282000,__ +packet,407.240000,__ +packet,407.324000,__ +packet,407.574000,__ +packet,407.449000,__ +packet,407.407000,__ +packet,407.490000,__ +packet,407.532000,__ +packet,407.741000,__ +packet,407.657000,__ +packet,407.616000,__ +packet,407.699000,__ +packet,407.866000,__ +packet,407.782000,__ +packet,407.824000,__ +packet,408.074000,__ +packet,407.949000,__ +packet,407.908000,__ +packet,407.991000,__ +packet,408.033000,__ +packet,408.199000,__ +packet,408.116000,__ +packet,408.158000,__ +packet,408.366000,__ +packet,408.283000,__ +packet,408.241000,__ +packet,408.325000,__ +packet,408.575000,__ +packet,408.450000,__ +packet,408.408000,__ +packet,408.491000,__ +packet,408.533000,__ +packet,408.700000,__ +packet,408.617000,__ +packet,408.658000,__ +packet,408.992000,__ +packet,408.825000,__ +packet,408.742000,__ +packet,408.783000,__ +packet,408.867000,__ +packet,408.909000,__ +packet,408.950000,__ +packet,409.159000,__ +packet,409.075000,__ +packet,409.034000,__ +packet,409.117000,__ +packet,409.367000,__ +packet,409.242000,__ +packet,409.200000,__ +packet,409.284000,__ +packet,409.326000,__ +packet,409.534000,__ +packet,409.451000,__ +packet,409.409000,__ +packet,409.492000,__ +packet,409.618000,__ +packet,409.576000,__ +packet,409.826000,__ +packet,409.701000,__ +packet,409.659000,__ +packet,409.743000,__ +packet,409.784000,__ +packet,409.951000,__ +packet,409.868000,__ +packet,409.910000,__ +packet,409.993000,__ +packet,410.035000,K_ +packet,410.118000,__ +packet,410.076000,__ +packet,410.160000,__ +packet,410.201000,__ +packet,410.243000,__ +packet,410.327000,__ +packet,410.285000,__ +packet,410.368000,__ +packet,410.535000,__ +packet,410.452000,__ +packet,410.410000,__ +packet,410.493000,__ +packet,410.744000,__ +packet,410.619000,__ +packet,410.577000,__ +packet,410.660000,__ +packet,410.702000,__ +packet,410.785000,__ +packet,410.994000,__ +packet,410.869000,__ +packet,410.827000,__ +packet,410.911000,__ +packet,410.952000,__ +packet,411.119000,__ +packet,411.036000,__ +packet,411.077000,__ +packet,411.328000,__ +packet,411.202000,__ +packet,411.161000,__ +packet,411.244000,__ +packet,411.286000,__ +packet,411.411000,__ +packet,411.369000,__ +packet,411.453000,K_ +packet,411.620000,__ +packet,411.536000,__ +packet,411.494000,__ +packet,411.578000,__ +packet,411.828000,__ +packet,411.703000,__ +packet,411.661000,__ +packet,411.745000,__ +packet,411.786000,__ +packet,411.953000,__ +packet,411.870000,__ +packet,411.912000,__ +packet,412.120000,__ +packet,412.037000,__ +packet,411.995000,__ +packet,412.078000,__ +packet,412.245000,__ +packet,412.162000,__ +packet,412.203000,__ +packet,412.370000,__ +packet,412.287000,__ +packet,412.329000,__ +packet,412.412000,__ +packet,412.454000,K_ +packet,412.537000,__ +packet,412.495000,__ +packet,412.704000,__ +packet,412.621000,__ +packet,412.579000,__ +packet,412.662000,__ +packet,412.787000,__ +packet,412.746000,__ +packet,412.871000,__ +packet,412.829000,__ +packet,412.996000,__ +packet,412.913000,__ +packet,412.954000,__ +packet,413.121000,__ +packet,413.038000,__ +packet,413.079000,__ +packet,413.330000,__ +packet,413.204000,__ +packet,413.163000,__ +packet,413.246000,__ +packet,413.288000,__ +packet,413.496000,__ +packet,413.413000,__ +packet,413.371000,__ +packet,413.455000,__ +packet,413.663000,__ +packet,413.580000,__ +packet,413.538000,__ +packet,413.622000,__ +packet,413.830000,__ +packet,413.747000,__ +packet,413.705000,__ +packet,413.788000,__ +packet,413.872000,__ +packet,414.080000,__ +packet,413.955000,__ +packet,413.914000,__ +packet,413.997000,__ +packet,414.039000,__ +packet,414.205000,__ +packet,414.122000,__ +packet,414.164000,__ +packet,414.372000,__ +packet,414.289000,__ +packet,414.247000,__ +packet,414.331000,__ +packet,414.581000,__ +packet,414.456000,__ +packet,414.414000,__ +packet,414.497000,__ +packet,414.539000,__ +packet,414.664000,__ +packet,414.623000,__ +packet,414.998000,__ +packet,414.831000,__ +packet,414.706000,__ +packet,414.748000,__ +packet,414.789000,__ +packet,414.873000,__ +packet,414.915000,__ +packet,414.956000,__ +packet,415.123000,__ +packet,415.040000,__ +packet,415.081000,__ +packet,415.290000,__ +packet,415.206000,__ +packet,415.165000,__ +packet,415.248000,__ +packet,415.373000,__ +packet,415.332000,__ +packet,415.624000,__ +packet,415.498000,__ +packet,415.415000,__ +packet,415.457000,__ +packet,415.540000,__ +packet,415.582000,__ +packet,415.749000,__ +packet,415.665000,__ +packet,415.707000,__ +packet,415.790000,K_ +packet,415.999000,__ +packet,415.874000,__ +packet,415.832000,__ +packet,415.916000,__ +packet,415.957000,__ +packet,416.249000,__ +packet,416.124000,__ +packet,416.041000,__ +packet,416.082000,__ +packet,416.166000,__ +packet,416.207000,__ +packet,416.499000,__ +packet,416.374000,__ +packet,416.291000,__ +packet,416.333000,__ +packet,416.416000,__ +packet,416.458000,__ +packet,416.666000,__ +packet,416.583000,__ +packet,416.541000,__ +packet,416.625000,__ +packet,416.875000,__ +packet,416.750000,__ +packet,416.708000,__ +packet,416.791000,__ +packet,416.833000,__ +packet,417.125000,__ +packet,417.000000,__ +packet,416.917000,__ +packet,416.958000,__ +packet,417.042000,__ +packet,417.083000,__ +packet,417.250000,__ +packet,417.167000,__ +packet,417.208000,__ +packet,417.417000,__ +packet,417.334000,__ +packet,417.292000,__ +packet,417.375000,__ +packet,417.584000,__ +packet,417.500000,__ +packet,417.459000,__ +packet,417.542000,__ +packet,417.626000,__ +packet,417.792000,__ +packet,417.709000,__ +packet,417.667000,__ +packet,417.751000,__ +packet,417.918000,__ +packet,417.834000,__ +packet,417.876000,__ +packet,418.001000,__ +packet,417.959000,__ +packet,418.043000,K_ +packet,418.084000,__ +packet,418.293000,__ +packet,418.168000,__ +packet,418.126000,__ +packet,418.209000,__ +packet,418.251000,__ +packet,418.376000,__ +packet,418.335000,__ +packet,418.585000,__ +packet,418.460000,__ +packet,418.418000,__ +packet,418.501000,__ +packet,418.543000,__ +packet,418.627000,__ +packet,418.835000,__ +packet,418.710000,__ +packet,418.668000,__ +packet,418.752000,__ +packet,418.793000,__ +packet,419.127000,__ +packet,418.960000,__ +packet,418.877000,__ +packet,418.919000,__ +packet,419.002000,__ +packet,419.044000,__ +packet,419.085000,__ +packet,419.336000,__ +packet,419.210000,__ +packet,419.169000,__ +packet,419.252000,__ +packet,419.294000,__ +packet,419.502000,__ +packet,419.419000,__ +packet,419.377000,__ +packet,419.461000,__ +packet,419.628000,__ +packet,419.544000,__ +packet,419.586000,__ +packet,419.794000,__ +packet,419.711000,__ +packet,419.669000,__ +packet,419.753000,__ +packet,420.003000,__ +packet,419.878000,__ +packet,419.836000,__ +packet,419.920000,__ +packet,419.961000,__ +packet,420.045000,K_ +packet,420.170000,__ +packet,420.086000,__ +packet,420.128000,__ +packet,420.337000,__ +packet,420.253000,__ +packet,420.211000,__ +packet,420.295000,__ +packet,420.378000,__ +packet,420.545000,__ +packet,420.462000,__ +packet,420.420000,__ +packet,420.503000,__ +packet,420.879000,__ +packet,420.712000,__ +packet,420.587000,__ +packet,420.629000,__ +packet,420.670000,__ +packet,420.754000,__ +packet,420.795000,__ +packet,420.837000,__ +packet,421.087000,__ +packet,420.962000,__ +packet,420.921000,__ +packet,421.004000,__ +packet,421.046000,__ +packet,421.254000,__ +packet,421.171000,__ +packet,421.129000,__ +packet,421.212000,__ +packet,421.379000,__ +packet,421.296000,__ +packet,421.338000,__ +packet,421.588000,__ +packet,421.463000,__ +packet,421.421000,__ +packet,421.504000,__ +packet,421.546000,__ +packet,421.838000,__ +packet,421.713000,__ +packet,421.630000,__ +packet,421.671000,__ +packet,421.755000,__ +packet,421.796000,__ +packet,422.088000,__ +packet,421.963000,__ +packet,421.880000,__ +packet,421.922000,__ +packet,422.005000,__ +packet,422.047000,__ +packet,422.213000,__ +packet,422.130000,__ +packet,422.172000,__ +packet,422.255000,K_ +packet,422.422000,__ +packet,422.339000,__ +packet,422.297000,__ +packet,422.380000,__ +packet,422.464000,__ +packet,422.631000,__ +packet,422.547000,__ +packet,422.505000,__ +packet,422.589000,__ +packet,422.839000,__ +packet,422.714000,__ +packet,422.672000,__ +packet,422.756000,__ +packet,422.797000,__ +packet,422.964000,__ +packet,422.881000,__ +packet,422.923000,__ +packet,423.131000,__ +packet,423.048000,__ +packet,423.006000,__ +packet,423.089000,__ +packet,423.340000,__ +packet,423.214000,__ +packet,423.173000,__ +packet,423.256000,__ +packet,423.298000,__ +packet,423.632000,__ +packet,423.465000,__ +packet,423.381000,__ +packet,423.423000,__ +packet,423.506000,__ +packet,423.548000,__ +packet,423.590000,__ +packet,423.840000,__ +packet,423.715000,__ +packet,423.673000,__ +packet,423.757000,__ +packet,423.798000,__ +packet,423.882000,__ +packet,424.049000,__ +packet,423.965000,__ +packet,423.924000,__ +packet,424.007000,__ +packet,424.215000,__ +packet,424.132000,__ +packet,424.090000,__ +packet,424.174000,__ +packet,424.257000,__ +packet,424.382000,__ +packet,424.299000,__ +packet,424.341000,__ +packet,424.591000,__ +packet,424.466000,__ +packet,424.424000,__ +packet,424.507000,__ +packet,424.549000,__ +packet,424.716000,__ +packet,424.633000,__ +packet,424.674000,__ +packet,424.883000,__ +packet,424.799000,__ +packet,424.758000,__ +packet,424.841000,__ +packet,425.091000,__ +packet,424.966000,__ +packet,424.925000,__ +packet,425.008000,__ +packet,425.050000,__ +packet,425.175000,__ +packet,425.133000,__ +packet,425.216000,K_ +packet,425.383000,__ +packet,425.300000,__ +packet,425.258000,__ +packet,425.342000,__ +packet,425.634000,__ +packet,425.508000,__ +packet,425.425000,__ +packet,425.467000,__ +packet,425.550000,__ +packet,425.592000,__ +packet,425.717000,__ +packet,425.675000,__ +packet,426.009000,__ +packet,425.842000,__ +packet,425.759000,__ +packet,425.800000,__ +packet,425.884000,__ +packet,425.926000,__ +packet,425.967000,__ +packet,426.176000,__ +packet,426.092000,__ +packet,426.051000,__ +packet,426.134000,__ +packet,426.384000,__ +packet,426.259000,__ +packet,426.217000,__ +packet,426.301000,__ +packet,426.343000,__ +packet,426.593000,__ +packet,426.468000,__ +packet,426.426000,__ +packet,426.509000,__ +packet,426.551000,__ +packet,426.760000,__ +packet,426.676000,__ +packet,426.635000,__ +packet,426.718000,__ +packet,426.968000,__ +packet,426.843000,__ +packet,426.801000,__ +packet,426.885000,__ +packet,426.927000,__ +packet,427.135000,__ +packet,427.052000,__ +packet,427.010000,__ +packet,427.093000,__ +packet,427.385000,__ +packet,427.260000,__ +packet,427.177000,__ +packet,427.218000,__ +packet,427.302000,__ +packet,427.344000,__ +packet,427.510000,__ +packet,427.427000,__ +packet,427.469000,__ +packet,427.594000,__ +packet,427.552000,__ +packet,427.636000,__ +packet,427.677000,__ +packet,427.844000,__ +packet,427.761000,__ +packet,427.719000,__ +packet,427.802000,__ +packet,427.886000,__ +packet,428.136000,__ +packet,428.011000,__ +packet,427.928000,__ +packet,427.969000,__ +packet,428.053000,__ +packet,428.094000,__ +packet,428.303000,__ +packet,428.219000,__ +packet,428.178000,__ +packet,428.261000,__ +packet,428.386000,__ +packet,428.345000,__ +packet,428.553000,__ +packet,428.470000,__ +packet,428.428000,__ +packet,428.511000,__ +packet,428.720000,__ +packet,428.637000,__ +packet,428.595000,__ +packet,428.678000,__ +packet,429.012000,__ +packet,428.845000,__ +packet,428.762000,__ +packet,428.803000,__ +packet,428.887000,__ +packet,428.929000,__ +packet,428.970000,__ +packet,429.346000,__ +packet,429.179000,__ +packet,429.054000,__ +packet,429.095000,__ +packet,429.137000,__ +packet,429.220000,__ +packet,429.262000,__ +packet,429.304000,__ +packet,429.554000,__ +packet,429.429000,__ +packet,429.387000,__ +packet,429.471000,__ +packet,429.512000,__ +packet,429.596000,K_ +packet,429.679000,__ +packet,429.638000,__ +packet,429.721000,__ +packet,429.930000,__ +packet,429.804000,__ +packet,429.763000,__ +packet,429.846000,__ +packet,429.888000,__ +packet,430.096000,__ +packet,430.013000,__ +packet,429.971000,__ +packet,430.055000,__ +packet,430.221000,__ +packet,430.138000,__ +packet,430.180000,__ +packet,430.388000,__ +packet,430.305000,__ +packet,430.263000,__ +packet,430.347000,__ +packet,430.555000,__ +packet,430.472000,__ +packet,430.430000,__ +packet,430.513000,__ +packet,430.764000,__ +packet,430.639000,__ +packet,430.597000,__ +packet,430.680000,__ +packet,430.722000,__ +packet,430.889000,__ +packet,430.805000,__ +packet,430.847000,__ +packet,430.931000,__ +packet,430.972000,K_ +packet,431.139000,__ +packet,431.056000,__ +packet,431.014000,__ +packet,431.097000,__ +packet,431.306000,__ +packet,431.222000,__ +packet,431.181000,__ +packet,431.264000,__ +packet,431.598000,__ +packet,431.431000,__ +packet,431.348000,__ +packet,431.389000,__ +packet,431.473000,__ +packet,431.514000,__ +packet,431.556000,__ +packet,431.723000,__ +packet,431.640000,__ +packet,431.681000,__ +packet,431.806000,__ +packet,431.765000,__ +packet,431.973000,__ +packet,431.890000,__ +packet,431.848000,__ +packet,431.932000,__ +packet,432.140000,__ +packet,432.057000,__ +packet,432.015000,__ +packet,432.098000,__ +packet,432.390000,__ +packet,432.265000,__ +packet,432.182000,__ +packet,432.223000,__ +packet,432.307000,__ +packet,432.349000,__ +packet,432.599000,__ +packet,432.474000,__ +packet,432.432000,__ +packet,432.515000,__ +packet,432.557000,__ +packet,432.766000,__ +packet,432.682000,__ +packet,432.641000,__ +packet,432.724000,__ +packet,432.807000,K_ +packet,432.974000,__ +packet,432.891000,__ +packet,432.849000,__ +packet,432.933000,__ +packet,433.224000,__ +packet,433.099000,__ +packet,433.016000,__ +packet,433.058000,__ +packet,433.141000,__ +packet,433.183000,__ +packet,433.475000,__ +packet,433.350000,__ +packet,433.266000,__ +packet,433.308000,__ +packet,433.391000,__ +packet,433.433000,__ +packet,433.725000,__ +packet,433.600000,__ +packet,433.516000,__ +packet,433.558000,__ +packet,433.642000,__ +packet,433.683000,__ +packet,433.975000,__ +packet,433.850000,__ +packet,433.767000,__ +packet,433.808000,__ +packet,433.892000,__ +packet,433.934000,__ +packet,434.017000,__ +packet,434.059000,K_ +packet,434.142000,__ +packet,434.100000,__ +packet,434.517000,__ +packet,434.309000,__ +packet,434.184000,__ +packet,434.225000,__ +packet,434.267000,__ +packet,434.351000,__ +packet,434.392000,__ +packet,434.434000,__ +packet,434.476000,__ +packet,434.893000,__ +packet,434.684000,__ +packet,434.559000,__ +packet,434.601000,__ +packet,434.643000,__ +packet,434.726000,__ +packet,434.768000,__ +packet,434.809000,__ +packet,434.851000,__ +packet,435.143000,__ +packet,435.018000,__ +packet,434.935000,__ +packet,434.976000,__ +packet,435.060000,__ +packet,435.101000,__ +packet,435.393000,__ +packet,435.268000,__ +packet,435.185000,__ +packet,435.226000,__ +packet,435.310000,__ +packet,435.352000,__ +packet,435.518000,__ +packet,435.435000,__ +packet,435.477000,__ +packet,435.644000,__ +packet,435.560000,__ +packet,435.602000,__ +packet,435.769000,__ +packet,435.685000,__ +packet,435.727000,__ +packet,436.019000,__ +packet,435.894000,__ +packet,435.810000,__ +packet,435.852000,__ +packet,435.936000,__ +packet,435.977000,__ +packet,436.144000,__ +packet,436.061000,__ +packet,436.102000,__ +packet,436.394000,__ +packet,436.269000,__ +packet,436.186000,__ +packet,436.227000,__ +packet,436.311000,__ +packet,436.353000,__ +packet,436.519000,__ +packet,436.436000,__ +packet,436.478000,__ +packet,436.645000,__ +packet,436.561000,__ +packet,436.603000,__ +packet,436.853000,__ +packet,436.728000,__ +packet,436.686000,__ +packet,436.770000,__ +packet,436.811000,__ +packet,437.103000,__ +packet,436.978000,__ +packet,436.895000,__ +packet,436.937000,__ +packet,437.020000,__ +packet,437.062000,__ +packet,437.145000,K_ +packet,437.312000,__ +packet,437.228000,__ +packet,437.187000,__ +packet,437.270000,__ +packet,437.479000,__ +packet,437.395000,__ +packet,437.354000,__ +packet,437.437000,__ +packet,437.520000,__ +packet,437.604000,__ +packet,437.562000,__ +packet,437.771000,__ +packet,437.687000,__ +packet,437.646000,__ +packet,437.729000,__ +packet,438.146000,__ +packet,437.938000,__ +packet,437.812000,__ +packet,437.854000,__ +packet,437.896000,__ +packet,437.979000,__ +packet,438.021000,__ +packet,438.063000,__ +packet,438.104000,__ +packet,438.313000,__ +packet,438.229000,__ +packet,438.188000,__ +packet,438.271000,__ +packet,438.480000,__ +packet,438.396000,__ +packet,438.355000,__ +packet,438.438000,__ +packet,438.855000,__ +packet,438.647000,__ +packet,438.521000,__ +packet,438.563000,__ +packet,438.605000,__ +packet,438.688000,__ +packet,438.730000,__ +packet,438.772000,__ +packet,438.813000,__ +packet,438.939000,__ +packet,438.897000,__ +packet,439.314000,__ +packet,439.105000,__ +packet,438.980000,__ +packet,439.022000,__ +packet,439.064000,__ +packet,439.147000,__ +packet,439.189000,__ +packet,439.230000,__ +packet,439.272000,__ +packet,439.481000,__ +packet,439.397000,__ +packet,439.356000,__ +packet,439.439000,__ +packet,439.648000,__ +packet,439.564000,__ +packet,439.522000,__ +packet,439.606000,__ +packet,439.814000,__ +packet,439.731000,__ +packet,439.689000,__ +packet,439.773000,__ +packet,439.981000,__ +packet,439.898000,__ +packet,439.856000,__ +packet,439.940000,__ +packet,440.023000,K_ +packet,440.231000,__ +packet,440.106000,__ +packet,440.065000,__ +packet,440.148000,__ +packet,440.190000,__ +packet,440.398000,__ +packet,440.315000,__ +packet,440.273000,__ +packet,440.357000,__ +packet,440.649000,__ +packet,440.523000,__ +packet,440.440000,__ +packet,440.482000,__ +packet,440.565000,__ +packet,440.607000,__ +packet,440.774000,__ +packet,440.690000,__ +packet,440.732000,__ +packet,440.941000,__ +packet,440.857000,__ +packet,440.815000,__ +packet,440.899000,__ +packet,441.149000,__ +packet,441.024000,__ +packet,440.982000,__ +packet,441.066000,__ +packet,441.107000,__ +packet,441.274000,__ +packet,441.191000,__ +packet,441.232000,__ +packet,441.524000,__ +packet,441.399000,__ +packet,441.316000,__ +packet,441.358000,__ +packet,441.441000,__ +packet,441.483000,__ +packet,441.650000,__ +packet,441.566000,__ +packet,441.608000,__ +packet,441.858000,__ +packet,441.733000,__ +packet,441.691000,__ +packet,441.775000,__ +packet,441.816000,__ +packet,442.025000,__ +packet,441.942000,__ +packet,441.900000,__ +packet,441.983000,__ +packet,442.317000,__ +packet,442.150000,__ +packet,442.067000,__ +packet,442.108000,__ +packet,442.192000,__ +packet,442.233000,__ +packet,442.275000,__ +packet,442.651000,__ +packet,442.484000,__ +packet,442.359000,__ +packet,442.400000,__ +packet,442.442000,__ +packet,442.525000,__ +packet,442.567000,__ +packet,442.609000,__ +packet,442.692000,K_ +packet,442.901000,__ +packet,442.776000,__ +packet,442.734000,__ +packet,442.817000,__ +packet,442.859000,__ +packet,443.068000,__ +packet,442.984000,__ +packet,442.943000,__ +packet,443.026000,__ +packet,443.234000,__ +packet,443.151000,__ +packet,443.109000,__ +packet,443.193000,__ +packet,443.276000,__ +packet,443.318000,__ +packet,443.360000,__ +packet,443.401000,__ +packet,443.443000,__ +packet,443.485000,__ +packet,443.526000,__ +packet,443.652000,__ +packet,443.568000,__ +packet,443.610000,__ +packet,443.902000,__ +packet,443.777000,__ +packet,443.693000,__ +packet,443.735000,__ +packet,443.818000,__ +packet,443.860000,__ +packet,444.069000,__ +packet,443.985000,__ +packet,443.944000,__ +packet,444.027000,__ +packet,444.402000,__ +packet,444.235000,__ +packet,444.110000,__ +packet,444.152000,__ +packet,444.194000,__ +packet,444.277000,__ +packet,444.319000,__ +packet,444.361000,__ +packet,444.653000,__ +packet,444.527000,__ +packet,444.444000,__ +packet,444.486000,__ +packet,444.569000,__ +packet,444.611000,__ +packet,444.903000,__ +packet,444.778000,__ +packet,444.694000,__ +packet,444.736000,__ +packet,444.819000,__ +packet,444.861000,__ +packet,445.153000,__ +packet,445.028000,__ +packet,444.945000,__ +packet,444.986000,__ +packet,445.070000,__ +packet,445.111000,__ +packet,445.195000,__ +packet,445.403000,__ +packet,445.278000,__ +packet,445.236000,__ +packet,445.320000,__ +packet,445.362000,__ +packet,445.570000,__ +packet,445.487000,__ +packet,445.445000,__ +packet,445.528000,__ +packet,445.695000,__ +packet,445.612000,__ +packet,445.654000,__ +packet,445.779000,__ +packet,445.737000,__ +packet,445.820000,__ +packet,445.987000,__ +packet,445.904000,__ +packet,445.862000,__ +packet,445.946000,__ +packet,446.154000,__ +packet,446.071000,__ +packet,446.029000,__ +packet,446.112000,__ +packet,446.321000,__ +packet,446.237000,__ +packet,446.196000,__ +packet,446.279000,__ +packet,446.529000,__ +packet,446.404000,__ +packet,446.363000,__ +packet,446.446000,__ +packet,446.488000,__ +packet,446.696000,__ +packet,446.613000,__ +packet,446.571000,__ +packet,446.655000,__ +packet,446.780000,__ +packet,446.738000,__ +packet,446.988000,__ +packet,446.863000,__ +packet,446.821000,__ +packet,446.905000,__ +packet,446.947000,__ +packet,447.197000,__ +packet,447.072000,__ +packet,447.030000,__ +packet,447.113000,__ +packet,447.155000,__ +packet,447.364000,__ +packet,447.280000,__ +packet,447.238000,__ +packet,447.322000,__ +packet,447.530000,__ +packet,447.447000,__ +packet,447.405000,__ +packet,447.489000,__ +packet,447.697000,__ +packet,447.614000,__ +packet,447.572000,__ +packet,447.656000,__ +packet,447.864000,__ +packet,447.781000,__ +packet,447.739000,__ +packet,447.822000,__ +packet,448.031000,__ +packet,447.948000,__ +packet,447.906000,__ +packet,447.989000,__ +packet,448.198000,__ +packet,448.114000,__ +packet,448.073000,__ +packet,448.156000,__ +packet,448.490000,__ +packet,448.323000,__ +packet,448.239000,__ +packet,448.281000,__ +packet,448.365000,__ +packet,448.406000,__ +packet,448.448000,__ +packet,448.531000,__ +packet,448.782000,__ +packet,448.657000,__ +packet,448.573000,__ +packet,448.615000,__ +packet,448.698000,__ +packet,448.740000,__ +packet,448.907000,__ +packet,448.823000,__ +packet,448.865000,__ +packet,448.949000,K_ +packet,448.990000,__ +packet,449.324000,__ +packet,449.157000,__ +packet,449.032000,__ +packet,449.074000,__ +packet,449.115000,__ +packet,449.199000,__ +packet,449.240000,__ +packet,449.282000,__ +packet,449.658000,__ +packet,449.491000,__ +packet,449.366000,__ +packet,449.407000,__ +packet,449.449000,__ +packet,449.532000,__ +packet,449.574000,__ +packet,449.616000,__ +packet,449.991000,__ +packet,449.824000,__ +packet,449.699000,__ +packet,449.741000,__ +packet,449.783000,__ +packet,449.866000,__ +packet,449.908000,__ +packet,449.950000,__ +packet,450.325000,__ +packet,450.158000,__ +packet,450.033000,__ +packet,450.075000,__ +packet,450.116000,__ +packet,450.200000,__ +packet,450.241000,__ +packet,450.283000,__ +packet,450.659000,__ +packet,450.492000,__ +packet,450.367000,__ +packet,450.408000,__ +packet,450.450000,__ +packet,450.533000,__ +packet,450.575000,__ +packet,450.617000,__ +packet,450.992000,__ +packet,450.825000,__ +packet,450.700000,__ +packet,450.742000,__ +packet,450.784000,__ +packet,450.867000,__ +packet,450.909000,__ +packet,450.951000,__ +packet,451.159000,__ +packet,451.076000,__ +packet,451.034000,__ +packet,451.117000,__ +packet,451.201000,K_ +packet,451.284000,__ +packet,451.242000,__ +packet,451.534000,__ +packet,451.409000,__ +packet,451.326000,__ +packet,451.368000,__ +packet,451.451000,__ +packet,451.493000,__ +packet,451.743000,__ +packet,451.618000,__ +packet,451.576000,__ +packet,451.660000,__ +packet,451.701000,__ +packet,452.118000,__ +packet,451.910000,__ +packet,451.785000,__ +packet,451.826000,__ +packet,451.868000,__ +packet,451.952000,__ +packet,451.993000,__ +packet,452.035000,__ +packet,452.077000,__ +packet,452.285000,__ +packet,452.202000,__ +packet,452.160000,__ +packet,452.243000,__ +packet,452.494000,__ +packet,452.369000,__ +packet,452.327000,__ +packet,452.410000,__ +packet,452.452000,__ +packet,452.786000,__ +packet,452.619000,__ +packet,452.535000,__ +packet,452.577000,__ +packet,452.661000,__ +packet,452.702000,__ +packet,452.744000,__ +packet,453.078000,__ +packet,452.911000,__ +packet,452.827000,__ +packet,452.869000,__ +packet,452.953000,__ +packet,452.994000,__ +packet,453.036000,__ +packet,453.119000,K_ +packet,453.328000,__ +packet,453.203000,__ +packet,453.161000,__ +packet,453.244000,__ +packet,453.286000,__ +packet,453.495000,__ +packet,453.411000,__ +packet,453.370000,__ +packet,453.453000,__ +packet,453.620000,__ +packet,453.536000,__ +packet,453.578000,__ +packet,453.787000,__ +packet,453.703000,__ +packet,453.662000,__ +packet,453.745000,__ +packet,453.995000,__ +packet,453.870000,__ +packet,453.828000,__ +packet,453.912000,__ +packet,453.954000,__ +packet,454.329000,__ +packet,454.162000,__ +packet,454.037000,__ +packet,454.079000,__ +packet,454.120000,__ +packet,454.204000,__ +packet,454.245000,__ +packet,454.287000,__ +packet,454.496000,__ +packet,454.412000,__ +packet,454.371000,__ +packet,454.454000,__ +packet,454.663000,__ +packet,454.579000,__ +packet,454.537000,__ +packet,454.621000,__ +packet,454.788000,__ +packet,454.704000,__ +packet,454.746000,__ +packet,455.080000,__ +packet,454.913000,__ +packet,454.829000,__ +packet,454.871000,__ +packet,454.955000,__ +packet,454.996000,__ +packet,455.038000,__ +packet,455.413000,__ +packet,455.246000,__ +packet,455.121000,__ +packet,455.163000,__ +packet,455.205000,__ +packet,455.288000,__ +packet,455.330000,__ +packet,455.372000,__ +packet,455.664000,__ +packet,455.538000,__ +packet,455.455000,__ +packet,455.497000,__ +packet,455.580000,__ +packet,455.622000,__ +packet,455.747000,__ +packet,455.705000,__ +packet,455.914000,__ +packet,455.830000,__ +packet,455.789000,__ +packet,455.872000,__ +packet,455.997000,__ +packet,455.956000,__ +packet,456.164000,__ +packet,456.081000,__ +packet,456.039000,__ +packet,456.122000,__ +packet,456.289000,__ +packet,456.206000,__ +packet,456.247000,__ +packet,456.414000,__ +packet,456.331000,__ +packet,456.373000,__ +packet,456.581000,__ +packet,456.498000,__ +packet,456.456000,__ +packet,456.539000,__ +packet,456.790000,__ +packet,456.665000,__ +packet,456.623000,__ +packet,456.706000,__ +packet,456.748000,__ +packet,456.915000,__ +packet,456.831000,__ +packet,456.873000,__ +packet,456.957000,K_ +packet,457.165000,__ +packet,457.040000,__ +packet,456.998000,__ +packet,457.082000,__ +packet,457.123000,__ +packet,457.332000,__ +packet,457.248000,__ +packet,457.207000,__ +packet,457.290000,__ +packet,457.707000,__ +packet,457.499000,__ +packet,457.374000,__ +packet,457.415000,__ +packet,457.457000,__ +packet,457.540000,__ +packet,457.582000,__ +packet,457.624000,__ +packet,457.666000,__ +packet,457.874000,__ +packet,457.791000,__ +packet,457.749000,__ +packet,457.832000,__ +packet,458.166000,__ +packet,457.999000,__ +packet,457.916000,__ +packet,457.958000,__ +packet,458.041000,__ +packet,458.083000,__ +packet,458.124000,__ +packet,458.249000,__ +packet,458.208000,__ +packet,458.416000,__ +packet,458.333000,__ +packet,458.291000,__ +packet,458.375000,__ +packet,458.583000,__ +packet,458.500000,__ +packet,458.458000,__ +packet,458.541000,__ +packet,458.750000,__ +packet,458.667000,__ +packet,458.625000,__ +packet,458.708000,__ +packet,458.875000,__ +packet,458.792000,__ +packet,458.833000,__ +packet,459.125000,__ +packet,459.000000,__ +packet,458.917000,__ +packet,458.959000,__ +packet,459.042000,__ +packet,459.084000,__ +packet,459.334000,__ +packet,459.209000,__ +packet,459.167000,__ +packet,459.250000,__ +packet,459.292000,__ +packet,459.501000,__ +packet,459.417000,__ +packet,459.376000,__ +packet,459.459000,__ +packet,459.751000,__ +packet,459.626000,__ +packet,459.542000,__ +packet,459.584000,__ +packet,459.668000,__ +packet,459.709000,__ +packet,459.918000,__ +packet,459.834000,__ +packet,459.793000,__ +packet,459.876000,__ +packet,459.960000,K_ +packet,460.126000,__ +packet,460.043000,__ +packet,460.001000,__ +packet,460.085000,__ +packet,460.418000,__ +packet,460.251000,__ +packet,460.168000,__ +packet,460.210000,__ +packet,460.293000,__ +packet,460.335000,__ +packet,460.377000,__ +packet,460.627000,__ +packet,460.502000,__ +packet,460.460000,__ +packet,460.543000,__ +packet,460.585000,__ +packet,460.877000,__ +packet,460.752000,__ +packet,460.669000,__ +packet,460.710000,__ +packet,460.794000,__ +packet,460.835000,__ +packet,461.002000,__ +packet,460.919000,__ +packet,460.961000,__ +packet,461.252000,__ +packet,461.127000,__ +packet,461.044000,__ +packet,461.086000,__ +packet,461.169000,__ +packet,461.211000,__ +packet,461.294000,__ +packet,461.378000,__ +packet,461.336000,__ +packet,461.670000,__ +packet,461.503000,__ +packet,461.419000,__ +packet,461.461000,__ +packet,461.544000,__ +packet,461.586000,__ +packet,461.628000,__ +packet,461.878000,__ +packet,461.753000,__ +packet,461.711000,__ +packet,461.795000,__ +packet,461.836000,__ +packet,462.045000,__ +packet,461.962000,__ +packet,461.920000,__ +packet,462.003000,__ +packet,462.170000,__ +packet,462.087000,__ +packet,462.128000,__ +packet,462.295000,__ +packet,462.212000,__ +packet,462.253000,__ +packet,462.545000,__ +packet,462.420000,__ +packet,462.337000,__ +packet,462.379000,__ +packet,462.462000,__ +packet,462.504000,__ +packet,462.712000,__ +packet,462.629000,__ +packet,462.587000,__ +packet,462.671000,__ +packet,462.754000,__ +packet,462.796000,__ +packet,462.837000,__ +packet,462.879000,__ +packet,462.921000,__ +packet,463.004000,__ +packet,462.963000,__ +packet,463.129000,__ +packet,463.046000,__ +packet,463.088000,__ +packet,463.296000,__ +packet,463.213000,__ +packet,463.171000,__ +packet,463.254000,__ +packet,463.505000,__ +packet,463.380000,__ +packet,463.338000,__ +packet,463.421000,__ +packet,463.463000,__ +packet,463.546000,K_ +packet,463.588000,__ +packet,463.755000,__ +packet,463.672000,__ +packet,463.630000,__ +packet,463.713000,__ +packet,463.964000,__ +packet,463.838000,__ +packet,463.797000,__ +packet,463.880000,__ +packet,463.922000,__ +packet,464.130000,__ +packet,464.047000,__ +packet,464.005000,__ +packet,464.089000,__ +packet,464.255000,__ +packet,464.172000,__ +packet,464.214000,__ +packet,464.422000,__ +packet,464.339000,__ +packet,464.297000,__ +packet,464.381000,__ +packet,464.756000,__ +packet,464.589000,__ +packet,464.464000,__ +packet,464.506000,__ +packet,464.547000,__ +packet,464.631000,__ +packet,464.673000,__ +packet,464.714000,__ +packet,464.923000,__ +packet,464.839000,__ +packet,464.798000,__ +packet,464.881000,__ +packet,465.090000,__ +packet,465.006000,__ +packet,464.965000,__ +packet,465.048000,__ +packet,465.173000,__ +packet,465.131000,__ +packet,465.382000,__ +packet,465.256000,__ +packet,465.215000,__ +packet,465.298000,__ +packet,465.340000,__ +packet,465.548000,__ +packet,465.465000,__ +packet,465.423000,__ +packet,465.507000,__ +packet,465.715000,__ +packet,465.632000,__ +packet,465.590000,__ +packet,465.674000,__ +packet,465.882000,__ +packet,465.799000,__ +packet,465.757000,__ +packet,465.840000,__ +packet,465.966000,__ +packet,465.924000,__ +packet,466.007000,K_ +packet,466.174000,__ +packet,466.091000,__ +packet,466.049000,__ +packet,466.132000,__ +packet,466.299000,__ +packet,466.216000,__ +packet,466.257000,__ +packet,466.341000,__ +packet,466.424000,__ +packet,466.383000,__ +packet,466.508000,__ +packet,466.466000,__ +packet,466.675000,__ +packet,466.591000,__ +packet,466.549000,__ +packet,466.633000,__ +packet,466.758000,__ +packet,466.716000,__ +packet,466.925000,__ +packet,466.841000,__ +packet,466.800000,__ +packet,466.883000,__ +packet,467.092000,__ +packet,467.008000,__ +packet,466.967000,__ +packet,467.050000,__ +packet,467.300000,__ +packet,467.175000,__ +packet,467.133000,__ +packet,467.217000,__ +packet,467.258000,__ +packet,467.634000,__ +packet,467.467000,__ +packet,467.342000,__ +packet,467.384000,__ +packet,467.425000,__ +packet,467.509000,__ +packet,467.550000,__ +packet,467.592000,__ +packet,467.926000,__ +packet,467.759000,__ +packet,467.676000,__ +packet,467.717000,__ +packet,467.801000,__ +packet,467.842000,__ +packet,467.884000,__ +packet,468.051000,__ +packet,467.968000,__ +packet,468.009000,__ +packet,468.093000,K_ +packet,468.259000,__ +packet,468.176000,__ +packet,468.134000,__ +packet,468.218000,__ +packet,468.426000,__ +packet,468.343000,__ +packet,468.301000,__ +packet,468.385000,__ +packet,468.593000,__ +packet,468.510000,__ +packet,468.468000,__ +packet,468.551000,__ +packet,468.635000,__ +packet,468.802000,__ +packet,468.718000,__ +packet,468.677000,__ +packet,468.760000,__ +packet,468.969000,__ +packet,468.885000,__ +packet,468.843000,__ +packet,468.927000,__ +packet,469.135000,__ +packet,469.052000,__ +packet,469.010000,__ +packet,469.094000,__ +packet,469.302000,__ +packet,469.219000,__ +packet,469.177000,__ +packet,469.260000,__ +packet,469.344000,__ +packet,469.427000,__ +packet,469.386000,__ +packet,469.594000,__ +packet,469.511000,__ +packet,469.469000,__ +packet,469.552000,__ +packet,469.636000,__ +packet,469.678000,__ +packet,469.844000,__ +packet,469.761000,__ +packet,469.719000,__ +packet,469.803000,__ +packet,469.928000,__ +packet,469.886000,__ +packet,469.970000,__ +packet,470.136000,__ +packet,470.053000,__ +packet,470.011000,__ +packet,470.095000,__ +packet,470.303000,__ +packet,470.220000,__ +packet,470.178000,__ +packet,470.261000,__ +packet,470.428000,__ +packet,470.345000,__ +packet,470.387000,__ +packet,470.553000,__ +packet,470.470000,__ +packet,470.512000,__ +packet,470.720000,__ +packet,470.637000,__ +packet,470.595000,__ +packet,470.679000,__ +packet,470.887000,__ +packet,470.804000,__ +packet,470.762000,__ +packet,470.845000,__ +packet,471.012000,__ +packet,470.929000,__ +packet,470.971000,__ +packet,471.137000,__ +packet,471.054000,__ +packet,471.096000,__ +packet,471.262000,__ +packet,471.179000,__ +packet,471.221000,__ +packet,471.388000,__ +packet,471.304000,__ +packet,471.346000,__ +packet,471.513000,__ +packet,471.429000,__ +packet,471.471000,__ +packet,471.638000,__ +packet,471.554000,__ +packet,471.596000,__ +packet,471.721000,__ +packet,471.680000,__ +packet,471.846000,__ +packet,471.763000,__ +packet,471.805000,__ +packet,472.013000,__ +packet,471.930000,__ +packet,471.888000,__ +packet,471.972000,__ +packet,472.222000,__ +packet,472.097000,__ +packet,472.055000,__ +packet,472.138000,__ +packet,472.180000,__ +packet,472.389000,__ +packet,472.305000,__ +packet,472.263000,__ +packet,472.347000,__ +packet,472.555000,__ +packet,472.472000,__ +packet,472.430000,__ +packet,472.514000,__ +packet,472.889000,__ +packet,472.722000,__ +packet,472.597000,__ +packet,472.639000,__ +packet,472.681000,__ +packet,472.764000,__ +packet,472.806000,__ +packet,472.847000,__ +packet,472.931000,K_ +packet,473.014000,__ +packet,472.973000,__ +packet,473.098000,__ +packet,473.056000,__ +packet,473.139000,__ +packet,473.223000,__ +packet,473.181000,__ +packet,473.306000,__ +packet,473.264000,__ +packet,473.431000,__ +packet,473.348000,__ +packet,473.390000,__ +packet,473.598000,__ +packet,473.515000,__ +packet,473.473000,__ +packet,473.556000,__ +packet,473.640000,__ +packet,473.682000,__ +packet,473.723000,__ +packet,473.765000,__ +packet,473.807000,__ +packet,473.848000,__ +packet,473.890000,__ +packet,473.932000,__ +packet,473.974000,__ +packet,474.015000,__ +packet,474.057000,__ +packet,474.099000,__ +packet,474.182000,__ +packet,474.140000,__ +packet,474.349000,__ +packet,474.265000,__ +packet,474.224000,__ +packet,474.307000,__ +packet,474.516000,__ +packet,474.432000,__ +packet,474.391000,__ +packet,474.474000,__ +packet,474.683000,__ +packet,474.599000,__ +packet,474.557000,__ +packet,474.641000,__ +packet,474.766000,__ +packet,474.724000,__ +packet,474.849000,__ +packet,474.808000,__ +packet,474.891000,__ +packet,475.016000,__ +packet,474.933000,__ +packet,474.975000,__ +packet,475.141000,__ +packet,475.058000,__ +packet,475.100000,__ +packet,475.266000,__ +packet,475.183000,__ +packet,475.225000,__ +packet,475.308000,__ +packet,475.350000,__ +packet,475.392000,__ +packet,475.517000,__ +packet,475.433000,__ +packet,475.475000,__ +packet,475.600000,__ +packet,475.558000,__ +packet,475.642000,__ +packet,475.725000,__ +packet,475.684000,__ +packet,475.767000,K_ +packet,475.934000,__ +packet,475.850000,__ +packet,475.809000,__ +packet,475.892000,__ +packet,476.101000,__ +packet,476.017000,__ +packet,475.976000,__ +packet,476.059000,__ +packet,476.267000,__ +packet,476.184000,__ +packet,476.142000,__ +packet,476.226000,__ +packet,476.434000,__ +packet,476.351000,__ +packet,476.309000,__ +packet,476.393000,__ +packet,476.601000,__ +packet,476.518000,__ +packet,476.476000,__ +packet,476.559000,__ +packet,476.768000,__ +packet,476.685000,__ +packet,476.643000,__ +packet,476.726000,__ +packet,476.935000,__ +packet,476.851000,__ +packet,476.810000,__ +packet,476.893000,__ +packet,477.102000,__ +packet,477.018000,__ +packet,476.977000,__ +packet,477.060000,__ +packet,477.268000,__ +packet,477.185000,__ +packet,477.143000,__ +packet,477.227000,__ +packet,477.352000,__ +packet,477.310000,__ +packet,477.727000,__ +packet,477.519000,__ +packet,477.394000,__ +packet,477.435000,__ +packet,477.477000,__ +packet,477.560000,__ +packet,477.602000,__ +packet,477.644000,__ +packet,477.686000,__ +packet,477.769000,K_ +packet,477.936000,__ +packet,477.852000,__ +packet,477.811000,__ +packet,477.894000,__ +packet,478.228000,__ +packet,478.061000,__ +packet,477.978000,__ +packet,478.019000,__ +packet,478.103000,__ +packet,478.144000,__ +packet,478.186000,__ +packet,478.311000,__ +packet,478.269000,__ +packet,478.645000,__ +packet,478.478000,__ +packet,478.353000,__ +packet,478.395000,__ +packet,478.436000,__ +packet,478.520000,__ +packet,478.561000,__ +packet,478.603000,__ +packet,479.020000,__ +packet,478.812000,__ +packet,478.687000,__ +packet,478.728000,__ +packet,478.770000,__ +packet,478.853000,__ +packet,478.895000,__ +packet,478.937000,__ +packet,478.979000,__ +packet,479.187000,__ +packet,479.104000,__ +packet,479.062000,__ +packet,479.145000,__ +packet,479.396000,__ +packet,479.270000,__ +packet,479.229000,__ +packet,479.312000,__ +packet,479.354000,__ +packet,479.437000,__ +packet,479.646000,__ +packet,479.521000,__ +packet,479.479000,__ +packet,479.562000,__ +packet,479.604000,__ +packet,480.021000,__ +packet,479.813000,__ +packet,479.688000,__ +packet,479.729000,__ +packet,479.771000,__ +packet,479.854000,__ +packet,479.896000,__ +packet,479.938000,__ +packet,479.980000,__ +packet,480.355000,__ +packet,480.188000,__ +packet,480.063000,__ +packet,480.105000,__ +packet,480.146000,__ +packet,480.230000,__ +packet,480.271000,__ +packet,480.313000,__ +packet,480.689000,__ +packet,480.522000,__ +packet,480.397000,__ +packet,480.438000,__ +packet,480.480000,__ +packet,480.563000,__ +packet,480.605000,__ +packet,480.647000,__ +packet,480.814000,__ +packet,480.730000,__ +packet,480.772000,__ +packet,480.855000,K_ +packet,481.022000,__ +packet,480.939000,__ +packet,480.897000,__ +packet,480.981000,__ +packet,481.189000,__ +packet,481.106000,__ +packet,481.064000,__ +packet,481.147000,__ +packet,481.398000,__ +packet,481.272000,__ +packet,481.231000,__ +packet,481.314000,__ +packet,481.356000,__ +packet,481.481000,__ +packet,481.439000,__ +packet,481.523000,__ +packet,481.773000,__ +packet,481.648000,__ +packet,481.564000,__ +packet,481.606000,__ +packet,481.690000,__ +packet,481.731000,__ +packet,481.940000,__ +packet,481.856000,__ +packet,481.815000,__ +packet,481.898000,__ +packet,482.148000,__ +packet,482.023000,__ +packet,481.982000,__ +packet,482.065000,__ +packet,482.107000,__ +packet,482.399000,__ +packet,482.273000,__ +packet,482.190000,__ +packet,482.232000,__ +packet,482.315000,__ +packet,482.357000,__ +packet,482.440000,K_ +packet,482.816000,__ +packet,482.607000,__ +packet,482.482000,__ +packet,482.524000,__ +packet,482.565000,__ +packet,482.649000,__ +packet,482.691000,__ +packet,482.732000,__ +packet,482.774000,__ +packet,483.191000,__ +packet,482.983000,__ +packet,482.857000,__ +packet,482.899000,__ +packet,482.941000,__ +packet,483.024000,__ +packet,483.066000,__ +packet,483.108000,__ +packet,483.149000,__ +packet,483.358000,__ +packet,483.274000,__ +packet,483.233000,__ +packet,483.316000,__ +packet,483.441000,__ +packet,483.400000,__ +packet,483.483000,__ +packet,483.525000,__ +packet,483.692000,__ +packet,483.608000,__ +packet,483.566000,__ +packet,483.650000,__ +packet,483.817000,__ +packet,483.733000,__ +packet,483.775000,__ +packet,483.858000,__ +packet,483.942000,__ +packet,483.900000,__ +packet,484.109000,__ +packet,484.025000,__ +packet,483.984000,__ +packet,484.067000,__ +packet,484.275000,__ +packet,484.192000,__ +packet,484.150000,__ +packet,484.234000,__ +packet,484.442000,__ +packet,484.359000,__ +packet,484.317000,__ +packet,484.401000,__ +packet,484.567000,__ +packet,484.484000,__ +packet,484.526000,__ +packet,484.818000,__ +packet,484.693000,__ +packet,484.609000,__ +packet,484.651000,__ +packet,484.734000,__ +packet,484.776000,__ +packet,484.943000,__ +packet,484.859000,__ +packet,484.901000,__ +packet,485.068000,__ +packet,484.985000,__ +packet,485.026000,__ +packet,485.110000,K_ +packet,485.151000,__ +packet,485.318000,__ +packet,485.235000,__ +packet,485.193000,__ +packet,485.276000,__ +packet,485.360000,__ +packet,485.443000,__ +packet,485.402000,__ +packet,485.610000,__ +packet,485.527000,__ +packet,485.485000,__ +packet,485.568000,__ +packet,485.819000,__ +packet,485.694000,__ +packet,485.652000,__ +packet,485.735000,__ +packet,485.777000,__ +packet,486.069000,__ +packet,485.944000,__ +packet,485.860000,__ +packet,485.902000,__ +packet,485.986000,__ +packet,486.027000,__ +packet,486.111000,__ +packet,486.403000,__ +packet,486.236000,__ +packet,486.152000,__ +packet,486.194000,__ +packet,486.277000,__ +packet,486.319000,__ +packet,486.361000,__ +packet,486.444000,K_ +packet,486.653000,__ +packet,486.528000,__ +packet,486.486000,__ +packet,486.569000,__ +packet,486.611000,__ +packet,486.736000,__ +packet,486.695000,__ +packet,486.903000,__ +packet,486.820000,__ +packet,486.778000,__ +packet,486.861000,__ +packet,487.112000,__ +packet,486.987000,__ +packet,486.945000,__ +packet,487.028000,__ +packet,487.070000,__ +packet,487.445000,__ +packet,487.278000,__ +packet,487.153000,__ +packet,487.195000,__ +packet,487.237000,__ +packet,487.320000,__ +packet,487.362000,__ +packet,487.404000,__ +packet,487.696000,__ +packet,487.570000,__ +packet,487.487000,__ +packet,487.529000,__ +packet,487.612000,__ +packet,487.654000,__ +packet,487.904000,__ +packet,487.779000,__ +packet,487.737000,__ +packet,487.821000,__ +packet,487.862000,__ +packet,488.154000,__ +packet,488.029000,__ +packet,487.946000,__ +packet,487.988000,__ +packet,488.071000,__ +packet,488.113000,__ +packet,488.405000,__ +packet,488.279000,__ +packet,488.196000,__ +packet,488.238000,__ +packet,488.321000,__ +packet,488.363000,__ +packet,488.571000,__ +packet,488.488000,__ +packet,488.446000,__ +packet,488.530000,__ +packet,488.822000,__ +packet,488.697000,__ +packet,488.613000,__ +packet,488.655000,__ +packet,488.738000,__ +packet,488.780000,__ +packet,488.947000,__ +packet,488.863000,__ +packet,488.905000,__ +packet,489.280000,__ +packet,489.114000,__ +packet,488.989000,__ +packet,489.030000,__ +packet,489.072000,__ +packet,489.155000,__ +packet,489.197000,__ +packet,489.239000,__ +packet,489.531000,__ +packet,489.406000,__ +packet,489.322000,__ +packet,489.364000,__ +packet,489.447000,__ +packet,489.489000,__ +packet,489.823000,__ +packet,489.656000,__ +packet,489.572000,__ +packet,489.614000,__ +packet,489.698000,__ +packet,489.739000,__ +packet,489.781000,__ +packet,489.948000,__ +packet,489.864000,__ +packet,489.906000,__ +packet,490.073000,__ +packet,489.990000,__ +packet,490.031000,__ +packet,490.198000,__ +packet,490.115000,__ +packet,490.156000,__ +packet,490.448000,__ +packet,490.323000,__ +packet,490.240000,__ +packet,490.281000,__ +packet,490.365000,__ +packet,490.407000,__ +packet,490.490000,__ +packet,490.532000,K_ +packet,490.573000,__ +packet,490.824000,__ +packet,490.699000,__ +packet,490.615000,__ +packet,490.657000,__ +packet,490.740000,__ +packet,490.782000,__ +packet,491.032000,__ +packet,490.907000,__ +packet,490.865000,__ +packet,490.949000,__ +packet,490.991000,__ +packet,491.199000,__ +packet,491.116000,__ +packet,491.074000,__ +packet,491.157000,__ +packet,491.324000,__ +packet,491.241000,__ +packet,491.282000,__ +packet,491.658000,__ +packet,491.491000,__ +packet,491.366000,__ +packet,491.408000,__ +packet,491.449000,__ +packet,491.533000,__ +packet,491.574000,__ +packet,491.616000,__ +packet,491.700000,__ +packet,491.908000,__ +packet,491.783000,__ +packet,491.741000,__ +packet,491.825000,__ +packet,491.866000,__ +packet,492.033000,__ +packet,491.950000,__ +packet,491.992000,__ +packet,492.409000,__ +packet,492.200000,__ +packet,492.075000,__ +packet,492.117000,__ +packet,492.158000,__ +packet,492.242000,__ +packet,492.283000,__ +packet,492.325000,__ +packet,492.367000,__ +packet,492.659000,__ +packet,492.534000,__ +packet,492.450000,__ +packet,492.492000,__ +packet,492.575000,__ +packet,492.617000,__ +packet,492.909000,__ +packet,492.784000,__ +packet,492.701000,__ +packet,492.742000,__ +packet,492.826000,__ +packet,492.867000,__ +packet,493.159000,__ +packet,493.034000,__ +packet,492.951000,__ +packet,492.993000,__ +packet,493.076000,__ +packet,493.118000,__ +packet,493.368000,__ +packet,493.243000,__ +packet,493.201000,__ +packet,493.284000,__ +packet,493.326000,__ +packet,493.451000,__ +packet,493.410000,__ +packet,493.660000,__ +packet,493.535000,__ +packet,493.493000,__ +packet,493.576000,__ +packet,493.618000,__ +packet,493.910000,__ +packet,493.785000,__ +packet,493.702000,__ +packet,493.743000,__ +packet,493.827000,__ +packet,493.868000,__ +packet,494.119000,__ +packet,493.994000,__ +packet,493.952000,__ +packet,494.035000,__ +packet,494.077000,__ +packet,494.327000,__ +packet,494.202000,__ +packet,494.160000,__ +packet,494.244000,__ +packet,494.285000,__ +packet,494.452000,__ +packet,494.369000,__ +packet,494.411000,__ +packet,494.661000,__ +packet,494.536000,__ +packet,494.494000,__ +packet,494.577000,__ +packet,494.619000,__ +packet,494.786000,__ +packet,494.703000,__ +packet,494.744000,__ +packet,494.911000,__ +packet,494.828000,__ +packet,494.869000,__ +packet,495.161000,__ +packet,495.036000,__ +packet,494.953000,__ +packet,494.995000,__ +packet,495.078000,__ +packet,495.120000,__ +packet,495.328000,__ +packet,495.245000,__ +packet,495.203000,__ +packet,495.286000,__ +packet,495.495000,__ +packet,495.412000,__ +packet,495.370000,__ +packet,495.453000,__ +packet,495.537000,K_ +packet,495.578000,__ +packet,495.745000,__ +packet,495.662000,__ +packet,495.620000,__ +packet,495.704000,__ +packet,495.912000,__ +packet,495.829000,__ +packet,495.787000,__ +packet,495.870000,__ +packet,496.079000,__ +packet,495.996000,__ +packet,495.954000,__ +packet,496.037000,__ +packet,496.204000,__ +packet,496.121000,__ +packet,496.162000,__ +packet,496.287000,__ +packet,496.246000,__ +packet,496.454000,__ +packet,496.371000,__ +packet,496.329000,__ +packet,496.413000,__ +packet,496.663000,__ +packet,496.538000,__ +packet,496.496000,__ +packet,496.579000,__ +packet,496.621000,__ +packet,496.705000,__ +packet,496.913000,__ +packet,496.788000,__ +packet,496.746000,__ +packet,496.830000,__ +packet,496.871000,__ +packet,496.997000,__ +packet,496.955000,__ +packet,497.038000,K_ +packet,497.205000,__ +packet,497.122000,__ +packet,497.080000,__ +packet,497.163000,__ +packet,497.580000,__ +packet,497.372000,__ +packet,497.247000,__ +packet,497.288000,__ +packet,497.330000,__ +packet,497.414000,__ +packet,497.455000,__ +packet,497.497000,__ +packet,497.539000,__ +packet,497.747000,__ +packet,497.664000,__ +packet,497.622000,__ +packet,497.706000,__ +packet,497.914000,__ +packet,497.831000,__ +packet,497.789000,__ +packet,497.872000,__ +packet,498.039000,__ +packet,497.956000,__ +packet,497.998000,__ +packet,498.248000,__ +packet,498.123000,__ +packet,498.081000,__ +packet,498.164000,__ +packet,498.206000,__ +packet,498.289000,__ +packet,498.456000,__ +packet,498.373000,__ +packet,498.331000,__ +packet,498.415000,__ +packet,498.623000,__ +packet,498.540000,__ +packet,498.498000,__ +packet,498.581000,__ +packet,498.790000,__ +packet,498.707000,__ +packet,498.665000,__ +packet,498.748000,__ +packet,498.957000,__ +packet,498.873000,__ +packet,498.832000,__ +packet,498.915000,__ +packet,499.165000,__ +packet,499.040000,__ +packet,498.999000,__ +packet,499.082000,__ +packet,499.124000,__ +packet,499.332000,__ +packet,499.249000,__ +packet,499.207000,__ +packet,499.290000,__ +packet,499.457000,__ +packet,499.374000,__ +packet,499.416000,__ +packet,499.582000,__ +packet,499.499000,__ +packet,499.541000,__ +packet,499.749000,__ +packet,499.666000,__ +packet,499.624000,__ +packet,499.708000,__ +packet,499.874000,__ +packet,499.791000,__ +packet,499.833000,__ +packet,500.083000,__ +packet,499.958000,__ +packet,499.916000,__ +packet,500.000000,__ +packet,500.041000,__ +packet,500.250000,__ +packet,500.166000,__ +packet,500.125000,__ +packet,500.208000,__ +packet,500.375000,__ +packet,500.291000,__ +packet,500.333000,__ +packet,500.542000,__ +packet,500.458000,__ +packet,500.417000,__ +packet,500.500000,__ +packet,500.709000,__ +packet,500.625000,__ +packet,500.583000,__ +packet,500.667000,__ +packet,500.917000,__ +packet,500.792000,__ +packet,500.750000,__ +packet,500.834000,__ +packet,500.875000,__ +packet,501.042000,__ +packet,500.959000,__ +packet,501.001000,__ +packet,501.209000,__ +packet,501.126000,__ +packet,501.084000,__ +packet,501.167000,__ +packet,501.376000,__ +packet,501.292000,__ +packet,501.251000,__ +packet,501.334000,__ +packet,501.501000,__ +packet,501.418000,__ +packet,501.459000,__ +packet,501.626000,__ +packet,501.543000,__ +packet,501.584000,__ +packet,501.793000,__ +packet,501.710000,__ +packet,501.668000,__ +packet,501.751000,__ +packet,501.918000,__ +packet,501.835000,__ +packet,501.876000,__ +packet,502.168000,__ +packet,502.043000,__ +packet,501.960000,__ +packet,502.002000,__ +packet,502.085000,__ +packet,502.127000,__ +packet,502.460000,__ +packet,502.293000,__ +packet,502.210000,__ +packet,502.252000,__ +packet,502.335000,__ +packet,502.377000,__ +packet,502.419000,__ +packet,502.627000,__ +packet,502.544000,__ +packet,502.502000,__ +packet,502.585000,__ +packet,502.794000,__ +packet,502.711000,__ +packet,502.669000,__ +packet,502.752000,__ +packet,503.044000,__ +packet,502.919000,__ +packet,502.836000,__ +packet,502.877000,__ +packet,502.961000,__ +packet,503.003000,__ +packet,503.086000,__ +packet,503.211000,__ +packet,503.128000,__ +packet,503.169000,__ +packet,503.378000,__ +packet,503.294000,__ +packet,503.253000,__ +packet,503.336000,__ +packet,503.545000,__ +packet,503.461000,__ +packet,503.420000,__ +packet,503.503000,__ +packet,503.795000,__ +packet,503.670000,__ +packet,503.586000,__ +packet,503.628000,__ +packet,503.712000,__ +packet,503.753000,__ +packet,503.920000,__ +packet,503.837000,__ +packet,503.878000,__ +packet,504.087000,__ +packet,504.004000,__ +packet,503.962000,__ +packet,504.045000,__ +packet,504.212000,__ +packet,504.129000,__ +packet,504.170000,__ +packet,504.421000,__ +packet,504.295000,__ +packet,504.254000,__ +packet,504.337000,__ +packet,504.379000,__ +packet,504.713000,__ +packet,504.546000,__ +packet,504.462000,__ +packet,504.504000,__ +packet,504.587000,__ +packet,504.629000,__ +packet,504.671000,__ +packet,504.921000,__ +packet,504.796000,__ +packet,504.754000,__ +packet,504.838000,__ +packet,504.879000,__ +packet,505.088000,__ +packet,505.005000,__ +packet,504.963000,__ +packet,505.046000,__ +packet,505.296000,__ +packet,505.171000,__ +packet,505.130000,__ +packet,505.213000,__ +packet,505.255000,__ +packet,505.422000,__ +packet,505.338000,__ +packet,505.380000,__ +packet,505.588000,__ +packet,505.505000,__ +packet,505.463000,__ +packet,505.547000,__ +packet,505.839000,__ +packet,505.714000,__ +packet,505.630000,__ +packet,505.672000,__ +packet,505.755000,__ +packet,505.797000,__ +packet,505.964000,__ +packet,505.880000,__ +packet,505.922000,__ +packet,506.214000,__ +packet,506.089000,__ +packet,506.006000,__ +packet,506.047000,__ +packet,506.131000,__ +packet,506.172000,__ +packet,506.339000,__ +packet,506.256000,__ +packet,506.297000,__ +packet,506.381000,__ +packet,506.423000,__ +packet,506.464000,__ +packet,506.506000,__ +packet,506.548000,__ +packet,506.589000,__ +packet,506.631000,__ +packet,506.673000,__ +packet,506.715000,__ +packet,506.756000,__ +packet,506.798000,__ +packet,506.840000,__ +packet,506.881000,__ +packet,506.923000,__ +packet,506.965000,__ +packet,507.048000,__ +packet,507.007000,__ +packet,507.090000,__ +packet,507.132000,__ +packet,507.215000,__ +packet,507.173000,__ +packet,507.257000,__ +packet,507.424000,__ +packet,507.340000,__ +packet,507.298000,__ +packet,507.382000,__ +packet,507.465000,K_ +packet,507.632000,__ +packet,507.549000,__ +packet,507.507000,__ +packet,507.590000,__ +packet,507.799000,__ +packet,507.716000,__ +packet,507.674000,__ +packet,507.757000,__ +packet,507.966000,__ +packet,507.882000,__ +packet,507.841000,__ +packet,507.924000,__ +packet,508.049000,__ +packet,508.008000,__ +packet,508.091000,__ +packet,508.174000,__ +packet,508.133000,__ +packet,508.383000,__ +packet,508.258000,__ +packet,508.216000,__ +packet,508.299000,__ +packet,508.341000,__ +packet,508.550000,__ +packet,508.466000,__ +packet,508.425000,__ +packet,508.508000,__ +packet,508.591000,__ +packet,508.717000,__ +packet,508.633000,__ +packet,508.675000,__ +packet,508.883000,__ +packet,508.800000,__ +packet,508.758000,__ +packet,508.842000,__ +packet,508.925000,__ +packet,509.134000,__ +packet,509.009000,__ +packet,508.967000,__ +packet,509.050000,__ +packet,509.092000,__ +packet,509.300000,__ +packet,509.217000,__ +packet,509.175000,__ +packet,509.259000,__ +packet,509.467000,__ +packet,509.384000,__ +packet,509.342000,__ +packet,509.426000,__ +packet,509.509000,K_ +packet,509.676000,__ +packet,509.592000,__ +packet,509.551000,__ +packet,509.634000,__ +packet,509.884000,__ +packet,509.759000,__ +packet,509.718000,__ +packet,509.801000,__ +packet,509.843000,__ +packet,510.010000,__ +packet,509.926000,__ +packet,509.968000,__ +packet,510.176000,__ +packet,510.093000,__ +packet,510.051000,__ +packet,510.135000,__ +packet,510.343000,__ +packet,510.260000,__ +packet,510.218000,__ +packet,510.301000,__ +packet,510.593000,__ +packet,510.468000,__ +packet,510.385000,__ +packet,510.427000,__ +packet,510.510000,__ +packet,510.552000,__ +packet,510.844000,__ +packet,510.719000,__ +packet,510.635000,__ +packet,510.677000,__ +packet,510.760000,__ +packet,510.802000,__ +packet,510.885000,__ +packet,510.927000,K_ +packet,510.969000,__ +packet,511.219000,__ +packet,511.094000,__ +packet,511.011000,__ +packet,511.052000,__ +packet,511.136000,__ +packet,511.177000,__ +packet,511.386000,__ +packet,511.302000,__ +packet,511.261000,__ +packet,511.344000,__ +packet,511.720000,__ +packet,511.553000,__ +packet,511.428000,__ +packet,511.469000,__ +packet,511.511000,__ +packet,511.594000,__ +packet,511.636000,__ +packet,511.678000,__ +packet,511.845000,__ +packet,511.761000,__ +packet,511.803000,__ +packet,512.012000,__ +packet,511.928000,__ +packet,511.886000,__ +packet,511.970000,__ +packet,512.178000,__ +packet,512.095000,__ +packet,512.053000,__ +packet,512.137000,__ +packet,512.220000,__ +packet,512.595000,__ +packet,512.387000,__ +packet,512.262000,__ +packet,512.303000,__ +packet,512.345000,__ +packet,512.429000,__ +packet,512.470000,__ +packet,512.512000,__ +packet,512.554000,__ +packet,512.762000,__ +packet,512.679000,__ +packet,512.637000,__ +packet,512.721000,__ +packet,512.929000,__ +packet,512.846000,__ +packet,512.804000,__ +packet,512.887000,__ +packet,512.971000,__ +packet,513.138000,__ +packet,513.054000,__ +packet,513.013000,__ +packet,513.096000,__ +packet,513.263000,__ +packet,513.179000,__ +packet,513.221000,__ +packet,513.471000,__ +packet,513.346000,__ +packet,513.304000,__ +packet,513.388000,__ +packet,513.430000,__ +packet,513.680000,__ +packet,513.555000,__ +packet,513.513000,__ +packet,513.596000,__ +packet,513.638000,__ +packet,513.972000,__ +packet,513.805000,__ +packet,513.722000,__ +packet,513.763000,__ +packet,513.847000,__ +packet,513.888000,__ +packet,513.930000,__ +packet,514.222000,__ +packet,514.097000,__ +packet,514.014000,__ +packet,514.055000,__ +packet,514.139000,__ +packet,514.180000,__ +packet,514.431000,__ +packet,514.305000,__ +packet,514.264000,__ +packet,514.347000,__ +packet,514.389000,__ +packet,514.556000,__ +packet,514.472000,__ +packet,514.514000,__ +packet,514.806000,__ +packet,514.681000,__ +packet,514.597000,__ +packet,514.639000,__ +packet,514.723000,__ +packet,514.764000,__ +packet,514.931000,__ +packet,514.848000,__ +packet,514.889000,__ +packet,515.098000,__ +packet,515.015000,__ +packet,514.973000,__ +packet,515.056000,__ +packet,515.306000,__ +packet,515.181000,__ +packet,515.140000,__ +packet,515.223000,__ +packet,515.265000,__ +packet,515.432000,__ +packet,515.348000,__ +packet,515.390000,__ +packet,515.807000,__ +packet,515.598000,__ +packet,515.473000,__ +packet,515.515000,__ +packet,515.557000,__ +packet,515.640000,__ +packet,515.682000,__ +packet,515.724000,__ +packet,515.765000,__ +packet,516.057000,__ +packet,515.932000,__ +packet,515.849000,__ +packet,515.890000,__ +packet,515.974000,__ +packet,516.016000,__ +packet,516.099000,__ +packet,516.266000,__ +packet,516.182000,__ +packet,516.141000,__ +packet,516.224000,__ +packet,516.349000,__ +packet,516.307000,__ +packet,516.558000,__ +packet,516.433000,__ +packet,516.391000,__ +packet,516.474000,__ +packet,516.516000,__ +packet,516.683000,__ +packet,516.599000,__ +packet,516.641000,__ +packet,516.808000,__ +packet,516.725000,__ +packet,516.766000,__ +packet,516.933000,__ +packet,516.850000,__ +packet,516.891000,__ +packet,517.100000,__ +packet,517.017000,__ +packet,516.975000,__ +packet,517.058000,__ +packet,517.308000,__ +packet,517.183000,__ +packet,517.142000,__ +packet,517.225000,__ +packet,517.267000,__ +packet,517.475000,__ +packet,517.392000,__ +packet,517.350000,__ +packet,517.434000,__ +packet,517.684000,__ +packet,517.559000,__ +packet,517.517000,__ +packet,517.600000,__ +packet,517.642000,__ +packet,517.892000,__ +packet,517.767000,__ +packet,517.726000,__ +packet,517.809000,__ +packet,517.851000,__ +packet,518.226000,__ +packet,518.059000,__ +packet,517.934000,__ +packet,517.976000,__ +packet,518.018000,__ +packet,518.101000,__ +packet,518.143000,__ +packet,518.184000,__ +packet,518.309000,__ +packet,518.268000,__ +packet,518.435000,__ +packet,518.351000,__ +packet,518.393000,__ +packet,518.560000,__ +packet,518.476000,__ +packet,518.518000,__ +packet,518.601000,K_ +packet,518.727000,__ +packet,518.643000,__ +packet,518.685000,__ +packet,518.810000,__ +packet,518.768000,__ +packet,518.977000,__ +packet,518.893000,__ +packet,518.852000,__ +packet,518.935000,__ +packet,519.310000,__ +packet,519.144000,__ +packet,519.019000,__ +packet,519.060000,__ +packet,519.102000,__ +packet,519.185000,__ +packet,519.227000,__ +packet,519.269000,__ +packet,519.352000,__ +packet,519.436000,__ +packet,519.394000,__ +packet,519.561000,__ +packet,519.477000,__ +packet,519.519000,__ +packet,519.602000,__ +packet,519.769000,__ +packet,519.686000,__ +packet,519.644000,__ +packet,519.728000,__ +packet,519.936000,__ +packet,519.853000,__ +packet,519.811000,__ +packet,519.894000,__ +packet,520.103000,__ +packet,520.020000,__ +packet,519.978000,__ +packet,520.061000,__ +packet,520.311000,__ +packet,520.186000,__ +packet,520.145000,__ +packet,520.228000,__ +packet,520.270000,__ +packet,520.395000,__ +packet,520.353000,__ +packet,520.437000,K_ +packet,520.603000,__ +packet,520.520000,__ +packet,520.478000,__ +packet,520.562000,__ +packet,520.895000,__ +packet,520.729000,__ +packet,520.645000,__ +packet,520.687000,__ +packet,520.770000,__ +packet,520.812000,__ +packet,520.854000,__ +packet,521.271000,__ +packet,521.062000,__ +packet,520.937000,__ +packet,520.979000,__ +packet,521.021000,__ +packet,521.104000,__ +packet,521.146000,__ +packet,521.187000,__ +packet,521.229000,__ +packet,521.479000,__ +packet,521.354000,__ +packet,521.312000,__ +packet,521.396000,__ +packet,521.438000,__ +packet,521.688000,__ +packet,521.563000,__ +packet,521.521000,__ +packet,521.604000,__ +packet,521.646000,__ +packet,521.855000,__ +packet,521.771000,__ +packet,521.730000,__ +packet,521.813000,__ +packet,521.938000,__ +packet,521.896000,__ +packet,522.105000,__ +packet,522.022000,__ +packet,521.980000,__ +packet,522.063000,__ +packet,522.355000,__ +packet,522.230000,__ +packet,522.147000,__ +packet,522.188000,__ +packet,522.272000,__ +packet,522.313000,__ +packet,522.564000,__ +packet,522.439000,__ +packet,522.397000,__ +packet,522.480000,__ +packet,522.522000,__ +packet,522.605000,__ +packet,522.731000,__ +packet,522.647000,__ +packet,522.689000,__ +packet,522.772000,K_ +packet,522.856000,__ +packet,522.814000,__ +packet,522.981000,__ +packet,522.897000,__ +packet,522.939000,__ +packet,523.314000,__ +packet,523.148000,__ +packet,523.023000,__ +packet,523.064000,__ +packet,523.106000,__ +packet,523.189000,__ +packet,523.231000,__ +packet,523.273000,__ +packet,523.440000,__ +packet,523.356000,__ +packet,523.398000,__ +packet,523.565000,__ +packet,523.481000,__ +packet,523.523000,__ +packet,523.648000,__ +packet,523.606000,__ +packet,523.857000,__ +packet,523.732000,__ +packet,523.690000,__ +packet,523.773000,__ +packet,523.815000,__ +packet,524.024000,__ +packet,523.940000,__ +packet,523.898000,__ +packet,523.982000,__ +packet,524.232000,__ +packet,524.107000,__ +packet,524.065000,__ +packet,524.149000,__ +packet,524.190000,__ +packet,524.274000,K_ +packet,524.315000,__ +packet,524.357000,__ +packet,524.441000,__ +packet,524.399000,__ +packet,524.607000,__ +packet,524.524000,__ +packet,524.482000,__ +packet,524.566000,__ +packet,524.816000,__ +packet,524.691000,__ +packet,524.649000,__ +packet,524.733000,__ +packet,524.774000,__ +packet,524.941000,__ +packet,524.858000,__ +packet,524.899000,__ +packet,524.983000,__ +packet,525.025000,__ +packet,525.233000,__ +packet,525.108000,__ +packet,525.066000,__ +packet,525.150000,__ +packet,525.191000,__ +packet,525.358000,__ +packet,525.275000,__ +packet,525.316000,__ +packet,525.525000,__ +packet,525.442000,__ +packet,525.400000,__ +packet,525.483000,__ +packet,525.650000,__ +packet,525.567000,__ +packet,525.608000,__ +packet,525.817000,__ +packet,525.734000,__ +packet,525.692000,__ +packet,525.775000,__ +packet,526.151000,__ +packet,525.984000,__ +packet,525.859000,__ +packet,525.900000,__ +packet,525.942000,__ +packet,526.026000,__ +packet,526.067000,__ +packet,526.109000,__ +packet,526.192000,__ +packet,526.234000,__ +packet,526.276000,__ +packet,526.443000,__ +packet,526.359000,__ +packet,526.317000,__ +packet,526.401000,__ +packet,526.651000,__ +packet,526.526000,__ +packet,526.484000,__ +packet,526.568000,__ +packet,526.609000,__ +packet,526.693000,K_ +packet,526.860000,__ +packet,526.776000,__ +packet,526.735000,__ +packet,526.818000,__ +packet,527.152000,__ +packet,526.985000,__ +packet,526.901000,__ +packet,526.943000,__ +packet,527.027000,__ +packet,527.068000,__ +packet,527.110000,__ +packet,527.318000,__ +packet,527.235000,__ +packet,527.193000,__ +packet,527.277000,__ +packet,527.569000,__ +packet,527.444000,__ +packet,527.360000,__ +packet,527.402000,__ +packet,527.485000,__ +packet,527.527000,__ +packet,527.694000,__ +packet,527.610000,__ +packet,527.652000,__ +packet,527.861000,__ +packet,527.777000,__ +packet,527.736000,__ +packet,527.819000,__ +packet,528.236000,__ +packet,528.028000,__ +packet,527.902000,__ +packet,527.944000,__ +packet,527.986000,__ +packet,528.069000,__ +packet,528.111000,__ +packet,528.153000,__ +packet,528.194000,__ +packet,528.570000,__ +packet,528.403000,__ +packet,528.278000,__ +packet,528.319000,__ +packet,528.361000,__ +packet,528.445000,__ +packet,528.486000,__ +packet,528.528000,__ +packet,528.737000,__ +packet,528.653000,__ +packet,528.611000,__ +packet,528.695000,__ +packet,528.862000,__ +packet,528.778000,__ +packet,528.820000,__ +packet,529.070000,__ +packet,528.945000,__ +packet,528.903000,__ +packet,528.987000,__ +packet,529.029000,__ +packet,529.362000,__ +packet,529.195000,__ +packet,529.112000,__ +packet,529.154000,__ +packet,529.237000,__ +packet,529.279000,__ +packet,529.320000,__ +packet,529.446000,__ +packet,529.404000,__ +packet,529.821000,__ +packet,529.612000,__ +packet,529.487000,__ +packet,529.529000,__ +packet,529.571000,__ +packet,529.654000,__ +packet,529.696000,__ +packet,529.738000,__ +packet,529.779000,__ +packet,530.155000,__ +packet,529.988000,__ +packet,529.863000,__ +packet,529.904000,__ +packet,529.946000,__ +packet,530.030000,__ +packet,530.071000,__ +packet,530.113000,__ +packet,530.488000,__ +packet,530.321000,__ +packet,530.196000,__ +packet,530.238000,__ +packet,530.280000,__ +packet,530.363000,__ +packet,530.405000,__ +packet,530.447000,__ +packet,530.739000,__ +packet,530.613000,__ +packet,530.530000,__ +packet,530.572000,__ +packet,530.655000,__ +packet,530.697000,__ +packet,531.114000,__ +packet,530.905000,__ +packet,530.780000,__ +packet,530.822000,__ +packet,530.864000,__ +packet,530.947000,__ +packet,530.989000,__ +packet,531.031000,__ +packet,531.072000,__ +packet,531.322000,__ +packet,531.197000,__ +packet,531.156000,__ +packet,531.239000,__ +packet,531.281000,__ +packet,531.573000,__ +packet,531.448000,__ +packet,531.364000,__ +packet,531.406000,__ +packet,531.489000,__ +packet,531.531000,__ +packet,531.698000,__ +packet,531.614000,__ +packet,531.656000,__ +packet,531.906000,__ +packet,531.781000,__ +packet,531.740000,__ +packet,531.823000,__ +packet,531.865000,__ +packet,532.073000,__ +packet,531.990000,__ +packet,531.948000,__ +packet,532.032000,__ +packet,532.240000,__ +packet,532.157000,__ +packet,532.115000,__ +packet,532.198000,__ +packet,532.323000,__ +packet,532.282000,__ +packet,532.490000,__ +packet,532.407000,__ +packet,532.365000,__ +packet,532.449000,__ +packet,532.657000,__ +packet,532.574000,__ +packet,532.532000,__ +packet,532.615000,__ +packet,532.824000,__ +packet,532.741000,__ +packet,532.699000,__ +packet,532.782000,__ +packet,532.991000,__ +packet,532.907000,__ +packet,532.866000,__ +packet,532.949000,__ +packet,533.074000,__ +packet,533.033000,__ +packet,533.199000,__ +packet,533.116000,__ +packet,533.158000,__ +packet,533.491000,__ +packet,533.324000,__ +packet,533.241000,__ +packet,533.283000,__ +packet,533.366000,__ +packet,533.408000,__ +packet,533.450000,__ +packet,533.658000,__ +packet,533.575000,__ +packet,533.533000,__ +packet,533.616000,__ +packet,533.825000,__ +packet,533.742000,__ +packet,533.700000,__ +packet,533.783000,__ +packet,534.159000,__ +packet,533.992000,__ +packet,533.867000,__ +packet,533.908000,__ +packet,533.950000,__ +packet,534.034000,__ +packet,534.075000,__ +packet,534.117000,__ +packet,534.200000,K_ +packet,534.367000,__ +packet,534.284000,__ +packet,534.242000,__ +packet,534.325000,__ +packet,534.576000,__ +packet,534.451000,__ +packet,534.409000,__ +packet,534.492000,__ +packet,534.534000,__ +packet,534.743000,__ +packet,534.659000,__ +packet,534.617000,__ +packet,534.701000,__ +packet,534.868000,__ +packet,534.784000,__ +packet,534.826000,__ +packet,535.076000,__ +packet,534.951000,__ +packet,534.909000,__ +packet,534.993000,__ +packet,535.035000,__ +packet,535.243000,__ +packet,535.160000,__ +packet,535.118000,__ +packet,535.201000,__ +packet,535.410000,__ +packet,535.326000,__ +packet,535.285000,__ +packet,535.368000,__ +packet,535.577000,__ +packet,535.493000,__ +packet,535.452000,__ +packet,535.535000,__ +packet,535.744000,__ +packet,535.660000,__ +packet,535.618000,__ +packet,535.702000,__ +packet,535.869000,__ +packet,535.785000,__ +packet,535.827000,__ +packet,536.077000,__ +packet,535.952000,__ +packet,535.910000,__ +packet,535.994000,__ +packet,536.036000,__ +packet,536.244000,__ +packet,536.161000,__ +packet,536.119000,__ +packet,536.202000,__ +packet,536.411000,__ +packet,536.327000,__ +packet,536.286000,__ +packet,536.369000,__ +packet,536.578000,__ +packet,536.494000,__ +packet,536.453000,__ +packet,536.536000,__ +packet,536.745000,__ +packet,536.661000,__ +packet,536.619000,__ +packet,536.703000,__ +packet,536.870000,__ +packet,536.786000,__ +packet,536.828000,__ +packet,537.037000,__ +packet,536.953000,__ +packet,536.911000,__ +packet,536.995000,__ +packet,537.203000,__ +packet,537.120000,__ +packet,537.078000,__ +packet,537.162000,__ +packet,537.328000,__ +packet,537.245000,__ +packet,537.287000,__ +packet,537.454000,__ +packet,537.370000,__ +packet,537.412000,__ +packet,537.620000,__ +packet,537.537000,__ +packet,537.495000,__ +packet,537.579000,__ +packet,537.787000,__ +packet,537.704000,__ +packet,537.662000,__ +packet,537.746000,__ +packet,538.121000,__ +packet,537.954000,__ +packet,537.829000,__ +packet,537.871000,__ +packet,537.912000,__ +packet,537.996000,__ +packet,538.038000,__ +packet,538.079000,__ +packet,538.329000,__ +packet,538.204000,__ +packet,538.163000,__ +packet,538.246000,__ +packet,538.288000,__ +packet,538.580000,__ +packet,538.455000,__ +packet,538.371000,__ +packet,538.413000,__ +packet,538.496000,__ +packet,538.538000,__ +packet,538.621000,__ +packet,538.788000,__ +packet,538.705000,__ +packet,538.663000,__ +packet,538.747000,__ +packet,538.872000,__ +packet,538.830000,__ +packet,539.164000,__ +packet,538.997000,__ +packet,538.913000,__ +packet,538.955000,__ +packet,539.039000,__ +packet,539.080000,__ +packet,539.122000,__ +packet,539.372000,__ +packet,539.247000,__ +packet,539.205000,__ +packet,539.289000,__ +packet,539.330000,__ +packet,539.581000,__ +packet,539.456000,__ +packet,539.414000,__ +packet,539.497000,__ +packet,539.539000,__ +packet,539.873000,__ +packet,539.706000,__ +packet,539.622000,__ +packet,539.664000,__ +packet,539.748000,__ +packet,539.789000,__ +packet,539.831000,__ +packet,540.040000,__ +packet,539.956000,__ +packet,539.914000,__ +packet,539.998000,__ +packet,540.206000,__ +packet,540.123000,__ +packet,540.081000,__ +packet,540.165000,__ +packet,540.457000,__ +packet,540.331000,__ +packet,540.248000,__ +packet,540.290000,__ +packet,540.373000,__ +packet,540.415000,__ +packet,540.790000,__ +packet,540.623000,__ +packet,540.498000,__ +packet,540.540000,__ +packet,540.582000,__ +packet,540.665000,__ +packet,540.707000,__ +packet,540.749000,__ +packet,540.999000,__ +packet,540.874000,__ +packet,540.832000,__ +packet,540.915000,__ +packet,540.957000,__ +packet,541.124000,__ +packet,541.041000,__ +packet,541.082000,__ +packet,541.207000,__ +packet,541.166000,__ +packet,541.499000,__ +packet,541.332000,__ +packet,541.249000,__ +packet,541.291000,__ +packet,541.374000,__ +packet,541.416000,__ +packet,541.458000,__ +packet,541.583000,__ +packet,541.541000,__ +packet,541.750000,__ +packet,541.666000,__ +packet,541.624000,__ +packet,541.708000,__ +packet,541.833000,__ +packet,541.791000,__ +packet,542.208000,__ +packet,542.000000,__ +packet,541.875000,__ +packet,541.916000,__ +packet,541.958000,__ +packet,542.042000,__ +packet,542.083000,__ +packet,542.125000,__ +packet,542.167000,__ +packet,542.417000,__ +packet,542.292000,__ +packet,542.250000,__ +packet,542.333000,__ +packet,542.375000,__ +packet,542.500000,__ +packet,542.459000,__ +packet,542.667000,__ +packet,542.584000,__ +packet,542.542000,__ +packet,542.625000,__ +packet,542.834000,__ +packet,542.751000,__ +packet,542.709000,__ +packet,542.792000,__ +packet,542.876000,__ +packet,543.043000,__ +packet,542.959000,__ +packet,542.917000,__ +packet,543.001000,__ +packet,543.126000,__ +packet,543.084000,__ +packet,543.293000,__ +packet,543.209000,__ +packet,543.168000,__ +packet,543.251000,__ +packet,543.334000,__ +packet,543.376000,K_ +packet,543.418000,__ +packet,543.460000,__ +packet,543.501000,__ +packet,543.543000,__ +packet,543.585000,__ +packet,543.626000,__ +packet,543.668000,__ +packet,543.710000,__ +packet,543.793000,__ +packet,543.752000,__ +packet,543.877000,__ +packet,543.835000,__ +packet,543.918000,__ +packet,544.002000,__ +packet,543.960000,__ +packet,544.085000,__ +packet,544.044000,__ +packet,544.210000,__ +packet,544.127000,__ +packet,544.169000,__ +packet,544.335000,__ +packet,544.252000,__ +packet,544.294000,__ +packet,544.461000,__ +packet,544.377000,__ +packet,544.419000,__ +packet,544.544000,__ +packet,544.502000,__ +packet,544.669000,__ +packet,544.586000,__ +packet,544.627000,__ +packet,544.711000,K_ +packet,544.794000,__ +packet,544.753000,__ +packet,544.836000,__ +packet,544.878000,__ +packet,544.919000,__ +packet,545.003000,__ +packet,544.961000,__ +packet,545.253000,__ +packet,545.128000,__ +packet,545.045000,__ +packet,545.086000,__ +packet,545.170000,__ +packet,545.211000,__ +packet,545.420000,__ +packet,545.336000,__ +packet,545.295000,__ +packet,545.378000,__ +packet,545.462000,__ +packet,545.670000,__ +packet,545.545000,__ +packet,545.503000,__ +packet,545.587000,__ +packet,545.628000,__ +packet,545.712000,__ +packet,545.754000,__ +packet,545.795000,__ +packet,545.837000,__ +packet,545.879000,__ +packet,545.962000,__ +packet,545.920000,__ +packet,546.004000,__ +packet,546.046000,__ +packet,546.087000,__ +packet,546.129000,__ +packet,546.171000,__ +packet,546.212000,__ +packet,546.296000,__ +packet,546.254000,__ +packet,546.337000,__ +packet,546.463000,__ +packet,546.379000,__ +packet,546.421000,__ +packet,546.629000,__ +packet,546.546000,__ +packet,546.504000,__ +packet,546.588000,__ +packet,546.838000,__ +packet,546.713000,__ +packet,546.671000,__ +packet,546.755000,__ +packet,546.796000,__ +packet,546.880000,__ +packet,547.005000,__ +packet,546.921000,__ +packet,546.963000,__ +packet,547.047000,K_ +packet,547.213000,__ +packet,547.130000,__ +packet,547.088000,__ +packet,547.172000,__ +packet,547.338000,__ +packet,547.255000,__ +packet,547.297000,__ +packet,547.380000,__ +packet,547.422000,__ +packet,547.714000,__ +packet,547.547000,__ +packet,547.464000,__ +packet,547.505000,__ +packet,547.589000,__ +packet,547.630000,__ +packet,547.672000,__ +packet,547.881000,__ +packet,547.797000,__ +packet,547.756000,__ +packet,547.839000,__ +packet,547.922000,__ +packet,547.964000,__ +packet,548.048000,__ +packet,548.006000,__ +packet,548.173000,__ +packet,548.089000,__ +packet,548.131000,__ +packet,548.339000,__ +packet,548.256000,__ +packet,548.214000,__ +packet,548.298000,__ +packet,548.381000,__ +packet,548.506000,__ +packet,548.423000,__ +packet,548.465000,__ +packet,548.673000,__ +packet,548.590000,__ +packet,548.548000,__ +packet,548.631000,__ +packet,548.882000,__ +packet,548.757000,__ +packet,548.715000,__ +packet,548.798000,__ +packet,548.840000,__ +packet,549.007000,__ +packet,548.923000,__ +packet,548.965000,__ +packet,549.174000,__ +packet,549.090000,__ +packet,549.049000,__ +packet,549.132000,__ +packet,549.382000,__ +packet,549.257000,__ +packet,549.215000,__ +packet,549.299000,__ +packet,549.340000,__ +packet,549.632000,__ +packet,549.507000,__ +packet,549.424000,__ +packet,549.466000,__ +packet,549.549000,__ +packet,549.591000,__ +packet,549.758000,__ +packet,549.674000,__ +packet,549.716000,__ +packet,549.966000,__ +packet,549.841000,__ +packet,549.799000,__ +packet,549.883000,__ +packet,549.924000,__ +packet,550.091000,__ +packet,550.008000,__ +packet,550.050000,__ +packet,550.300000,__ +packet,550.175000,__ +packet,550.133000,__ +packet,550.216000,__ +packet,550.258000,__ +packet,550.383000,__ +packet,550.341000,__ +packet,550.508000,__ +packet,550.425000,__ +packet,550.467000,__ +packet,550.675000,__ +packet,550.592000,__ +packet,550.550000,__ +packet,550.633000,__ +packet,550.842000,__ +packet,550.759000,__ +packet,550.717000,__ +packet,550.800000,__ +packet,551.009000,__ +packet,550.925000,__ +packet,550.884000,__ +packet,550.967000,__ +packet,551.176000,__ +packet,551.092000,__ +packet,551.051000,__ +packet,551.134000,__ +packet,551.342000,__ +packet,551.259000,__ +packet,551.217000,__ +packet,551.301000,__ +packet,551.509000,__ +packet,551.426000,__ +packet,551.384000,__ +packet,551.468000,__ +packet,551.801000,__ +packet,551.634000,__ +packet,551.551000,__ +packet,551.593000,__ +packet,551.676000,__ +packet,551.718000,__ +packet,551.760000,__ +packet,552.010000,__ +packet,551.885000,__ +packet,551.843000,__ +packet,551.926000,__ +packet,551.968000,__ +packet,552.052000,K_ +packet,552.177000,__ +packet,552.093000,__ +packet,552.135000,__ +packet,552.385000,__ +packet,552.260000,__ +packet,552.218000,__ +packet,552.302000,__ +packet,552.343000,__ +packet,552.469000,__ +packet,552.427000,__ +packet,552.844000,__ +packet,552.635000,__ +packet,552.510000,__ +packet,552.552000,__ +packet,552.594000,__ +packet,552.677000,__ +packet,552.719000,__ +packet,552.761000,__ +packet,552.802000,__ +packet,553.053000,__ +packet,552.927000,__ +packet,552.886000,__ +packet,552.969000,__ +packet,553.011000,__ +packet,553.136000,__ +packet,553.094000,__ +packet,553.261000,__ +packet,553.178000,__ +packet,553.219000,__ +packet,553.428000,__ +packet,553.344000,__ +packet,553.303000,__ +packet,553.386000,__ +packet,553.636000,__ +packet,553.511000,__ +packet,553.470000,__ +packet,553.553000,__ +packet,553.595000,__ +packet,553.678000,__ +packet,553.720000,__ +packet,553.803000,__ +packet,553.762000,__ +packet,553.887000,__ +packet,553.845000,__ +packet,554.012000,__ +packet,553.928000,__ +packet,553.970000,__ +packet,554.095000,__ +packet,554.054000,__ +packet,554.137000,__ +packet,554.262000,__ +packet,554.179000,__ +packet,554.220000,__ +packet,554.387000,__ +packet,554.304000,__ +packet,554.345000,__ +packet,554.512000,__ +packet,554.429000,__ +packet,554.471000,__ +packet,554.596000,__ +packet,554.554000,__ +packet,554.637000,__ +packet,554.846000,__ +packet,554.721000,__ +packet,554.679000,__ +packet,554.763000,__ +packet,554.804000,__ +packet,554.971000,__ +packet,554.888000,__ +packet,554.929000,__ +packet,555.013000,__ +packet,555.055000,K_ +packet,555.221000,__ +packet,555.138000,__ +packet,555.096000,__ +packet,555.180000,__ +packet,555.430000,__ +packet,555.305000,__ +packet,555.263000,__ +packet,555.346000,__ +packet,555.388000,__ +packet,555.555000,__ +packet,555.472000,__ +packet,555.513000,__ +packet,555.722000,__ +packet,555.638000,__ +packet,555.597000,__ +packet,555.680000,__ +packet,555.805000,__ +packet,555.764000,__ +packet,555.972000,__ +packet,555.889000,__ +packet,555.847000,__ +packet,555.930000,__ +packet,556.056000,__ +packet,556.014000,__ +packet,556.139000,__ +packet,556.097000,__ +packet,556.222000,__ +packet,556.181000,__ +packet,556.306000,__ +packet,556.264000,__ +packet,556.473000,__ +packet,556.389000,__ +packet,556.347000,__ +packet,556.431000,__ +packet,556.514000,K_ +packet,556.598000,__ +packet,556.556000,__ +packet,556.723000,__ +packet,556.639000,__ +packet,556.681000,__ +packet,556.765000,__ +packet,556.848000,__ +packet,556.806000,__ +packet,556.890000,__ +packet,556.931000,__ +packet,556.973000,__ +packet,557.057000,__ +packet,557.015000,__ +packet,557.098000,__ +packet,557.140000,__ +packet,557.182000,__ +packet,557.223000,__ +packet,557.265000,__ +packet,557.432000,__ +packet,557.348000,__ +packet,557.307000,__ +packet,557.390000,__ +packet,557.599000,__ +packet,557.515000,__ +packet,557.474000,__ +packet,557.557000,__ +packet,557.640000,__ +packet,557.682000,__ +packet,557.724000,__ +packet,557.766000,__ +packet,557.807000,__ +packet,557.974000,__ +packet,557.891000,__ +packet,557.849000,__ +packet,557.932000,__ +packet,558.016000,__ +packet,558.058000,__ +packet,558.099000,__ +packet,558.183000,__ +packet,558.141000,__ +packet,558.266000,__ +packet,558.224000,__ +packet,558.475000,__ +packet,558.349000,__ +packet,558.308000,__ +packet,558.391000,__ +packet,558.433000,__ +packet,558.600000,__ +packet,558.516000,__ +packet,558.558000,__ +packet,558.850000,__ +packet,558.725000,__ +packet,558.641000,__ +packet,558.683000,__ +packet,558.767000,__ +packet,558.808000,__ +packet,559.017000,__ +packet,558.933000,__ +packet,558.892000,__ +packet,558.975000,__ +packet,559.309000,__ +packet,559.142000,__ +packet,559.059000,__ +packet,559.100000,__ +packet,559.184000,__ +packet,559.225000,__ +packet,559.267000,__ +packet,559.434000,__ +packet,559.350000,__ +packet,559.392000,__ +packet,559.601000,__ +packet,559.517000,__ +packet,559.476000,__ +packet,559.559000,__ +packet,559.726000,__ +packet,559.642000,__ +packet,559.684000,__ +packet,559.976000,__ +packet,559.851000,__ +packet,559.768000,__ +packet,559.809000,__ +packet,559.893000,__ +packet,559.934000,__ +packet,560.226000,__ +packet,560.101000,__ +packet,560.018000,__ +packet,560.060000,__ +packet,560.143000,__ +packet,560.185000,__ +packet,560.393000,__ +packet,560.310000,__ +packet,560.268000,__ +packet,560.351000,__ +packet,560.518000,__ +packet,560.435000,__ +packet,560.477000,__ +packet,560.685000,__ +packet,560.602000,__ +packet,560.560000,__ +packet,560.643000,__ +packet,560.769000,__ +packet,560.727000,__ +packet,560.935000,__ +packet,560.852000,__ +packet,560.810000,__ +packet,560.894000,__ +packet,561.019000,__ +packet,560.977000,__ +packet,561.061000,__ +packet,561.102000,K_ +packet,561.144000,__ +packet,561.311000,__ +packet,561.227000,__ +packet,561.186000,__ +packet,561.269000,__ +packet,561.478000,__ +packet,561.394000,__ +packet,561.352000,__ +packet,561.436000,__ +packet,561.603000,__ +packet,561.519000,__ +packet,561.561000,__ +packet,561.644000,__ +packet,561.728000,__ +packet,561.686000,__ +packet,561.811000,__ +packet,561.770000,__ +packet,561.853000,__ +packet,561.895000,__ +packet,561.978000,__ +packet,561.936000,__ +packet,562.062000,__ +packet,562.020000,__ +packet,562.103000,__ +packet,562.145000,__ +packet,562.187000,__ +packet,562.228000,__ +packet,562.270000,__ +packet,562.312000,__ +packet,562.353000,__ +packet,562.437000,__ +packet,562.395000,__ +packet,562.687000,__ +packet,562.562000,__ +packet,562.479000,__ +packet,562.520000,__ +packet,562.604000,__ +packet,562.645000,__ +packet,562.729000,__ +packet,562.854000,__ +packet,562.771000,__ +packet,562.812000,__ +packet,562.979000,__ +packet,562.896000,__ +packet,562.937000,__ +packet,563.021000,__ +packet,563.104000,__ +packet,563.063000,__ +packet,563.188000,__ +packet,563.146000,__ +packet,563.229000,__ +packet,563.271000,__ +packet,563.313000,__ +packet,563.354000,__ +packet,563.521000,__ +packet,563.438000,__ +packet,563.396000,__ +packet,563.480000,__ +packet,563.646000,__ +packet,563.563000,__ +packet,563.605000,__ +packet,563.897000,__ +packet,563.772000,__ +packet,563.688000,__ +packet,563.730000,__ +packet,563.813000,__ +packet,563.855000,__ +packet,564.105000,__ +packet,563.980000,__ +packet,563.938000,__ +packet,564.022000,__ +packet,564.064000,__ +packet,564.189000,__ +packet,564.147000,__ +packet,564.230000,K_ +packet,564.314000,__ +packet,564.272000,__ +packet,564.397000,__ +packet,564.355000,__ +packet,564.606000,__ +packet,564.481000,__ +packet,564.439000,__ +packet,564.522000,__ +packet,564.564000,__ +packet,564.689000,__ +packet,564.647000,__ +packet,564.856000,__ +packet,564.773000,__ +packet,564.731000,__ +packet,564.814000,__ +packet,565.148000,__ +packet,564.981000,__ +packet,564.898000,__ +packet,564.939000,__ +packet,565.023000,__ +packet,565.065000,__ +packet,565.106000,__ +packet,565.440000,__ +packet,565.273000,__ +packet,565.190000,__ +packet,565.231000,__ +packet,565.315000,__ +packet,565.356000,__ +packet,565.398000,__ +packet,565.607000,__ +packet,565.523000,__ +packet,565.482000,__ +packet,565.565000,__ +packet,565.899000,__ +packet,565.732000,__ +packet,565.648000,__ +packet,565.690000,__ +packet,565.774000,__ +packet,565.815000,__ +packet,565.857000,__ +packet,566.024000,__ +packet,565.940000,__ +packet,565.982000,__ +packet,566.149000,__ +packet,566.066000,__ +packet,566.107000,__ +packet,566.316000,__ +packet,566.232000,__ +packet,566.191000,__ +packet,566.274000,__ +packet,566.524000,__ +packet,566.399000,__ +packet,566.357000,__ +packet,566.441000,__ +packet,566.483000,__ +packet,566.775000,__ +packet,566.649000,__ +packet,566.566000,__ +packet,566.608000,__ +packet,566.691000,__ +packet,566.733000,__ +packet,566.816000,K_ +packet,566.983000,__ +packet,566.900000,__ +packet,566.858000,__ +packet,566.941000,__ +packet,567.150000,__ +packet,567.067000,__ +packet,567.025000,__ +packet,567.108000,__ +packet,567.442000,__ +packet,567.275000,__ +packet,567.192000,__ +packet,567.233000,__ +packet,567.317000,__ +packet,567.358000,__ +packet,567.400000,__ +packet,567.609000,__ +packet,567.525000,__ +packet,567.484000,__ +packet,567.567000,__ +packet,567.734000,__ +packet,567.650000,__ +packet,567.692000,__ +packet,568.109000,__ +packet,567.901000,__ +packet,567.776000,__ +packet,567.817000,__ +packet,567.859000,__ +packet,567.942000,__ +packet,567.984000,__ +packet,568.026000,__ +packet,568.068000,__ +packet,568.485000,__ +packet,568.276000,__ +packet,568.151000,__ +packet,568.193000,__ +packet,568.234000,__ +packet,568.318000,__ +packet,568.359000,__ +packet,568.401000,__ +packet,568.443000,__ +packet,568.777000,__ +packet,568.610000,__ +packet,568.526000,__ +packet,568.568000,__ +packet,568.651000,__ +packet,568.693000,__ +packet,568.735000,__ +packet,569.027000,__ +packet,568.902000,__ +packet,568.818000,__ +packet,568.860000,__ +packet,568.943000,__ +packet,568.985000,__ +packet,569.277000,__ +packet,569.152000,__ +packet,569.069000,__ +packet,569.110000,__ +packet,569.194000,__ +packet,569.235000,__ +packet,569.444000,__ +packet,569.360000,__ +packet,569.319000,__ +packet,569.402000,__ +packet,569.527000,__ +packet,569.486000,__ +packet,569.694000,__ +packet,569.611000,__ +packet,569.569000,__ +packet,569.652000,__ +packet,569.861000,__ +packet,569.778000,__ +packet,569.736000,__ +packet,569.819000,__ +packet,570.236000,__ +packet,570.028000,__ +packet,569.903000,__ +packet,569.944000,__ +packet,569.986000,__ +packet,570.070000,__ +packet,570.111000,__ +packet,570.153000,__ +packet,570.195000,__ +packet,570.320000,__ +packet,570.278000,__ +packet,570.528000,__ +packet,570.403000,__ +packet,570.361000,__ +packet,570.445000,__ +packet,570.487000,__ +packet,570.779000,__ +packet,570.653000,__ +packet,570.570000,__ +packet,570.612000,__ +packet,570.695000,__ +packet,570.737000,__ +packet,570.945000,__ +packet,570.862000,__ +packet,570.820000,__ +packet,570.904000,__ +packet,571.071000,__ +packet,570.987000,__ +packet,571.029000,__ +packet,571.112000,K_ +packet,571.279000,__ +packet,571.196000,__ +packet,571.154000,__ +packet,571.237000,__ +packet,571.654000,__ +packet,571.446000,__ +packet,571.321000,__ +packet,571.362000,__ +packet,571.404000,__ +packet,571.488000,__ +packet,571.529000,__ +packet,571.571000,__ +packet,571.613000,__ +packet,571.863000,__ +packet,571.738000,__ +packet,571.696000,__ +packet,571.780000,__ +packet,571.821000,__ +packet,571.905000,__ +packet,572.113000,__ +packet,571.988000,__ +packet,571.946000,__ +packet,572.030000,__ +packet,572.072000,__ +packet,572.280000,__ +packet,572.197000,__ +packet,572.155000,__ +packet,572.238000,__ +packet,572.405000,__ +packet,572.322000,__ +packet,572.363000,__ +packet,572.489000,__ +packet,572.447000,__ +packet,572.781000,__ +packet,572.614000,__ +packet,572.530000,__ +packet,572.572000,__ +packet,572.655000,__ +packet,572.697000,__ +packet,572.739000,__ +packet,572.947000,__ +packet,572.864000,__ +packet,572.822000,__ +packet,572.906000,__ +packet,573.198000,__ +packet,573.073000,__ +packet,572.989000,__ +packet,573.031000,__ +packet,573.114000,__ +packet,573.156000,__ +packet,573.364000,__ +packet,573.281000,__ +packet,573.239000,__ +packet,573.323000,__ +packet,573.490000,__ +packet,573.406000,__ +packet,573.448000,__ +packet,573.615000,__ +packet,573.531000,__ +packet,573.573000,__ +packet,573.782000,__ +packet,573.698000,__ +packet,573.656000,__ +packet,573.740000,__ +packet,573.823000,__ +packet,573.948000,__ +packet,573.865000,__ +packet,573.907000,__ +packet,574.115000,__ +packet,574.032000,__ +packet,573.990000,__ +packet,574.074000,__ +packet,574.282000,__ +packet,574.199000,__ +packet,574.157000,__ +packet,574.240000,__ +packet,574.324000,K_ +packet,574.532000,__ +packet,574.407000,__ +packet,574.365000,__ +packet,574.449000,__ +packet,574.491000,__ +packet,574.616000,__ +packet,574.574000,__ +packet,574.657000,__ +packet,574.783000,__ +packet,574.699000,__ +packet,574.741000,__ +packet,574.866000,__ +packet,574.824000,__ +packet,574.908000,__ +packet,574.991000,__ +packet,574.949000,__ +packet,575.116000,__ +packet,575.033000,__ +packet,575.075000,__ +packet,575.158000,__ +packet,575.283000,__ +packet,575.200000,__ +packet,575.241000,__ +packet,575.325000,__ +packet,575.533000,__ +packet,575.408000,__ +packet,575.366000,__ +packet,575.450000,__ +packet,575.492000,__ +packet,575.575000,__ +packet,575.742000,__ +packet,575.658000,__ +packet,575.617000,__ +packet,575.700000,__ +packet,575.992000,__ +packet,575.867000,__ +packet,575.784000,__ +packet,575.825000,__ +packet,575.909000,__ +packet,575.950000,__ +packet,576.201000,__ +packet,576.076000,__ +packet,576.034000,__ +packet,576.117000,__ +packet,576.159000,__ +packet,576.242000,K_ +packet,576.451000,__ +packet,576.326000,__ +packet,576.284000,__ +packet,576.367000,__ +packet,576.409000,__ +packet,576.493000,__ +packet,576.701000,__ +packet,576.576000,__ +packet,576.534000,__ +packet,576.618000,__ +packet,576.659000,__ +packet,576.743000,__ +packet,576.910000,__ +packet,576.826000,__ +packet,576.785000,__ +packet,576.868000,__ +packet,577.077000,__ +packet,576.993000,__ +packet,576.951000,__ +packet,577.035000,__ +packet,577.202000,__ +packet,577.118000,__ +packet,577.160000,__ +packet,577.368000,__ +packet,577.285000,__ +packet,577.243000,__ +packet,577.327000,__ +packet,577.452000,__ +packet,577.410000,__ +packet,577.577000,__ +packet,577.494000,__ +packet,577.535000,__ +packet,577.702000,__ +packet,577.619000,__ +packet,577.660000,__ +packet,577.786000,__ +packet,577.744000,__ +packet,577.827000,__ +packet,577.911000,__ +packet,577.869000,__ +packet,578.078000,__ +packet,577.994000,__ +packet,577.952000,__ +packet,578.036000,__ +packet,578.119000,__ +packet,578.161000,__ +packet,578.369000,__ +packet,578.244000,__ +packet,578.203000,__ +packet,578.286000,__ +packet,578.328000,__ +packet,578.536000,__ +packet,578.453000,__ +packet,578.411000,__ +packet,578.495000,__ +packet,578.703000,__ +packet,578.620000,__ +packet,578.578000,__ +packet,578.661000,__ +packet,578.870000,__ +packet,578.787000,__ +packet,578.745000,__ +packet,578.828000,__ +packet,579.245000,__ +packet,579.037000,__ +packet,578.912000,__ +packet,578.953000,__ +packet,578.995000,__ +packet,579.079000,__ +packet,579.120000,__ +packet,579.162000,__ +packet,579.204000,__ +packet,579.412000,__ +packet,579.329000,__ +packet,579.287000,__ +packet,579.370000,__ +packet,579.662000,__ +packet,579.537000,__ +packet,579.454000,__ +packet,579.496000,__ +packet,579.579000,__ +packet,579.621000,__ +packet,579.704000,__ +packet,579.746000,K_ +packet,579.829000,__ +packet,579.788000,__ +packet,579.996000,__ +packet,579.913000,__ +packet,579.871000,__ +packet,579.954000,__ +packet,580.163000,__ +packet,580.080000,__ +packet,580.038000,__ +packet,580.121000,__ +packet,580.455000,__ +packet,580.288000,__ +packet,580.205000,__ +packet,580.246000,__ +packet,580.330000,__ +packet,580.371000,__ +packet,580.413000,__ +packet,580.789000,__ +packet,580.622000,__ +packet,580.497000,__ +packet,580.538000,__ +packet,580.580000,__ +packet,580.663000,__ +packet,580.705000,__ +packet,580.747000,__ +packet,580.955000,__ +packet,580.872000,__ +packet,580.830000,__ +packet,580.914000,__ +packet,581.122000,__ +packet,581.039000,__ +packet,580.997000,__ +packet,581.081000,__ +packet,581.414000,__ +packet,581.247000,__ +packet,581.164000,__ +packet,581.206000,__ +packet,581.289000,__ +packet,581.331000,__ +packet,581.372000,__ +packet,581.664000,__ +packet,581.539000,__ +packet,581.456000,__ +packet,581.498000,__ +packet,581.581000,__ +packet,581.623000,__ +packet,581.706000,__ +packet,581.748000,K_ +packet,581.831000,__ +packet,581.790000,__ +packet,581.998000,__ +packet,581.915000,__ +packet,581.873000,__ +packet,581.956000,__ +packet,582.165000,__ +packet,582.082000,__ +packet,582.040000,__ +packet,582.123000,__ +packet,582.373000,__ +packet,582.248000,__ +packet,582.207000,__ +packet,582.290000,__ +packet,582.332000,__ +packet,582.540000,__ +packet,582.457000,__ +packet,582.415000,__ +packet,582.499000,__ +packet,582.665000,__ +packet,582.582000,__ +packet,582.624000,__ +packet,582.832000,__ +packet,582.749000,__ +packet,582.707000,__ +packet,582.791000,__ +packet,583.041000,__ +packet,582.916000,__ +packet,582.874000,__ +packet,582.957000,__ +packet,582.999000,__ +packet,583.166000,__ +packet,583.083000,__ +packet,583.124000,__ +packet,583.333000,__ +packet,583.249000,__ +packet,583.208000,__ +packet,583.291000,__ +packet,583.541000,__ +packet,583.416000,__ +packet,583.374000,__ +packet,583.458000,__ +packet,583.500000,__ +packet,583.666000,__ +packet,583.583000,__ +packet,583.625000,__ +packet,583.833000,__ +packet,583.750000,__ +packet,583.708000,__ +packet,583.792000,__ +packet,583.875000,__ +packet,583.917000,K_ +packet,584.000000,__ +packet,583.958000,__ +packet,584.167000,__ +packet,584.084000,__ +packet,584.042000,__ +packet,584.125000,__ +packet,584.334000,__ +packet,584.250000,__ +packet,584.209000,__ +packet,584.292000,__ +packet,584.501000,__ +packet,584.417000,__ +packet,584.375000,__ +packet,584.459000,__ +packet,584.542000,__ +packet,584.709000,__ +packet,584.626000,__ +packet,584.584000,__ +packet,584.667000,__ +packet,584.876000,__ +packet,584.793000,__ +packet,584.751000,__ +packet,584.834000,__ +packet,585.001000,__ +packet,584.918000,__ +packet,584.959000,__ +packet,585.168000,__ +packet,585.085000,__ +packet,585.043000,__ +packet,585.126000,__ +packet,585.335000,__ +packet,585.251000,__ +packet,585.210000,__ +packet,585.293000,__ +packet,585.502000,__ +packet,585.418000,__ +packet,585.376000,__ +packet,585.460000,__ +packet,585.543000,__ +packet,585.585000,K_ +packet,585.668000,__ +packet,585.627000,__ +packet,585.710000,__ +packet,585.835000,__ +packet,585.752000,__ +packet,585.794000,__ +packet,585.877000,__ +packet,586.044000,__ +packet,585.960000,__ +packet,585.919000,__ +packet,586.002000,__ +packet,586.211000,__ +packet,586.127000,__ +packet,586.086000,__ +packet,586.169000,__ +packet,586.336000,__ +packet,586.252000,__ +packet,586.294000,__ +packet,586.544000,__ +packet,586.419000,__ +packet,586.377000,__ +packet,586.461000,__ +packet,586.503000,__ +packet,586.669000,__ +packet,586.586000,__ +packet,586.628000,__ +packet,586.878000,__ +packet,586.753000,__ +packet,586.711000,__ +packet,586.795000,__ +packet,586.836000,__ +packet,587.045000,__ +packet,586.961000,__ +packet,586.920000,__ +packet,587.003000,__ +packet,587.087000,K_ +packet,587.212000,__ +packet,587.128000,__ +packet,587.170000,__ +packet,587.337000,__ +packet,587.253000,__ +packet,587.295000,__ +packet,587.545000,__ +packet,587.420000,__ +packet,587.378000,__ +packet,587.462000,__ +packet,587.504000,__ +packet,587.670000,__ +packet,587.587000,__ +packet,587.629000,__ +packet,587.879000,__ +packet,587.754000,__ +packet,587.712000,__ +packet,587.796000,__ +packet,587.837000,__ +packet,588.046000,__ +packet,587.962000,__ +packet,587.921000,__ +packet,588.004000,__ +packet,588.213000,__ +packet,588.129000,__ +packet,588.088000,__ +packet,588.171000,__ +packet,588.379000,__ +packet,588.296000,__ +packet,588.254000,__ +packet,588.338000,__ +packet,588.546000,__ +packet,588.463000,__ +packet,588.421000,__ +packet,588.505000,__ +packet,588.713000,__ +packet,588.630000,__ +packet,588.588000,__ +packet,588.671000,__ +packet,588.838000,__ +packet,588.755000,__ +packet,588.797000,__ +packet,589.047000,__ +packet,588.922000,__ +packet,588.880000,__ +packet,588.963000,__ +packet,589.005000,__ +packet,589.214000,__ +packet,589.130000,__ +packet,589.089000,__ +packet,589.172000,__ +packet,589.339000,__ +packet,589.255000,__ +packet,589.297000,__ +packet,589.547000,__ +packet,589.422000,__ +packet,589.380000,__ +packet,589.464000,__ +packet,589.506000,__ +packet,589.714000,__ +packet,589.631000,__ +packet,589.589000,__ +packet,589.672000,__ +packet,589.881000,__ +packet,589.798000,__ +packet,589.756000,__ +packet,589.839000,__ +packet,590.006000,__ +packet,589.923000,__ +packet,589.964000,__ +packet,590.173000,__ +packet,590.090000,__ +packet,590.048000,__ +packet,590.131000,__ +packet,590.340000,__ +packet,590.256000,__ +packet,590.215000,__ +packet,590.298000,__ +packet,590.381000,__ +packet,590.423000,K_ +packet,590.548000,__ +packet,590.465000,__ +packet,590.507000,__ +packet,590.715000,__ +packet,590.632000,__ +packet,590.590000,__ +packet,590.673000,__ +packet,590.965000,__ +packet,590.840000,__ +packet,590.757000,__ +packet,590.799000,__ +packet,590.882000,__ +packet,590.924000,__ +packet,591.091000,__ +packet,591.007000,__ +packet,591.049000,__ +packet,591.299000,__ +packet,591.174000,__ +packet,591.132000,__ +packet,591.216000,__ +packet,591.257000,__ +packet,591.424000,__ +packet,591.341000,__ +packet,591.382000,__ +packet,591.633000,__ +packet,591.508000,__ +packet,591.466000,__ +packet,591.549000,__ +packet,591.591000,__ +packet,591.674000,__ +packet,591.883000,__ +packet,591.758000,__ +packet,591.716000,__ +packet,591.800000,__ +packet,591.841000,__ +packet,592.008000,__ +packet,591.925000,__ +packet,591.966000,__ +packet,592.217000,__ +packet,592.092000,__ +packet,592.050000,__ +packet,592.133000,__ +packet,592.175000,__ +packet,592.383000,__ +packet,592.300000,__ +packet,592.258000,__ +packet,592.342000,__ +packet,592.550000,__ +packet,592.467000,__ +packet,592.425000,__ +packet,592.509000,__ +packet,592.759000,__ +packet,592.634000,__ +packet,592.592000,__ +packet,592.675000,__ +packet,592.717000,__ +packet,592.926000,__ +packet,592.842000,__ +packet,592.801000,__ +packet,592.884000,__ +packet,593.134000,__ +packet,593.009000,__ +packet,592.967000,__ +packet,593.051000,__ +packet,593.093000,__ +packet,593.384000,__ +packet,593.259000,__ +packet,593.176000,__ +packet,593.218000,__ +packet,593.301000,__ +packet,593.343000,__ +packet,593.510000,__ +packet,593.426000,__ +packet,593.468000,__ +packet,593.676000,__ +packet,593.593000,__ +packet,593.551000,__ +packet,593.635000,__ +packet,593.843000,__ +packet,593.760000,__ +packet,593.718000,__ +packet,593.802000,__ +packet,594.094000,__ +packet,593.968000,__ +packet,593.885000,__ +packet,593.927000,__ +packet,594.010000,__ +packet,594.052000,__ +packet,594.260000,__ +packet,594.177000,__ +packet,594.135000,__ +packet,594.219000,__ +packet,594.427000,__ +packet,594.344000,__ +packet,594.302000,__ +packet,594.385000,__ +packet,594.636000,__ +packet,594.511000,__ +packet,594.469000,__ +packet,594.552000,__ +packet,594.594000,__ +packet,594.677000,__ +packet,594.886000,__ +packet,594.761000,__ +packet,594.719000,__ +packet,594.803000,__ +packet,594.844000,__ +packet,595.011000,__ +packet,594.928000,__ +packet,594.969000,__ +packet,595.178000,__ +packet,595.095000,__ +packet,595.053000,__ +packet,595.136000,__ +packet,595.428000,__ +packet,595.303000,__ +packet,595.220000,__ +packet,595.261000,__ +packet,595.345000,__ +packet,595.386000,__ +packet,595.720000,__ +packet,595.553000,__ +packet,595.470000,__ +packet,595.512000,__ +packet,595.595000,__ +packet,595.637000,__ +packet,595.678000,__ +packet,595.845000,__ +packet,595.762000,__ +packet,595.804000,__ +packet,596.012000,__ +packet,595.929000,__ +packet,595.887000,__ +packet,595.970000,__ +packet,596.054000,__ +packet,596.221000,__ +packet,596.137000,__ +packet,596.096000,__ +packet,596.179000,__ +packet,596.387000,__ +packet,596.304000,__ +packet,596.262000,__ +packet,596.346000,__ +packet,596.638000,__ +packet,596.513000,__ +packet,596.429000,__ +packet,596.471000,__ +packet,596.554000,__ +packet,596.596000,__ +packet,596.888000,__ +packet,596.763000,__ +packet,596.679000,__ +packet,596.721000,__ +packet,596.805000,__ +packet,596.846000,__ +packet,596.930000,K_ +packet,597.097000,__ +packet,597.013000,__ +packet,596.971000,__ +packet,597.055000,__ +packet,597.388000,__ +packet,597.222000,__ +packet,597.138000,__ +packet,597.180000,__ +packet,597.263000,__ +packet,597.305000,__ +packet,597.347000,__ +packet,597.555000,__ +packet,597.472000,__ +packet,597.430000,__ +packet,597.514000,__ +packet,597.722000,__ +packet,597.639000,__ +packet,597.597000,__ +packet,597.680000,__ +packet,597.889000,__ +packet,597.806000,__ +packet,597.764000,__ +packet,597.847000,__ +packet,597.931000,__ +packet,598.056000,__ +packet,597.972000,__ +packet,598.014000,__ +packet,598.264000,__ +packet,598.139000,__ +packet,598.098000,__ +packet,598.181000,__ +packet,598.223000,__ +packet,598.431000,__ +packet,598.348000,__ +packet,598.306000,__ +packet,598.389000,__ +packet,598.681000,__ +packet,598.556000,__ +packet,598.473000,__ +packet,598.515000,__ +packet,598.598000,__ +packet,598.640000,__ +packet,598.890000,__ +packet,598.765000,__ +packet,598.723000,__ +packet,598.807000,__ +packet,598.848000,__ +packet,598.932000,__ +packet,599.015000,__ +packet,598.973000,__ +packet,599.140000,__ +packet,599.057000,__ +packet,599.099000,__ +packet,599.182000,__ +packet,599.224000,__ +packet,599.265000,__ +packet,599.390000,__ +packet,599.307000,__ +packet,599.349000,__ +packet,599.432000,__ +packet,599.474000,__ +packet,599.557000,__ +packet,599.516000,__ +packet,599.599000,__ +packet,599.641000,__ +packet,599.724000,__ +packet,599.682000,__ +packet,599.808000,__ +packet,599.766000,__ +packet,599.849000,__ +packet,599.933000,__ +packet,599.891000,__ +packet,599.974000,__ +packet,600.100000,__ +packet,600.016000,__ +packet,600.058000,__ +packet,600.225000,__ +packet,600.141000,__ +packet,600.183000,__ +packet,600.350000,__ +packet,600.266000,__ +packet,600.308000,__ +packet,600.391000,__ +packet,600.475000,__ +packet,600.433000,__ +packet,600.558000,__ +packet,600.517000,__ +packet,600.642000,__ +packet,600.600000,__ +packet,600.683000,__ +packet,600.725000,__ +packet,600.809000,__ +packet,600.767000,__ +packet,600.892000,__ +packet,600.850000,__ +packet,600.934000,__ +packet,600.975000,__ +packet,601.101000,__ +packet,601.017000,__ +packet,601.059000,__ +packet,601.267000,__ +packet,601.184000,__ +packet,601.142000,__ +packet,601.226000,__ +packet,601.309000,__ +packet,601.351000,__ +packet,601.476000,__ +packet,601.392000,__ +packet,601.434000,__ +packet,601.518000,__ +packet,601.601000,__ +packet,601.559000,__ +packet,601.726000,__ +packet,601.643000,__ +packet,601.684000,__ +packet,601.810000,__ +packet,601.768000,__ +packet,601.851000,__ +packet,601.893000,__ +packet,601.935000,__ +packet,601.976000,__ +packet,602.018000,__ +packet,602.060000,__ +packet,602.143000,__ +packet,602.102000,__ +packet,602.227000,__ +packet,602.185000,__ +packet,602.352000,__ +packet,602.268000,__ +packet,602.310000,__ +packet,602.519000,__ +packet,602.435000,__ +packet,602.393000,__ +packet,602.477000,__ +packet,602.685000,__ +packet,602.602000,__ +packet,602.560000,__ +packet,602.644000,__ +packet,602.852000,__ +packet,602.769000,__ +packet,602.727000,__ +packet,602.811000,__ +packet,602.894000,__ +packet,602.936000,__ +packet,603.103000,__ +packet,603.019000,__ +packet,602.977000,__ +packet,603.061000,__ +packet,603.269000,__ +packet,603.186000,__ +packet,603.144000,__ +packet,603.228000,__ +packet,603.311000,__ +packet,603.353000,__ +packet,603.394000,__ +packet,603.436000,__ +packet,603.561000,__ +packet,603.478000,__ +packet,603.520000,__ +packet,603.686000,__ +packet,603.603000,__ +packet,603.645000,__ +packet,603.770000,__ +packet,603.728000,__ +packet,603.895000,__ +packet,603.812000,__ +packet,603.853000,__ +packet,604.062000,__ +packet,603.978000,__ +packet,603.937000,__ +packet,604.020000,__ +packet,604.145000,__ +packet,604.104000,__ +packet,604.187000,__ +packet,604.229000,__ +packet,604.270000,__ +packet,604.354000,__ +packet,604.312000,__ +packet,604.395000,__ +packet,604.437000,__ +packet,604.479000,__ +packet,604.521000,__ +packet,604.646000,__ +packet,604.562000,__ +packet,604.604000,__ +packet,604.687000,__ +packet,604.771000,__ +packet,604.729000,__ +packet,604.979000,__ +packet,604.854000,__ +packet,604.813000,__ +packet,604.896000,__ +packet,604.938000,__ +packet,605.230000,__ +packet,605.105000,__ +packet,605.021000,__ +packet,605.063000,__ +packet,605.146000,__ +packet,605.188000,__ +packet,605.396000,__ +packet,605.313000,__ +packet,605.271000,__ +packet,605.355000,__ +packet,605.563000,__ +packet,605.480000,__ +packet,605.438000,__ +packet,605.522000,__ +packet,605.647000,__ +packet,605.605000,__ +packet,605.688000,K_ +packet,605.814000,__ +packet,605.730000,__ +packet,605.772000,__ +packet,606.022000,__ +packet,605.897000,__ +packet,605.855000,__ +packet,605.939000,__ +packet,605.980000,__ +packet,606.189000,__ +packet,606.106000,__ +packet,606.064000,__ +packet,606.147000,__ +packet,606.314000,__ +packet,606.231000,__ +packet,606.272000,__ +packet,606.481000,__ +packet,606.397000,__ +packet,606.356000,__ +packet,606.439000,__ +packet,606.689000,__ +packet,606.564000,__ +packet,606.523000,__ +packet,606.606000,__ +packet,606.648000,__ +packet,606.856000,__ +packet,606.773000,__ +packet,606.731000,__ +packet,606.815000,__ +packet,606.981000,__ +packet,606.898000,__ +packet,606.940000,__ +packet,607.190000,__ +packet,607.065000,__ +packet,607.023000,__ +packet,607.107000,__ +packet,607.148000,__ +packet,607.357000,__ +packet,607.273000,__ +packet,607.232000,__ +packet,607.315000,__ +packet,607.524000,__ +packet,607.440000,__ +packet,607.398000,__ +packet,607.482000,__ +packet,607.649000,__ +packet,607.565000,__ +packet,607.607000,__ +packet,607.816000,__ +packet,607.732000,__ +packet,607.690000,__ +packet,607.774000,__ +packet,607.982000,__ +packet,607.899000,__ +packet,607.857000,__ +packet,607.941000,__ +packet,608.149000,__ +packet,608.066000,__ +packet,608.024000,__ +packet,608.108000,__ +packet,608.316000,__ +packet,608.233000,__ +packet,608.191000,__ +packet,608.274000,__ +packet,608.483000,__ +packet,608.399000,__ +packet,608.358000,__ +packet,608.441000,__ +packet,608.650000,__ +packet,608.566000,__ +packet,608.525000,__ +packet,608.608000,__ +packet,608.817000,__ +packet,608.733000,__ +packet,608.691000,__ +packet,608.775000,__ +packet,608.983000,__ +packet,608.900000,__ +packet,608.858000,__ +packet,608.942000,__ +packet,609.150000,__ +packet,609.067000,__ +packet,609.025000,__ +packet,609.109000,__ +packet,609.317000,__ +packet,609.234000,__ +packet,609.192000,__ +packet,609.275000,__ +packet,609.526000,__ +packet,609.400000,__ +packet,609.359000,__ +packet,609.442000,__ +packet,609.484000,__ +packet,609.692000,__ +packet,609.609000,__ +packet,609.567000,__ +packet,609.651000,__ +packet,609.818000,__ +packet,609.734000,__ +packet,609.776000,__ +packet,610.026000,__ +packet,609.901000,__ +packet,609.859000,__ +packet,609.943000,__ +packet,609.984000,__ +packet,610.193000,__ +packet,610.110000,__ +packet,610.068000,__ +packet,610.151000,__ +packet,610.360000,__ +packet,610.276000,__ +packet,610.235000,__ +packet,610.318000,__ +packet,610.443000,__ +packet,610.401000,__ +packet,610.485000,K_ +packet,610.568000,__ +packet,610.527000,__ +packet,610.735000,__ +packet,610.652000,__ +packet,610.610000,__ +packet,610.693000,__ +packet,610.944000,__ +packet,610.819000,__ +packet,610.777000,__ +packet,610.860000,__ +packet,610.902000,__ +packet,611.069000,__ +packet,610.985000,__ +packet,611.027000,__ +packet,611.236000,__ +packet,611.152000,__ +packet,611.111000,__ +packet,611.194000,__ +packet,611.444000,__ +packet,611.319000,__ +packet,611.277000,__ +packet,611.361000,__ +packet,611.402000,__ +packet,611.569000,__ +packet,611.486000,__ +packet,611.528000,__ +packet,611.778000,__ +packet,611.653000,__ +packet,611.611000,__ +packet,611.694000,__ +packet,611.736000,__ +packet,611.820000,__ +packet,611.986000,__ +packet,611.903000,__ +packet,611.861000,__ +packet,611.945000,__ +packet,612.153000,__ +packet,612.070000,__ +packet,612.028000,__ +packet,612.112000,__ +packet,612.320000,__ +packet,612.237000,__ +packet,612.195000,__ +packet,612.278000,__ +packet,612.445000,__ +packet,612.362000,__ +packet,612.403000,__ +packet,612.570000,__ +packet,612.487000,__ +packet,612.529000,__ +packet,612.779000,__ +packet,612.654000,__ +packet,612.612000,__ +packet,612.695000,__ +packet,612.737000,__ +packet,612.946000,__ +packet,612.862000,__ +packet,612.821000,__ +packet,612.904000,__ +packet,613.071000,__ +packet,612.987000,__ +packet,613.029000,__ +packet,613.238000,__ +packet,613.154000,__ +packet,613.113000,__ +packet,613.196000,__ +packet,613.446000,__ +packet,613.321000,__ +packet,613.279000,__ +packet,613.363000,__ +packet,613.404000,__ +packet,613.571000,__ +packet,613.488000,__ +packet,613.530000,__ +packet,613.780000,__ +packet,613.655000,__ +packet,613.613000,__ +packet,613.696000,__ +packet,613.738000,__ +packet,613.905000,__ +packet,613.822000,__ +packet,613.863000,__ +packet,614.072000,__ +packet,613.988000,__ +packet,613.947000,__ +packet,614.030000,__ +packet,614.280000,__ +packet,614.155000,__ +packet,614.114000,__ +packet,614.197000,__ +packet,614.239000,__ +packet,614.447000,__ +packet,614.364000,__ +packet,614.322000,__ +packet,614.405000,__ +packet,614.572000,__ +packet,614.489000,__ +packet,614.531000,__ +packet,614.697000,__ +packet,614.614000,__ +packet,614.656000,__ +packet,614.823000,__ +packet,614.739000,__ +packet,614.781000,__ +packet,614.989000,__ +packet,614.906000,__ +packet,614.864000,__ +packet,614.948000,__ +packet,615.198000,__ +packet,615.073000,__ +packet,615.031000,__ +packet,615.115000,__ +packet,615.156000,__ +packet,615.240000,K_ +packet,615.365000,__ +packet,615.281000,__ +packet,615.323000,__ +packet,615.490000,__ +packet,615.406000,__ +packet,615.448000,__ +packet,615.657000,__ +packet,615.573000,__ +packet,615.532000,__ +packet,615.615000,__ +packet,615.824000,__ +packet,615.740000,__ +packet,615.698000,__ +packet,615.782000,__ +packet,616.032000,__ +packet,615.907000,__ +packet,615.865000,__ +packet,615.949000,__ +packet,615.990000,__ +packet,616.199000,__ +packet,616.116000,__ +packet,616.074000,__ +packet,616.157000,__ +packet,616.324000,__ +packet,616.241000,__ +packet,616.282000,__ +packet,616.491000,__ +packet,616.407000,__ +packet,616.366000,__ +packet,616.449000,__ +packet,616.699000,__ +packet,616.574000,__ +packet,616.533000,__ +packet,616.616000,__ +packet,616.658000,__ +packet,616.866000,__ +packet,616.783000,__ +packet,616.741000,__ +packet,616.825000,__ +packet,617.033000,__ +packet,616.950000,__ +packet,616.908000,__ +packet,616.991000,__ +packet,617.158000,__ +packet,617.075000,__ +packet,617.117000,__ +packet,617.200000,__ +packet,617.325000,__ +packet,617.242000,__ +packet,617.283000,__ +packet,617.492000,__ +packet,617.408000,__ +packet,617.367000,__ +packet,617.450000,__ +packet,617.534000,__ +packet,617.700000,__ +packet,617.617000,__ +packet,617.575000,__ +packet,617.659000,__ +packet,617.826000,__ +packet,617.742000,__ +packet,617.784000,__ +packet,617.992000,__ +packet,617.909000,__ +packet,617.867000,__ +packet,617.951000,__ +packet,618.159000,__ +packet,618.076000,__ +packet,618.034000,__ +packet,618.118000,__ +packet,618.326000,__ +packet,618.243000,__ +packet,618.201000,__ +packet,618.284000,__ +packet,618.493000,__ +packet,618.409000,__ +packet,618.368000,__ +packet,618.451000,__ +packet,618.660000,__ +packet,618.576000,__ +packet,618.535000,__ +packet,618.618000,__ +packet,618.827000,__ +packet,618.743000,__ +packet,618.701000,__ +packet,618.785000,__ +packet,618.993000,__ +packet,618.910000,__ +packet,618.868000,__ +packet,618.952000,__ +packet,619.202000,__ +packet,619.077000,__ +packet,619.035000,__ +packet,619.119000,__ +packet,619.160000,__ +packet,619.327000,__ +packet,619.244000,__ +packet,619.285000,__ +packet,619.494000,__ +packet,619.410000,__ +packet,619.369000,__ +packet,619.452000,__ +packet,619.661000,__ +packet,619.577000,__ +packet,619.536000,__ +packet,619.619000,__ +packet,619.828000,__ +packet,619.744000,__ +packet,619.702000,__ +packet,619.786000,__ +packet,620.036000,__ +packet,619.911000,__ +packet,619.869000,__ +packet,619.953000,__ +packet,619.994000,__ +packet,620.203000,__ +packet,620.120000,__ +packet,620.078000,__ +packet,620.161000,__ +packet,620.370000,__ +packet,620.286000,__ +packet,620.245000,__ +packet,620.328000,__ +packet,620.495000,__ +packet,620.411000,__ +packet,620.453000,__ +packet,620.662000,__ +packet,620.578000,__ +packet,620.537000,__ +packet,620.620000,__ +packet,620.829000,__ +packet,620.745000,__ +packet,620.703000,__ +packet,620.787000,__ +packet,620.995000,__ +packet,620.912000,__ +packet,620.870000,__ +packet,620.954000,__ +packet,621.162000,__ +packet,621.079000,__ +packet,621.037000,__ +packet,621.121000,__ +packet,621.329000,__ +packet,621.246000,__ +packet,621.204000,__ +packet,621.287000,__ +packet,621.496000,__ +packet,621.412000,__ +packet,621.371000,__ +packet,621.454000,__ +packet,621.663000,__ +packet,621.579000,__ +packet,621.538000,__ +packet,621.621000,__ +packet,621.830000,__ +packet,621.746000,__ +packet,621.704000,__ +packet,621.788000,__ +packet,621.996000,__ +packet,621.913000,__ +packet,621.871000,__ +packet,621.955000,__ +packet,622.163000,__ +packet,622.080000,__ +packet,622.038000,__ +packet,622.122000,__ +packet,622.330000,__ +packet,622.247000,__ +packet,622.205000,__ +packet,622.288000,__ +packet,622.497000,__ +packet,622.413000,__ +packet,622.372000,__ +packet,622.455000,__ +packet,622.664000,__ +packet,622.580000,__ +packet,622.539000,__ +packet,622.622000,__ +packet,622.831000,__ +packet,622.747000,__ +packet,622.705000,__ +packet,622.789000,__ +packet,622.997000,__ +packet,622.914000,__ +packet,622.872000,__ +packet,622.956000,__ +packet,623.164000,__ +packet,623.081000,__ +packet,623.039000,__ +packet,623.123000,__ +packet,623.331000,__ +packet,623.248000,__ +packet,623.206000,__ +packet,623.289000,__ +packet,623.498000,__ +packet,623.414000,__ +packet,623.373000,__ +packet,623.456000,__ +packet,623.665000,__ +packet,623.581000,__ +packet,623.540000,__ +packet,623.623000,__ +packet,623.873000,__ +packet,623.748000,__ +packet,623.706000,__ +packet,623.790000,__ +packet,623.832000,__ +packet,623.998000,__ +packet,623.915000,__ +packet,623.957000,__ +packet,624.207000,__ +packet,624.082000,__ +packet,624.040000,__ +packet,624.124000,__ +packet,624.165000,__ +packet,624.332000,__ +packet,624.249000,__ +packet,624.290000,__ +packet,624.541000,__ +packet,624.415000,__ +packet,624.374000,__ +packet,624.457000,__ +packet,624.499000,__ +packet,624.666000,__ +packet,624.582000,__ +packet,624.624000,__ +packet,624.749000,__ +packet,624.707000,__ +packet,624.791000,__ +packet,624.916000,__ +packet,624.833000,__ +packet,624.874000,__ +packet,624.999000,__ +packet,624.958000,__ +packet,625.125000,__ +packet,625.041000,__ +packet,625.083000,__ +packet,625.208000,__ +packet,625.166000,__ +packet,625.333000,__ +packet,625.250000,__ +packet,625.291000,__ +packet,625.416000,__ +packet,625.375000,__ +packet,625.458000,__ +packet,625.500000,__ +packet,625.542000,__ +packet,625.583000,__ +packet,625.625000,K_ +packet,625.792000,__ +packet,625.708000,__ +packet,625.667000,__ +packet,625.750000,__ +packet,625.875000,__ +packet,625.834000,__ +packet,626.000000,__ +packet,625.917000,__ +packet,625.959000,__ +packet,626.209000,__ +packet,626.084000,__ +packet,626.042000,__ +packet,626.126000,__ +packet,626.167000,__ +packet,626.376000,__ +packet,626.292000,__ +packet,626.251000,__ +packet,626.334000,__ +packet,626.543000,__ +packet,626.459000,__ +packet,626.417000,__ +packet,626.501000,__ +packet,626.793000,__ +packet,626.668000,__ +packet,626.584000,__ +packet,626.626000,__ +packet,626.709000,__ +packet,626.751000,__ +packet,627.085000,__ +packet,626.918000,__ +packet,626.835000,__ +packet,626.876000,__ +packet,626.960000,__ +packet,627.001000,__ +packet,627.043000,__ +packet,627.460000,__ +packet,627.252000,__ +packet,627.127000,__ +packet,627.168000,__ +packet,627.210000,__ +packet,627.293000,__ +packet,627.335000,__ +packet,627.377000,__ +packet,627.418000,__ +packet,627.752000,__ +packet,627.585000,__ +packet,627.502000,__ +packet,627.544000,__ +packet,627.627000,__ +packet,627.669000,__ +packet,627.710000,__ +packet,627.877000,__ +packet,627.794000,__ +packet,627.836000,__ +packet,628.044000,__ +packet,627.961000,__ +packet,627.919000,__ +packet,628.002000,__ +packet,628.211000,__ +packet,628.128000,__ +packet,628.086000,__ +packet,628.169000,__ +packet,628.378000,__ +packet,628.294000,__ +packet,628.253000,__ +packet,628.336000,__ +packet,628.628000,__ +packet,628.503000,__ +packet,628.419000,__ +packet,628.461000,__ +packet,628.545000,__ +packet,628.586000,__ +packet,628.878000,__ +packet,628.753000,__ +packet,628.670000,__ +packet,628.711000,__ +packet,628.795000,__ +packet,628.837000,__ +packet,629.129000,__ +packet,629.003000,__ +packet,628.920000,__ +packet,628.962000,__ +packet,629.045000,__ +packet,629.087000,__ +packet,629.379000,__ +packet,629.254000,__ +packet,629.170000,__ +packet,629.212000,__ +packet,629.295000,__ +packet,629.337000,__ +packet,629.546000,__ +packet,629.462000,__ +packet,629.420000,__ +packet,629.504000,__ +packet,629.587000,K_ +packet,629.629000,__ +packet,629.963000,__ +packet,629.796000,__ +packet,629.671000,__ +packet,629.712000,__ +packet,629.754000,__ +packet,629.838000,__ +packet,629.879000,__ +packet,629.921000,__ +packet,630.296000,__ +packet,630.130000,__ +packet,630.004000,__ +packet,630.046000,__ +packet,630.088000,__ +packet,630.171000,__ +packet,630.213000,__ +packet,630.255000,__ +packet,630.630000,__ +packet,630.463000,__ +packet,630.338000,__ +packet,630.380000,__ +packet,630.421000,__ +packet,630.505000,__ +packet,630.547000,__ +packet,630.588000,__ +packet,630.964000,__ +packet,630.797000,__ +packet,630.672000,__ +packet,630.713000,__ +packet,630.755000,__ +packet,630.839000,__ +packet,630.880000,__ +packet,630.922000,__ +packet,631.005000,K_ +packet,631.047000,__ +packet,631.256000,__ +packet,631.131000,__ +packet,631.089000,__ +packet,631.172000,__ +packet,631.214000,__ +packet,631.339000,__ +packet,631.297000,__ +packet,631.548000,__ +packet,631.422000,__ +packet,631.381000,__ +packet,631.464000,__ +packet,631.506000,__ +packet,631.631000,__ +packet,631.589000,__ +packet,631.673000,__ +packet,631.923000,__ +packet,631.798000,__ +packet,631.714000,__ +packet,631.756000,__ +packet,631.840000,__ +packet,631.881000,__ +packet,632.132000,__ +packet,632.006000,__ +packet,631.965000,__ +packet,632.048000,__ +packet,632.090000,__ +packet,632.215000,__ +packet,632.173000,__ +packet,632.423000,__ +packet,632.298000,__ +packet,632.257000,__ +packet,632.340000,__ +packet,632.382000,__ +packet,632.507000,__ +packet,632.465000,__ +packet,632.549000,__ +packet,632.590000,K_ +packet,632.632000,__ +packet,632.715000,__ +packet,632.674000,__ +packet,632.882000,__ +packet,632.799000,__ +packet,632.757000,__ +packet,632.841000,__ +packet,633.049000,__ +packet,632.966000,__ +packet,632.924000,__ +packet,633.007000,__ +packet,633.216000,__ +packet,633.133000,__ +packet,633.091000,__ +packet,633.174000,__ +packet,633.341000,__ +packet,633.258000,__ +packet,633.299000,__ +packet,633.550000,__ +packet,633.424000,__ +packet,633.383000,__ +packet,633.466000,__ +packet,633.508000,__ +packet,633.716000,__ +packet,633.633000,__ +packet,633.591000,__ +packet,633.675000,__ +packet,633.842000,__ +packet,633.758000,__ +packet,633.800000,__ +packet,634.008000,__ +packet,633.925000,__ +packet,633.883000,__ +packet,633.967000,__ +packet,634.134000,__ +packet,634.050000,__ +packet,634.092000,__ +packet,634.175000,K_ +packet,634.217000,__ +packet,634.259000,__ +packet,634.467000,__ +packet,634.342000,__ +packet,634.300000,__ +packet,634.384000,__ +packet,634.425000,__ +packet,634.592000,__ +packet,634.509000,__ +packet,634.551000,__ +packet,634.717000,__ +packet,634.634000,__ +packet,634.676000,__ +packet,634.884000,__ +packet,634.801000,__ +packet,634.759000,__ +packet,634.843000,__ +packet,635.260000,__ +packet,635.051000,__ +packet,634.926000,__ +packet,634.968000,__ +packet,635.009000,__ +packet,635.093000,__ +packet,635.135000,__ +packet,635.176000,__ +packet,635.218000,__ +packet,635.635000,__ +packet,635.426000,__ +packet,635.301000,__ +packet,635.343000,__ +packet,635.385000,__ +packet,635.468000,__ +packet,635.510000,__ +packet,635.552000,__ +packet,635.593000,__ +packet,635.677000,K_ +packet,635.802000,__ +packet,635.718000,__ +packet,635.760000,__ +packet,635.969000,__ +packet,635.885000,__ +packet,635.844000,__ +packet,635.927000,__ +packet,636.094000,__ +packet,636.010000,__ +packet,636.052000,__ +packet,636.261000,__ +packet,636.177000,__ +packet,636.136000,__ +packet,636.219000,__ +packet,636.469000,__ +packet,636.344000,__ +packet,636.302000,__ +packet,636.386000,__ +packet,636.427000,__ +packet,636.594000,__ +packet,636.511000,__ +packet,636.553000,__ +packet,636.761000,__ +packet,636.678000,__ +packet,636.636000,__ +packet,636.719000,__ +packet,636.970000,__ +packet,636.845000,__ +packet,636.803000,__ +packet,636.886000,__ +packet,636.928000,__ +packet,637.137000,__ +packet,637.053000,__ +packet,637.011000,__ +packet,637.095000,__ +packet,637.262000,__ +packet,637.178000,__ +packet,637.220000,__ +packet,637.470000,__ +packet,637.345000,__ +packet,637.303000,__ +packet,637.387000,__ +packet,637.428000,__ +packet,637.637000,__ +packet,637.554000,__ +packet,637.512000,__ +packet,637.595000,__ +packet,637.762000,__ +packet,637.679000,__ +packet,637.720000,__ +packet,637.971000,__ +packet,637.846000,__ +packet,637.804000,__ +packet,637.887000,__ +packet,637.929000,__ +packet,638.096000,__ +packet,638.012000,__ +packet,638.054000,__ +packet,638.263000,__ +packet,638.179000,__ +packet,638.138000,__ +packet,638.221000,__ +packet,638.471000,__ +packet,638.346000,__ +packet,638.304000,__ +packet,638.388000,__ +packet,638.429000,__ +packet,638.555000,__ +packet,638.513000,__ +packet,638.596000,K_ +packet,638.763000,__ +packet,638.680000,__ +packet,638.638000,__ +packet,638.721000,__ +packet,638.972000,__ +packet,638.847000,__ +packet,638.805000,__ +packet,638.888000,__ +packet,638.930000,__ +packet,639.097000,__ +packet,639.013000,__ +packet,639.055000,__ +packet,639.264000,__ +packet,639.180000,__ +packet,639.139000,__ +packet,639.222000,__ +packet,639.472000,__ +packet,639.347000,__ +packet,639.305000,__ +packet,639.389000,__ +packet,639.430000,__ +packet,639.639000,__ +packet,639.556000,__ +packet,639.514000,__ +packet,639.597000,__ +packet,639.806000,__ +packet,639.722000,__ +packet,639.681000,__ +packet,639.764000,__ +packet,639.931000,__ +packet,639.848000,__ +packet,639.889000,__ +packet,640.014000,__ +packet,639.973000,__ +packet,640.181000,__ +packet,640.098000,__ +packet,640.056000,__ +packet,640.140000,__ +packet,640.223000,__ +packet,640.306000,__ +packet,640.265000,__ +packet,640.348000,__ +packet,640.515000,__ +packet,640.431000,__ +packet,640.390000,__ +packet,640.473000,__ +packet,640.723000,__ +packet,640.598000,__ +packet,640.557000,__ +packet,640.640000,__ +packet,640.682000,__ +packet,640.890000,__ +packet,640.807000,__ +packet,640.765000,__ +packet,640.849000,__ +packet,641.015000,__ +packet,640.932000,__ +packet,640.974000,__ +packet,641.182000,__ +packet,641.099000,__ +packet,641.057000,__ +packet,641.141000,__ +packet,641.391000,__ +packet,641.266000,__ +packet,641.224000,__ +packet,641.307000,__ +packet,641.349000,__ +packet,641.516000,__ +packet,641.432000,__ +packet,641.474000,__ +packet,641.724000,__ +packet,641.599000,__ +packet,641.558000,__ +packet,641.641000,__ +packet,641.683000,__ +packet,641.891000,__ +packet,641.808000,__ +packet,641.766000,__ +packet,641.850000,__ +packet,642.058000,__ +packet,641.975000,__ +packet,641.933000,__ +packet,642.016000,__ +packet,642.183000,__ +packet,642.100000,__ +packet,642.142000,__ +packet,642.350000,__ +packet,642.267000,__ +packet,642.225000,__ +packet,642.308000,__ +packet,642.559000,__ +packet,642.433000,__ +packet,642.392000,__ +packet,642.475000,__ +packet,642.517000,__ +packet,642.600000,__ +packet,642.642000,__ +packet,642.684000,K_ +packet,642.934000,__ +packet,642.809000,__ +packet,642.725000,__ +packet,642.767000,__ +packet,642.851000,__ +packet,642.892000,__ +packet,643.268000,__ +packet,643.101000,__ +packet,642.976000,__ +packet,643.017000,__ +packet,643.059000,__ +packet,643.143000,__ +packet,643.184000,__ +packet,643.226000,__ +packet,643.643000,__ +packet,643.434000,__ +packet,643.309000,__ +packet,643.351000,__ +packet,643.393000,__ +packet,643.476000,__ +packet,643.518000,__ +packet,643.560000,__ +packet,643.601000,__ +packet,643.810000,__ +packet,643.726000,__ +packet,643.685000,__ +packet,643.768000,__ +packet,643.977000,__ +packet,643.893000,__ +packet,643.852000,__ +packet,643.935000,__ +packet,644.144000,__ +packet,644.060000,__ +packet,644.018000,__ +packet,644.102000,__ +packet,644.310000,__ +packet,644.227000,__ +packet,644.185000,__ +packet,644.269000,__ +packet,644.435000,__ +packet,644.352000,__ +packet,644.394000,__ +packet,644.561000,__ +packet,644.477000,__ +packet,644.519000,__ +packet,644.727000,__ +packet,644.644000,__ +packet,644.602000,__ +packet,644.686000,__ +packet,644.894000,__ +packet,644.811000,__ +packet,644.769000,__ +packet,644.853000,__ +packet,644.978000,__ +packet,644.936000,__ +packet,645.228000,__ +packet,645.103000,__ +packet,645.019000,__ +packet,645.061000,__ +packet,645.145000,__ +packet,645.186000,__ +packet,645.436000,__ +packet,645.311000,__ +packet,645.270000,__ +packet,645.353000,__ +packet,645.395000,__ +packet,645.478000,K_ +packet,645.603000,__ +packet,645.520000,__ +packet,645.562000,__ +packet,645.770000,__ +packet,645.687000,__ +packet,645.645000,__ +packet,645.728000,__ +packet,645.895000,__ +packet,645.812000,__ +packet,645.854000,__ +packet,646.104000,__ +packet,645.979000,__ +packet,645.937000,__ +packet,646.020000,__ +packet,646.062000,__ +packet,646.271000,__ +packet,646.187000,__ +packet,646.146000,__ +packet,646.229000,__ +packet,646.396000,__ +packet,646.312000,__ +packet,646.354000,__ +packet,646.604000,__ +packet,646.479000,__ +packet,646.437000,__ +packet,646.521000,__ +packet,646.563000,__ +packet,646.771000,__ +packet,646.688000,__ +packet,646.646000,__ +packet,646.729000,__ +packet,646.938000,__ +packet,646.855000,__ +packet,646.813000,__ +packet,646.896000,__ +packet,646.980000,K_ +packet,647.147000,__ +packet,647.063000,__ +packet,647.021000,__ +packet,647.105000,__ +packet,647.397000,__ +packet,647.272000,__ +packet,647.188000,__ +packet,647.230000,__ +packet,647.313000,__ +packet,647.355000,__ +packet,647.772000,__ +packet,647.564000,__ +packet,647.438000,__ +packet,647.480000,__ +packet,647.522000,__ +packet,647.605000,__ +packet,647.647000,__ +packet,647.689000,__ +packet,647.730000,__ +packet,647.939000,__ +packet,647.856000,__ +packet,647.814000,__ +packet,647.897000,__ +packet,648.148000,__ +packet,648.022000,__ +packet,647.981000,__ +packet,648.064000,__ +packet,648.106000,__ +packet,648.273000,__ +packet,648.189000,__ +packet,648.231000,__ +packet,648.314000,__ +packet,648.356000,__ +packet,648.398000,__ +packet,648.439000,__ +packet,648.481000,__ +packet,648.523000,__ +packet,648.565000,__ +packet,648.606000,__ +packet,648.857000,__ +packet,648.731000,__ +packet,648.648000,__ +packet,648.690000,__ +packet,648.773000,__ +packet,648.815000,__ +packet,648.898000,__ +packet,648.940000,__ +packet,648.982000,__ +packet,649.023000,__ +packet,649.065000,__ +packet,649.149000,__ +packet,649.107000,__ +packet,649.274000,__ +packet,649.190000,__ +packet,649.232000,__ +packet,649.482000,__ +packet,649.357000,__ +packet,649.315000,__ +packet,649.399000,__ +packet,649.440000,__ +packet,649.607000,__ +packet,649.524000,__ +packet,649.566000,__ +packet,649.816000,__ +packet,649.691000,__ +packet,649.649000,__ +packet,649.732000,__ +packet,649.774000,__ +packet,649.983000,__ +packet,649.899000,__ +packet,649.858000,__ +packet,649.941000,__ +packet,650.066000,__ +packet,650.024000,__ +packet,650.108000,__ +packet,650.191000,__ +packet,650.150000,__ +packet,650.358000,__ +packet,650.275000,__ +packet,650.233000,__ +packet,650.316000,__ +packet,650.525000,__ +packet,650.441000,__ +packet,650.400000,__ +packet,650.483000,__ +packet,650.692000,__ +packet,650.608000,__ +packet,650.567000,__ +packet,650.650000,__ +packet,650.942000,__ +packet,650.817000,__ +packet,650.733000,__ +packet,650.775000,__ +packet,650.859000,__ +packet,650.900000,__ +packet,651.109000,__ +packet,651.025000,__ +packet,650.984000,__ +packet,651.067000,__ +packet,651.276000,__ +packet,651.192000,__ +packet,651.151000,__ +packet,651.234000,__ +packet,651.401000,__ +packet,651.317000,__ +packet,651.359000,__ +packet,651.442000,K_ +packet,651.484000,__ +packet,651.693000,__ +packet,651.568000,__ +packet,651.526000,__ +packet,651.609000,__ +packet,651.651000,__ +packet,651.901000,__ +packet,651.776000,__ +packet,651.734000,__ +packet,651.818000,__ +packet,651.860000,__ +packet,652.026000,__ +packet,651.943000,__ +packet,651.985000,__ +packet,652.193000,__ +packet,652.110000,__ +packet,652.068000,__ +packet,652.152000,__ +packet,652.360000,__ +packet,652.277000,__ +packet,652.235000,__ +packet,652.318000,__ +packet,652.527000,__ +packet,652.443000,__ +packet,652.402000,__ +packet,652.485000,__ +packet,652.735000,__ +packet,652.610000,__ +packet,652.569000,__ +packet,652.652000,__ +packet,652.694000,__ +packet,652.861000,__ +packet,652.777000,__ +packet,652.819000,__ +packet,653.027000,__ +packet,652.944000,__ +packet,652.902000,__ +packet,652.986000,__ +packet,653.194000,__ +packet,653.111000,__ +packet,653.069000,__ +packet,653.153000,__ +packet,653.361000,__ +packet,653.278000,__ +packet,653.236000,__ +packet,653.319000,__ +packet,653.611000,__ +packet,653.486000,__ +packet,653.403000,__ +packet,653.444000,__ +packet,653.528000,__ +packet,653.570000,__ +packet,653.778000,__ +packet,653.695000,__ +packet,653.653000,__ +packet,653.736000,__ +packet,654.070000,__ +packet,653.903000,__ +packet,653.820000,__ +packet,653.862000,__ +packet,653.945000,__ +packet,653.987000,__ +packet,654.028000,__ +packet,654.279000,__ +packet,654.154000,__ +packet,654.112000,__ +packet,654.195000,__ +packet,654.237000,__ +packet,654.487000,__ +packet,654.362000,__ +packet,654.320000,__ +packet,654.404000,__ +packet,654.445000,__ +packet,654.571000,__ +packet,654.529000,__ +packet,654.737000,__ +packet,654.654000,__ +packet,654.612000,__ +packet,654.696000,__ +packet,654.904000,__ +packet,654.821000,__ +packet,654.779000,__ +packet,654.863000,__ +packet,654.946000,K_ +packet,655.029000,__ +packet,654.988000,__ +packet,655.196000,__ +packet,655.113000,__ +packet,655.071000,__ +packet,655.155000,__ +packet,655.363000,__ +packet,655.280000,__ +packet,655.238000,__ +packet,655.321000,__ +packet,655.530000,__ +packet,655.446000,__ +packet,655.405000,__ +packet,655.488000,__ +packet,655.738000,__ +packet,655.613000,__ +packet,655.572000,__ +packet,655.655000,__ +packet,655.697000,__ +packet,655.864000,__ +packet,655.780000,__ +packet,655.822000,__ +packet,656.072000,__ +packet,655.947000,__ +packet,655.905000,__ +packet,655.989000,__ +packet,656.030000,__ +packet,656.322000,__ +packet,656.197000,__ +packet,656.114000,__ +packet,656.156000,__ +packet,656.239000,__ +packet,656.281000,__ +packet,656.573000,__ +packet,656.447000,__ +packet,656.364000,__ +packet,656.406000,__ +packet,656.489000,__ +packet,656.531000,__ +packet,656.698000,__ +packet,656.614000,__ +packet,656.656000,__ +packet,656.906000,__ +packet,656.781000,__ +packet,656.739000,__ +packet,656.823000,__ +packet,656.865000,__ +packet,657.031000,__ +packet,656.948000,__ +packet,656.990000,__ +packet,657.157000,__ +packet,657.073000,__ +packet,657.115000,__ +packet,657.323000,__ +packet,657.240000,__ +packet,657.198000,__ +packet,657.282000,__ +packet,657.407000,__ +packet,657.365000,__ +packet,657.448000,K_ +packet,657.615000,__ +packet,657.532000,__ +packet,657.490000,__ +packet,657.574000,__ +packet,657.782000,__ +packet,657.699000,__ +packet,657.657000,__ +packet,657.740000,__ +packet,657.991000,__ +packet,657.866000,__ +packet,657.824000,__ +packet,657.907000,__ +packet,657.949000,__ +packet,658.241000,__ +packet,658.116000,__ +packet,658.032000,__ +packet,658.074000,__ +packet,658.158000,__ +packet,658.199000,__ +packet,658.324000,__ +packet,658.283000,__ +packet,658.575000,__ +packet,658.449000,__ +packet,658.366000,__ +packet,658.408000,__ +packet,658.491000,__ +packet,658.533000,__ +packet,658.741000,__ +packet,658.658000,__ +packet,658.616000,__ +packet,658.700000,__ +packet,658.867000,__ +packet,658.783000,__ +packet,658.825000,__ +packet,659.117000,__ +packet,658.992000,__ +packet,658.908000,__ +packet,658.950000,__ +packet,659.033000,__ +packet,659.075000,__ +packet,659.284000,__ +packet,659.200000,__ +packet,659.159000,__ +packet,659.242000,__ +packet,659.492000,__ +packet,659.367000,__ +packet,659.325000,__ +packet,659.409000,__ +packet,659.450000,__ +packet,659.659000,__ +packet,659.576000,__ +packet,659.534000,__ +packet,659.617000,__ +packet,659.868000,__ +packet,659.742000,__ +packet,659.701000,__ +packet,659.784000,__ +packet,659.826000,__ +packet,660.118000,__ +packet,659.993000,__ +packet,659.909000,__ +packet,659.951000,__ +packet,660.034000,__ +packet,660.076000,__ +packet,660.160000,__ +packet,660.201000,K_ +packet,660.243000,__ +packet,660.493000,__ +packet,660.368000,__ +packet,660.285000,__ +packet,660.326000,__ +packet,660.410000,__ +packet,660.451000,__ +packet,660.743000,__ +packet,660.618000,__ +packet,660.535000,__ +packet,660.577000,__ +packet,660.660000,__ +packet,660.702000,__ +packet,660.910000,__ +packet,660.827000,__ +packet,660.785000,__ +packet,660.869000,__ +packet,660.994000,__ +packet,660.952000,__ +packet,661.119000,__ +packet,661.035000,__ +packet,661.077000,__ +packet,661.202000,__ +packet,661.161000,__ +packet,661.411000,__ +packet,661.286000,__ +packet,661.244000,__ +packet,661.327000,__ +packet,661.369000,__ +packet,661.494000,__ +packet,661.452000,__ +packet,661.619000,__ +packet,661.536000,__ +packet,661.578000,__ +packet,661.870000,__ +packet,661.744000,__ +packet,661.661000,__ +packet,661.703000,__ +packet,661.786000,__ +packet,661.828000,__ +packet,661.911000,__ +packet,661.953000,K_ +packet,662.078000,__ +packet,661.995000,__ +packet,662.036000,__ +packet,662.203000,__ +packet,662.120000,__ +packet,662.162000,__ +packet,662.412000,__ +packet,662.287000,__ +packet,662.245000,__ +packet,662.328000,__ +packet,662.370000,__ +packet,662.662000,__ +packet,662.537000,__ +packet,662.453000,__ +packet,662.495000,__ +packet,662.579000,__ +packet,662.620000,__ +packet,662.787000,__ +packet,662.704000,__ +packet,662.745000,__ +packet,662.871000,__ +packet,662.829000,__ +packet,663.037000,__ +packet,662.954000,__ +packet,662.912000,__ +packet,662.996000,__ +packet,663.204000,__ +packet,663.121000,__ +packet,663.079000,__ +packet,663.163000,__ +packet,663.413000,__ +packet,663.288000,__ +packet,663.246000,__ +packet,663.329000,__ +packet,663.371000,__ +packet,663.580000,__ +packet,663.496000,__ +packet,663.454000,__ +packet,663.538000,__ +packet,663.705000,__ +packet,663.621000,__ +packet,663.663000,__ +packet,663.913000,__ +packet,663.788000,__ +packet,663.746000,__ +packet,663.830000,__ +packet,663.872000,__ +packet,664.080000,__ +packet,663.997000,__ +packet,663.955000,__ +packet,664.038000,__ +packet,664.205000,__ +packet,664.122000,__ +packet,664.164000,__ +packet,664.414000,__ +packet,664.289000,__ +packet,664.247000,__ +packet,664.330000,__ +packet,664.372000,__ +packet,664.539000,__ +packet,664.455000,__ +packet,664.497000,__ +packet,664.706000,__ +packet,664.622000,__ +packet,664.581000,__ +packet,664.664000,__ +packet,664.914000,__ +packet,664.789000,__ +packet,664.747000,__ +packet,664.831000,__ +packet,664.873000,__ +packet,665.039000,__ +packet,664.956000,__ +packet,664.998000,__ +packet,665.206000,__ +packet,665.123000,__ +packet,665.081000,__ +packet,665.165000,__ +packet,665.415000,__ +packet,665.290000,__ +packet,665.248000,__ +packet,665.331000,__ +packet,665.373000,__ +packet,665.456000,K_ +packet,665.498000,__ +packet,665.707000,__ +packet,665.582000,__ +packet,665.540000,__ +packet,665.623000,__ +packet,665.665000,__ +packet,665.957000,__ +packet,665.832000,__ +packet,665.748000,__ +packet,665.790000,__ +packet,665.874000,__ +packet,665.915000,__ +packet,666.124000,__ +packet,666.040000,__ +packet,665.999000,__ +packet,666.082000,__ +packet,666.374000,__ +packet,666.249000,__ +packet,666.166000,__ +packet,666.207000,__ +packet,666.291000,__ +packet,666.332000,__ +packet,666.541000,__ +packet,666.457000,__ +packet,666.416000,__ +packet,666.499000,__ +packet,666.624000,__ +packet,666.583000,__ +packet,666.666000,__ +packet,666.708000,K_ +packet,666.833000,__ +packet,666.749000,__ +packet,666.791000,__ +packet,667.000000,__ +packet,666.916000,__ +packet,666.875000,__ +packet,666.958000,__ +packet,667.167000,__ +packet,667.083000,__ +packet,667.041000,__ +packet,667.125000,__ +packet,667.333000,__ +packet,667.250000,__ +packet,667.208000,__ +packet,667.292000,__ +packet,667.500000,__ +packet,667.417000,__ +packet,667.375000,__ +packet,667.458000,__ +packet,667.625000,__ +packet,667.542000,__ +packet,667.584000,__ +packet,667.792000,__ +packet,667.709000,__ +packet,667.667000,__ +packet,667.750000,__ +packet,667.834000,__ +packet,668.001000,__ +packet,667.917000,__ +packet,667.876000,__ +packet,667.959000,__ +packet,668.168000,__ +packet,668.084000,__ +packet,668.042000,__ +packet,668.126000,__ +packet,668.293000,__ +packet,668.209000,__ +packet,668.251000,__ +packet,668.501000,__ +packet,668.376000,__ +packet,668.334000,__ +packet,668.418000,__ +packet,668.459000,__ +packet,668.585000,__ +packet,668.543000,__ +packet,668.751000,__ +packet,668.668000,__ +packet,668.626000,__ +packet,668.710000,__ +packet,669.002000,__ +packet,668.877000,__ +packet,668.793000,__ +packet,668.835000,__ +packet,668.918000,__ +packet,668.960000,__ +packet,669.043000,K_ +packet,669.085000,__ +packet,669.419000,__ +packet,669.252000,__ +packet,669.127000,__ +packet,669.169000,__ +packet,669.210000,__ +packet,669.294000,__ +packet,669.335000,__ +packet,669.377000,__ +packet,669.502000,__ +packet,669.460000,__ +packet,669.752000,__ +packet,669.627000,__ +packet,669.544000,__ +packet,669.586000,__ +packet,669.669000,__ +packet,669.711000,__ +packet,669.836000,__ +packet,669.794000,__ +packet,670.128000,__ +packet,669.961000,__ +packet,669.878000,__ +packet,669.919000,__ +packet,670.003000,__ +packet,670.044000,__ +packet,670.086000,__ +packet,670.420000,__ +packet,670.253000,__ +packet,670.170000,__ +packet,670.211000,__ +packet,670.295000,__ +packet,670.336000,__ +packet,670.378000,__ +packet,670.795000,__ +packet,670.587000,__ +packet,670.461000,__ +packet,670.503000,__ +packet,670.545000,__ +packet,670.628000,__ +packet,670.670000,__ +packet,670.712000,__ +packet,670.753000,__ +packet,671.171000,__ +packet,670.962000,__ +packet,670.837000,__ +packet,670.879000,__ +packet,670.920000,__ +packet,671.004000,__ +packet,671.045000,__ +packet,671.087000,__ +packet,671.129000,__ +packet,671.421000,__ +packet,671.296000,__ +packet,671.212000,__ +packet,671.254000,__ +packet,671.337000,__ +packet,671.379000,__ +packet,671.504000,__ +packet,671.462000,__ +packet,671.880000,__ +packet,671.671000,__ +packet,671.546000,__ +packet,671.588000,__ +packet,671.629000,__ +packet,671.713000,__ +packet,671.754000,__ +packet,671.796000,__ +packet,671.838000,__ +packet,672.130000,__ +packet,672.005000,__ +packet,671.921000,__ +packet,671.963000,__ +packet,672.046000,__ +packet,672.088000,__ +packet,672.422000,__ +packet,672.255000,__ +packet,672.172000,__ +packet,672.213000,__ +packet,672.297000,__ +packet,672.338000,__ +packet,672.380000,__ +packet,672.463000,K_ +packet,672.547000,__ +packet,672.505000,__ +packet,672.630000,__ +packet,672.589000,__ +packet,672.714000,__ +packet,672.672000,__ +packet,672.922000,__ +packet,672.797000,__ +packet,672.755000,__ +packet,672.839000,__ +packet,672.881000,__ +packet,673.089000,__ +packet,673.006000,__ +packet,672.964000,__ +packet,673.047000,__ +packet,673.256000,__ +packet,673.173000,__ +packet,673.131000,__ +packet,673.214000,__ +packet,673.298000,__ +packet,673.506000,__ +packet,673.381000,__ +packet,673.339000,__ +packet,673.423000,__ +packet,673.464000,__ +packet,673.631000,__ +packet,673.548000,__ +packet,673.590000,__ +packet,673.798000,__ +packet,673.715000,__ +packet,673.673000,__ +packet,673.756000,__ +packet,674.007000,__ +packet,673.882000,__ +packet,673.840000,__ +packet,673.923000,__ +packet,673.965000,__ +packet,674.174000,__ +packet,674.090000,__ +packet,674.048000,__ +packet,674.132000,__ +packet,674.299000,__ +packet,674.215000,__ +packet,674.257000,__ +packet,674.424000,__ +packet,674.340000,__ +packet,674.382000,__ +packet,674.757000,__ +packet,674.591000,__ +packet,674.465000,__ +packet,674.507000,__ +packet,674.549000,__ +packet,674.632000,__ +packet,674.674000,__ +packet,674.716000,__ +packet,674.966000,__ +packet,674.841000,__ +packet,674.799000,__ +packet,674.883000,__ +packet,674.924000,__ +packet,675.133000,__ +packet,675.049000,__ +packet,675.008000,__ +packet,675.091000,__ +packet,675.300000,__ +packet,675.216000,__ +packet,675.175000,__ +packet,675.258000,__ +packet,675.508000,__ +packet,675.383000,__ +packet,675.341000,__ +packet,675.425000,__ +packet,675.466000,__ +packet,675.633000,__ +packet,675.550000,__ +packet,675.592000,__ +packet,675.925000,__ +packet,675.758000,__ +packet,675.675000,__ +packet,675.717000,__ +packet,675.800000,__ +packet,675.842000,__ +packet,675.884000,__ +packet,676.092000,__ +packet,676.009000,__ +packet,675.967000,__ +packet,676.050000,__ +packet,676.134000,__ +packet,676.176000,__ +packet,676.217000,K_ +packet,676.301000,__ +packet,676.259000,__ +packet,676.384000,__ +packet,676.342000,__ +packet,676.551000,__ +packet,676.467000,__ +packet,676.426000,__ +packet,676.509000,__ +packet,676.634000,__ +packet,676.593000,__ +packet,676.801000,__ +packet,676.718000,__ +packet,676.676000,__ +packet,676.759000,__ +packet,677.010000,__ +packet,676.885000,__ +packet,676.843000,__ +packet,676.926000,__ +packet,676.968000,__ +packet,677.177000,__ +packet,677.093000,__ +packet,677.051000,__ +packet,677.135000,__ +packet,677.302000,__ +packet,677.218000,__ +packet,677.260000,__ +packet,677.427000,__ +packet,677.343000,__ +packet,677.385000,__ +packet,677.594000,__ +packet,677.510000,__ +packet,677.468000,__ +packet,677.552000,__ +packet,677.719000,__ +packet,677.635000,__ +packet,677.677000,__ +packet,677.886000,__ +packet,677.802000,__ +packet,677.760000,__ +packet,677.844000,__ +packet,678.052000,__ +packet,677.969000,__ +packet,677.927000,__ +packet,678.011000,__ +packet,678.178000,__ +packet,678.094000,__ +packet,678.136000,__ +packet,678.344000,__ +packet,678.261000,__ +packet,678.219000,__ +packet,678.303000,__ +packet,678.511000,__ +packet,678.428000,__ +packet,678.386000,__ +packet,678.469000,__ +packet,678.595000,__ +packet,678.553000,__ +packet,678.678000,__ +packet,678.636000,__ +packet,678.887000,__ +packet,678.761000,__ +packet,678.720000,__ +packet,678.803000,__ +packet,678.845000,__ +packet,679.012000,__ +packet,678.928000,__ +packet,678.970000,__ +packet,679.137000,__ +packet,679.053000,__ +packet,679.095000,__ +packet,679.304000,__ +packet,679.220000,__ +packet,679.179000,__ +packet,679.262000,__ +packet,679.429000,__ +packet,679.345000,__ +packet,679.387000,__ +packet,679.637000,__ +packet,679.512000,__ +packet,679.470000,__ +packet,679.554000,__ +packet,679.596000,__ +packet,679.804000,__ +packet,679.721000,__ +packet,679.679000,__ +packet,679.762000,__ +packet,679.929000,__ +packet,679.846000,__ +packet,679.888000,__ +packet,680.138000,__ +packet,680.013000,__ +packet,679.971000,__ +packet,680.054000,__ +packet,680.096000,__ +packet,680.221000,__ +packet,680.180000,__ +packet,680.346000,__ +packet,680.263000,__ +packet,680.305000,__ +packet,680.513000,__ +packet,680.430000,__ +packet,680.388000,__ +packet,680.471000,__ +packet,680.763000,__ +packet,680.638000,__ +packet,680.555000,__ +packet,680.597000,__ +packet,680.680000,__ +packet,680.722000,__ +packet,680.847000,__ +packet,680.805000,__ +packet,681.014000,__ +packet,680.930000,__ +packet,680.889000,__ +packet,680.972000,__ +packet,681.181000,__ +packet,681.097000,__ +packet,681.055000,__ +packet,681.139000,__ +packet,681.222000,K_ +packet,681.389000,__ +packet,681.306000,__ +packet,681.264000,__ +packet,681.347000,__ +packet,681.556000,__ +packet,681.472000,__ +packet,681.431000,__ +packet,681.514000,__ +packet,681.723000,__ +packet,681.639000,__ +packet,681.598000,__ +packet,681.681000,__ +packet,681.890000,__ +packet,681.806000,__ +packet,681.764000,__ +packet,681.848000,__ +packet,682.015000,__ +packet,681.931000,__ +packet,681.973000,__ +packet,682.223000,__ +packet,682.098000,__ +packet,682.056000,__ +packet,682.140000,__ +packet,682.182000,__ +packet,682.390000,__ +packet,682.307000,__ +packet,682.265000,__ +packet,682.348000,__ +packet,682.515000,__ +packet,682.432000,__ +packet,682.473000,__ +packet,682.640000,__ +packet,682.557000,__ +packet,682.599000,__ +packet,682.849000,__ +packet,682.724000,__ +packet,682.682000,__ +packet,682.765000,__ +packet,682.807000,__ +packet,682.974000,__ +packet,682.891000,__ +packet,682.932000,__ +packet,683.141000,__ +packet,683.057000,__ +packet,683.016000,__ +packet,683.099000,__ +packet,683.349000,__ +packet,683.224000,__ +packet,683.183000,__ +packet,683.266000,__ +packet,683.308000,__ +packet,683.391000,__ +packet,683.600000,__ +packet,683.474000,__ +packet,683.433000,__ +packet,683.516000,__ +packet,683.558000,__ +packet,683.766000,__ +packet,683.683000,__ +packet,683.641000,__ +packet,683.725000,__ +packet,683.933000,__ +packet,683.850000,__ +packet,683.808000,__ +packet,683.892000,__ +packet,684.058000,__ +packet,683.975000,__ +packet,684.017000,__ +packet,684.267000,__ +packet,684.142000,__ +packet,684.100000,__ +packet,684.184000,__ +packet,684.225000,__ +packet,684.392000,__ +packet,684.309000,__ +packet,684.350000,__ +packet,684.601000,__ +packet,684.475000,__ +packet,684.434000,__ +packet,684.517000,__ +packet,684.559000,__ +packet,684.767000,__ +packet,684.684000,__ +packet,684.642000,__ +packet,684.726000,__ +packet,684.893000,__ +packet,684.809000,__ +packet,684.851000,__ +packet,684.934000,__ +packet,684.976000,K_ +packet,685.143000,__ +packet,685.059000,__ +packet,685.018000,__ +packet,685.101000,__ +packet,685.351000,__ +packet,685.226000,__ +packet,685.185000,__ +packet,685.268000,__ +packet,685.310000,__ +packet,685.476000,__ +packet,685.393000,__ +packet,685.435000,__ +packet,685.643000,__ +packet,685.560000,__ +packet,685.518000,__ +packet,685.602000,__ +packet,685.852000,__ +packet,685.727000,__ +packet,685.685000,__ +packet,685.768000,__ +packet,685.810000,__ +packet,686.019000,__ +packet,685.935000,__ +packet,685.894000,__ +packet,685.977000,__ +packet,686.186000,__ +packet,686.102000,__ +packet,686.060000,__ +packet,686.144000,__ +packet,686.352000,__ +packet,686.269000,__ +packet,686.227000,__ +packet,686.311000,__ +packet,686.477000,__ +packet,686.394000,__ +packet,686.436000,__ +packet,686.644000,__ +packet,686.561000,__ +packet,686.519000,__ +packet,686.603000,__ +packet,686.853000,__ +packet,686.728000,__ +packet,686.686000,__ +packet,686.769000,__ +packet,686.811000,__ +packet,687.020000,__ +packet,686.936000,__ +packet,686.895000,__ +packet,686.978000,__ +packet,687.103000,__ +packet,687.061000,__ +packet,687.353000,__ +packet,687.228000,__ +packet,687.145000,__ +packet,687.187000,__ +packet,687.270000,__ +packet,687.312000,__ +packet,687.478000,__ +packet,687.395000,__ +packet,687.437000,__ +packet,687.520000,K_ +packet,687.645000,__ +packet,687.562000,__ +packet,687.604000,__ +packet,687.896000,__ +packet,687.770000,__ +packet,687.687000,__ +packet,687.729000,__ +packet,687.812000,__ +packet,687.854000,__ +packet,688.271000,__ +packet,688.062000,__ +packet,687.937000,__ +packet,687.979000,__ +packet,688.021000,__ +packet,688.104000,__ +packet,688.146000,__ +packet,688.188000,__ +packet,688.229000,__ +packet,688.521000,__ +packet,688.396000,__ +packet,688.313000,__ +packet,688.354000,__ +packet,688.438000,__ +packet,688.479000,__ +packet,688.771000,__ +packet,688.646000,__ +packet,688.563000,__ +packet,688.605000,__ +packet,688.688000,__ +packet,688.730000,__ +packet,689.022000,__ +packet,688.897000,__ +packet,688.813000,__ +packet,688.855000,__ +packet,688.938000,__ +packet,688.980000,__ +packet,689.355000,__ +packet,689.189000,__ +packet,689.063000,__ +packet,689.105000,__ +packet,689.147000,__ +packet,689.230000,__ +packet,689.272000,__ +packet,689.314000,__ +packet,689.647000,__ +packet,689.480000,__ +packet,689.397000,__ +packet,689.439000,__ +packet,689.522000,__ +packet,689.564000,__ +packet,689.606000,__ +packet,689.856000,__ +packet,689.731000,__ +packet,689.689000,__ +packet,689.772000,__ +packet,689.814000,__ +packet,690.231000,__ +packet,690.023000,__ +packet,689.898000,__ +packet,689.939000,__ +packet,689.981000,__ +packet,690.064000,__ +packet,690.106000,__ +packet,690.148000,__ +packet,690.190000,__ +packet,690.273000,__ +packet,690.523000,__ +packet,690.398000,__ +packet,690.315000,__ +packet,690.356000,__ +packet,690.440000,__ +packet,690.481000,__ +packet,690.773000,__ +packet,690.648000,__ +packet,690.565000,__ +packet,690.607000,__ +packet,690.690000,__ +packet,690.732000,__ +packet,691.024000,__ +packet,690.899000,__ +packet,690.815000,__ +packet,690.857000,__ +packet,690.940000,__ +packet,690.982000,__ +packet,691.232000,__ +packet,691.107000,__ +packet,691.065000,__ +packet,691.149000,__ +packet,691.191000,__ +packet,691.274000,K_ +packet,691.441000,__ +packet,691.357000,__ +packet,691.316000,__ +packet,691.399000,__ +packet,691.649000,__ +packet,691.524000,__ +packet,691.482000,__ +packet,691.566000,__ +packet,691.608000,__ +packet,691.900000,__ +packet,691.774000,__ +packet,691.691000,__ +packet,691.733000,__ +packet,691.816000,__ +packet,691.858000,__ +packet,692.275000,__ +packet,692.066000,__ +packet,691.941000,__ +packet,691.983000,__ +packet,692.025000,__ +packet,692.108000,__ +packet,692.150000,__ +packet,692.192000,__ +packet,692.233000,__ +packet,692.400000,__ +packet,692.317000,__ +packet,692.358000,__ +packet,692.567000,__ +packet,692.483000,__ +packet,692.442000,__ +packet,692.525000,__ +packet,692.901000,__ +packet,692.734000,__ +packet,692.609000,__ +packet,692.650000,__ +packet,692.692000,__ +packet,692.775000,__ +packet,692.817000,__ +packet,692.859000,__ +packet,693.151000,__ +packet,693.026000,__ +packet,692.942000,__ +packet,692.984000,__ +packet,693.067000,__ +packet,693.109000,__ +packet,693.401000,__ +packet,693.276000,__ +packet,693.193000,__ +packet,693.234000,__ +packet,693.318000,__ +packet,693.359000,__ +packet,693.735000,__ +packet,693.568000,__ +packet,693.443000,__ +packet,693.484000,__ +packet,693.526000,__ +packet,693.610000,__ +packet,693.651000,__ +packet,693.693000,__ +packet,694.068000,__ +packet,693.902000,__ +packet,693.776000,__ +packet,693.818000,__ +packet,693.860000,__ +packet,693.943000,__ +packet,693.985000,__ +packet,694.027000,__ +packet,694.235000,__ +packet,694.152000,__ +packet,694.110000,__ +packet,694.194000,__ +packet,694.402000,__ +packet,694.319000,__ +packet,694.277000,__ +packet,694.360000,__ +packet,694.611000,__ +packet,694.485000,__ +packet,694.444000,__ +packet,694.527000,__ +packet,694.569000,__ +packet,694.652000,__ +packet,694.861000,__ +packet,694.736000,__ +packet,694.694000,__ +packet,694.777000,__ +packet,694.819000,__ +packet,694.903000,__ +packet,695.069000,__ +packet,694.986000,__ +packet,694.944000,__ +packet,695.028000,__ +packet,695.236000,__ +packet,695.153000,__ +packet,695.111000,__ +packet,695.195000,__ +packet,695.403000,__ +packet,695.320000,__ +packet,695.278000,__ +packet,695.361000,__ +packet,695.612000,__ +packet,695.486000,__ +packet,695.445000,__ +packet,695.528000,__ +packet,695.570000,__ +packet,695.737000,__ +packet,695.653000,__ +packet,695.695000,__ +packet,695.862000,__ +packet,695.778000,__ +packet,695.820000,__ +packet,695.987000,__ +packet,695.904000,__ +packet,695.945000,__ +packet,696.112000,__ +packet,696.029000,__ +packet,696.070000,__ +packet,696.154000,__ +packet,696.237000,__ +packet,696.196000,__ +packet,696.404000,__ +packet,696.321000,__ +packet,696.279000,__ +packet,696.362000,__ +packet,696.487000,__ +packet,696.446000,__ +packet,696.613000,__ +packet,696.529000,__ +packet,696.571000,__ +packet,696.821000,__ +packet,696.696000,__ +packet,696.654000,__ +packet,696.738000,__ +packet,696.779000,__ +packet,696.946000,__ +packet,696.863000,__ +packet,696.905000,__ +packet,697.113000,__ +packet,697.030000,__ +packet,696.988000,__ +packet,697.071000,__ +packet,697.197000,__ +packet,697.155000,__ +packet,697.363000,__ +packet,697.280000,__ +packet,697.238000,__ +packet,697.322000,__ +packet,697.530000,__ +packet,697.447000,__ +packet,697.405000,__ +packet,697.488000,__ +packet,697.697000,__ +packet,697.614000,__ +packet,697.572000,__ +packet,697.655000,__ +packet,697.864000,__ +packet,697.780000,__ +packet,697.739000,__ +packet,697.822000,__ +packet,697.989000,__ +packet,697.906000,__ +packet,697.947000,__ +packet,698.198000,__ +packet,698.072000,__ +packet,698.031000,__ +packet,698.114000,__ +packet,698.156000,__ +packet,698.239000,__ +packet,698.281000,__ +packet,698.323000,K_ +packet,698.364000,__ +packet,698.406000,__ +packet,698.448000,__ +packet,698.489000,__ +packet,698.531000,__ +packet,698.573000,__ +packet,698.781000,__ +packet,698.656000,__ +packet,698.615000,__ +packet,698.698000,__ +packet,698.740000,__ +packet,699.032000,__ +packet,698.907000,__ +packet,698.823000,__ +packet,698.865000,__ +packet,698.948000,__ +packet,698.990000,__ +packet,699.407000,__ +packet,699.199000,__ +packet,699.073000,__ +packet,699.115000,__ +packet,699.157000,__ +packet,699.240000,__ +packet,699.282000,__ +packet,699.324000,__ +packet,699.365000,__ +packet,699.449000,__ +packet,699.490000,K_ +packet,699.574000,__ +packet,699.532000,__ +packet,699.616000,__ +packet,699.657000,__ +packet,699.699000,__ +packet,699.741000,__ +packet,699.782000,__ +packet,699.824000,__ +packet,699.866000,__ +packet,699.908000,__ +packet,699.949000,__ +packet,699.991000,__ +packet,700.033000,__ +packet,700.074000,__ +packet,700.116000,__ +packet,700.158000,__ +packet,700.241000,__ +packet,700.200000,__ +packet,700.533000,__ +packet,700.366000,__ +packet,700.283000,__ +packet,700.325000,__ +packet,700.408000,__ +packet,700.450000,__ +packet,700.491000,__ +packet,700.700000,__ +packet,700.617000,__ +packet,700.575000,__ +packet,700.658000,__ +packet,700.909000,__ +packet,700.783000,__ +packet,700.742000,__ +packet,700.825000,__ +packet,700.867000,__ +packet,701.075000,__ +packet,700.992000,__ +packet,700.950000,__ +packet,701.034000,__ +packet,701.117000,__ +packet,701.159000,__ +packet,701.326000,__ +packet,701.242000,__ +packet,701.201000,__ +packet,701.284000,__ +packet,701.576000,__ +packet,701.451000,__ +packet,701.367000,__ +packet,701.409000,__ +packet,701.492000,__ +packet,701.534000,__ +packet,701.743000,__ +packet,701.659000,__ +packet,701.618000,__ +packet,701.701000,__ +packet,701.910000,__ +packet,701.826000,__ +packet,701.784000,__ +packet,701.868000,__ +packet,702.076000,__ +packet,701.993000,__ +packet,701.951000,__ +packet,702.035000,__ +packet,702.243000,__ +packet,702.160000,__ +packet,702.118000,__ +packet,702.202000,__ +packet,702.410000,__ +packet,702.327000,__ +packet,702.285000,__ +packet,702.368000,__ +packet,702.452000,__ +packet,702.493000,K_ +packet,702.702000,__ +packet,702.577000,__ +packet,702.535000,__ +packet,702.619000,__ +packet,702.660000,__ +packet,702.744000,__ +packet,702.785000,__ +packet,702.827000,__ +packet,702.869000,__ +packet,702.911000,__ +packet,702.952000,__ +packet,702.994000,__ +packet,703.036000,__ +packet,703.077000,__ +packet,703.119000,__ +packet,703.161000,__ +packet,703.203000,__ +packet,703.244000,__ +packet,703.286000,__ +packet,703.328000,__ +packet,703.369000,__ +packet,703.411000,__ +packet,703.453000,__ +packet,703.494000,__ +packet,703.536000,__ +packet,703.578000,__ +packet,703.620000,__ +packet,703.661000,__ +packet,703.703000,__ +packet,703.745000,__ +packet,703.786000,__ +packet,703.828000,__ +packet,703.870000,__ +packet,703.912000,__ +packet,703.953000,__ +packet,703.995000,__ +packet,704.037000,__ +packet,704.078000,__ +packet,704.120000,__ +packet,704.495000,__ +packet,704.287000,__ +packet,704.162000,__ +packet,704.204000,__ +packet,704.245000,__ +packet,704.329000,__ +packet,704.370000,__ +packet,704.412000,__ +packet,704.454000,__ +packet,704.871000,__ +packet,704.662000,__ +packet,704.537000,__ +packet,704.579000,__ +packet,704.621000,__ +packet,704.704000,__ +packet,704.746000,__ +packet,704.787000,__ +packet,704.829000,__ +packet,705.163000,__ +packet,704.996000,__ +packet,704.913000,__ +packet,704.954000,__ +packet,705.038000,__ +packet,705.079000,__ +packet,705.121000,__ +packet,705.538000,__ +packet,705.330000,__ +packet,705.205000,__ +packet,705.246000,__ +packet,705.288000,__ +packet,705.371000,__ +packet,705.413000,__ +packet,705.455000,__ +packet,705.496000,__ +packet,705.914000,__ +packet,705.705000,__ +packet,705.580000,__ +packet,705.622000,__ +packet,705.663000,__ +packet,705.747000,__ +packet,705.788000,__ +packet,705.830000,__ +packet,705.872000,__ +packet,706.289000,__ +packet,706.080000,__ +packet,705.955000,__ +packet,705.997000,__ +packet,706.039000,__ +packet,706.122000,__ +packet,706.164000,__ +packet,706.206000,__ +packet,706.247000,__ +stream,N/A +format,706.336000 diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_result.json b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_result.json new file mode 100644 index 000000000..72bf552cf --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_result.json @@ -0,0 +1 @@ +{"TotalDuration":7063360000,"KeyframeTicks":[0,103850000,133880000,145150000,165580000,186440000,196450000,209790000,314060000,326990000,396230000,407070000,432520000,476310000,523020000,535540000,550550000,631050000,646480000,665670000,686520000,732400000,772020000,796210000,856690000,887970000,903820000,934270000,983070000,1056060000,1087750000,1187850000,1222050000,1251250000,1265430000,1305470000,1333830000,1345510000,1356770000,1368450000,1427260000,1460630000,1500670000,1540710000,1584500000,1607020000,1627880000,1639550000,1672090000,1685020000,1789290000,1883130000,1909820000,1931510000,1996580000,2017020000,2035370000,2051220000,2065400000,2085000000,2109190000,2120870000,2168420000,2253920000,2295210000,2374460000,2478730000,2582160000,2607190000,2697280000,2783610000,2825320000,2899560000,2929590000,2979230000,3017600000,3048880000,3073490000,3117700000,3141050000,3158160000,3200700000,3279530000,3299960000,3312890000,3332910000,3369200000,3379630000,3438440000,3459290000,3490990000,3533110000,3562730000,3600260000,3624040000,3672000000,3722050000,3753330000,3771270000,3875540000,3957290000,4016100000,4100350000,4114530000,4124540000,4157900000,4180430000,4200450000,4222550000,4252160000,4295960000,4309720000,4328070000,4340590000,4371450000,4400230000,4426920000,4489490000,4512010000,4531190000,4569570000,4599600000,4635460000,4660070000,4680930000,4729310000,4757670000,4777690000,4808550000,4824400000,4851100000,4864440000,4905320000,4955370000,4970380000,5074650000,5095090000,5109270000,5186010000,5204370000,5227720000,5242740000,5266930000,5342000000,5433760000,5447110000,5470470000,5520520000,5550550000,5565140000,5611020000,5642300000,5668160000,5711120000,5743240000,5762420000,5797460000,5817480000,5839170000,5855850000,5870870000,5904230000,5969300000,6056880000,6104850000,6152400000,6256250000,6295870000,6310050000,6325900000,6341750000,6356770000,6385960000,6426840000,6454780000,6469800000,6514420000,6549460000,6574480000,6602010000,6619530000,6654560000,6667080000,6690430000,6724630000,6762170000,6812220000,6849760000,6875200000,6912740000,6983230000,6994900000,7024930000]} diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration.txt b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration.txt new file mode 100644 index 000000000..36c9e5772 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration.txt @@ -0,0 +1,2398 @@ +packet,0.000000,K_ +packet,0.375000,__ +packet,0.167000,__ +packet,0.042000,__ +packet,0.083000,__ +packet,0.125000,__ +packet,0.209000,__ +packet,0.250000,__ +packet,0.292000,__ +packet,0.334000,__ +packet,0.751000,__ +packet,0.542000,__ +packet,0.417000,__ +packet,0.459000,__ +packet,0.501000,__ +packet,0.584000,__ +packet,0.626000,__ +packet,0.667000,__ +packet,0.709000,__ +packet,0.959000,__ +packet,0.834000,__ +packet,0.792000,__ +packet,0.876000,__ +packet,0.918000,__ +packet,1.001000,__ +packet,1.168000,__ +packet,1.084000,__ +packet,1.043000,__ +packet,1.126000,__ +packet,1.335000,__ +packet,1.251000,__ +packet,1.210000,__ +packet,1.293000,__ +packet,1.460000,__ +packet,1.376000,__ +packet,1.418000,__ +packet,1.668000,__ +packet,1.543000,__ +packet,1.502000,__ +packet,1.585000,__ +packet,1.627000,__ +packet,1.793000,__ +packet,1.710000,__ +packet,1.752000,__ +packet,1.960000,__ +packet,1.877000,__ +packet,1.835000,__ +packet,1.919000,__ +packet,2.127000,__ +packet,2.044000,__ +packet,2.002000,__ +packet,2.085000,__ +packet,2.211000,__ +packet,2.169000,__ +packet,2.377000,__ +packet,2.294000,__ +packet,2.252000,__ +packet,2.336000,__ +packet,2.461000,__ +packet,2.419000,__ +packet,2.503000,__ +packet,2.544000,__ +packet,2.628000,__ +packet,2.586000,__ +packet,2.669000,__ +packet,2.711000,__ +packet,2.794000,__ +packet,2.753000,__ +packet,2.836000,__ +packet,2.878000,__ +packet,2.920000,__ +packet,2.961000,__ +packet,3.003000,__ +packet,3.045000,__ +packet,3.086000,__ +packet,3.128000,__ +packet,3.170000,__ +packet,3.212000,__ +packet,3.337000,__ +packet,3.253000,__ +packet,3.295000,__ +packet,3.420000,__ +packet,3.378000,__ +packet,3.462000,__ +packet,3.504000,__ +packet,3.545000,__ +packet,3.587000,__ +packet,3.629000,__ +packet,3.670000,__ +packet,3.712000,__ +packet,3.754000,__ +packet,3.837000,__ +packet,3.795000,__ +packet,3.879000,__ +packet,3.921000,__ +packet,3.962000,__ +packet,4.004000,__ +packet,4.046000,__ +packet,4.087000,__ +packet,4.129000,__ +packet,4.171000,__ +packet,4.213000,__ +packet,4.254000,__ +packet,4.338000,__ +packet,4.296000,__ +packet,4.421000,__ +packet,4.379000,__ +packet,4.505000,__ +packet,4.463000,__ +packet,4.588000,__ +packet,4.546000,__ +packet,4.630000,__ +packet,4.671000,__ +packet,4.713000,__ +packet,4.838000,__ +packet,4.755000,__ +packet,4.796000,__ +packet,4.880000,__ +packet,4.922000,__ +packet,4.963000,__ +packet,5.005000,__ +packet,5.047000,__ +packet,5.088000,__ +packet,5.130000,__ +packet,5.172000,__ +packet,5.214000,__ +packet,5.297000,__ +packet,5.255000,__ +packet,5.339000,__ +packet,5.380000,__ +packet,5.422000,__ +packet,5.464000,__ +packet,5.506000,__ +packet,5.547000,__ +packet,5.589000,__ +packet,5.631000,__ +packet,5.756000,__ +packet,5.672000,__ +packet,5.714000,__ +packet,5.923000,__ +packet,5.839000,__ +packet,5.797000,__ +packet,5.881000,__ +packet,6.089000,__ +packet,6.006000,__ +packet,5.964000,__ +packet,6.048000,__ +packet,6.256000,__ +packet,6.173000,__ +packet,6.131000,__ +packet,6.215000,__ +packet,6.381000,__ +packet,6.298000,__ +packet,6.340000,__ +packet,6.465000,__ +packet,6.423000,__ +packet,6.590000,__ +packet,6.507000,__ +packet,6.548000,__ +packet,6.632000,__ +packet,6.715000,__ +packet,6.673000,__ +packet,6.757000,__ +packet,6.882000,__ +packet,6.798000,__ +packet,6.840000,__ +packet,6.924000,__ +packet,6.965000,__ +packet,7.007000,__ +packet,7.049000,__ +packet,7.090000,__ +packet,7.132000,__ +packet,7.174000,__ +packet,7.216000,__ +packet,7.257000,__ +packet,7.299000,__ +packet,7.341000,__ +packet,7.382000,__ +packet,7.424000,__ +packet,7.466000,__ +packet,7.508000,__ +packet,7.549000,__ +packet,7.591000,__ +packet,7.633000,__ +packet,7.674000,__ +packet,7.716000,__ +packet,7.758000,__ +packet,7.799000,__ +packet,7.841000,__ +packet,7.966000,__ +packet,7.883000,__ +packet,7.925000,__ +packet,8.050000,__ +packet,8.008000,__ +packet,8.091000,__ +packet,8.133000,__ +packet,8.217000,__ +packet,8.175000,__ +packet,8.258000,__ +packet,8.300000,__ +packet,8.342000,__ +packet,8.383000,__ +packet,8.425000,__ +packet,8.467000,__ +packet,8.509000,__ +packet,8.550000,__ +packet,8.592000,__ +packet,8.634000,__ +packet,8.717000,__ +packet,8.675000,__ +packet,8.759000,__ +packet,8.800000,__ +packet,8.842000,__ +packet,8.884000,__ +packet,8.926000,__ +packet,8.967000,__ +packet,9.009000,__ +packet,9.051000,__ +packet,9.092000,__ +packet,9.134000,__ +packet,9.176000,__ +packet,9.218000,__ +packet,9.259000,__ +packet,9.301000,__ +packet,9.343000,__ +packet,9.384000,__ +packet,9.426000,__ +packet,9.468000,__ +packet,9.510000,__ +packet,9.551000,__ +packet,9.593000,__ +packet,9.635000,__ +packet,9.718000,__ +packet,9.676000,__ +packet,9.801000,__ +packet,9.760000,__ +packet,9.927000,__ +packet,9.843000,__ +packet,9.885000,__ +packet,10.010000,__ +packet,9.968000,__ +packet,10.052000,__ +packet,10.093000,__ +packet,10.177000,__ +packet,10.135000,__ +packet,10.260000,__ +packet,10.219000,__ +packet,10.302000,__ +packet,10.344000,__ +packet,10.385000,K_ +packet,10.427000,__ +packet,10.469000,__ +packet,10.511000,__ +packet,10.552000,__ +packet,10.594000,__ +packet,10.636000,__ +packet,10.677000,__ +packet,10.719000,__ +packet,10.761000,__ +packet,10.802000,__ +packet,10.844000,__ +packet,10.886000,__ +packet,10.928000,__ +packet,10.969000,__ +packet,11.011000,__ +packet,11.053000,__ +packet,11.094000,__ +packet,11.220000,__ +packet,11.136000,__ +packet,11.178000,__ +packet,11.345000,__ +packet,11.261000,__ +packet,11.303000,__ +packet,11.553000,__ +packet,11.428000,__ +packet,11.386000,__ +packet,11.470000,__ +packet,11.512000,__ +packet,11.637000,__ +packet,11.595000,__ +packet,11.762000,__ +packet,11.678000,__ +packet,11.720000,__ +packet,11.845000,__ +packet,11.803000,__ +packet,11.887000,__ +packet,11.929000,__ +packet,11.970000,__ +packet,12.012000,__ +packet,12.054000,__ +packet,12.095000,__ +packet,12.137000,__ +packet,12.179000,__ +packet,12.262000,__ +packet,12.221000,__ +packet,12.304000,__ +packet,12.346000,__ +packet,12.387000,__ +packet,12.429000,__ +packet,12.471000,__ +packet,12.513000,__ +packet,12.554000,__ +packet,12.596000,__ +packet,12.846000,__ +packet,12.721000,__ +packet,12.638000,__ +packet,12.679000,__ +packet,12.763000,__ +packet,12.804000,__ +packet,12.971000,__ +packet,12.888000,__ +packet,12.930000,__ +packet,13.180000,__ +packet,13.055000,__ +packet,13.013000,__ +packet,13.096000,__ +packet,13.138000,__ +packet,13.347000,__ +packet,13.263000,__ +packet,13.222000,__ +packet,13.305000,__ +packet,13.388000,K_ +packet,13.472000,__ +packet,13.430000,__ +packet,13.722000,__ +packet,13.597000,__ +packet,13.514000,__ +packet,13.555000,__ +packet,13.639000,__ +packet,13.680000,__ +packet,13.847000,__ +packet,13.764000,__ +packet,13.805000,__ +packet,13.889000,__ +packet,13.972000,__ +packet,13.931000,__ +packet,14.139000,__ +packet,14.056000,__ +packet,14.014000,__ +packet,14.097000,__ +packet,14.389000,__ +packet,14.264000,__ +packet,14.181000,__ +packet,14.223000,__ +packet,14.306000,__ +packet,14.348000,__ +packet,14.473000,__ +packet,14.431000,__ +packet,14.515000,K_ +packet,14.681000,__ +packet,14.598000,__ +packet,14.556000,__ +packet,14.640000,__ +packet,14.765000,__ +packet,14.723000,__ +packet,14.806000,__ +packet,14.848000,__ +packet,14.890000,__ +packet,14.932000,__ +packet,14.973000,__ +packet,15.015000,__ +packet,15.057000,__ +packet,15.098000,__ +packet,15.140000,__ +packet,15.224000,__ +packet,15.182000,__ +packet,15.265000,__ +packet,15.432000,__ +packet,15.349000,__ +packet,15.307000,__ +packet,15.390000,__ +packet,15.557000,__ +packet,15.474000,__ +packet,15.516000,__ +packet,15.599000,__ +packet,15.641000,__ +packet,15.724000,__ +packet,15.682000,__ +packet,15.766000,__ +packet,15.891000,__ +packet,15.807000,__ +packet,15.849000,__ +packet,15.974000,__ +packet,15.933000,__ +packet,16.016000,__ +packet,16.058000,__ +packet,16.099000,__ +packet,16.225000,__ +packet,16.141000,__ +packet,16.183000,__ +packet,16.266000,__ +packet,16.308000,__ +packet,16.350000,__ +packet,16.391000,__ +packet,16.433000,__ +packet,16.475000,__ +packet,16.517000,__ +packet,16.558000,K_ +packet,16.683000,__ +packet,16.600000,__ +packet,16.642000,__ +packet,16.808000,__ +packet,16.725000,__ +packet,16.767000,__ +packet,16.934000,__ +packet,16.850000,__ +packet,16.892000,__ +packet,17.017000,__ +packet,16.975000,__ +packet,17.100000,__ +packet,17.059000,__ +packet,17.267000,__ +packet,17.184000,__ +packet,17.142000,__ +packet,17.226000,__ +packet,17.434000,__ +packet,17.351000,__ +packet,17.309000,__ +packet,17.392000,__ +packet,17.518000,__ +packet,17.476000,__ +packet,17.684000,__ +packet,17.601000,__ +packet,17.559000,__ +packet,17.643000,__ +packet,17.851000,__ +packet,17.768000,__ +packet,17.726000,__ +packet,17.809000,__ +packet,18.018000,__ +packet,17.935000,__ +packet,17.893000,__ +packet,17.976000,__ +packet,18.101000,__ +packet,18.060000,__ +packet,18.185000,__ +packet,18.143000,__ +packet,18.352000,__ +packet,18.268000,__ +packet,18.227000,__ +packet,18.310000,__ +packet,18.519000,__ +packet,18.435000,__ +packet,18.393000,__ +packet,18.477000,__ +packet,18.602000,__ +packet,18.560000,__ +packet,18.644000,K_ +packet,18.769000,__ +packet,18.685000,__ +packet,18.727000,__ +packet,19.019000,__ +packet,18.894000,__ +packet,18.810000,__ +packet,18.852000,__ +packet,18.936000,__ +packet,18.977000,__ +packet,19.228000,__ +packet,19.102000,__ +packet,19.061000,__ +packet,19.144000,__ +packet,19.186000,__ +packet,19.603000,__ +packet,19.394000,__ +packet,19.269000,__ +packet,19.311000,__ +packet,19.353000,__ +packet,19.436000,__ +packet,19.478000,__ +packet,19.520000,__ +packet,19.561000,__ +packet,19.645000,K_ +packet,19.770000,__ +packet,19.686000,__ +packet,19.728000,__ +packet,19.937000,__ +packet,19.853000,__ +packet,19.811000,__ +packet,19.895000,__ +packet,20.187000,__ +packet,20.062000,__ +packet,19.978000,__ +packet,20.020000,__ +packet,20.103000,__ +packet,20.145000,__ +packet,20.354000,__ +packet,20.270000,__ +packet,20.229000,__ +packet,20.312000,__ +packet,20.646000,__ +packet,20.479000,__ +packet,20.395000,__ +packet,20.437000,__ +packet,20.521000,__ +packet,20.562000,__ +packet,20.604000,__ +packet,20.938000,__ +packet,20.771000,__ +packet,20.687000,__ +packet,20.729000,__ +packet,20.812000,__ +packet,20.854000,__ +packet,20.896000,__ +packet,20.979000,K_ +packet,21.146000,__ +packet,21.063000,__ +packet,21.021000,__ +packet,21.104000,__ +packet,21.271000,__ +packet,21.188000,__ +packet,21.230000,__ +packet,21.438000,__ +packet,21.355000,__ +packet,21.313000,__ +packet,21.396000,__ +packet,21.563000,__ +packet,21.480000,__ +packet,21.522000,__ +packet,21.688000,__ +packet,21.605000,__ +packet,21.647000,__ +packet,21.855000,__ +packet,21.772000,__ +packet,21.730000,__ +packet,21.813000,__ +packet,21.980000,__ +packet,21.897000,__ +packet,21.939000,__ +packet,22.189000,__ +packet,22.064000,__ +packet,22.022000,__ +packet,22.105000,__ +packet,22.147000,__ +packet,22.356000,__ +packet,22.272000,__ +packet,22.231000,__ +packet,22.314000,__ +packet,22.523000,__ +packet,22.439000,__ +packet,22.397000,__ +packet,22.481000,__ +packet,22.731000,__ +packet,22.606000,__ +packet,22.564000,__ +packet,22.648000,__ +packet,22.689000,__ +packet,22.981000,__ +packet,22.856000,__ +packet,22.773000,__ +packet,22.814000,__ +packet,22.898000,__ +packet,22.940000,__ +packet,23.232000,__ +packet,23.106000,__ +packet,23.023000,__ +packet,23.065000,__ +packet,23.148000,__ +packet,23.190000,__ +packet,23.315000,__ +packet,23.273000,__ +packet,23.398000,__ +packet,23.357000,__ +packet,23.565000,__ +packet,23.482000,__ +packet,23.440000,__ +packet,23.524000,__ +packet,23.607000,__ +packet,23.690000,__ +packet,23.649000,__ +packet,23.774000,__ +packet,23.732000,__ +packet,23.941000,__ +packet,23.857000,__ +packet,23.815000,__ +packet,23.899000,__ +packet,23.982000,__ +packet,24.024000,__ +packet,24.066000,__ +packet,24.107000,__ +packet,24.233000,__ +packet,24.149000,__ +packet,24.191000,__ +packet,24.358000,__ +packet,24.274000,__ +packet,24.316000,__ +packet,24.525000,__ +packet,24.441000,__ +packet,24.399000,__ +packet,24.483000,__ +packet,24.650000,__ +packet,24.566000,__ +packet,24.608000,__ +packet,24.775000,__ +packet,24.691000,__ +packet,24.733000,__ +packet,24.983000,__ +packet,24.858000,__ +packet,24.816000,__ +packet,24.900000,__ +packet,24.942000,__ +packet,25.359000,__ +packet,25.150000,__ +packet,25.025000,__ +packet,25.067000,__ +packet,25.108000,__ +packet,25.192000,__ +packet,25.234000,__ +packet,25.275000,__ +packet,25.317000,__ +packet,25.651000,__ +packet,25.484000,__ +packet,25.400000,__ +packet,25.442000,__ +packet,25.526000,__ +packet,25.567000,__ +packet,25.609000,__ +packet,25.734000,__ +packet,25.692000,__ +packet,25.776000,__ +packet,25.943000,__ +packet,25.859000,__ +packet,25.817000,__ +packet,25.901000,__ +packet,26.068000,__ +packet,25.984000,__ +packet,26.026000,__ +packet,26.443000,__ +packet,26.235000,__ +packet,26.109000,__ +packet,26.151000,__ +packet,26.193000,__ +packet,26.276000,__ +packet,26.318000,__ +packet,26.360000,__ +packet,26.401000,__ +packet,26.693000,__ +packet,26.568000,__ +packet,26.485000,__ +packet,26.527000,__ +packet,26.610000,__ +packet,26.652000,__ +packet,26.944000,__ +packet,26.818000,__ +packet,26.735000,__ +packet,26.777000,__ +packet,26.860000,__ +packet,26.902000,__ +packet,27.319000,__ +packet,27.110000,__ +packet,26.985000,__ +packet,27.027000,__ +packet,27.069000,__ +packet,27.152000,__ +packet,27.194000,__ +packet,27.236000,__ +packet,27.277000,__ +packet,27.694000,__ +packet,27.486000,__ +packet,27.361000,__ +packet,27.402000,__ +packet,27.444000,__ +packet,27.528000,__ +packet,27.569000,__ +packet,27.611000,__ +packet,27.653000,__ +packet,27.736000,__ +packet,28.111000,__ +packet,27.903000,__ +packet,27.778000,__ +packet,27.819000,__ +packet,27.861000,__ +packet,27.945000,__ +packet,27.986000,__ +packet,28.028000,__ +packet,28.070000,__ +packet,28.445000,__ +packet,28.278000,__ +packet,28.153000,__ +packet,28.195000,__ +packet,28.237000,__ +packet,28.320000,__ +packet,28.362000,__ +packet,28.403000,__ +packet,28.695000,__ +packet,28.570000,__ +packet,28.487000,__ +packet,28.529000,__ +packet,28.612000,__ +packet,28.654000,__ +packet,28.987000,__ +packet,28.820000,__ +packet,28.737000,__ +packet,28.779000,__ +packet,28.862000,__ +packet,28.904000,__ +packet,28.946000,__ +packet,29.363000,__ +packet,29.154000,__ +packet,29.029000,__ +packet,29.071000,__ +packet,29.112000,__ +packet,29.196000,__ +packet,29.238000,__ +packet,29.279000,__ +packet,29.321000,__ +packet,29.738000,__ +packet,29.530000,__ +packet,29.404000,__ +packet,29.446000,__ +packet,29.488000,__ +packet,29.571000,__ +packet,29.613000,__ +packet,29.655000,__ +packet,29.696000,__ +packet,29.863000,__ +packet,29.780000,__ +packet,29.821000,__ +packet,30.113000,__ +packet,29.988000,__ +packet,29.905000,__ +packet,29.947000,__ +packet,30.030000,__ +packet,30.072000,__ +packet,30.280000,__ +packet,30.197000,__ +packet,30.155000,__ +packet,30.239000,__ +packet,30.572000,__ +packet,30.405000,__ +packet,30.322000,__ +packet,30.364000,__ +packet,30.447000,__ +packet,30.489000,__ +packet,30.531000,__ +packet,30.656000,__ +packet,30.614000,__ +packet,30.989000,__ +packet,30.822000,__ +packet,30.697000,__ +packet,30.739000,__ +packet,30.781000,__ +packet,30.864000,__ +packet,30.906000,__ +packet,30.948000,__ +packet,31.073000,__ +packet,31.031000,__ +packet,31.365000,__ +packet,31.198000,__ +packet,31.114000,__ +packet,31.156000,__ +packet,31.240000,__ +packet,31.281000,__ +packet,31.323000,__ +packet,31.406000,K_ +packet,31.573000,__ +packet,31.490000,__ +packet,31.448000,__ +packet,31.532000,__ +packet,31.698000,__ +packet,31.615000,__ +packet,31.657000,__ +packet,32.074000,__ +packet,31.865000,__ +packet,31.740000,__ +packet,31.782000,__ +packet,31.823000,__ +packet,31.907000,__ +packet,31.949000,__ +packet,31.990000,__ +packet,32.032000,__ +packet,32.449000,__ +packet,32.241000,__ +packet,32.115000,__ +packet,32.157000,__ +packet,32.199000,__ +packet,32.282000,__ +packet,32.324000,__ +packet,32.366000,__ +packet,32.407000,__ +packet,32.658000,__ +packet,32.533000,__ +packet,32.491000,__ +packet,32.574000,__ +packet,32.616000,__ +packet,32.699000,K_ +packet,32.908000,__ +packet,32.783000,__ +packet,32.741000,__ +packet,32.824000,__ +packet,32.866000,__ +packet,33.116000,__ +packet,32.991000,__ +packet,32.950000,__ +packet,33.033000,__ +packet,33.075000,__ +packet,33.367000,__ +packet,33.242000,__ +packet,33.158000,__ +packet,33.200000,__ +packet,33.283000,__ +packet,33.325000,__ +packet,33.617000,__ +packet,33.492000,__ +packet,33.408000,__ +packet,33.450000,__ +packet,33.534000,__ +packet,33.575000,__ +packet,33.825000,__ +packet,33.700000,__ +packet,33.659000,__ +packet,33.742000,__ +packet,33.784000,__ +packet,34.117000,__ +packet,33.951000,__ +packet,33.867000,__ +packet,33.909000,__ +packet,33.992000,__ +packet,34.034000,__ +packet,34.076000,__ +packet,34.368000,__ +packet,34.243000,__ +packet,34.159000,__ +packet,34.201000,__ +packet,34.284000,__ +packet,34.326000,__ +packet,34.576000,__ +packet,34.451000,__ +packet,34.409000,__ +packet,34.493000,__ +packet,34.535000,__ +packet,34.701000,__ +packet,34.618000,__ +packet,34.660000,__ +packet,34.952000,__ +packet,34.826000,__ +packet,34.743000,__ +packet,34.785000,__ +packet,34.868000,__ +packet,34.910000,__ +packet,35.118000,__ +packet,35.035000,__ +packet,34.993000,__ +packet,35.077000,__ +packet,35.410000,__ +packet,35.244000,__ +packet,35.160000,__ +packet,35.202000,__ +packet,35.285000,__ +packet,35.327000,__ +packet,35.369000,__ +packet,35.494000,__ +packet,35.452000,__ +packet,35.744000,__ +packet,35.619000,__ +packet,35.536000,__ +packet,35.577000,__ +packet,35.661000,__ +packet,35.702000,__ +packet,35.911000,__ +packet,35.827000,__ +packet,35.786000,__ +packet,35.869000,__ +packet,36.078000,__ +packet,35.994000,__ +packet,35.953000,__ +packet,36.036000,__ +packet,36.245000,__ +packet,36.161000,__ +packet,36.119000,__ +packet,36.203000,__ +packet,36.370000,__ +packet,36.286000,__ +packet,36.328000,__ +packet,36.537000,__ +packet,36.453000,__ +packet,36.411000,__ +packet,36.495000,__ +packet,36.620000,__ +packet,36.578000,__ +packet,36.662000,__ +packet,36.787000,__ +packet,36.703000,__ +packet,36.745000,__ +packet,36.995000,__ +packet,36.870000,__ +packet,36.828000,__ +packet,36.912000,__ +packet,36.954000,__ +packet,37.162000,__ +packet,37.079000,__ +packet,37.037000,__ +packet,37.120000,__ +packet,37.329000,__ +packet,37.246000,__ +packet,37.204000,__ +packet,37.287000,__ +packet,37.454000,__ +packet,37.371000,__ +packet,37.412000,__ +packet,37.579000,__ +packet,37.496000,__ +packet,37.538000,__ +packet,37.704000,__ +packet,37.621000,__ +packet,37.663000,__ +packet,37.955000,__ +packet,37.829000,__ +packet,37.746000,__ +packet,37.788000,__ +packet,37.871000,__ +packet,37.913000,__ +packet,38.163000,__ +packet,38.038000,__ +packet,37.996000,__ +packet,38.080000,__ +packet,38.121000,__ +packet,38.330000,__ +packet,38.247000,__ +packet,38.205000,__ +packet,38.288000,__ +packet,38.455000,__ +packet,38.372000,__ +packet,38.413000,__ +packet,38.539000,__ +packet,38.497000,__ +packet,38.705000,__ +packet,38.622000,__ +packet,38.580000,__ +packet,38.664000,__ +packet,38.914000,__ +packet,38.789000,__ +packet,38.747000,__ +packet,38.830000,__ +packet,38.872000,__ +packet,39.081000,__ +packet,38.997000,__ +packet,38.956000,__ +packet,39.039000,__ +packet,39.414000,__ +packet,39.248000,__ +packet,39.122000,__ +packet,39.164000,__ +packet,39.206000,__ +packet,39.289000,__ +packet,39.331000,__ +packet,39.373000,__ +packet,39.540000,__ +packet,39.456000,__ +packet,39.498000,__ +packet,39.581000,__ +packet,39.623000,K_ +packet,39.831000,__ +packet,39.706000,__ +packet,39.665000,__ +packet,39.748000,__ +packet,39.790000,__ +packet,39.998000,__ +packet,39.915000,__ +packet,39.873000,__ +packet,39.957000,__ +packet,40.165000,__ +packet,40.082000,__ +packet,40.040000,__ +packet,40.123000,__ +packet,40.290000,__ +packet,40.207000,__ +packet,40.249000,__ +packet,40.499000,__ +packet,40.374000,__ +packet,40.332000,__ +packet,40.415000,__ +packet,40.457000,__ +packet,40.666000,__ +packet,40.582000,__ +packet,40.541000,__ +packet,40.624000,__ +packet,40.707000,K_ +packet,40.832000,__ +packet,40.749000,__ +packet,40.791000,__ +packet,40.999000,__ +packet,40.916000,__ +packet,40.874000,__ +packet,40.958000,__ +packet,41.166000,__ +packet,41.083000,__ +packet,41.041000,__ +packet,41.124000,__ +packet,41.333000,__ +packet,41.250000,__ +packet,41.208000,__ +packet,41.291000,__ +packet,41.500000,__ +packet,41.416000,__ +packet,41.375000,__ +packet,41.458000,__ +packet,41.542000,__ +packet,41.583000,__ +packet,41.792000,__ +packet,41.667000,__ +packet,41.625000,__ +packet,41.708000,__ +packet,41.750000,__ +packet,41.917000,__ +packet,41.833000,__ +packet,41.875000,__ +packet,42.125000,__ +packet,42.000000,__ +packet,41.959000,__ +packet,42.042000,__ +packet,42.084000,__ +packet,42.251000,__ +packet,42.167000,__ +packet,42.209000,__ +packet,42.459000,__ +packet,42.334000,__ +packet,42.292000,__ +packet,42.376000,__ +packet,42.417000,__ +packet,42.626000,__ +packet,42.543000,__ +packet,42.501000,__ +packet,42.584000,__ +packet,42.751000,__ +packet,42.668000,__ +packet,42.709000,__ +packet,42.918000,__ +packet,42.834000,__ +packet,42.793000,__ +packet,42.876000,__ +packet,43.126000,__ +packet,43.001000,__ +packet,42.960000,__ +packet,43.043000,__ +packet,43.085000,__ +packet,43.210000,__ +packet,43.168000,__ +packet,43.252000,K_ +packet,43.418000,__ +packet,43.335000,__ +packet,43.293000,__ +packet,43.377000,__ +packet,43.544000,__ +packet,43.460000,__ +packet,43.502000,__ +packet,43.710000,__ +packet,43.627000,__ +packet,43.585000,__ +packet,43.669000,__ +packet,43.835000,__ +packet,43.752000,__ +packet,43.794000,__ +packet,44.002000,__ +packet,43.919000,__ +packet,43.877000,__ +packet,43.961000,__ +packet,44.169000,__ +packet,44.086000,__ +packet,44.044000,__ +packet,44.127000,__ +packet,44.336000,__ +packet,44.253000,__ +packet,44.211000,__ +packet,44.294000,__ +packet,44.503000,__ +packet,44.419000,__ +packet,44.378000,__ +packet,44.461000,__ +packet,44.670000,__ +packet,44.586000,__ +packet,44.545000,__ +packet,44.628000,__ +packet,44.878000,__ +packet,44.753000,__ +packet,44.711000,__ +packet,44.795000,__ +packet,44.836000,__ +packet,44.962000,__ +packet,44.920000,__ +packet,45.128000,__ +packet,45.045000,__ +packet,45.003000,__ +packet,45.087000,__ +packet,45.212000,__ +packet,45.170000,__ +packet,45.295000,__ +packet,45.254000,__ +packet,45.379000,__ +packet,45.337000,__ +packet,45.546000,__ +packet,45.462000,__ +packet,45.420000,__ +packet,45.504000,__ +packet,45.671000,__ +packet,45.587000,__ +packet,45.629000,__ +packet,45.837000,__ +packet,45.754000,__ +packet,45.712000,__ +packet,45.796000,__ +packet,46.004000,__ +packet,45.921000,__ +packet,45.879000,__ +packet,45.963000,__ +packet,46.088000,__ +packet,46.046000,__ +packet,46.171000,__ +packet,46.129000,__ +packet,46.296000,__ +packet,46.213000,__ +packet,46.255000,__ +packet,46.380000,__ +packet,46.338000,__ +packet,46.421000,__ +packet,46.505000,__ +packet,46.463000,__ +packet,46.672000,__ +packet,46.588000,__ +packet,46.547000,__ +packet,46.630000,__ +packet,46.880000,__ +packet,46.755000,__ +packet,46.713000,__ +packet,46.797000,__ +packet,46.838000,__ +packet,47.005000,__ +packet,46.922000,__ +packet,46.964000,__ +packet,47.089000,__ +packet,47.047000,__ +packet,47.297000,__ +packet,47.172000,__ +packet,47.130000,__ +packet,47.214000,__ +packet,47.256000,__ +packet,47.464000,__ +packet,47.381000,__ +packet,47.339000,__ +packet,47.422000,__ +packet,47.589000,__ +packet,47.506000,__ +packet,47.548000,__ +packet,47.631000,K_ +packet,47.798000,__ +packet,47.714000,__ +packet,47.673000,__ +packet,47.756000,__ +packet,48.006000,__ +packet,47.881000,__ +packet,47.839000,__ +packet,47.923000,__ +packet,47.965000,__ +packet,48.131000,__ +packet,48.048000,__ +packet,48.090000,__ +packet,48.340000,__ +packet,48.215000,__ +packet,48.173000,__ +packet,48.257000,__ +packet,48.298000,__ +packet,48.507000,__ +packet,48.423000,__ +packet,48.382000,__ +packet,48.465000,__ +packet,48.632000,__ +packet,48.549000,__ +packet,48.590000,__ +packet,48.715000,__ +packet,48.674000,__ +packet,48.882000,__ +packet,48.799000,__ +packet,48.757000,__ +packet,48.840000,__ +packet,49.049000,__ +packet,48.966000,__ +packet,48.924000,__ +packet,49.007000,__ +packet,49.216000,__ +packet,49.132000,__ +packet,49.091000,__ +packet,49.174000,__ +packet,49.424000,__ +packet,49.299000,__ +packet,49.258000,__ +packet,49.341000,__ +packet,49.383000,__ +packet,49.591000,__ +packet,49.508000,__ +packet,49.466000,__ +packet,49.550000,__ +packet,49.675000,__ +packet,49.633000,__ +packet,49.841000,__ +packet,49.758000,__ +packet,49.716000,__ +packet,49.800000,__ +packet,50.008000,__ +packet,49.925000,__ +packet,49.883000,__ +packet,49.967000,__ +packet,50.133000,__ +packet,50.050000,__ +packet,50.092000,__ +packet,50.217000,__ +packet,50.175000,__ +packet,50.425000,__ +packet,50.300000,__ +packet,50.259000,__ +packet,50.342000,__ +packet,50.384000,__ +packet,50.592000,__ +packet,50.509000,__ +packet,50.467000,__ +packet,50.551000,__ +packet,50.717000,__ +packet,50.634000,__ +packet,50.676000,__ +packet,50.884000,__ +packet,50.801000,__ +packet,50.759000,__ +packet,50.842000,__ +packet,51.051000,__ +packet,50.968000,__ +packet,50.926000,__ +packet,51.009000,__ +packet,51.218000,__ +packet,51.134000,__ +packet,51.093000,__ +packet,51.176000,__ +packet,51.426000,__ +packet,51.301000,__ +packet,51.260000,__ +packet,51.343000,__ +packet,51.385000,__ +packet,51.593000,__ +packet,51.510000,__ +packet,51.468000,__ +packet,51.552000,__ +packet,51.760000,__ +packet,51.677000,__ +packet,51.635000,__ +packet,51.718000,__ +packet,51.885000,__ +packet,51.802000,__ +packet,51.843000,__ +packet,52.094000,__ +packet,51.969000,__ +packet,51.927000,__ +packet,52.010000,__ +packet,52.052000,__ +packet,52.261000,__ +packet,52.177000,__ +packet,52.135000,__ +packet,52.219000,__ +packet,52.302000,K_ +packet,52.469000,__ +packet,52.386000,__ +packet,52.344000,__ +packet,52.427000,__ +packet,52.594000,__ +packet,52.511000,__ +packet,52.553000,__ +packet,52.761000,__ +packet,52.678000,__ +packet,52.636000,__ +packet,52.719000,__ +packet,52.844000,__ +packet,52.803000,__ +packet,52.928000,__ +packet,52.886000,__ +packet,53.095000,__ +packet,53.011000,__ +packet,52.970000,__ +packet,53.053000,__ +packet,53.262000,__ +packet,53.178000,__ +packet,53.136000,__ +packet,53.220000,__ +packet,53.470000,__ +packet,53.345000,__ +packet,53.303000,__ +packet,53.387000,__ +packet,53.428000,__ +packet,53.512000,__ +packet,53.554000,K_ +packet,53.637000,__ +packet,53.595000,__ +packet,53.887000,__ +packet,53.762000,__ +packet,53.679000,__ +packet,53.720000,__ +packet,53.804000,__ +packet,53.845000,__ +packet,54.096000,__ +packet,53.971000,__ +packet,53.929000,__ +packet,54.012000,__ +packet,54.054000,__ +packet,54.346000,__ +packet,54.221000,__ +packet,54.137000,__ +packet,54.179000,__ +packet,54.263000,__ +packet,54.304000,__ +packet,54.513000,__ +packet,54.429000,__ +packet,54.388000,__ +packet,54.471000,__ +packet,54.763000,__ +packet,54.638000,__ +packet,54.555000,__ +packet,54.596000,__ +packet,54.680000,__ +packet,54.721000,__ +packet,54.930000,__ +packet,54.846000,__ +packet,54.805000,__ +packet,54.888000,__ +packet,55.013000,__ +packet,54.972000,__ +packet,55.055000,K_ +packet,55.180000,__ +packet,55.097000,__ +packet,55.138000,__ +packet,55.305000,__ +packet,55.222000,__ +packet,55.264000,__ +packet,55.430000,__ +packet,55.347000,__ +packet,55.389000,__ +packet,55.556000,__ +packet,55.472000,__ +packet,55.514000,__ +packet,55.722000,__ +packet,55.639000,__ +packet,55.597000,__ +packet,55.681000,__ +packet,56.098000,__ +packet,55.889000,__ +packet,55.764000,__ +packet,55.806000,__ +packet,55.847000,__ +packet,55.931000,__ +packet,55.973000,__ +packet,56.014000,__ +packet,56.056000,__ +packet,56.265000,__ +packet,56.181000,__ +packet,56.139000,__ +packet,56.223000,__ +packet,56.473000,__ +packet,56.348000,__ +packet,56.306000,__ +packet,56.390000,__ +packet,56.431000,__ +packet,56.640000,__ +packet,56.557000,__ +packet,56.515000,__ +packet,56.598000,__ +packet,56.682000,__ +packet,56.807000,__ +packet,56.723000,__ +packet,56.765000,__ +packet,56.848000,__ +packet,56.890000,__ +packet,56.932000,__ +packet,57.057000,__ +packet,56.974000,__ +packet,57.015000,__ +packet,57.099000,__ +packet,57.224000,__ +packet,57.140000,__ +packet,57.182000,__ +packet,57.266000,__ +packet,57.307000,__ +packet,57.474000,__ +packet,57.391000,__ +packet,57.349000,__ +packet,57.432000,__ +packet,57.516000,__ +packet,57.641000,__ +packet,57.558000,__ +packet,57.599000,__ +packet,57.683000,__ +packet,57.724000,__ +packet,57.933000,__ +packet,57.808000,__ +packet,57.766000,__ +packet,57.849000,__ +packet,57.891000,__ +packet,58.058000,__ +packet,57.975000,__ +packet,58.016000,__ +packet,58.267000,__ +packet,58.141000,__ +packet,58.100000,__ +packet,58.183000,__ +packet,58.225000,__ +packet,58.392000,__ +packet,58.308000,__ +packet,58.350000,__ +packet,58.559000,__ +packet,58.475000,__ +packet,58.433000,__ +packet,58.517000,__ +packet,58.725000,__ +packet,58.642000,__ +packet,58.600000,__ +packet,58.684000,__ +packet,58.809000,__ +packet,58.767000,__ +packet,59.017000,__ +packet,58.892000,__ +packet,58.850000,__ +packet,58.934000,__ +packet,58.976000,__ +packet,59.184000,__ +packet,59.101000,__ +packet,59.059000,__ +packet,59.142000,__ +packet,59.518000,__ +packet,59.351000,__ +packet,59.226000,__ +packet,59.268000,__ +packet,59.309000,__ +packet,59.393000,__ +packet,59.434000,__ +packet,59.476000,__ +packet,59.601000,__ +packet,59.560000,__ +packet,59.768000,__ +packet,59.685000,__ +packet,59.643000,__ +packet,59.726000,__ +packet,59.935000,__ +packet,59.851000,__ +packet,59.810000,__ +packet,59.893000,__ +packet,60.269000,__ +packet,60.102000,__ +packet,59.977000,__ +packet,60.018000,__ +packet,60.060000,__ +packet,60.143000,__ +packet,60.185000,__ +packet,60.227000,__ +packet,60.477000,__ +packet,60.352000,__ +packet,60.310000,__ +packet,60.394000,__ +packet,60.435000,__ +packet,60.644000,__ +packet,60.561000,__ +packet,60.519000,__ +packet,60.602000,__ +packet,60.811000,__ +packet,60.727000,__ +packet,60.686000,__ +packet,60.769000,__ +packet,61.019000,__ +packet,60.894000,__ +packet,60.852000,__ +packet,60.936000,__ +packet,60.978000,__ +packet,61.228000,__ +packet,61.103000,__ +packet,61.061000,__ +packet,61.144000,__ +packet,61.186000,__ +packet,61.603000,__ +packet,61.395000,__ +packet,61.270000,__ +packet,61.311000,__ +packet,61.353000,__ +packet,61.436000,__ +packet,61.478000,__ +packet,61.520000,__ +packet,61.562000,__ +packet,61.979000,__ +packet,61.770000,__ +packet,61.645000,__ +packet,61.687000,__ +packet,61.728000,__ +packet,61.812000,__ +packet,61.853000,__ +packet,61.895000,__ +packet,61.937000,__ +packet,62.271000,__ +packet,62.104000,__ +packet,62.020000,__ +packet,62.062000,__ +packet,62.145000,__ +packet,62.187000,__ +packet,62.229000,__ +packet,62.396000,__ +packet,62.312000,__ +packet,62.354000,__ +packet,62.604000,__ +packet,62.479000,__ +packet,62.437000,__ +packet,62.521000,__ +packet,62.563000,__ +packet,62.896000,__ +packet,62.729000,__ +packet,62.646000,__ +packet,62.688000,__ +packet,62.771000,__ +packet,62.813000,__ +packet,62.854000,__ +packet,63.063000,__ +packet,62.980000,__ +packet,62.938000,__ +packet,63.021000,__ +packet,63.105000,K_ +packet,63.355000,__ +packet,63.230000,__ +packet,63.146000,__ +packet,63.188000,__ +packet,63.272000,__ +packet,63.313000,__ +packet,63.605000,__ +packet,63.480000,__ +packet,63.397000,__ +packet,63.438000,__ +packet,63.522000,__ +packet,63.564000,__ +packet,63.855000,__ +packet,63.730000,__ +packet,63.647000,__ +packet,63.689000,__ +packet,63.772000,__ +packet,63.814000,__ +packet,64.106000,__ +packet,63.981000,__ +packet,63.897000,__ +packet,63.939000,__ +packet,64.022000,__ +packet,64.064000,__ +packet,64.231000,__ +packet,64.147000,__ +packet,64.189000,__ +packet,64.606000,__ +packet,64.398000,__ +packet,64.273000,__ +packet,64.314000,__ +packet,64.356000,__ +packet,64.439000,__ +packet,64.481000,__ +packet,64.523000,__ +packet,64.565000,__ +packet,64.648000,K_ +packet,64.773000,__ +packet,64.690000,__ +packet,64.731000,__ +packet,64.898000,__ +packet,64.815000,__ +packet,64.856000,__ +packet,65.107000,__ +packet,64.982000,__ +packet,64.940000,__ +packet,65.023000,__ +packet,65.065000,__ +packet,65.274000,__ +packet,65.190000,__ +packet,65.148000,__ +packet,65.232000,__ +packet,65.649000,__ +packet,65.440000,__ +packet,65.315000,__ +packet,65.357000,__ +packet,65.399000,__ +packet,65.482000,__ +packet,65.524000,__ +packet,65.566000,__ +packet,65.607000,__ +packet,65.816000,__ +packet,65.732000,__ +packet,65.691000,__ +packet,65.774000,__ +packet,66.024000,__ +packet,65.899000,__ +packet,65.857000,__ +packet,65.941000,__ +packet,65.983000,__ +packet,66.191000,__ +packet,66.108000,__ +packet,66.066000,__ +packet,66.149000,__ +packet,66.233000,__ +packet,66.400000,__ +packet,66.316000,__ +packet,66.275000,__ +packet,66.358000,__ +packet,66.525000,__ +packet,66.441000,__ +packet,66.483000,__ +packet,66.567000,K_ +packet,66.733000,__ +packet,66.650000,__ +packet,66.608000,__ +packet,66.692000,__ +packet,66.984000,__ +packet,66.858000,__ +packet,66.775000,__ +packet,66.817000,__ +packet,66.900000,__ +packet,66.942000,__ +packet,67.359000,__ +packet,67.150000,__ +packet,67.025000,__ +packet,67.067000,__ +packet,67.109000,__ +packet,67.192000,__ +packet,67.234000,__ +packet,67.276000,__ +packet,67.317000,__ +packet,67.734000,__ +packet,67.526000,__ +packet,67.401000,__ +packet,67.442000,__ +packet,67.484000,__ +packet,67.568000,__ +packet,67.609000,__ +packet,67.651000,__ +packet,67.693000,__ +packet,67.859000,__ +packet,67.776000,__ +packet,67.818000,__ +packet,68.151000,__ +packet,67.985000,__ +packet,67.901000,__ +packet,67.943000,__ +packet,68.026000,__ +packet,68.068000,__ +packet,68.110000,__ +packet,68.402000,__ +packet,68.277000,__ +packet,68.193000,__ +packet,68.235000,__ +packet,68.318000,__ +packet,68.360000,__ +packet,68.610000,__ +packet,68.485000,__ +packet,68.443000,__ +packet,68.527000,__ +packet,68.569000,__ +packet,68.652000,K_ +packet,68.819000,__ +packet,68.735000,__ +packet,68.694000,__ +packet,68.777000,__ +packet,68.986000,__ +packet,68.902000,__ +packet,68.860000,__ +packet,68.944000,__ +packet,69.361000,__ +packet,69.152000,__ +packet,69.027000,__ +packet,69.069000,__ +packet,69.111000,__ +packet,69.194000,__ +packet,69.236000,__ +packet,69.278000,__ +packet,69.319000,__ +packet,69.444000,__ +packet,69.403000,__ +packet,69.820000,__ +packet,69.611000,__ +packet,69.486000,__ +packet,69.528000,__ +packet,69.570000,__ +packet,69.653000,__ +packet,69.695000,__ +packet,69.736000,__ +packet,69.778000,__ +packet,70.028000,__ +packet,69.903000,__ +packet,69.861000,__ +packet,69.945000,__ +packet,69.987000,__ +packet,70.195000,__ +packet,70.112000,__ +packet,70.070000,__ +packet,70.153000,__ +packet,70.529000,__ +packet,70.362000,__ +packet,70.237000,__ +packet,70.279000,__ +packet,70.320000,__ +packet,70.404000,__ +packet,70.445000,__ +packet,70.487000,__ +packet,70.862000,__ +packet,70.696000,__ +packet,70.571000,__ +packet,70.612000,__ +packet,70.654000,__ +packet,70.737000,__ +packet,70.779000,__ +packet,70.821000,__ +packet,71.029000,__ +packet,70.946000,__ +packet,70.904000,__ +packet,70.988000,__ +packet,71.280000,__ +packet,71.154000,__ +packet,71.071000,__ +packet,71.113000,__ +packet,71.196000,__ +packet,71.238000,__ +packet,71.488000,__ +packet,71.363000,__ +packet,71.321000,__ +packet,71.405000,__ +packet,71.446000,__ +packet,71.863000,__ +packet,71.655000,__ +packet,71.530000,__ +packet,71.572000,__ +packet,71.613000,__ +packet,71.697000,__ +packet,71.738000,__ +packet,71.780000,__ +packet,71.822000,__ +packet,72.114000,__ +packet,71.989000,__ +packet,71.905000,__ +packet,71.947000,__ +packet,72.030000,__ +packet,72.072000,__ +packet,72.281000,__ +packet,72.197000,__ +packet,72.155000,__ +packet,72.239000,__ +packet,72.406000,__ +packet,72.322000,__ +packet,72.364000,__ +packet,72.614000,__ +packet,72.489000,__ +packet,72.447000,__ +packet,72.531000,__ +packet,72.573000,__ +packet,72.739000,__ +packet,72.656000,__ +packet,72.698000,__ +packet,73.031000,__ +packet,72.864000,__ +packet,72.781000,__ +packet,72.823000,__ +packet,72.906000,__ +packet,72.948000,__ +packet,72.990000,__ +packet,73.198000,__ +packet,73.115000,__ +packet,73.073000,__ +packet,73.156000,__ +packet,73.240000,K_ +packet,73.407000,__ +packet,73.323000,__ +packet,73.282000,__ +packet,73.365000,__ +packet,73.532000,__ +packet,73.448000,__ +packet,73.490000,__ +packet,73.699000,__ +packet,73.615000,__ +packet,73.574000,__ +packet,73.657000,__ +packet,73.865000,__ +packet,73.782000,__ +packet,73.740000,__ +packet,73.824000,__ +packet,74.032000,__ +packet,73.949000,__ +packet,73.907000,__ +packet,73.991000,__ +packet,74.199000,__ +packet,74.116000,__ +packet,74.074000,__ +packet,74.157000,__ +packet,74.283000,__ +packet,74.241000,__ +packet,74.408000,__ +packet,74.324000,__ +packet,74.366000,__ +packet,74.533000,__ +packet,74.449000,__ +packet,74.491000,__ +packet,74.700000,__ +packet,74.616000,__ +packet,74.575000,__ +packet,74.658000,__ +packet,74.866000,__ +packet,74.783000,__ +packet,74.741000,__ +packet,74.825000,__ +packet,75.033000,__ +packet,74.950000,__ +packet,74.908000,__ +packet,74.992000,__ +packet,75.200000,__ +packet,75.117000,__ +packet,75.075000,__ +packet,75.158000,__ +packet,75.409000,__ +packet,75.284000,__ +packet,75.242000,__ +packet,75.325000,__ +packet,75.367000,__ +packet,75.784000,__ +packet,75.576000,__ +packet,75.450000,__ +packet,75.492000,__ +packet,75.534000,__ +packet,75.617000,__ +packet,75.659000,__ +packet,75.701000,__ +packet,75.742000,__ +packet,75.826000,__ +packet,76.201000,__ +packet,75.993000,__ +packet,75.867000,__ +packet,75.909000,__ +packet,75.951000,__ +packet,76.034000,__ +packet,76.076000,__ +packet,76.118000,__ +packet,76.159000,__ +packet,76.368000,__ +packet,76.285000,__ +packet,76.243000,__ +packet,76.326000,__ +packet,76.451000,__ +packet,76.410000,__ +packet,76.618000,__ +packet,76.535000,__ +packet,76.493000,__ +packet,76.577000,__ +packet,76.827000,__ +packet,76.702000,__ +packet,76.660000,__ +packet,76.743000,__ +packet,76.785000,__ +packet,76.952000,__ +packet,76.868000,__ +packet,76.910000,__ +packet,77.160000,__ +packet,77.035000,__ +packet,76.994000,__ +packet,77.077000,__ +packet,77.119000,__ +packet,77.202000,K_ +packet,77.369000,__ +packet,77.286000,__ +packet,77.244000,__ +packet,77.327000,__ +packet,77.536000,__ +packet,77.452000,__ +packet,77.411000,__ +packet,77.494000,__ +packet,77.661000,__ +packet,77.578000,__ +packet,77.619000,__ +packet,77.828000,__ +packet,77.744000,__ +packet,77.703000,__ +packet,77.786000,__ +packet,77.953000,__ +packet,77.869000,__ +packet,77.911000,__ +packet,78.036000,__ +packet,77.995000,__ +packet,78.245000,__ +packet,78.120000,__ +packet,78.078000,__ +packet,78.161000,__ +packet,78.203000,__ +packet,78.328000,__ +packet,78.287000,__ +packet,78.495000,__ +packet,78.412000,__ +packet,78.370000,__ +packet,78.453000,__ +packet,78.620000,__ +packet,78.537000,__ +packet,78.579000,__ +packet,78.787000,__ +packet,78.704000,__ +packet,78.662000,__ +packet,78.745000,__ +packet,79.037000,__ +packet,78.912000,__ +packet,78.829000,__ +packet,78.870000,__ +packet,78.954000,__ +packet,78.996000,__ +packet,79.288000,__ +packet,79.162000,__ +packet,79.079000,__ +packet,79.121000,__ +packet,79.204000,__ +packet,79.246000,__ +packet,79.538000,__ +packet,79.413000,__ +packet,79.329000,__ +packet,79.371000,__ +packet,79.454000,__ +packet,79.496000,__ +packet,79.580000,__ +packet,79.621000,K_ +packet,79.663000,__ +packet,79.997000,__ +packet,79.830000,__ +packet,79.705000,__ +packet,79.746000,__ +packet,79.788000,__ +packet,79.871000,__ +packet,79.913000,__ +packet,79.955000,__ +packet,80.038000,__ +packet,80.247000,__ +packet,80.122000,__ +packet,80.080000,__ +packet,80.163000,__ +packet,80.205000,__ +packet,80.372000,__ +packet,80.289000,__ +packet,80.330000,__ +packet,80.539000,__ +packet,80.455000,__ +packet,80.414000,__ +packet,80.497000,__ +packet,80.706000,__ +packet,80.622000,__ +packet,80.581000,__ +packet,80.664000,__ +packet,80.872000,__ +packet,80.789000,__ +packet,80.747000,__ +packet,80.831000,__ +packet,81.039000,__ +packet,80.956000,__ +packet,80.914000,__ +packet,80.998000,__ +packet,81.248000,__ +packet,81.123000,__ +packet,81.081000,__ +packet,81.164000,__ +packet,81.206000,__ +packet,81.290000,__ +packet,81.456000,__ +packet,81.373000,__ +packet,81.331000,__ +packet,81.415000,__ +packet,81.623000,__ +packet,81.540000,__ +packet,81.498000,__ +packet,81.582000,__ +packet,81.999000,__ +packet,81.790000,__ +packet,81.665000,__ +packet,81.707000,__ +packet,81.748000,__ +packet,81.832000,__ +packet,81.873000,__ +packet,81.915000,__ +packet,81.957000,__ +packet,82.040000,__ +packet,82.249000,__ +packet,82.124000,__ +packet,82.082000,__ +packet,82.165000,__ +packet,82.207000,__ +packet,82.291000,__ +packet,82.541000,__ +packet,82.416000,__ +packet,82.332000,__ +packet,82.374000,__ +packet,82.457000,__ +packet,82.499000,__ +packet,82.916000,__ +packet,82.708000,__ +packet,82.583000,__ +packet,82.624000,__ +packet,82.666000,__ +packet,82.749000,__ +packet,82.791000,__ +packet,82.833000,__ +packet,82.874000,__ +packet,83.166000,__ +packet,83.041000,__ +packet,82.958000,__ +packet,83.000000,__ +packet,83.083000,__ +packet,83.125000,__ +packet,83.458000,__ +packet,83.292000,__ +packet,83.208000,__ +packet,83.250000,__ +packet,83.333000,__ +packet,83.375000,__ +packet,83.417000,__ +packet,83.584000,__ +packet,83.500000,__ +packet,83.542000,__ +packet,83.667000,__ +packet,83.625000,__ +packet,83.792000,__ +packet,83.709000,__ +packet,83.750000,__ +packet,83.959000,__ +packet,83.875000,__ +packet,83.834000,__ +packet,83.917000,__ +packet,84.167000,__ +packet,84.042000,__ +packet,84.001000,__ +packet,84.084000,__ +packet,84.126000,__ +packet,84.293000,__ +packet,84.209000,__ +packet,84.251000,__ +packet,84.459000,__ +packet,84.376000,__ +packet,84.334000,__ +packet,84.418000,__ +packet,84.751000,__ +packet,84.585000,__ +packet,84.501000,__ +packet,84.543000,__ +packet,84.626000,__ +packet,84.668000,__ +packet,84.710000,__ +packet,84.918000,__ +packet,84.835000,__ +packet,84.793000,__ +packet,84.876000,__ +packet,85.168000,__ +packet,85.043000,__ +packet,84.960000,__ +packet,85.002000,__ +packet,85.085000,__ +packet,85.127000,__ +packet,85.502000,__ +packet,85.335000,__ +packet,85.210000,__ +packet,85.252000,__ +packet,85.294000,__ +packet,85.377000,__ +packet,85.419000,__ +packet,85.460000,__ +packet,85.586000,__ +packet,85.544000,__ +packet,85.627000,__ +packet,85.669000,K_ +packet,85.752000,__ +packet,85.711000,__ +packet,85.919000,__ +packet,85.836000,__ +packet,85.794000,__ +packet,85.877000,__ +packet,86.044000,__ +packet,85.961000,__ +packet,86.003000,__ +packet,86.253000,__ +packet,86.128000,__ +packet,86.086000,__ +packet,86.169000,__ +packet,86.211000,__ +packet,86.461000,__ +packet,86.336000,__ +packet,86.295000,__ +packet,86.378000,__ +packet,86.420000,__ +packet,86.628000,__ +packet,86.545000,__ +packet,86.503000,__ +packet,86.587000,__ +packet,86.795000,__ +packet,86.712000,__ +packet,86.670000,__ +packet,86.753000,__ +packet,86.962000,__ +packet,86.878000,__ +packet,86.837000,__ +packet,86.920000,__ +packet,87.129000,__ +packet,87.045000,__ +packet,87.004000,__ +packet,87.087000,__ +packet,87.296000,__ +packet,87.212000,__ +packet,87.170000,__ +packet,87.254000,__ +packet,87.462000,__ +packet,87.379000,__ +packet,87.337000,__ +packet,87.421000,__ +packet,87.838000,__ +packet,87.629000,__ +packet,87.504000,__ +packet,87.546000,__ +packet,87.588000,__ +packet,87.671000,__ +packet,87.713000,__ +packet,87.754000,__ +packet,87.796000,__ +packet,87.921000,__ +packet,87.879000,__ +packet,88.046000,__ +packet,87.963000,__ +packet,88.005000,__ +packet,88.255000,__ +packet,88.130000,__ +packet,88.088000,__ +packet,88.171000,__ +packet,88.213000,__ +packet,88.297000,__ +packet,88.463000,__ +packet,88.380000,__ +packet,88.338000,__ +packet,88.422000,__ +packet,88.630000,__ +packet,88.547000,__ +packet,88.505000,__ +packet,88.589000,__ +packet,88.755000,__ +packet,88.672000,__ +packet,88.714000,__ +packet,88.797000,K_ +packet,88.880000,__ +packet,88.839000,__ +packet,88.922000,__ +packet,88.964000,__ +packet,89.131000,__ +packet,89.047000,__ +packet,89.006000,__ +packet,89.089000,__ +packet,89.506000,__ +packet,89.298000,__ +packet,89.172000,__ +packet,89.214000,__ +packet,89.256000,__ +packet,89.339000,__ +packet,89.381000,__ +packet,89.423000,__ +packet,89.464000,__ +packet,89.756000,__ +packet,89.631000,__ +packet,89.548000,__ +packet,89.590000,__ +packet,89.673000,__ +packet,89.715000,__ +packet,89.881000,__ +packet,89.798000,__ +packet,89.840000,__ +packet,90.090000,__ +packet,89.965000,__ +packet,89.923000,__ +packet,90.007000,__ +packet,90.048000,__ +packet,90.340000,__ +packet,90.215000,__ +packet,90.132000,__ +packet,90.173000,__ +packet,90.257000,__ +packet,90.299000,__ +packet,90.382000,K_ +packet,90.591000,__ +packet,90.465000,__ +packet,90.424000,__ +packet,90.507000,__ +packet,90.549000,__ +packet,90.841000,__ +packet,90.716000,__ +packet,90.632000,__ +packet,90.674000,__ +packet,90.757000,__ +packet,90.799000,__ +packet,90.882000,__ +packet,91.049000,__ +packet,90.966000,__ +packet,90.924000,__ +packet,91.008000,__ +packet,91.258000,__ +packet,91.133000,__ +packet,91.091000,__ +packet,91.174000,__ +packet,91.216000,__ +packet,91.550000,__ +packet,91.383000,__ +packet,91.300000,__ +packet,91.341000,__ +packet,91.425000,__ +packet,91.466000,__ +packet,91.508000,__ +packet,91.758000,__ +packet,91.633000,__ +packet,91.592000,__ +packet,91.675000,__ +packet,91.717000,__ +packet,92.009000,__ +packet,91.883000,__ +packet,91.800000,__ +packet,91.842000,__ +packet,91.925000,__ +packet,91.967000,__ +packet,92.259000,__ +packet,92.134000,__ +packet,92.050000,__ +packet,92.092000,__ +packet,92.175000,__ +packet,92.217000,__ +packet,92.634000,__ +packet,92.426000,__ +packet,92.301000,__ +packet,92.342000,__ +packet,92.384000,__ +packet,92.467000,__ +packet,92.509000,__ +packet,92.551000,__ +packet,92.593000,__ +packet,92.926000,__ +packet,92.759000,__ +packet,92.676000,__ +packet,92.718000,__ +packet,92.801000,__ +packet,92.843000,__ +packet,92.884000,__ +packet,93.176000,__ +packet,93.051000,__ +packet,92.968000,__ +packet,93.010000,__ +packet,93.093000,__ +packet,93.135000,__ +packet,93.385000,__ +packet,93.260000,__ +packet,93.218000,__ +packet,93.302000,__ +packet,93.343000,__ +packet,93.427000,K_ +packet,93.468000,__ +packet,93.552000,__ +packet,93.510000,__ +packet,93.719000,__ +packet,93.635000,__ +packet,93.594000,__ +packet,93.677000,__ +packet,93.969000,__ +packet,93.844000,__ +packet,93.760000,__ +packet,93.802000,__ +packet,93.885000,__ +packet,93.927000,__ +packet,94.094000,__ +packet,94.011000,__ +packet,94.052000,__ +packet,94.136000,__ +packet,94.303000,__ +packet,94.219000,__ +packet,94.177000,__ +packet,94.261000,__ +packet,94.511000,__ +packet,94.386000,__ +packet,94.344000,__ +packet,94.428000,__ +packet,94.469000,__ +packet,94.636000,__ +packet,94.553000,__ +packet,94.595000,__ +packet,94.803000,__ +packet,94.720000,__ +packet,94.678000,__ +packet,94.761000,__ +packet,94.886000,__ +packet,94.845000,__ +packet,95.012000,__ +packet,94.928000,__ +packet,94.970000,__ +packet,95.053000,__ +packet,95.220000,__ +packet,95.137000,__ +packet,95.095000,__ +packet,95.178000,__ +packet,95.387000,__ +packet,95.304000,__ +packet,95.262000,__ +packet,95.345000,__ +packet,95.679000,__ +packet,95.512000,__ +packet,95.429000,__ +packet,95.470000,__ +packet,95.554000,__ +packet,95.596000,__ +packet,95.637000,__ +packet,95.762000,__ +packet,95.721000,__ +packet,96.138000,__ +packet,95.929000,__ +packet,95.804000,__ +packet,95.846000,__ +packet,95.887000,__ +packet,95.971000,__ +packet,96.013000,__ +packet,96.054000,__ +packet,96.096000,__ +packet,96.430000,__ +packet,96.263000,__ +packet,96.179000,__ +packet,96.221000,__ +packet,96.305000,__ +packet,96.346000,__ +packet,96.388000,__ +packet,96.722000,__ +packet,96.555000,__ +packet,96.471000,__ +packet,96.513000,__ +packet,96.597000,__ +packet,96.638000,__ +packet,96.680000,__ +packet,96.930000,__ +packet,96.805000,__ +packet,96.763000,__ +packet,96.847000,__ +packet,96.888000,__ +packet,97.014000,__ +packet,96.972000,__ +packet,97.055000,__ +packet,97.264000,__ +packet,97.139000,__ +packet,97.097000,__ +packet,97.180000,__ +packet,97.222000,__ +packet,97.306000,__ +packet,97.431000,__ +packet,97.347000,__ +packet,97.389000,__ +packet,97.556000,__ +packet,97.472000,__ +packet,97.514000,__ +packet,97.806000,__ +packet,97.681000,__ +packet,97.598000,__ +packet,97.639000,__ +packet,97.723000,__ +packet,97.764000,__ +packet,97.931000,__ +packet,97.848000,__ +packet,97.889000,__ +packet,98.056000,__ +packet,97.973000,__ +packet,98.015000,__ +packet,98.181000,__ +packet,98.098000,__ +packet,98.140000,__ +packet,98.265000,__ +packet,98.223000,__ +packet,98.307000,K_ +packet,98.390000,__ +packet,98.348000,__ +packet,98.515000,__ +packet,98.432000,__ +packet,98.473000,__ +packet,98.599000,__ +packet,98.557000,__ +packet,98.765000,__ +packet,98.682000,__ +packet,98.640000,__ +packet,98.724000,__ +packet,98.974000,__ +packet,98.849000,__ +packet,98.807000,__ +packet,98.890000,__ +packet,98.932000,__ +packet,99.016000,__ +packet,99.182000,__ +packet,99.099000,__ +packet,99.057000,__ +packet,99.141000,__ +packet,99.391000,__ +packet,99.266000,__ +packet,99.224000,__ +packet,99.308000,__ +packet,99.349000,__ +packet,99.641000,__ +packet,99.516000,__ +packet,99.433000,__ +packet,99.474000,__ +packet,99.558000,__ +packet,99.600000,__ +packet,99.891000,__ +packet,99.766000,__ +packet,99.683000,__ +packet,99.725000,__ +packet,99.808000,__ +packet,99.850000,__ +stream,100 +format,101 diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration_result.json b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration_result.json new file mode 100644 index 000000000..05494ea4a --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/FfProbe/Test Data/keyframes_streamduration_result.json @@ -0,0 +1 @@ +{"TotalDuration":1000000000,"KeyframeTicks":[0,103850000,133880000,145150000,165580000,186440000,196450000,209790000,314060000,326990000,396230000,407070000,432520000,476310000,523020000,535540000,550550000,631050000,646480000,665670000,686520000,732400000,772020000,796210000,856690000,887970000,903820000,934270000,983070000]} diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj new file mode 100644 index 000000000..268631e58 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj @@ -0,0 +1,53 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <IsPackable>false</IsPackable> + <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> + <RootNamespace>Jellyfin.MediaEncoding.Keyframes</RootNamespace> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="coverlet.collector" Version="3.1.2"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="../../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" /> + </ItemGroup> + + <ItemGroup> + <None Update="FfProbe/Test Data/keyframes.txt"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="FfProbe/Test Data/keyframes_result.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="FfProbe/Test Data/keyframes_streamduration.txt"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="FfProbe/Test Data/keyframes_streamduration_result.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + </ItemGroup> + +</Project> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index f366f553a..7803e49e1 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -21,15 +21,19 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 3b6259abd..9da80c312 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -7,15 +7,19 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs new file mode 100644 index 000000000..b396b5440 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs @@ -0,0 +1,111 @@ +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Emby.Naming.ExternalFiles; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using Moq; +using Xunit; + +namespace Jellyfin.Naming.Tests.ExternalFiles; + +public class ExternalPathParserTests +{ + private readonly ExternalPathParser _audioPathParser; + private readonly ExternalPathParser _subtitlePathParser; + + public ExternalPathParserTests() + { + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); + var frenchCultureDto = new CultureDto("French", "French", "fr", new[] { "fre", "fra" }); + + var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) + .Returns(englishCultureDto); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"fr.*", RegexOptions.IgnoreCase))) + .Returns(frenchCultureDto); + + _audioPathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Audio); + _subtitlePathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Subtitle); + } + + [Theory] + [InlineData("")] + [InlineData("MyVideo.ass")] + [InlineData("MyVideo.mks")] + [InlineData("MyVideo.sami")] + [InlineData("MyVideo.srt")] + [InlineData("MyVideo.m4v")] + public void ParseFile_AudioExtensionsNotMatched_ReturnsNull(string path) + { + Assert.Null(_audioPathParser.ParseFile(path, string.Empty)); + } + + [Theory] + [InlineData("MyVideo.aa")] + [InlineData("MyVideo.aac")] + [InlineData("MyVideo.flac")] + [InlineData("MyVideo.m4a")] + [InlineData("MyVideo.mka")] + [InlineData("MyVideo.mp3")] + public void ParseFile_AudioExtensionsMatched_ReturnsPath(string path) + { + var actual = _audioPathParser.ParseFile(path, string.Empty); + Assert.NotNull(actual); + Assert.Equal(path, actual!.Path); + } + + [Theory] + [InlineData("")] + [InlineData("MyVideo.aa")] + [InlineData("MyVideo.aac")] + [InlineData("MyVideo.flac")] + [InlineData("MyVideo.mka")] + [InlineData("MyVideo.m4v")] + public void ParseFile_SubtitleExtensionsNotMatched_ReturnsNull(string path) + { + Assert.Null(_subtitlePathParser.ParseFile(path, string.Empty)); + } + + [Theory] + [InlineData("MyVideo.ass")] + [InlineData("MyVideo.mks")] + [InlineData("MyVideo.sami")] + [InlineData("MyVideo.srt")] + [InlineData("MyVideo.vtt")] + public void ParseFile_SubtitleExtensionsMatched_ReturnsPath(string path) + { + var actual = _subtitlePathParser.ParseFile(path, string.Empty); + Assert.NotNull(actual); + Assert.Equal(path, actual!.Path); + } + + [Theory] + [InlineData("", null, null)] + [InlineData(".default", null, null, true, false)] + [InlineData(".forced", null, null, false, true)] + [InlineData(".foreign", null, null, false, true)] + [InlineData(".default.forced", null, null, true, true)] + [InlineData(".forced.default", null, null, true, true)] + [InlineData(".DEFAULT.FORCED", null, null, true, true)] + [InlineData(".en", null, "eng")] + [InlineData(".EN", null, "eng")] + [InlineData(".fr.en", "fr", "eng")] + [InlineData(".en.fr", "en", "fre")] + [InlineData(".title.en.fr", "title.en", "fre")] + [InlineData(".Title Goes Here", "Title Goes Here", null)] + [InlineData(".Title.with.Separator", "Title.with.Separator", null)] + [InlineData(".title.en.default.forced", "title", "eng", true, true)] + [InlineData(".forced.default.en.title", "title", "eng", true, true)] + public void ParseFile_ExtraTokens_ParseToValues(string tokens, string? title, string? language, bool isDefault = false, bool isForced = false) + { + var path = "My.Video" + tokens + ".srt"; + + var actual = _subtitlePathParser.ParseFile(path, tokens); + + Assert.NotNull(actual); + Assert.Equal(title, actual!.Title); + Assert.Equal(language, actual.Language); + Assert.Equal(isDefault, actual.IsDefault); + Assert.Equal(isForced, actual.IsForced); + } +} diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 4c95e78b1..929764e42 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -12,10 +12,11 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> </ItemGroup> <ItemGroup> @@ -24,6 +25,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs deleted file mode 100644 index 2446660f3..000000000 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Emby.Naming.Common; -using Emby.Naming.Subtitles; -using Xunit; - -namespace Jellyfin.Naming.Tests.Subtitles -{ - public class SubtitleParserTests - { - private readonly NamingOptions _namingOptions = new NamingOptions(); - - [Theory] - [InlineData("The Skin I Live In (2011).srt", null, false, false)] - [InlineData("The Skin I Live In (2011).eng.srt", "eng", false, false)] - [InlineData("The Skin I Live In (2011).eng.default.srt", "eng", true, false)] - [InlineData("The Skin I Live In (2011).eng.forced.srt", "eng", false, true)] - [InlineData("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true)] - [InlineData("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true)] - [InlineData("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true)] - public void SubtitleParser_ValidFileName_Parses(string input, string language, bool isDefault, bool isForced) - { - var parser = new SubtitleParser(_namingOptions); - - var result = parser.ParseFile(input); - - Assert.Equal(language, result?.Language, true); - Assert.Equal(isDefault, result?.IsDefault); - Assert.Equal(isForced, result?.IsForced); - Assert.Equal(input, result?.Path); - } - - [Theory] - [InlineData("The Skin I Live In (2011).mp4")] - [InlineData("")] - public void SubtitleParser_InvalidFileName_ReturnsNull(string input) - { - var parser = new SubtitleParser(_namingOptions); - - Assert.Null(parser.ParseFile(input)); - } - } -} diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 7f9b60b9e..5c3c39bf6 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -12,16 +12,20 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 4338c812d..59c737c7d 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -13,14 +13,14 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="coverlet.collector" Version="3.1.0"> + <PackageReference Include="coverlet.collector" Version="3.1.2"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> @@ -28,6 +28,10 @@ <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs new file mode 100644 index 000000000..aec523882 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class AudioResolverTests +{ + private readonly AudioResolver _audioResolver; + + public AudioResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); + + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; + + // build resolver to test with + var localizationManager = Mock.Of<ILocalizationManager>(); + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = new List<MediaStream> + { + new() + } + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex))) + .Returns(true); + + _audioResolver = new AudioResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Theory] + [InlineData("My.Video.srt", false, false)] + [InlineData("My.Video.mp3", false, true)] + [InlineData("My.Video.srt", true, false)] + [InlineData("My.Video.mp3", true, true)] + public async void GetExternalStreams_MixedFilenames_PicksAudio(string file, bool metadataDirectory, bool matches) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv" + }; + + var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = await _audioResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + + if (matches) + { + Assert.Single(streams); + var actual = streams[0]; + Assert.Equal(MediaStreamType.Audio, actual.Type); + } + else + { + Assert.Empty(streams); + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs new file mode 100644 index 000000000..98b4a6ccf --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class MediaInfoResolverTests +{ + public const string VideoDirectoryPath = "Test Data/Video"; + public const string VideoDirectoryRegex = @"Test Data[/\\]Video"; + public const string MetadataDirectoryPath = "library/00/00000000000000000000000000000000"; + public const string MetadataDirectoryRegex = @"library.*"; + + private readonly ILocalizationManager _localizationManager; + private readonly MediaInfoResolver _subtitleResolver; + + public MediaInfoResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); + + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; + + // build resolver to test with + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); + + var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) + .Returns(englishCultureDto); + _localizationManager = localizationManager.Object; + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = new List<MediaStream> + { + new() + } + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsAny<string>())) + .Returns(false); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + _subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Fact] + public void GetExternalFiles_BadProtocol_ReturnsNoSubtitles() + { + // need a media source manager capable of returning something other than file protocol + var mediaSourceManager = new Mock<IMediaSourceManager>(); + mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*"))) + .Returns(MediaProtocol.Http); + BaseItem.MediaSourceManager = mediaSourceManager.Object; + + var video = new Movie + { + Path = "https://url.com/My.Video.mkv" + }; + + Assert.Empty(_subtitleResolver.GetExternalFiles(video, Mock.Of<IDirectoryService>(), false)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetExternalFiles_MissingDirectory_DirectoryNotQueried(bool metadataDirectory) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + string containingFolderPath, metadataPath; + + if (metadataDirectory) + { + containingFolderPath = VideoDirectoryPath; + metadataPath = "invalid"; + } + else + { + containingFolderPath = "invalid"; + metadataPath = MetadataDirectoryPath; + } + + var video = new Mock<Movie>(); + video.Setup(m => m.Path) + .Returns(VideoDirectoryPath + "/My.Video.mkv"); + video.Setup(m => m.ContainingFolderPath) + .Returns(containingFolderPath); + video.Setup(m => m.GetInternalMetadataPath()) + .Returns(metadataPath); + + string pathNotFoundRegex = metadataDirectory ? MetadataDirectoryRegex : VideoDirectoryRegex; + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + // any path other than test target exists and provides an empty listing + directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + _subtitleResolver.GetExternalFiles(video.Object, directoryService.Object, false); + + directoryService.Verify( + ds => ds.GetFilePaths(It.IsRegex(pathNotFoundRegex), It.IsAny<bool>(), It.IsAny<bool>()), + Times.Never); + } + + [Theory] + [InlineData("My.Video.mkv", "My.Video.srt", null)] + [InlineData("My.Video.mkv", "My.Video.en.srt", "eng")] + [InlineData("My.Video.mkv", "My.Video.en.srt", "eng", true)] + [InlineData("Example Movie (2021).mp4", "Example Movie (2021).English.Srt", "eng")] + [InlineData("[LTDB] Who Framed Roger Rabbit (1998) - [Bluray-1080p].mkv", "[LTDB] Who Framed Roger Rabbit (1998) - [Bluray-1080p].en.srt", "eng")] + public void GetExternalFiles_NameMatching_MatchesAndParsesToken(string movie, string file, string? language, bool metadataDirectory = false) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/" + movie + }; + + var directoryService = GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList(); + + Assert.Single(streams); + var actual = streams[0]; + Assert.Equal(language, actual.Language); + Assert.Null(actual.Title); + } + + [Theory] + [InlineData("cover.jpg")] + [InlineData("My.Video.mp3")] + [InlineData("My.Video.png")] + [InlineData("My.Video.txt")] + [InlineData("My.Video Sequel.srt")] + [InlineData("Some.Other.Video.srt")] + public void GetExternalFiles_NameMatching_RejectsNonMatches(string file) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var directoryService = GetDirectoryServiceForExternalFile(file); + var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList(); + + Assert.Empty(streams); + } + + [Theory] + [InlineData("https://url.com/My.Video.mkv")] + [InlineData(VideoDirectoryPath)] // valid but no files found for this test + public async void GetExternalStreams_BadPaths_ReturnsNoSubtitles(string path) + { + // need a media source manager capable of returning something other than file protocol + var mediaSourceManager = new Mock<IMediaSourceManager>(); + mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*"))) + .Returns(MediaProtocol.Http); + BaseItem.MediaSourceManager = mediaSourceManager.Object; + + var video = new Movie + { + Path = path + }; + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict); + var fileSystem = Mock.Of<IFileSystem>(); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, fileSystem, new NamingOptions()); + + var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None); + + Assert.Empty(streams); + } + + private static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data() + { + var data = new TheoryData<string, MediaStream[], MediaStream[]>(); + + // filename and stream have no metadata set + string file = "My.Video.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }); + + // filename has metadata + file = "My.Video.Title1.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title1", 0, true, true) + }); + + // single stream with metadata + file = "My.Video.mks"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true) + }); + + // stream wins for title/language, filename wins for flags when conflicting + file = "My.Video.Title2.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0, true, true) + }); + + // multiple stream with metadata - filename flags ignored but other data filled in when missing from stream + file = "My.Video.Title3.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0, true, true), + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title3", 0, true, true), + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1) + }); + + return data; + } + + [Theory] + [MemberData(nameof(GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data))] + public async void GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly(string file, MediaStream[] inputStreams, MediaStream[] expectedStreams) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = inputStreams.ToList() + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + + var directoryService = GetDirectoryServiceForExternalFile(file); + var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + + Assert.Equal(expectedStreams.Length, streams.Count); + for (var i = 0; i < expectedStreams.Length; i++) + { + var expected = expectedStreams[i]; + var actual = streams[i]; + + Assert.True(actual.IsExternal); + Assert.Equal(expected.Index, actual.Index); + Assert.Equal(expected.Type, actual.Type); + Assert.Equal(expected.Path, actual.Path); + Assert.Equal(expected.IsDefault, actual.IsDefault); + Assert.Equal(expected.IsForced, actual.IsForced); + Assert.Equal(expected.Language, actual.Language); + Assert.Equal(expected.Title, actual.Title); + } + } + + [Theory] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(2, 1)] + [InlineData(2, 2)] + public async void GetExternalStreams_StreamIndex_HandlesFilesAndContainers(int fileCount, int streamCount) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var files = new string[fileCount]; + for (int i = 0; i < fileCount; i++) + { + files[i] = $"{VideoDirectoryPath}/My.Video.{i}.srt"; + } + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(files); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + List<MediaStream> GenerateMediaStreams() + { + var mediaStreams = new List<MediaStream>(); + for (int i = 0; i < streamCount; i++) + { + mediaStreams.Add(new()); + } + + return mediaStreams; + } + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = GenerateMediaStreams() + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + + int startIndex = 1; + var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None); + + Assert.Equal(fileCount * streamCount, streams.Count); + for (var i = 0; i < streams.Count; i++) + { + Assert.Equal(startIndex + i, streams[i].Index); + // intentional integer division to ensure correct number of streams come back from each file + Assert.Matches(@$".*\.{i / streamCount}\.srt", streams[i].Path); + } + } + + private static MediaStream CreateMediaStream(string path, string? language, string? title, int index, bool isForced = false, bool isDefault = false) + { + return new MediaStream + { + Index = index, + Type = MediaStreamType.Subtitle, + Path = path, + IsDefault = isDefault, + IsForced = isForced, + Language = language, + Title = title + }; + } + + /// <summary> + /// Provides an <see cref="IDirectoryService"/> that when queried for the test video/metadata directory will return a path including the provided file name. + /// </summary> + /// <param name="file">The name of the file to locate.</param> + /// <param name="useMetadataDirectory"><c>true</c> if the file belongs in the metadata directory.</param> + /// <returns>A mocked <see cref="IDirectoryService"/>.</returns> + public static IDirectoryService GetDirectoryServiceForExternalFile(string file, bool useMetadataDirectory = false) + { + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + if (useMetadataDirectory) + { + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(new[] { MetadataDirectoryPath + "/" + file }); + } + else + { + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(new[] { VideoDirectoryPath + "/" + file }); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + } + + return directoryService.Object; + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs index 040ea5d1d..0e6457ce3 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -1,129 +1,86 @@ -#pragma warning disable CA1002 // Do not expose generic lists - using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; using MediaBrowser.Providers.MediaInfo; using Moq; using Xunit; -namespace Jellyfin.Providers.Tests.MediaInfo +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class SubtitleResolverTests { - public class SubtitleResolverTests - { - public static TheoryData<List<MediaStream>, string, int, string[], MediaStream[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData() - { - var data = new TheoryData<List<MediaStream>, string, int, string[], MediaStream[]>(); + private readonly SubtitleResolver _subtitleResolver; - var index = 0; - data.Add( - new List<MediaStream>(), - "/video/My.Video.mkv", - index, - new[] - { - "/video/My.Video.mp3", - "/video/My.Video.png", - "/video/My.Video.srt", - "/video/My.Video.txt", - "/video/My.Video.vtt", - "/video/My.Video.ass", - "/video/My.Video.sub", - "/video/My.Video.ssa", - "/video/My.Video.smi", - "/video/My.Video.sami", - "/video/My.Video.en.srt", - "/video/My.Video.default.en.srt", - "/video/My.Video.default.forced.en.srt", - "/video/My.Video.en.default.forced.srt", - "/video/My.Video.With.Additional.Garbage.en.srt", - "/video/My.Video With Additional Garbage.srt" - }, - new[] - { - CreateMediaStream("/video/My.Video.srt", "srt", null, index++), - CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++), - CreateMediaStream("/video/My.Video.ass", "ass", null, index++), - CreateMediaStream("/video/My.Video.sub", "sub", null, index++), - CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++), - CreateMediaStream("/video/My.Video.smi", "smi", null, index++), - CreateMediaStream("/video/My.Video.sami", "sami", null, index++), - CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++), - CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true), - CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true), - CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true), - CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index), - }); + public SubtitleResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); - return data; - } + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; - [Theory] - [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))] - public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List<MediaStream> streams, string videoPath, int startIndex, string[] files, MediaStream[] expectedResult) - { - new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files); + // build resolver to test with + var localizationManager = Mock.Of<ILocalizationManager>(); - Assert.Equal(expectedResult.Length, streams.Count); - for (var i = 0; i < expectedResult.Length; i++) + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo { - var expected = expectedResult[i]; - var actual = streams[i]; + MediaStreams = new List<MediaStream> + { + new() + } + })); - Assert.Equal(expected.Index, actual.Index); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.IsExternal, actual.IsExternal); - Assert.Equal(expected.Path, actual.Path); - Assert.Equal(expected.IsDefault, actual.IsDefault); - Assert.Equal(expected.IsForced, actual.IsForced); - Assert.Equal(expected.Language, actual.Language); - } - } + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex))) + .Returns(true); + + _subtitleResolver = new SubtitleResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Theory] + [InlineData("My.Video.srt", false, true)] + [InlineData("My.Video.mp3", false, false)] + [InlineData("My.Video.srt", true, true)] + [InlineData("My.Video.mp3", true, false)] + public async void GetExternalStreams_MixedFilenames_PicksSubtitles(string file, bool metadataDirectory, bool matches) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); - [Theory] - [InlineData("/video/My Video.mkv", "/video/My Video.srt", "srt", null, false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.srt", "srt", null, false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.foreign.srt", "srt", null, true, false)] - [InlineData("/video/My Video.mkv", "/video/My Video.forced.srt", "srt", null, true, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.srt", "srt", null, false, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.forced.default.srt", "srt", null, true, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.en.srt", "srt", "en", false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.en.srt", "srt", "en", false, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.forced.en.srt", "srt", "en", true, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.en.default.forced.srt", "srt", "en", true, true)] - public void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string videoPath, string file, string codec, string? language, bool isForced, bool isDefault) + var video = new Movie { - var streams = new List<MediaStream>(); - var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault); + Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv" + }; - new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file }); + var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + if (matches) + { Assert.Single(streams); - var actual = streams[0]; - - Assert.Equal(expected.Index, actual.Index); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.IsExternal, actual.IsExternal); - Assert.Equal(expected.Path, actual.Path); - Assert.Equal(expected.IsDefault, actual.IsDefault); - Assert.Equal(expected.IsForced, actual.IsForced); - Assert.Equal(expected.Language, actual.Language); + Assert.Equal(MediaStreamType.Subtitle, actual.Type); } - - private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false) + else { - return new() - { - Index = index, - Codec = codec, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = path, - IsDefault = isDefault, - IsForced = isForced, - Language = language - }; + Assert.Empty(streams); } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index f9228b1a7..fb8593345 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -21,16 +21,20 @@ <ItemGroup> <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 5c7c983c2..c21871297 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -1,4 +1,4 @@ -using Emby.Naming.Common; +using Emby.Naming.Common; using Emby.Server.Implementations.Library.Resolvers.TV; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -21,7 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library { var parent = new Folder { Name = "extras" }; - var episodeResolver = new EpisodeResolver(_namingOptions); + var episodeResolver = new EpisodeResolver(Mock.Of<ILogger<EpisodeResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) @@ -44,7 +45,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library // Have to create a mock because of moq proxies not being castable to a concrete implementation // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48 - var episodeResolver = new EpisodeResolverMock(_namingOptions); + var episodeResolver = new EpisodeResolverMock(Mock.Of<ILogger<EpisodeResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) @@ -61,7 +62,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library private class EpisodeResolverMock : EpisodeResolver { - public EpisodeResolverMock(NamingOptions namingOptions) : base(namingOptions) + public EpisodeResolverMock(ILogger<EpisodeResolver> logger, NamingOptions namingOptions) : base(logger, namingOptions) { } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs index f5c8cc970..599599071 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs new file mode 100644 index 000000000..d59f2f4e5 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs @@ -0,0 +1,30 @@ +using System; +using Emby.Server.Implementations.Library; +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library; + +public class MediaStreamSelectorTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetDefaultAudioStreamIndex_EmptyStreams_Null(bool preferDefaultTrack) + { + Assert.Null(MediaStreamSelector.GetDefaultAudioStreamIndex(Array.Empty<MediaStream>(), Array.Empty<string>(), preferDefaultTrack)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetDefaultAudioStreamIndex_WithoutDefault_NotNull(bool preferDefaultTrack) + { + var streams = new[] + { + new MediaStream() + }; + + Assert.NotNull(MediaStreamSelector.GetDefaultAudioStreamIndex(streams, Array.Empty<string>(), preferDefaultTrack)); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs index f2efcddba..efc3ac0c2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -17,7 +18,7 @@ public class MovieResolverTests [Fact] public void Resolve_GivenLocalAlternateVersion_ResolvesToVideo() { - var movieResolver = new MovieResolver(Mock.Of<IImageProcessor>(), _namingOptions); + var movieResolver = new MovieResolver(Mock.Of<IImageProcessor>(), Mock.Of<ILogger<MovieResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index e8fc495f9..0af39affa 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,14 +9,14 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.Priority" Version="1.1.6" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <ItemGroup> @@ -28,6 +28,10 @@ <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 3d34a18e7..adaf624a9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -7,11 +7,11 @@ using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Jellyfin.Server.Integration.Tests { @@ -74,7 +74,7 @@ namespace Jellyfin.Server.Integration.Tests appPaths, loggerFactory, commandLineOpts, - new ConfigurationBuilder().Build()); + startupConfig); _disposableComponents.Add(appHost); var serviceCollection = new ServiceCollection(); appHost.Init(serviceCollection); diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index b25b06c7b..bc829d0a1 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,17 +10,21 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 55cdfa2e0..c4469d10a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -13,15 +13,19 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> </ItemGroup> <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs index 3e726f23d..4f4ae5afb 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs @@ -123,6 +123,20 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers } [Fact] + public void Parse_GivenFileWithThumbWithoutAspect_Success() + { + var result = new MetadataResult<Episode> + { + Item = new Episode() + }; + + _parser.Fetch(result, "Test Data/Sonarr-Thumb.nfo", CancellationToken.None); + + Assert.Single(result.RemoteImages.Where(x => x.Type == ImageType.Primary)); + Assert.Equal("https://artworks.thetvdb.com/banners/episodes/359095/7081317.jpg", result.RemoteImages.First(x => x.Type == ImageType.Primary).Url); + } + + [Fact] public void Fetch_WithNullItem_ThrowsArgumentException() { var result = new MetadataResult<Episode>(); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo new file mode 100644 index 000000000..fb86768ef --- /dev/null +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo @@ -0,0 +1,34 @@ +<episodedetails> + <title>Sometimes a Genius's Every Action Is at the Mercy of X</title> + <season>1</season> + <episode>8</episode> + <aired>2019-05-26</aired> + <plot>After Nariyuki wins a smartphone in a lottery, he can't wait to use it for apps like the dictionary, schedule managing, and the like. He also learns that studying in the bathtub is effective and quickly puts the method into practice.</plot> + <uniqueid type="sonarr" default="true">4289</uniqueid> + <thumb>https://artworks.thetvdb.com/banners/episodes/359095/7081317.jpg</thumb> + <watched>false</watched> + <fileinfo> + <streamdetails> + <video> + <aspect>1.77777779</aspect> + <bitrate>2208901</bitrate> + <codec>x265</codec> + <framerate>23.976</framerate> + <height>1080</height> + <scantype></scantype> + <width>1920</width> + <duration>23.683416666666666</duration> + <durationinseconds>1421</durationinseconds> + </video> + <audio> + <bitrate>1468567</bitrate> + <channels>2</channels> + <codec>FLAC</codec> + <language>Japanese / Japanese</language> + </audio> + <subtitle> + <language>English</language> + </subtitle> + </streamdetails> + </fileinfo> +</episodedetails> |
