Compare commits

...

85 Commits

Author SHA1 Message Date
renovate[bot]
982e0c9370 Update dependency Polly to 8.6.2 (#14455)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-12 09:47:56 +02:00
JPVenson
55e681b9a6 Add SerilogExpressions reference (#14438) 2025-07-11 23:38:41 -06:00
JPVenson
7ba77804c4 Added advanced parallel process calculation (#14437) 2025-07-11 23:27:13 -06:00
Bas
af6f5a8ed0 Translated using Weblate (Dutch)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/
2025-07-11 18:13:18 +00:00
Ella Veter
1162fcebf8 Translated using Weblate (Dutch)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/
2025-07-11 16:50:00 +00:00
renovate[bot]
38d0367c42 Update dependency dotnet-ef to 9.0.7 (#14440)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 00:47:46 +02:00
renovate[bot]
7d3372018f Update Microsoft to 9.0.7 (#14441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 00:46:44 +02:00
renovate[bot]
8629831658 Update dependency z440.atl.core to v7 (#14391)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
2025-07-07 18:10:48 -06:00
Nyanmisaka
db55d983f8 Only enable VAAPI MJPEG encoder on Intel iHD driver (#14433) 2025-07-07 18:05:14 -06:00
JPVenson
4d5ba8d7a5 Only save images when changed (#14425)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
2025-07-07 09:14:01 -06:00
Bond-009
6d4169a449 Update dependency Diacritics to v4 (#14418)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-05 11:08:38 +02:00
Shane Powell
8dcb0bfecb Merge pull request #14309 from shanepowell/MediaInfoFixs
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Fix PeopleHelper.AddPerson Exceptions due to bad meta-data extracts.
2025-07-04 19:22:27 +02:00
Bond-009
844d69ab64 Read ALBUMARTISTS in preference to ARTISTS when PreferNonstandardArtistsTag set (#14413)
Jellyfin implemented ARTISTS multivalue tag but did not implement the equivalent ALBUMARTISTS multivalue tag.  This change adds ALBUMARTISTS support.  If present and PreferNonstandardArtistsTag is set, ALBUMARTISTS will be used in preference to ALBUMARTIST.  As with ARTISTS, the intent is to offer support for multiple album artists without affecting software that does not read ALBUMARTIST as a multivalued tag.

Example album before/after:

    ALBUM           : Amici e Rivali
    ARTIST          : Lawrence Brownlee / Michael Spyres
    album_artist    : Lawrence Brownlee
    ARTISTS         : Lawrence Brownlee;Michael Spyres
    ALBUMARTISTS    : Lawrence Brownlee;Michael Spyres

Before ALBUMARTISTS support, Jellyfin reports:
    Album Artist: Lawrence Brownlee [hyperlinked]
    On each track Artist: Lawrence Brownlee, Michael Spyres

After ALBUMARTISTS support, Jellyfin reoprts:
    Album Artist: Lawrence Brownlee [hyperlinked], Michael Spyres [hyperlinked]
    On each track Artist: none shown (no other artists in source metadata)

This is ideal as both key artists are hyperlinkable from their albums.

References to other products implementing ALBUMARTISTS:
- Navidrome: https://www.navidrome.org/docs/usage/tagging-guidelines/#handling-multiple-artists-and-collaborations
- Kodi: https://kodi.wiki/view/Music_tagging#albumartists
- MusicBrainz Picard: https://picard-docs.musicbrainz.org/en/variables/variables_basic.html (_albumartists tag)

Co-authored-by: Evan <evan@MacBook-Pro.local>
2025-07-04 19:19:26 +02:00
Bond-009
5c36b44484 Fix seasons random (#13224) (#14335)
Sorting was always enabled so removed the `enableSorting` parameter
in QueryResult method.

Co-authored-by: Maxime <>
2025-07-04 19:18:38 +02:00
renovate[bot]
4e4d7e7764 Update github/codeql-action action to v3.29.2 (#14400)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 17:18:29 +02:00
renovate[bot]
4c268a3579 Update dependency Diacritics to v4 2025-07-03 15:15:02 +00:00
Yago Raña Gayoso
77bcd2f5f6 Translated using Weblate (Galician)
Some checks failed
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
CodeQL / Analyze (csharp) (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gl/
2025-07-02 19:07:02 +00:00
Bill Thornton
8406924471 Merge pull request #14414 from thornbill/revert-pt-translations
Revert invalid Portuguese translations
2025-07-02 10:52:11 -04:00
Bill Thornton
67fd4ce187 Revert "Translated using Weblate (Portuguese)"
This reverts commit dde306b170.
2025-07-02 10:47:44 -04:00
Bill Thornton
b37b39773a Revert "Translated using Weblate (Portuguese)"
This reverts commit 9c817a97a9.
2025-07-02 10:47:31 -04:00
akshay
6f98767aed Translated using Weblate (Marathi)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mr/
2025-07-02 09:49:41 +00:00
Tal Sarid
643460f484 Translated using Weblate (Hebrew)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/
2025-07-02 08:16:42 +00:00
Evan
a4231bf428 Read ALBUMARTISTS in preference to ARTISTS when PreferNonstandardArtistsTag set
Jellyfin implemented ARTISTS multivalue tag but did not implement the equivalent ALBUMARTISTS multivalue tag.  This change adds ALBUMARTISTS support.  If present and PreferNonstandardArtistsTag is set, ALBUMARTISTS will be used in preference to ALBUMARTIST.  As with ARTISTS, the intent is to offer support for multiple album artists without affecting software that does not read ALBUMARTIST as a multivalued tag.

Example album before/after:

    ALBUM           : Amici e Rivali
    ARTIST          : Lawrence Brownlee / Michael Spyres
    album_artist    : Lawrence Brownlee
    ARTISTS         : Lawrence Brownlee;Michael Spyres
    ALBUMARTISTS    : Lawrence Brownlee;Michael Spyres

Before ALBUMARTISTS support, Jellyfin reports:
    Album Artist: Lawrence Brownlee [hyperlinked]
    On each track Artist: Lawrence Brownlee, Michael Spyres

After ALBUMARTISTS support, Jellyfin reoprts:
    Album Artist: Lawrence Brownlee [hyperlinked], Michael Spyres [hyperlinked]
    On each track Artist: none shown (no other artists in source metadata)

This is ideal as both key artists are hyperlinkable from their albums.

References to other products implementing ALBUMARTISTS:
- Navidrome: https://www.navidrome.org/docs/usage/tagging-guidelines/#handling-multiple-artists-and-collaborations
- Kodi: https://kodi.wiki/view/Music_tagging#albumartists
- MusicBrainz Picard: https://picard-docs.musicbrainz.org/en/variables/variables_basic.html (_albumartists tag)
2025-07-02 07:55:24 +08:00
Edson
9c817a97a9 Translated using Weblate (Portuguese)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2025-07-01 18:43:41 +00:00
Deleted User
dde306b170 Translated using Weblate (Portuguese)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2025-07-01 08:19:32 +00:00
Tal Sarid
e2b61d951b Translated using Weblate (Hebrew)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/
2025-07-01 00:32:48 +00:00
Gargotaire
9eff25bfed Translated using Weblate (Catalan)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/
2025-07-01 00:32:47 +00:00
Tommaso Morganti
ff4484eb4a Translated using Weblate (Italian)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/
2025-06-29 16:35:11 +00:00
Gargotaire
62b2adbf66 Translated using Weblate (Catalan)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/
2025-06-29 16:35:11 +00:00
gnattu
9ac8c2a2fa Fix compiler warning for CodeMigration.cs (#14390) 2025-06-29 08:00:29 -06:00
renovate[bot]
90e72fb687 Update dependency BitFaster.Caching to 2.5.4 (#14392)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 10:23:38 +02:00
Acrotos
630846798d Translated using Weblate (Romanian)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/
2025-06-28 20:50:29 +00:00
Petrică Andrei-Cosmin
9d5be19a27 Translated using Weblate (Romanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/
2025-06-28 20:50:29 +00:00
renovate[bot]
6058ab50f8 Update github/codeql-action action to v3.29.1 (#14389)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 11:23:12 +02:00
Ruben Teixeira
e3b379052d Translated using Weblate (Portuguese (Portugal))
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2025-06-28 00:39:50 +00:00
Nirwan
0b6f4b2bd9 Translated using Weblate (Indonesian)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/id/
2025-06-27 11:27:47 +00:00
Varun Sudarshan
4f6db1bc22 Translated using Weblate (Kannada)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kn/
2025-06-27 02:51:17 +00:00
Oatavandi
8c8c71125c Translated using Weblate (Tamil)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/
2025-06-27 02:51:17 +00:00
Tim Eisele
c6e568692e Fix modification checks and make sure to use UTC (#14347)
Some checks are pending
OpenAPI / OpenAPI - HEAD (push) Waiting to run
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
2025-06-26 17:50:37 -06:00
Archie
d5a76bdff8 Changed misspell of 'temporarily' in 503 response (#14377) 2025-06-26 17:49:07 -06:00
Nyanmisaka
ebdc756547 Fix -fps_mode option being applied on input (#14379) 2025-06-26 17:48:43 -06:00
stelle
10d0cec7b9 Translated using Weblate (Malay)
Some checks failed
Merge Conflict Labeler / Labeling (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2025-06-25 13:51:17 +00:00
Hasan Abdulaal
10cc651790 Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2025-06-25 13:51:16 +00:00
theguymadmax
7d18f3d6ed Improve cast and crew handling (#14370)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
2025-06-24 17:48:36 -06:00
Max Rumpf
9b8c12d433 Adapt LrcLyricParser to new LrcParser version (#14263) 2025-06-24 16:59:09 -06:00
JPVenson
ba0eb87371 Add migration to migrate disconnected UserData too (#14339)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
2025-06-23 08:36:49 -06:00
Mason Weigand
d561cef81f RemoveNowPlayingItem should also set FullNowPlayingItem to null (#14360) bb 2025-06-23 08:31:25 -06:00
theguymadmax
b528c1100f Fix missing music genre metadata (#14332) 2025-06-23 08:30:59 -06:00
Tim Eisele
96c9f4fdad Make keyframe extraction task cancellable (#14368) 2025-06-23 08:29:42 -06:00
Onni Saarni
6d077fcf40 Translated using Weblate (Finnish)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/
2025-06-22 07:12:05 +00:00
Niels van Velzen
ab99b2bad3 Merge pull request #14341 from Shadowghost/misc
Cleanup Migration
2025-06-22 08:49:24 +02:00
Tim Eisele
db36be7a6b Skip missing images when creating collages (#14344)
Some checks failed
Merge Conflict Labeler / Labeling (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
* Skip missing images when creating collages
2025-06-21 12:23:08 +02:00
Romulo Alves
85f158e1dd Translated using Weblate (Portuguese (Brazil))
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
2025-06-20 03:36:14 +00:00
RingoJet
e1365bd253 Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2025-06-20 03:36:14 +00:00
Shoham Peller
1ec66adc30 fix(devcontainer): correct apt-packages path (#14235)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
Feature path was incorrect, which failed the creation of the
devcontainer
2025-06-19 14:46:55 +02:00
Shadowghost
af0bcbc652 Fixup 2025-06-19 12:33:46 +02:00
Tim Eisele
b2312466e1 Update Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2025-06-19 11:40:40 +02:00
Bond-009
cc7915c2e6 Merge pull request #14338 from jellyfin/renovate/polly-monorepo
Update dependency Polly to 8.6.1
2025-06-19 11:28:13 +02:00
Bond-009
a537c66da1 Merge pull request #14322 from gnattu/disable-hdr-non-hdr-clients
Don't check dynamic metadata removal for static HDR formats
2025-06-19 11:27:23 +02:00
Bond-009
a43adf42f3 Merge pull request #14324 from jellyfin/renovate/microsoft
Update dependency System.Linq.Async to 6.0.3
2025-06-19 11:26:54 +02:00
Bond-009
6996c8a1de Merge pull request #14330 from jellyfin/qsv-dx11-static-pool
Fix QSV "static surface pool size exceeded" on Windows
2025-06-19 11:26:37 +02:00
Bond-009
f976630003 Merge pull request #14333 from theguymadmax/add-act-naming-option
Add act as a stacking option for music albums
2025-06-19 11:25:46 +02:00
Bond-009
965cf93419 Merge pull request #14328 from crobibero/trickplay-info
Use dto instead of db object when returning trickplay
2025-06-19 11:24:03 +02:00
Bond-009
70ea3f863a Merge pull request #14327 from crobibero/fix-startup
Fix startup logger, startup health check
2025-06-19 11:20:35 +02:00
Tim Eisele
989aef18af Update Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2025-06-18 23:08:05 +02:00
Shadowghost
ccb917b8df Cleanup logging and user data import skip on missing user 2025-06-18 21:33:32 +02:00
queeup
7cf6389ab5 Translated using Weblate (Turkish)
Some checks failed
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
CodeQL / Analyze (csharp) (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/
2025-06-18 15:47:10 +00:00
renovate[bot]
2473b89a8d Update dependency Polly to 8.6.1 2025-06-18 09:36:02 +00:00
Yusuke, Hirota
6575c69a4e Translated using Weblate (Japanese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2025-06-18 09:01:34 +00:00
nextlooper42
66d594836c Translated using Weblate (Slovak)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/
2025-06-18 09:01:34 +00:00
Roi Gabay
43028f735f Translated using Weblate (Hebrew)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/
2025-06-18 09:01:34 +00:00
Maxime
e83b992eef Fix seasons random (#13224)
Sorting was always enabled so removed the `enableSorting` parameter
in QueryResult method.
2025-06-18 00:41:09 +02:00
theguymadmax
8368d10d1b Add act as a stacking option for music albums 2025-06-17 12:16:32 -04:00
Nyanmisaka
e8291fc856 Fix QSV "static surface pool size exceeded" on Windows
d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay to prevent encoder async and bframes from exhausting the decoder pool.
2025-06-17 15:02:13 +08:00
Aindriú Mac Giolla Eoin
308707476d Translated using Weblate (Irish)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ga/
2025-06-17 01:01:35 +00:00
Daniel Szente
e252589900 Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2025-06-17 01:01:34 +00:00
Mathieu Funk
1220cac255 Translated using Weblate (English (United Kingdom))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/
2025-06-17 01:01:34 +00:00
Gallyam Biktashev
7218d82c21 Translated using Weblate (Russian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/
2025-06-17 01:01:34 +00:00
bdubz
a4524eb2ad Translated using Weblate (Spanish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2025-06-17 01:01:34 +00:00
Thadah D. Denyse
553ba56389 Translated using Weblate (Basque)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eu/
2025-06-17 01:01:34 +00:00
Cody Robibero
afa2103d42 Use dto instead of db object when returning trickplay 2025-06-16 18:55:21 -06:00
Cody Robibero
7256c9c89d Fix startup logger, startup health check 2025-06-16 18:32:29 -06:00
renovate[bot]
f3cdaeaa12 Update dependency System.Linq.Async to 6.0.3 2025-06-16 21:23:20 +00:00
gnattu
368808eba4 Don't check dynamic metadata removal for static HDR formats
Those videos have no metadata to be removed at all, just force transcoding when the client does not support it.
2025-06-17 02:29:16 +08:00
renovate[bot]
0fc8ed6aeb Update danielpalme/ReportGenerator-GitHub-Action action to v5.4.8 (#14316)
Some checks are pending
CodeQL / Analyze (csharp) (push) Waiting to run
OpenAPI / OpenAPI - Difference (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Unstable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - Publish Stable Spec (push) Blocked by required conditions
OpenAPI / OpenAPI - HEAD (push) Waiting to run
OpenAPI / OpenAPI - BASE (push) Waiting to run
Tests / run-tests (macos-latest) (push) Waiting to run
Tests / run-tests (ubuntu-latest) (push) Waiting to run
Tests / run-tests (windows-latest) (push) Waiting to run
Project Automation / Project board (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
2025-06-16 09:15:23 +02:00
83 changed files with 2846 additions and 395 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.6",
"version": "9.0.7",
"commands": [
"dotnet-ef"
]

View File

@@ -13,7 +13,7 @@
"dotnetRuntimeVersions": "9.0",
"aspNetCoreRuntimeVersions": "9.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
"preserve_apt_list": false,
"packages": [
"libfontconfig1"

View File

@@ -27,11 +27,11 @@ jobs:
dotnet-version: '9.0.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2

View File

@@ -35,7 +35,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@c9576654e2fea2faa7b69e59550b3805bf6a9977 # v5.4.7
uses: danielpalme/ReportGenerator-GitHub-Action@4c0f60daf67483745c34efdeadd4c4e78a19991e # v5.4.8
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"

View File

@@ -198,6 +198,7 @@
- [revam](https://github.com/revam)
- [allesmi](https://github.com/allesmi)
- [ThunderClapLP](https://github.com/ThunderClapLP)
- [Shoham Peller](https://github.com/spellr)
# Emby Contributors

View File

@@ -9,12 +9,12 @@
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BDInfo" Version="0.8.0" />
<PackageVersion Include="BitFaster.Caching" Version="2.5.3" />
<PackageVersion Include="BitFaster.Caching" Version="2.5.4" />
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Diacritics" Version="3.3.29" />
<PackageVersion Include="Diacritics" Version="4.0.14" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="FsCheck.Xunit" Version="3.3.0" />
@@ -24,31 +24,31 @@
<PackageVersion Include="Ignore" Version="0.2.1" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.12" />
<PackageVersion Include="LrcParser" Version="2025.228.1" />
<PackageVersion Include="LrcParser" Version="2025.623.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.6" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="MimeTypes" Version="2.5.2" />
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
@@ -59,9 +59,10 @@
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Polly" Version="8.6.0" />
<PackageVersion Include="Polly" Version="8.6.2" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
@@ -79,12 +80,12 @@
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageVersion Include="System.Text.Json" Version="9.0.6" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.6" />
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.7" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="z440.atl.core" Version="6.26.0" />
<PackageVersion Include="z440.atl.core" Version="7.0.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
@@ -92,4 +93,4 @@
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
<PackageVersion Include="xunit" Version="2.9.3" />
</ItemGroup>
</Project>
</Project>

View File

@@ -188,7 +188,8 @@ namespace Emby.Naming.Common
"disk",
"vol",
"volume",
"part"
"part",
"act"
};
ArtistSubfolders = new[]

View File

@@ -108,7 +108,7 @@ public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IH
var dateTaken = image.ImageTag.DateTime;
if (dateTaken.HasValue)
{
item.DateCreated = dateTaken.Value;
item.DateCreated = dateTaken.Value.ToUniversalTime();
item.PremiereDate = dateTaken.Value;
item.ProductionYear = dateTaken.Value.Year;
}

View File

@@ -1065,7 +1065,12 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.Trickplay))
{
dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
dto.Trickplay = trickplay.ToDictionary(
mediaStream => mediaStream.Key,
mediaStream => mediaStream.Value.ToDictionary(
width => width.Key,
width => new TrickplayInfoDto(width.Value)));
}
dto.ExtraType = video.ExtraType;

View File

@@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.HttpServer
RemoteEndPoint = remoteEndPoint;
_jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now;
LastActivityDate = DateTime.UtcNow;
}
/// <inheritdoc />

View File

@@ -43,13 +43,11 @@ namespace Emby.Server.Implementations.Images
protected IImageProcessor ImageProcessor { get; set; }
protected virtual IReadOnlyCollection<ImageType> SupportedImages { get; }
= new ImageType[] { ImageType.Primary };
= [ImageType.Primary];
/// <inheritdoc />
public string Name => "Dynamic Image Provider";
protected virtual int MaxImageAgeDays => 7;
public int Order => 0;
protected virtual bool Supports(BaseItem item) => true;
@@ -292,8 +290,14 @@ namespace Emby.Server.Implementations.Images
protected virtual bool HasChangedByDate(BaseItem item, ItemImageInfo image)
{
var age = DateTime.UtcNow - image.DateModified;
return age.TotalDays > MaxImageAgeDays;
var path = image.Path;
if (!string.IsNullOrEmpty(path))
{
var modificationDate = FileSystem.GetLastWriteTimeUtc(path);
return image.DateModified != modificationDate;
}
return false;
}
protected string CreateSingleImage(IEnumerable<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)

View File

@@ -1981,6 +1981,8 @@ namespace Emby.Server.Implementations.Library
return;
}
var anyChange = false;
foreach (var img in outdated)
{
var image = img;
@@ -2012,6 +2014,7 @@ namespace Emby.Server.Implementations.Library
try
{
size = _imageProcessor.GetImageDimensions(item, image);
anyChange = image.Width != size.Width || image.Height != size.Height;
image.Width = size.Width;
image.Height = size.Height;
}
@@ -2019,23 +2022,29 @@ namespace Emby.Server.Implementations.Library
{
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
size = default;
anyChange = image.Width != size.Width || image.Height != size.Height;
image.Width = 0;
image.Height = 0;
}
try
{
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
var blurhash = _imageProcessor.GetImageBlurHash(image.Path, size);
anyChange = anyChange || !blurhash.Equals(image.BlurHash, StringComparison.Ordinal);
image.BlurHash = blurhash;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path);
anyChange = anyChange || !string.IsNullOrEmpty(image.BlurHash);
image.BlurHash = string.Empty;
}
try
{
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
var modifiedDate = _fileSystem.GetLastWriteTimeUtc(image.Path);
anyChange = anyChange || modifiedDate != image.DateModified;
image.DateModified = modifiedDate;
}
catch (Exception ex)
{
@@ -2043,20 +2052,28 @@ namespace Emby.Server.Implementations.Library
}
}
_itemRepository.SaveImages(item);
if (anyChange)
{
_itemRepository.SaveImages(item);
}
RegisterItem(item);
}
/// <inheritdoc />
public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
_itemRepository.SaveItems(items, cancellationToken);
foreach (var item in items)
{
item.DateLastSaved = DateTime.UtcNow;
await RunMetadataSavers(item, updateReason).ConfigureAwait(false);
// Modify again, so saved value is after write time of externally saved metadata
item.DateLastSaved = DateTime.UtcNow;
}
_itemRepository.SaveItems(items, cancellationToken);
if (ItemUpdated is not null)
{
foreach (var item in items)
@@ -2097,8 +2114,6 @@ namespace Emby.Server.Implementations.Library
await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false);
}
item.DateLastSaved = DateTime.UtcNow;
await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
}
@@ -2384,12 +2399,13 @@ namespace Emby.Server.Implementations.Library
isNew = true;
}
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
var lastRefreshedUtc = item.DateLastRefreshed;
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
}
if (refresh)
@@ -2447,12 +2463,13 @@ namespace Emby.Server.Implementations.Library
isNew = true;
}
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
var lastRefreshedUtc = item.DateLastRefreshed;
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
}
if (refresh)
@@ -2522,12 +2539,13 @@ namespace Emby.Server.Implementations.Library
item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
}
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
var lastRefreshedUtc = item.DateLastRefreshed;
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
}
if (refresh)
@@ -2991,13 +3009,12 @@ namespace Emby.Server.Implementations.Library
{
var path = Person.GetPath(person.Name);
var info = Directory.CreateDirectory(path);
var lastWriteTime = info.LastWriteTimeUtc;
personEntity = new Person()
{
Name = person.Name,
Id = GetItemByNameId<Person>(path),
DateCreated = info.CreationTimeUtc,
DateModified = lastWriteTime,
DateModified = info.LastWriteTimeUtc,
Path = path
};

View File

@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Library
if (fileCreationDate is not null)
{
var dateCreated = fileCreationDate;
if (dateCreated.Equals(DateTime.MinValue))
if (dateCreated == DateTime.MinValue)
{
dateCreated = DateTime.UtcNow;
}

View File

@@ -136,5 +136,7 @@
"TaskExtractMediaSegments": "فحص مقاطع الوسائط",
"TaskExtractMediaSegmentsDescription": "يستخرج مقاطع وسائط من إضافات MediaSegment المُفعّلة.",
"TaskMoveTrickplayImages": "تغيير مكان صور المعاينة السريعة",
"TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة."
"TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة.",
"CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم",
"CleanupUserDataTaskDescription": "مسح جميع بيانات المستخدم (حالة المشاهدة، والحالة المفضلة وما إلى ذلك) من الوسائط التي لم تعد موجودة لمدة 90 يومًا على الأقل."
}

View File

@@ -43,7 +43,7 @@
"NameInstallFailed": "{0} instal·lació fallida",
"NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconeguda",
"NewVersionIsAvailable": "Una nova versió del servidor de Jellyfin està disponible per a descarregar.",
"NewVersionIsAvailable": "Hi ha disponible una versió nova del servidor de Jellyfin per a la descàrrega.",
"NotificationOptionApplicationUpdateAvailable": "Actualització de l'aplicatiu disponible",
"NotificationOptionApplicationUpdateInstalled": "Actualització de l'aplicatiu instal·lada",
"NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada",
@@ -64,7 +64,7 @@
"Playlists": "Llistes de reproducció",
"Plugin": "Complement",
"PluginInstalledWithName": "{0} ha estat instal·lat",
"PluginUninstalledWithName": "S'ha instalat {0}",
"PluginUninstalledWithName": "S'ha instal·lat {0}",
"PluginUpdatedWithName": "S'ha actualitzat {0}",
"ProviderValue": "Proveïdor: {0}",
"ScheduledTaskFailedWithName": "{0} ha fallat",
@@ -93,50 +93,50 @@
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versió {0}",
"TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.",
"TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin",
"TaskDownloadMissingSubtitles": "Descàrrega dels subtítols que faltin",
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals per internet.",
"TaskRefreshChannels": "Actualitza els canals",
"TaskCleanTranscodeDescription": "Elimina els arxius de transcodificacions que tinguin més d'un dia.",
"TaskCleanTranscode": "Neteja les transcodificacions",
"TaskCleanTranscode": "Neteja de les transcodificacions",
"TaskUpdatePluginsDescription": "Descarrega i instal·la els complements que estiguin configurats per a actualitzar-se automàticament.",
"TaskUpdatePlugins": "Actualitza els complements",
"TaskRefreshPeopleDescription": "Actualitza les metadades dels actors i directors de la mediateca.",
"TaskRefreshPeople": "Actualitza les persones",
"TaskUpdatePlugins": "Actualització dels complements",
"TaskRefreshPeopleDescription": "Actualització de les metadades dels actors i directors de la mediateca.",
"TaskRefreshPeople": "Actualització de les persones",
"TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.",
"TaskCleanLogs": "Neteja els registres",
"TaskRefreshLibraryDescription": "Escaneja la mediateca, a la cerca de fitxers nous i refresca les metadades.",
"TaskRefreshLibrary": "Escaneja la mediateca",
"TaskRefreshChapterImagesDescription": "Crea les miniatures dels vídeos que tinguin capítols.",
"TaskRefreshChapterImages": "Extreu les imatges dels capítols",
"TaskCleanCacheDescription": "Elimina la memòria cau no necessària per al servidor.",
"TaskCleanCache": "Elimina la memòria cau",
"TaskCleanLogs": "Neteja dels registres",
"TaskRefreshLibraryDescription": "Escaneig de la mediateca, a la cerca de fitxers nous i refresca les metadades.",
"TaskRefreshLibrary": "Escaneig de la mediateca",
"TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.",
"TaskRefreshChapterImages": "Extracció de les imatges dels capítols",
"TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.",
"TaskCleanCache": "Eliminació de la memòria cau",
"TasksChannelsCategory": "Canals per internet",
"TasksApplicationCategory": "Aplicatiu",
"TasksLibraryCategory": "Mediateca",
"TasksMaintenanceCategory": "Manteniment",
"TaskCleanActivityLogDescription": "Eliminades les entrades del registre d'activitats més antigues que l'antiguitat configurada.",
"TaskCleanActivityLog": "Buida el registre d'activitat",
"TaskCleanActivityLogDescription": "Eliminació de les entrades del registre d'activitats més antigues que l'antiguitat configurada.",
"TaskCleanActivityLog": "Buidat del registre d'activitat",
"Undefined": "Indefinit",
"Forced": "Forçat",
"Default": "Per defecte",
"TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després descanejar la mediateca o fer d'altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.",
"TaskOptimizeDatabase": "Optimitza la base de dades",
"TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot allargar-se molt en el temps.",
"TaskOptimizeDatabase": "Optimització de la base de dades",
"TaskKeyframeExtractorDescription": "Extractor de fotogrames clau dels fitxers de vídeo per a crear llistes de reproducció HLS més precises. Aquesta tasca pot allargar-se molt en el temps.",
"TaskKeyframeExtractor": "Extractor de fotogrames clau",
"External": "Extern",
"HearingImpaired": "Discapacitat auditiva",
"TaskRefreshTrickplayImages": "Genera imatges de previsualització",
"TaskRefreshTrickplayImagesDescription": "Crea imatges de previsualització per vídeos en les mediateques habilitades.",
"TaskRefreshTrickplayImages": "Generació d'imatges de previsualització",
"TaskRefreshTrickplayImagesDescription": "Creació d'imatges de previsualització per a vídeos en les mediateques habilitades.",
"TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.",
"TaskCleanCollectionsAndPlaylists": "Neteja les col·leccions i llistes de reproducció",
"TaskCleanCollectionsAndPlaylists": "Neteja de les col·leccions i llistes de reproducció",
"TaskAudioNormalization": "Estabilització de l'àudio",
"TaskAudioNormalizationDescription": "Escaneja arxius per dades d'estabilització de l'àudio.",
"TaskDownloadMissingLyricsDescription": "Baixa les lletres de les cançons",
"TaskDownloadMissingLyrics": "Baixa les lletres que falten",
"TaskDownloadMissingLyricsDescription": "Descàrrega de les lletres de les cançons",
"TaskDownloadMissingLyrics": "Descàrrega de les lletres que faltin",
"TaskExtractMediaSegments": "Escaneig de segments multimèdia",
"TaskExtractMediaSegmentsDescription": "Extreu o obté segments multimèdia usant els connectors MediaSegment activats.",
"TaskMoveTrickplayImages": "Migra la ubicació de la imatge de Trickplay",
"TaskMoveTrickplayImagesDescription": "Mou els fitxers trickplay existents segons la configuració de la mediateca.",
"TaskMoveTrickplayImages": "Migració de la ubicació de la imatge de previsualització",
"TaskMoveTrickplayImagesDescription": "Mou els fitxers existents d'imatges de previsualització segons la configuració de la mediateca.",
"CleanupUserDataTaskDescription": "Neteja totes les dades d'usuari (estat de la visualització, estat dels preferits, etc.) del contingut multimèdia que no ha estat present durant almenys 90 dies.",
"CleanupUserDataTask": "Tasca de neteja de dades d'usuari"
}

View File

@@ -136,5 +136,7 @@
"TaskExtractMediaSegments": "Media Segment Scan",
"TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.",
"TaskMoveTrickplayImages": "Migrate Trickplay Image Location",
"TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings."
"TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings.",
"CleanupUserDataTask": "User data cleanup task",
"CleanupUserDataTaskDescription": "Cleans all user data (Watch state, favourite status etc) from media that is no longer present for at least 90 days."
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.",
"TaskExtractMediaSegments": "Escaneo de segmentos de medios",
"TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.",
"TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay"
"TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay",
"CleanupUserDataTask": "Tarea de limpieza de datos del usuario",
"CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, favoritos, etc.) de los medios que ya no están disponibles desde hace al menos 90 días."
}

View File

@@ -135,5 +135,7 @@
"TaskExtractMediaSegmentsDescription": "Media segmentuak atera edo lortzen ditu MediaSegment gaituta duten pluginetik.",
"TaskMoveTrickplayImages": "Aldatu Trickplay irudien kokalekua",
"TaskMoveTrickplayImagesDescription": "Lehendik dauden trickplay fitxategiak liburutegiaren ezarpenen arabera mugitzen dira.",
"TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu."
"TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu.",
"CleanupUserDataTaskDescription": "Gutxienez 90 egunez dagoeneko existitzen ez den multimediatik erabiltzaile-datu guztiak (ikusteko egoera, gogokoen egoera, etab.) garbitzen ditu.",
"CleanupUserDataTask": "Erabiltzaileen datuak garbitzeko zeregina"
}

View File

@@ -135,5 +135,7 @@
"TaskDownloadMissingLyricsDescription": "Ladataan sanoituksia",
"TaskExtractMediaSegmentsDescription": "Poimii tai hankkii mediasegmenttejä MediaSegment-yhteensopivista laajennuksista.",
"TaskMoveTrickplayImages": "Siirrä Trickplay-kuvien sijainti",
"TaskMoveTrickplayImagesDescription": "Siirtää olemassa olevia trickplay-tiedostoja kirjaston asetusten mukaan."
"TaskMoveTrickplayImagesDescription": "Siirtää olemassa olevia trickplay-tiedostoja kirjaston asetusten mukaan.",
"CleanupUserDataTask": "Käyttäjätietojen puhdistustehtävä",
"CleanupUserDataTaskDescription": "Puhdistaa kaikki käyttäjätiedot (katselutila, suosikit ym.) medioista, joita ei ole ollut saatavilla yli 90 päivään."
}

View File

@@ -135,5 +135,7 @@
"TaskUpdatePlugins": "Nuashonraigh Breiseáin",
"TaskCleanTranscodeDescription": "Scriostar comhaid traschódaithe níos mó ná lá amháin d'aois.",
"TaskCleanTranscode": "Eolaire Transcode Glan",
"TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh"
"TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh",
"CleanupUserDataTask": "Tasc glantacháin sonraí úsáideora",
"CleanupUserDataTaskDescription": "Glanann sé gach sonraí úsáideora (stádas faire, stádas is fearr leat srl.) ó mheáin nach bhfuil i láthair a thuilleadh ar feadh 90 lá ar a laghad."
}

View File

@@ -135,5 +135,6 @@
"TaskMoveTrickplayImages": "Migrar a localización da imaxe de Trickplay",
"TaskMoveTrickplayImagesDescription": "Move os ficheiros de reprodución con trickplay existentes segundo a configuración da biblioteca.",
"TaskRefreshTrickplayImages": "Xerar imaxes de Trickplay",
"TaskAudioNormalizationDescription": "Analiza ficheiros para obter datos de normalización de audio."
"TaskAudioNormalizationDescription": "Analiza ficheiros para obter datos de normalización de audio.",
"CleanupUserDataTask": "Tarefa de limpeza de datos do usuario"
}

View File

@@ -32,8 +32,8 @@
"LabelIpAddressValue": "Ip כתובת: {0}",
"LabelRunningTimeValue": "משך צפייה: {0}",
"Latest": "אחרון",
"MessageApplicationUpdated": "שרת ג'ליפין עודכן",
"MessageApplicationUpdatedTo": "שרת ג'ליפין עודכן לגרסה {0}",
"MessageApplicationUpdated": "שרת Jellyfin עודכן",
"MessageApplicationUpdatedTo": "שרת Jellyfin עודכן לגרסה {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "סעיף הגדרת השרת {0} עודכן",
"MessageServerConfigurationUpdated": "תצורת השרת עודכנה",
"MixedContent": "תוכן מעורב",
@@ -43,7 +43,7 @@
"NameInstallFailed": "התקנת {0} נכשלה",
"NameSeasonNumber": "עונה {0}",
"NameSeasonUnknown": "עונה לא ידועה",
"NewVersionIsAvailable": "גרסה חדשה של שרת ג'ליפין זמינה להורדה.",
"NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.",
"NotificationOptionApplicationUpdateAvailable": "קיים עדכון זמין ליישום",
"NotificationOptionApplicationUpdateInstalled": "עדכון ליישום הותקן",
"NotificationOptionAudioPlayback": "ניגון שמע החל",
@@ -72,7 +72,7 @@
"ServerNameNeedsToBeRestarted": "{0} דורש הפעלה מחדש",
"Shows": "סדרות",
"Songs": "שירים",
"StartupEmbyServerIsLoading": "שרת ג'ליפין טוען. נא לנסות שוב בקרוב.",
"StartupEmbyServerIsLoading": "שרת Jellyfin בתהליך טעינה. נא לנסות שוב בקרוב.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "הורדת כתוביות מ־{0} עבור {1} נכשלה",
"Sync": "סנכרון",
@@ -100,14 +100,14 @@
"TasksLibraryCategory": "ספרייה",
"TasksMaintenanceCategory": "תחזוקה",
"TaskUpdatePlugins": "עדכן תוספים",
"TaskRefreshPeopleDescription": "מעדכן מטא נתונים עבור שחקנים ובמאים בספריית המדיה שלך.",
"TaskRefreshPeopleDescription": "מעדכן מטא-דאטה עבור שחקנים ובמאים בספריית המדיה שלך.",
"TaskRefreshPeople": "רענן אנשים",
"TaskCleanLogsDescription": "מוחק קבצי יומן בני יותר מ- {0} ימים.",
"TaskCleanLogs": "ניקוי תיקיית יומן",
"TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא נתונים.",
"TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא-דאטה.",
"TaskRefreshChapterImagesDescription": "יוצר תמונות ממוזערות לסרטונים שיש להם פרקים.",
"TasksChannelsCategory": "ערוצי אינטרנט",
"TaskDownloadMissingSubtitlesDescription": "חפש באינטרנט עבור הכתוביות החסרות בהתבסס על המטהיאטה.",
"TaskDownloadMissingSubtitlesDescription": "חפש באינטרנט כתוביות חסרות בהתבסס על המטא-דאטה.",
"TaskDownloadMissingSubtitles": "הורד כתוביות חסרות",
"TaskRefreshChannelsDescription": "רענן פרטי ערוץ אינטרנטי.",
"TaskRefreshChannels": "רענן ערוץ",
@@ -136,5 +136,7 @@
"TaskMoveTrickplayImages": "העברת מיקום של תמונות Trickplay",
"TaskExtractMediaSegments": "סריקת מדיה",
"TaskExtractMediaSegmentsDescription": "מחלץ חלקי מדיה מתוספים המאפשרים זאת.",
"TaskMoveTrickplayImagesDescription": "הזזת קבצי Trickplay קיימים בהתאם להגדרות הספרייה."
"TaskMoveTrickplayImagesDescription": "הזזת קבצי Trickplay קיימים בהתאם להגדרות הספרייה.",
"CleanupUserDataTaskDescription": "ניקוי כל המידע של המשתמש (מצב צפייה, מועדפים וכו) ממדיה שאינה קיימת מעל 90 יום.",
"CleanupUserDataTask": "משימת ניקוי מידע משתמש"
}

View File

@@ -136,5 +136,7 @@
"TaskDownloadMissingLyricsDescription": "Zenék szövegének letöltése",
"TaskMoveTrickplayImages": "Trickplay képek helyének átköltöztetése",
"TaskMoveTrickplayImagesDescription": "A médiatár-beállításoknak megfelelően áthelyezi a meglévő trickplay fájlokat.",
"TaskExtractMediaSegmentsDescription": "Kinyeri vagy megszerzi a médiaszegmenseket a MediaSegment támogatással rendelkező bővítményekből."
"TaskExtractMediaSegmentsDescription": "Kinyeri vagy megszerzi a médiaszegmenseket a MediaSegment támogatással rendelkező bővítményekből.",
"CleanupUserDataTaskDescription": "Legalább 90 napja nem elérhető médiákhoz kapcsolódó összes felhasználói adat (pl. megtekintési állapot, kedvencek) törlése.",
"CleanupUserDataTask": "Felhasználói adatok tisztítása feladat"
}

View File

@@ -129,5 +129,13 @@
"TaskAudioNormalizationDescription": "Pindai file untuk data normalisasi audio.",
"TaskAudioNormalization": "Normalisasi Audio",
"TaskCleanCollectionsAndPlaylists": "Bersihkan koleksi dan daftar putar",
"TaskCleanCollectionsAndPlaylistsDescription": "Menghapus item dari koleksi dan daftar putar yang sudah tidak ada."
"TaskCleanCollectionsAndPlaylistsDescription": "Menghapus item dari koleksi dan daftar putar yang sudah tidak ada.",
"TaskDownloadMissingLyricsDescription": "Unduh lirik untuk lagu",
"TaskExtractMediaSegmentsDescription": "Mengekstrak atau memperoleh segmen media dari plugin yang mendukung MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Memindahkan file trickplay yang sudah ada sesuai dengan pengaturan pustaka.",
"CleanupUserDataTaskDescription": "Membersihkan semua data pengguna (status tontonan, status favorit, dll.) dari media yang sudah tidak ada selama setidaknya 90 hari.",
"TaskExtractMediaSegments": "Scan Segmen media",
"TaskMoveTrickplayImages": "Migrasikan Lokasi Gambar Trickplay",
"TaskDownloadMissingLyrics": "Unduh Lirik yang Hilang",
"CleanupUserDataTask": "Tugas Pembersihan Data Pengguna"
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImages": "Sposta le immagini Trickplay",
"TaskMoveTrickplayImagesDescription": "Sposta le immagini Trickplay esistenti secondo la configurazione della libreria.",
"TaskExtractMediaSegmentsDescription": "Estrae o ottiene segmenti multimediali dai plugin abilitati MediaSegment.",
"TaskExtractMediaSegments": "Scansiona Segmento Media"
"TaskExtractMediaSegments": "Scansiona Segmento Media",
"CleanupUserDataTask": "Task di pulizia dei dati utente",
"CleanupUserDataTaskDescription": "Pulisce tutti i dati utente (stato di visione, status preferiti, ecc.) dai contenuti non più presenti da almeno 90 giorni."
}

View File

@@ -135,5 +135,7 @@
"TaskMoveTrickplayImages": "Trickplayの画像を移動",
"TaskMoveTrickplayImagesDescription": "ライブラリ設定によりTrickplayのファイルを移動。",
"TaskDownloadMissingLyrics": "失われた歌詞をダウンロード",
"TaskExtractMediaSegmentsDescription": "MediaSegment 対応プラグインからメディア セグメントを抽出または取得します。"
"TaskExtractMediaSegmentsDescription": "MediaSegment 対応プラグインからメディア セグメントを抽出または取得します。",
"CleanupUserDataTask": "ユーザーデータのクリーンアップタスク",
"CleanupUserDataTaskDescription": "90日以上存在しないメディアに対して、視聴状態やお気に入り状態などのユーザーデータをすべて削除します。"
}

View File

@@ -25,7 +25,7 @@
"DeviceOfflineWithName": "{0} ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ",
"DeviceOnlineWithName": "{0} ಸಂಪರ್ಕಗೊಂಡಿದೆ",
"External": "ಹೊರಗಿನ",
"FailedLoginAttemptWithUserName": "{0} ರಿಂದ ವಿಫಲ ಲಾಗಿನ್ ಪ್ರಯತ್ನ",
"FailedLoginAttemptWithUserName": "ವಿಫಲ ಲಾಗಿನ್ ಪ್ರಯತ್ನ ಸಂಖ್ಯೆ {0}",
"Favorites": "ಮೆಚ್ಚಿನವುಗಳು",
"Folders": "ಫೋಲ್ಡರ್‌ಗಳು",
"Forced": "ಬಲವಂತವಾಗಿ",
@@ -123,5 +123,13 @@
"TaskUpdatePlugins": "ಪ್ಲಗಿನ್‌ಗಳನ್ನು ನವೀಕರಿಸಿ",
"TaskCleanTranscode": "ಟ್ರಾನ್ಸ್‌ಕೋಡ್ ಡೈರೆಕ್ಟರಿಯನ್ನು ಸ್ವಚ್ಛಗೊಳಿಸಿ",
"TaskRefreshChannels": "ಚಾನಲ್‌ಗಳನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಿ",
"TaskRefreshChannelsDescription": "ಇಂಟರ್ನೆಟ್ ಚಾನಲ್ ಮಾಹಿತಿಯನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡುತ್ತದೆ."
"TaskRefreshChannelsDescription": "ಇಂಟರ್ನೆಟ್ ಚಾನಲ್ ಮಾಹಿತಿಯನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡುತ್ತದೆ.",
"TaskAudioNormalizationDescription": "ಧ್ವನಿ ಸಾಮಾನ್ಯೀಕರಣ ಮಾಹಿತಿಗಾಗಿ ಕಡತ‌ಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತದೆ.",
"TaskDownloadMissingLyricsDescription": "ಹಾಡುಗಳಿಗೆ ಸಾಹಿತ್ಯ ಪಡೆಯಿರಿ",
"TaskExtractMediaSegments": "ಮಾಧ್ಯಮ ವಿಭಾಗದ ಹುಡುಕು",
"TaskDownloadMissingLyrics": "ಇಲ್ಲದ ಸಾಹಿತ್ಯವನ್ನು ಪಡೆಯಿರಿ",
"TaskAudioNormalization": "ಧ್ವನಿ ಸಾಮಾನ್ಯೀಕರಣ",
"TaskRefreshTrickplayImages": "ಟ್ರಿಕ್‌ಪ್ಲೇ ಚಿತ್ರಗಳನ್ನು ರಚಿಸಿ",
"TaskCleanCollectionsAndPlaylists": "ಸಂಗ್ರಹಗಳು ಮತ್ತು ಪ್ಲೇಪಟ್ಟಿಗಳನ್ನು ಸ್ವಚ್ಛಗೊಳಿಸಿ",
"TaskCleanCollectionsAndPlaylistsDescription": "ಇಲ್ಲದ ಸಂಗ್ರಹಗಳು ಮತ್ತು ಪ್ಲೇಪಟ್ಟಿಗಳಿಂದ ವಸ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ."
}

View File

@@ -130,5 +130,7 @@
"TaskExtractMediaSegments": "मिडिया विभाग तपासणी",
"TaskMoveTrickplayImages": "ट्रिकप्ले प्रतिमेचे स्थान स्थलांतर करा",
"TaskDownloadMissingLyrics": "उपलब्ध नसलेली गीतपट्टी (Lyrics) डाउनलोड करा",
"TaskAudioNormalization": "ऑडिओ सामान्यीकरण"
"TaskAudioNormalization": "ऑडिओ सामान्यीकरण",
"TaskAudioNormalizationDescription": "ऑडिओ सामान्यीकरणाचा डाटा स्कॅन करतो.",
"TaskDownloadMissingLyricsDescription": "गाण्यांची गीतपट्टी (Lyrics) डाउनलोड करतो"
}

View File

@@ -136,5 +136,7 @@
"TaskCleanCollectionsAndPlaylists": "Bersihkan koleksi dan senarai audio video",
"TaskAudioNormalization": "Normalisasi Audio",
"TaskAudioNormalizationDescription": "Mengimbas fail-fail untuk data normalisasi audio.",
"TaskCleanCollectionsAndPlaylistsDescription": "Mengalih keluar item daripada koleksi dan senarai audio video yang tidak wujud lagi."
"TaskCleanCollectionsAndPlaylistsDescription": "Mengalih keluar item daripada koleksi dan senarai audio video yang tidak wujud lagi.",
"CleanupUserDataTaskDescription": "Membersihkan semua data pengguna (keadaan tontonan, status kegemaran, dan sebagainya) daripada media yang tidak lagi wujud sekurang-kurangnya selama 90 hari.",
"CleanupUserDataTask": "Tugas pembersihan data pengguna"
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImagesDescription": "Move os arquivos do trickplay de acordo com as configurações da biblioteca.",
"TaskExtractMediaSegments": "Varredura do segmento de mídia",
"TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de mídia de plug-ins habilitados para MediaSegment.",
"TaskMoveTrickplayImages": "Migrar o local da imagem do Trickplay"
"TaskMoveTrickplayImages": "Migrar o local da imagem do Trickplay",
"CleanupUserDataTask": "Tarefa de limpeza de dados do usuário",
"CleanupUserDataTaskDescription": "Limpa todos os dados do usuário (estado de visualização, status de favorito, etc.) de mídias que não estão presentes por pelo menos 90 dias."
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImages": "Migrar a localização da imagem do Trickplay",
"TaskDownloadMissingLyricsDescription": "Transferir letra para músicas",
"TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de multimédia a partir de plugins com suporte para MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Move os ficheiros trickplay existentes de acordo com as definições da mediateca."
"TaskMoveTrickplayImagesDescription": "Move os ficheiros trickplay existentes de acordo com as definições da mediateca.",
"CleanupUserDataTaskDescription": "Apaga todos os dados de utilizador (estados de reprodução, favoritos, etc) de arquivos média não presentes há 90 dias ou mais.",
"CleanupUserDataTask": "Limpeza de dados de utilizador"
}

View File

@@ -98,7 +98,7 @@
"TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.",
"TaskCleanTranscode": "Curățați directorul de transcodare",
"TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru extensiile care sunt configurate să se actualizeze automat.",
"TaskUpdatePlugins": "Actualizați Extensile",
"TaskUpdatePlugins": "Actualizați Extensiile",
"TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.",
"TaskRefreshPeople": "Actualizează Persoanele",
"TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.",
@@ -135,5 +135,7 @@
"TaskExtractMediaSegmentsDescription": "Extrage sau obține segmentele media de la pluginurile MediaSegment activate.",
"TaskMoveTrickplayImages": "Migrează locația imaginii Trickplay",
"TaskDownloadMissingLyrics": "Descarcă versurile lipsă",
"TaskDownloadMissingLyricsDescription": "Descarcă versuri pentru melodii"
"TaskDownloadMissingLyricsDescription": "Descarcă versuri pentru melodii",
"CleanupUserDataTask": "Sarcina de curatare a datelor utilizatorului",
"CleanupUserDataTaskDescription": "Sterge toate datele utilizatorului (starea vizionarii, starea favoritelor etc.) de pe suporturile media care nu mai sunt prezente timp de cel puțin 90 de zile."
}

View File

@@ -138,5 +138,5 @@
"TaskExtractMediaSegmentsDescription": "Извлекает или получает медиасегменты из плагинов MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Перемещает существующие файлы trickplay в соответствии с настройками медиатеки.",
"CleanupUserDataTask": "Задача очистки пользовательских данных",
"CleanupUserDataTaskDescription": "Очищает все пользовательские данные (состояние просмотра, статус избранного и т.д.) с носителей, на которых больше нет информации, по крайней мере, в течение 90 дней."
"CleanupUserDataTaskDescription": "Очищает все пользовательские данные (состояние просмотра, статус избранного и т.д.) с медиа, отсутствующих по меньшей мере в течение 90 дней."
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImages": "Presunúť umiestnenie obrázkov Trickplay",
"TaskMoveTrickplayImagesDescription": "Presunie existujúce súbory Trickplay podľa nastavení knižnice.",
"TaskDownloadMissingLyrics": "Stiahnuť chýbajúce texty piesní",
"TaskDownloadMissingLyricsDescription": "Stiahne texty pre piesne"
"TaskDownloadMissingLyricsDescription": "Stiahne texty pre piesne",
"CleanupUserDataTask": "Prečistiť používateľské dáta",
"CleanupUserDataTaskDescription": "Vyčistí všetky dáta používateľa (stav sledovania, stav obľúbených atď.) z médií, ktoré už neexistujú aspoň 90 dní."
}

View File

@@ -21,7 +21,7 @@
"Inherit": "மரபுரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
"Folders": "கோப்புறைகள்",
"FailedLoginAttemptWithUserName": "{0} இன் உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
"Collections": "தொகுப்புகள்",
@@ -133,5 +133,9 @@
"TaskDownloadMissingLyrics": "விடுபட்ட பாடல் வரிகளைப் பதிவிறக்கவும்",
"TaskDownloadMissingLyricsDescription": "பாடல்களுக்கான வரிகளைப் பதிவிறக்குகிறது",
"TaskMoveTrickplayImages": "ட்ரிக்பிளே பட இருப்பிடத்தை நகர்த்து",
"TaskMoveTrickplayImagesDescription": "நூலக அமைப்புகளுக்கு ஏற்ப ஏற்கனவே உள்ள ட்ரிக்பிளே கோப்புகளை நகர்த்துகிறது."
"TaskMoveTrickplayImagesDescription": "நூலக அமைப்புகளுக்கு ஏற்ப ஏற்கனவே உள்ள ட்ரிக்பிளே கோப்புகளை நகர்த்துகிறது.",
"TaskExtractMediaSegments": "மீடியா பிரிவு ஸ்கேன்",
"TaskExtractMediaSegmentsDescription": "மீடியாசெக்மென்ட் இயக்கப்பட்ட செருகுநிரல்களிலிருந்து மீடியா பிரிவுகளைப் பிரித்தெடுக்கிறது அல்லது பெறுகிறது.",
"CleanupUserDataTaskDescription": "குறைந்தது 90 நாட்களுக்கு இல்லாத மீடியாவிலிருந்து அனைத்து பயனர் தரவையும் (கண்காணிப்பு நிலை, பிடித்த நிலை போன்றவை) சுத்தம் செய்கிறது.",
"CleanupUserDataTask": "பயனர் தரவை சுத்தம் செய்யும் பணி"
}

View File

@@ -136,5 +136,7 @@
"TaskMoveTrickplayImagesDescription": "Mevcut trickplay dosyalarını kütüphane ayarlarına göre taşır.",
"TaskDownloadMissingLyrics": "Eksik şarkı sözlerini indir",
"TaskDownloadMissingLyricsDescription": "Şarkı sözlerini indirir",
"TaskExtractMediaSegmentsDescription": "MediaSegment özelliği etkin olan eklentilerden medya segmentlerini çıkarır veya alır."
"TaskExtractMediaSegmentsDescription": "MediaSegment özelliği etkin olan eklentilerden medya segmentlerini çıkarır veya alır.",
"CleanupUserDataTask": "Kullanıcı verisi temizleme görevi",
"CleanupUserDataTaskDescription": "En az 90 gün boyunca artık mevcut olmayan medyadaki tüm kullanıcı verilerini (İzleme durumu, favori durumu vb.) temizler."
}

View File

@@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.Plugins
Overview = packageInfo.Overview,
Owner = packageInfo.Owner,
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture),
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
Version = versionInfo.Version,
Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state.
AutoUpdate = true,

View File

@@ -474,6 +474,7 @@ namespace Emby.Server.Implementations.Session
private void RemoveNowPlayingItem(SessionInfo session)
{
session.NowPlayingItem = null;
session.FullNowPlayingItem = null;
session.PlayState = new PlayerStateInfo();
if (!string.IsNullOrEmpty(session.DeviceId))

View File

@@ -5,7 +5,6 @@ using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{

View File

@@ -256,7 +256,7 @@ public sealed class BaseItemRepository
dbQuery = ApplyGroupingFilter(dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToArray();
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
result.StartIndex = filter.StartIndex ?? 0;
return result;
}
@@ -275,7 +275,7 @@ public sealed class BaseItemRepository
dbQuery = ApplyGroupingFilter(dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToArray();
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
}
/// <inheritdoc/>
@@ -317,7 +317,7 @@ public sealed class BaseItemRepository
mainquery = ApplyGroupingFilter(mainquery, filter);
mainquery = ApplyQueryPaging(mainquery, filter);
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToArray();
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
}
/// <inheritdoc />
@@ -540,7 +540,7 @@ public sealed class BaseItemRepository
}
var itemValueMaps = tuples
.Select(e => (Item: e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
.Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
.ToArray();
var allListedItemValues = itemValueMaps
.SelectMany(f => f.Values)
@@ -567,7 +567,7 @@ public sealed class BaseItemRepository
var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
var valueMap = itemValueMaps
.Select(f => (Item: f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).ToArray()))
.Select(f => (f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).DistinctBy(e => e.ItemValueId).ToArray()))
.ToArray();
var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
@@ -655,7 +655,7 @@ public sealed class BaseItemRepository
return null;
}
return DeserialiseBaseItem(item);
return DeserializeBaseItem(item);
}
/// <summary>
@@ -701,12 +701,12 @@ public sealed class BaseItemRepository
dto.TotalBitrate = entity.TotalBitrate;
dto.ExternalId = entity.ExternalId;
dto.Size = entity.Size;
dto.Genres = entity.Genres?.Split('|') ?? [];
dto.DateCreated = entity.DateCreated.GetValueOrDefault();
dto.DateModified = entity.DateModified.GetValueOrDefault();
dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
dto.ChannelId = entity.ChannelId ?? Guid.Empty;
dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty);
dto.Width = entity.Width.GetValueOrDefault();
dto.Height = entity.Height.GetValueOrDefault();
@@ -733,7 +733,7 @@ public sealed class BaseItemRepository
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
dto.Studios = entity.Studios?.Split('|') ?? [];
dto.Tags = entity.Tags?.Split('|') ?? [];
dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
if (dto is IHasProgramAttributes hasProgramAttributes)
{
@@ -807,7 +807,7 @@ public sealed class BaseItemRepository
if (dto is Folder folder)
{
folder.DateLastMediaAdded = entity.DateLastMediaAdded;
folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
}
return dto;
@@ -867,11 +867,11 @@ public sealed class BaseItemRepository
entity.ExternalId = dto.ExternalId;
entity.Size = dto.Size;
entity.Genres = string.Join('|', dto.Genres);
entity.DateCreated = dto.DateCreated;
entity.DateModified = dto.DateModified;
entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
entity.ChannelId = dto.ChannelId;
entity.DateLastRefreshed = dto.DateLastRefreshed;
entity.DateLastSaved = dto.DateLastSaved;
entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
entity.OwnerId = dto.OwnerId.ToString();
entity.Width = dto.Width;
entity.Height = dto.Height;
@@ -981,7 +981,7 @@ public sealed class BaseItemRepository
if (dto is Folder folder)
{
entity.DateLastMediaAdded = folder.DateLastMediaAdded;
entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdded;
entity.IsFolder = folder.IsFolder;
}
@@ -1017,7 +1017,7 @@ public sealed class BaseItemRepository
return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
}
private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
private BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
{
ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
if (_serverConfigurationManager?.Configuration is null)
@@ -1026,7 +1026,7 @@ public sealed class BaseItemRepository
}
var typeToSerialise = GetType(baseItemEntity.Type);
return BaseItemRepository.DeserialiseBaseItem(
return BaseItemRepository.DeserializeBaseItem(
baseItemEntity,
_logger,
_appHost,
@@ -1034,7 +1034,7 @@ public sealed class BaseItemRepository
}
/// <summary>
/// Deserialises a BaseItemEntity and sets all properties.
/// Deserializes a BaseItemEntity and sets all properties.
/// </summary>
/// <param name="baseItemEntity">The DB entity.</param>
/// <param name="logger">Logger.</param>
@@ -1042,9 +1042,9 @@ public sealed class BaseItemRepository
/// <param name="skipDeserialization">If only mapping should be processed.</param>
/// <returns>A mapped BaseItem.</returns>
/// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
public static BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost? appHost, bool skipDeserialization = false)
public static BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost? appHost, bool skipDeserialization = false)
{
var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unknown type.");
var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialize unknown type.");
BaseItemDto? dto = null;
if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
{
@@ -1060,7 +1060,7 @@ public sealed class BaseItemRepository
if (dto is null)
{
dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialise unknown type.");
dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialize unknown type.");
}
return Map(baseItemEntity, dto, appHost);
@@ -1206,7 +1206,7 @@ public sealed class BaseItemRepository
.Where(e => e is not null)
.Select(e =>
{
return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
return (DeserializeBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
})
];
}
@@ -1221,7 +1221,7 @@ public sealed class BaseItemRepository
.Where(e => e is not null)
.Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
{
return (DeserialiseBaseItem(e, filter.SkipDeserialization), null);
return (DeserializeBaseItem(e, filter.SkipDeserialization), null);
})
];
}
@@ -1302,7 +1302,7 @@ public sealed class BaseItemRepository
{
Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
DateModified = e.DateModified,
DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
Height = e.Height,
Width = e.Width,
Type = (ImageType)e.ImageType

View File

@@ -253,7 +253,7 @@ namespace Jellyfin.Server.Extensions
c.AddSwaggerTypeMappings();
c.SchemaFilter<IgnoreEnumSchemaFilter>();
c.OperationFilter<RetryOnTemporarlyUnavailableFilter>();
c.OperationFilter<RetryOnTemporarilyUnavailableFilter>();
c.OperationFilter<SecurityRequirementsOperationFilter>();
c.OperationFilter<FileResponseFilter>();
c.OperationFilter<FileRequestFilter>();

View File

@@ -6,13 +6,13 @@ using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters;
internal class RetryOnTemporarlyUnavailableFilter : IOperationFilter
internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Responses.Add("503", new OpenApiResponse()
{
Description = "The server is currently starting or is temporarly not available.",
Description = "The server is currently starting or is temporarily not available.",
Headers = new Dictionary<string, OpenApiHeader>()
{
{

View File

@@ -53,6 +53,7 @@
<PackageReference Include="prometheus-net.AspNetCore" />
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.Enrichers.Thread" />
<PackageReference Include="Serilog.Expressions" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.Console" />

View File

@@ -0,0 +1,168 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Server.ServerSetupApp;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines;
/// <summary>
/// Migration to fix dates saved in the database to always be UTC.
/// </summary>
[JellyfinMigration("2025-06-20T18:00:00", nameof(FixDates))]
public class FixDates : IAsyncMigrationRoutine
{
private const int PageSize = 5000;
private readonly ILogger _logger;
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="FixDates"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="startupLogger">The startup logger for Startup UI integration.</param>
/// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
public FixDates(
ILogger<FixDates> logger,
IStartupLogger<FixDates> startupLogger,
IDbContextFactory<JellyfinDbContext> dbProvider)
{
_logger = startupLogger.With(logger);
_dbProvider = dbProvider;
}
/// <inheritdoc />
public async Task PerformAsync(CancellationToken cancellationToken)
{
if (!TimeZoneInfo.Local.Equals(TimeZoneInfo.Utc))
{
using var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
var sw = Stopwatch.StartNew();
await FixBaseItemsAsync(context, sw, cancellationToken).ConfigureAwait(false);
sw.Reset();
await FixChaptersAsync(context, sw, cancellationToken).ConfigureAwait(false);
sw.Reset();
await FixBaseItemImageInfos(context, sw, cancellationToken).ConfigureAwait(false);
}
}
private async Task FixBaseItemsAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
{
int itemCount = 0;
var baseQuery = context.BaseItems.OrderBy(e => e.Id);
var records = baseQuery.Count();
_logger.LogInformation("Fixing dates for {Count} BaseItems.", records);
sw.Start();
await foreach (var result in context.BaseItems.OrderBy(e => e.Id)
.WithPartitionProgress(
(partition) =>
_logger.LogInformation(
"Processing BaseItems batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
partition + 1,
Math.Min((partition + 1) * PageSize, records),
records,
sw.Elapsed))
.PartitionEagerAsync(PageSize, cancellationToken)
.WithCancellation(cancellationToken)
.ConfigureAwait(false))
{
result.DateCreated = ToUniversalTime(result.DateCreated);
result.DateLastMediaAdded = ToUniversalTime(result.DateLastMediaAdded);
result.DateLastRefreshed = ToUniversalTime(result.DateLastRefreshed);
result.DateLastSaved = ToUniversalTime(result.DateLastSaved);
result.DateModified = ToUniversalTime(result.DateModified);
itemCount++;
}
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
_logger.LogInformation("BaseItems: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
}
private async Task FixChaptersAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
{
int itemCount = 0;
var baseQuery = context.Chapters;
var records = baseQuery.Count();
_logger.LogInformation("Fixing dates for {Count} Chapters.", records);
sw.Start();
await foreach (var result in context.Chapters.OrderBy(e => e.ItemId)
.WithPartitionProgress(
(partition) =>
_logger.LogInformation(
"Processing Chapter batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
partition + 1,
Math.Min((partition + 1) * PageSize, records),
records,
sw.Elapsed))
.PartitionEagerAsync(PageSize, cancellationToken)
.WithCancellation(cancellationToken)
.ConfigureAwait(false))
{
result.ImageDateModified = ToUniversalTime(result.ImageDateModified, true);
itemCount++;
}
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Chapters: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
}
private async Task FixBaseItemImageInfos(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
{
int itemCount = 0;
var baseQuery = context.BaseItemImageInfos;
var records = baseQuery.Count();
_logger.LogInformation("Fixing dates for {Count} BaseItemImageInfos.", records);
sw.Start();
await foreach (var result in context.BaseItemImageInfos.OrderBy(e => e.Id)
.WithPartitionProgress(
(partition) =>
_logger.LogInformation(
"Processing BaseItemImageInfos batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
partition + 1,
Math.Min((partition + 1) * PageSize, records),
records,
sw.Elapsed))
.PartitionEagerAsync(PageSize, cancellationToken)
.WithCancellation(cancellationToken)
.ConfigureAwait(false))
{
result.DateModified = ToUniversalTime(result.DateModified);
itemCount++;
}
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
_logger.LogInformation("BaseItemImageInfos: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
}
private DateTime? ToUniversalTime(DateTime? dateTime, bool isUTC = false)
{
if (dateTime is null)
{
return null;
}
if (dateTime.Value.Year == 1 && dateTime.Value.Month == 1 && dateTime.Value.Day == 1)
{
return null;
}
if (dateTime.Value.Kind == DateTimeKind.Utc || isUTC)
{
return dateTime.Value;
}
return dateTime.Value.ToUniversalTime();
}
}

View File

@@ -94,7 +94,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
connection.Open();
var baseItemIds = new HashSet<Guid>();
using (var operation = GetPreparedDbContext("moving TypedBaseItem"))
using (var operation = GetPreparedDbContext("Moving TypedBaseItem"))
{
const string typedBaseItemsQuery =
"""
@@ -121,13 +121,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.BaseItems.Local.Count} BaseItem entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.BaseItems.Local.Count} BaseItem entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving ItemValues"))
using (var operation = GetPreparedDbContext("Moving ItemValues"))
{
// do not migrate inherited types as they are now properly mapped in search and lookup.
const string itemValueQuery =
@@ -138,7 +138,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
// EFCores local lookup sucks. We cannot use context.ItemValues.Local here because its just super slow.
var localItems = new Dictionary<(int Type, string Value), (Database.Implementations.Entities.ItemValue ItemValue, List<Guid> ItemIds)>();
using (new TrackedMigrationStep("loading ItemValues", _logger))
using (new TrackedMigrationStep("Loading ItemValues", _logger))
{
foreach (SqliteDataReader dto in connection.Query(itemValueQuery))
{
@@ -166,13 +166,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.ItemValues.Local.Count} ItemValues entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.ItemValues.Local.Count} ItemValues entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving UserData"))
using (var operation = GetPreparedDbContext("Moving UserData"))
{
var queryResult = connection.Query(
"""
@@ -181,14 +181,14 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.UserDataKey = UserDatas.key)
""");
using (new TrackedMigrationStep("loading UserData", _logger))
using (new TrackedMigrationStep("Loading UserData", _logger))
{
var users = operation.JellyfinDbContext.Users.AsNoTracking().ToImmutableArray();
var users = operation.JellyfinDbContext.Users.AsNoTracking().ToArray();
var userIdBlacklist = new HashSet<int>();
foreach (var entity in queryResult)
{
var userData = GetUserData(users, entity, userIdBlacklist);
var userData = GetUserData(users, entity, userIdBlacklist, _logger);
if (userData is null)
{
var userDataId = entity.GetString(0);
@@ -212,19 +212,17 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
userData.ItemId = refItem.Id;
operation.JellyfinDbContext.UserData.Add(userData);
}
users.Clear();
}
legacyBaseItemWithUserKeys.Clear();
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.UserData.Local.Count} UserData entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.UserData.Local.Count} UserData entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving MediaStreamInfos"))
using (var operation = GetPreparedDbContext("Moving MediaStreamInfos"))
{
const string mediaStreamQuery =
"""
@@ -237,7 +235,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = MediaStreams.ItemId)
""";
using (new TrackedMigrationStep("loading MediaStreamInfos", _logger))
using (new TrackedMigrationStep("Loading MediaStreamInfos", _logger))
{
foreach (SqliteDataReader dto in connection.Query(mediaStreamQuery))
{
@@ -245,13 +243,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.MediaStreamInfos.Local.Count} MediaStreamInfos entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.MediaStreamInfos.Local.Count} MediaStreamInfos entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving AttachmentStreamInfos"))
using (var operation = GetPreparedDbContext("Moving AttachmentStreamInfos"))
{
const string mediaAttachmentQuery =
"""
@@ -260,7 +258,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = mediaattachments.ItemId)
""";
using (new TrackedMigrationStep("loading AttachmentStreamInfos", _logger))
using (new TrackedMigrationStep("Loading AttachmentStreamInfos", _logger))
{
foreach (SqliteDataReader dto in connection.Query(mediaAttachmentQuery))
{
@@ -268,13 +266,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.AttachmentStreamInfos.Local.Count} AttachmentStreamInfos entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.AttachmentStreamInfos.Local.Count} AttachmentStreamInfos entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving People"))
using (var operation = GetPreparedDbContext("Moving People"))
{
const string personsQuery =
"""
@@ -284,14 +282,14 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
var peopleCache = new Dictionary<string, (People Person, List<PeopleBaseItemMap> Items)>();
using (new TrackedMigrationStep("loading People", _logger))
using (new TrackedMigrationStep("Loading People", _logger))
{
foreach (SqliteDataReader reader in connection.Query(personsQuery))
{
var itemId = reader.GetGuid(0);
if (!baseItemIds.Contains(itemId))
{
_logger.LogError("Dont save person {0} because its not in use by any BaseItem", reader.GetString(1));
_logger.LogError("Not saving person {0} because it's not in use by any BaseItem", reader.GetString(1));
continue;
}
@@ -330,13 +328,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
peopleCache.Clear();
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.Peoples.Local.Count} People entries and {operation.JellyfinDbContext.PeopleBaseItemMap.Local.Count} maps", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.Peoples.Local.Count} People entries and {operation.JellyfinDbContext.PeopleBaseItemMap.Local.Count} maps", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving Chapters"))
using (var operation = GetPreparedDbContext("Moving Chapters"))
{
const string chapterQuery =
"""
@@ -344,7 +342,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = Chapters2.ItemId)
""";
using (new TrackedMigrationStep("loading Chapters", _logger))
using (new TrackedMigrationStep("Loading Chapters", _logger))
{
foreach (SqliteDataReader dto in connection.Query(chapterQuery))
{
@@ -353,13 +351,13 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.Chapters.Local.Count} Chapters entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.Chapters.Local.Count} Chapters entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
}
using (var operation = GetPreparedDbContext("moving AncestorIds"))
using (var operation = GetPreparedDbContext("Moving AncestorIds"))
{
const string ancestorIdsQuery =
"""
@@ -370,7 +368,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.AncestorId)
""";
using (new TrackedMigrationStep("loading AncestorIds", _logger))
using (new TrackedMigrationStep("Loading AncestorIds", _logger))
{
foreach (SqliteDataReader dto in connection.Query(ancestorIdsQuery))
{
@@ -379,7 +377,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
}
}
using (new TrackedMigrationStep($"saving {operation.JellyfinDbContext.AncestorIds.Local.Count} AncestorId entries", _logger))
using (new TrackedMigrationStep($"Saving {operation.JellyfinDbContext.AncestorIds.Local.Count} AncestorId entries", _logger))
{
operation.JellyfinDbContext.SaveChanges();
}
@@ -404,19 +402,20 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
return new DatabaseMigrationStep(dbContext, operationName, _logger);
}
private UserData? GetUserData(ImmutableArray<User> users, SqliteDataReader dto, HashSet<int> userIdBlacklist)
internal static UserData? GetUserData(User[] users, SqliteDataReader dto, HashSet<int> userIdBlacklist, ILogger logger)
{
var internalUserId = dto.GetInt32(1);
var user = users.FirstOrDefault(e => e.InternalId == internalUserId);
if (userIdBlacklist.Contains(internalUserId))
{
return null;
}
var user = users.FirstOrDefault(e => e.InternalId == internalUserId);
if (user is null)
{
if (userIdBlacklist.Contains(internalUserId))
{
return null;
}
userIdBlacklist.Add(internalUserId);
_logger.LogError("Tried to find user with index '{Idx}' but there are only '{MaxIdx}' users.", internalUserId, users.Length);
logger.LogError("Tried to find user with index '{Idx}' but there are only '{MaxIdx}' users.", internalUserId, users.Length);
return null;
}
@@ -1168,7 +1167,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
entity.UnratedType = unratedType;
}
var baseItem = BaseItemRepository.DeserialiseBaseItem(entity, _logger, null, false);
var baseItem = BaseItemRepository.DeserializeBaseItem(entity, _logger, null, false);
var dataKeys = baseItem.GetUserDataKeys();
userDataKeys.AddRange(dataKeys);

View File

@@ -0,0 +1,107 @@
#pragma warning disable RS0030 // Do not use banned APIs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Server.Implementations.Item;
using Jellyfin.Server.ServerSetupApp;
using MediaBrowser.Controller;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines;
[JellyfinMigration("2025-06-18T01:00:00", nameof(MigrateLibraryUserData))]
[JellyfinMigrationBackup(JellyfinDb = true)]
internal class MigrateLibraryUserData : IAsyncMigrationRoutine
{
private const string DbFilename = "library.db.old";
private readonly IStartupLogger _logger;
private readonly IServerApplicationPaths _paths;
private readonly IDbContextFactory<JellyfinDbContext> _provider;
public MigrateLibraryUserData(
IStartupLogger<MigrateLibraryDb> startupLogger,
IDbContextFactory<JellyfinDbContext> provider,
IServerApplicationPaths paths)
{
_logger = startupLogger;
_provider = provider;
_paths = paths;
}
public async Task PerformAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Migrating the userdata from library.db.old may take a while, do not stop Jellyfin.");
var dataPath = _paths.DataPath;
var libraryDbPath = Path.Combine(dataPath, DbFilename);
if (!File.Exists(libraryDbPath))
{
_logger.LogError("Cannot migrate userdata from {LibraryDb} as it does not exist. This migration expects the MigrateLibraryDb to run first.", libraryDbPath);
return;
}
var dbContext = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
if (!await dbContext.BaseItems.AnyAsync(e => e.Id == BaseItemRepository.PlaceholderId, cancellationToken).ConfigureAwait(false))
{
// the placeholder baseitem has been deleted by the librarydb migration so we need to readd it.
await dbContext.BaseItems.AddAsync(
new Database.Implementations.Entities.BaseItemEntity()
{
Id = BaseItemRepository.PlaceholderId,
Type = "PLACEHOLDER",
Name = "This is a placeholder item for UserData that has been detacted from its original item"
},
cancellationToken)
.ConfigureAwait(false);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
var users = dbContext.Users.AsNoTracking().ToArray();
var userIdBlacklist = new HashSet<int>();
using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly");
var retentionDate = DateTime.UtcNow;
var queryResult = connection.Query(
"""
SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas
WHERE NOT EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.UserDataKey = UserDatas.key)
""");
foreach (var entity in queryResult)
{
var userData = MigrateLibraryDb.GetUserData(users, entity, userIdBlacklist, _logger);
if (userData is null)
{
var userDataId = entity.GetString(0);
var internalUserId = entity.GetInt32(1);
if (!userIdBlacklist.Contains(internalUserId))
{
_logger.LogError("Was not able to migrate user data with key {0} because its id {InternalId} does not match any existing user.", userDataId, internalUserId);
userIdBlacklist.Add(internalUserId);
}
continue;
}
userData.ItemId = BaseItemRepository.PlaceholderId;
userData.RetentionDate = retentionDate;
dbContext.UserData.Add(userData);
}
_logger.LogInformation("Try saving {NewSaved} UserData entries.", dbContext.UserData.Local.Count);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
}
}

View File

@@ -82,7 +82,7 @@ internal class CodeMigration(Type migrationType, JellyfinMigrationAttribute meta
}
}
private class NestedStartupLogger<TCategory> : StartupLogger<TCategory>, IStartupLogger<TCategory>
private class NestedStartupLogger<TCategory> : StartupLogger<TCategory>
{
public NestedStartupLogger(ILogger logger, StartupLogTopic topic) : base(logger, topic)
{

View File

@@ -19,7 +19,6 @@ using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
@@ -29,6 +28,8 @@ using Microsoft.Extensions.Primitives;
using Morestachio;
using Morestachio.Framework.IO.SingleStream;
using Morestachio.Rendering;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Jellyfin.Server.ServerSetupApp;
@@ -143,8 +144,10 @@ public sealed class SetupServer : IDisposable
var config = _configurationManager.GetNetworkConfiguration()!;
_startupServer = Host.CreateDefaultBuilder(["hostBuilder:reloadConfigOnChange=false"])
.UseConsoleLifetime()
.UseSerilog()
.ConfigureServices(serv =>
{
serv.AddSingleton(this);
serv.AddHealthChecks()
.AddCheck<SetupHealthcheck>("StartupCheck");
serv.Configure<ForwardedHeadersOptions>(options =>

View File

@@ -1423,23 +1423,16 @@ namespace MediaBrowser.Controller.Entities
public virtual bool RequiresRefresh()
{
if (string.IsNullOrEmpty(Path) || DateModified == default)
if (string.IsNullOrEmpty(Path) || DateModified == DateTime.MinValue)
{
return false;
}
var info = FileSystem.GetFileSystemInfo(Path);
if (info.Exists)
{
if (info.IsDirectory)
{
return info.LastWriteTimeUtc != DateModified;
}
return info.LastWriteTimeUtc != DateModified;
}
return false;
return info.Exists
? info.LastWriteTimeUtc != DateModified
: false;
}
public virtual List<string> GetUserDataKeys()

View File

@@ -695,7 +695,7 @@ namespace MediaBrowser.Controller.Entities
items = GetRecursiveChildren(user, query);
}
return PostFilterAndSort(items, query, true);
return PostFilterAndSort(items, query);
}
if (this is not UserRootFolder
@@ -959,10 +959,10 @@ namespace MediaBrowser.Controller.Entities
items = GetChildren(user, true, childQuery).Where(filter);
}
return PostFilterAndSort(items, query, true);
return PostFilterAndSort(items, query);
}
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
{
var user = query.User;
@@ -995,7 +995,7 @@ namespace MediaBrowser.Controller.Entities
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager);
}
private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(

View File

@@ -179,7 +179,7 @@ namespace MediaBrowser.Controller.Entities.TV
var items = GetEpisodes(user, query.DtoOptions, true).Where(filter);
return PostFilterAndSort(items, query, false);
return PostFilterAndSort(items, query);
}
/// <summary>

View File

@@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Entities
PresetViews = query.PresetViews
});
return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true);
return UserViewBuilder.SortAndPage(result, null, query, LibraryManager);
}
public override int GetChildCount(User user)

View File

@@ -438,22 +438,18 @@ namespace MediaBrowser.Controller.Entities
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
return SortAndPage(items, totalRecordLimit, query, libraryManager);
}
public static QueryResult<BaseItem> SortAndPage(
IEnumerable<BaseItem> items,
int? totalRecordLimit,
InternalItemsQuery query,
ILibraryManager libraryManager,
bool enableSorting)
ILibraryManager libraryManager)
{
if (enableSorting)
if (query.OrderBy.Count > 0)
{
if (query.OrderBy.Count > 0)
{
items = libraryManager.Sort(items, query.User, query.OrderBy);
}
items = libraryManager.Sort(items, query.User, query.OrderBy);
}
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();

View File

@@ -96,14 +96,33 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
}
}
private bool ShouldForceSequentialOperation()
{
// if the user either set the setting to 1 or it's unset and we have fewer than 4 cores it's better to run sequentially.
var fanoutSetting = _serverConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
return fanoutSetting == 1 || (fanoutSetting <= 0 && Environment.ProcessorCount <= 3);
}
private int CalculateScanConcurrencyLimit()
{
// when this is invoked, we already checked ShouldForceSequentialOperation for the sequential check.
var fanoutConcurrency = _serverConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
if (fanoutConcurrency <= 0)
{
// in case the user did not set a limit manually, we can assume he has 3 or more cores as already checked by ShouldForceSequentialOperation.
return Environment.ProcessorCount - 3;
}
return fanoutConcurrency;
}
private void Worker()
{
lock (_taskLock)
{
var fanoutConcurrency = _serverConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
var parallelism = (fanoutConcurrency > 0 ? fanoutConcurrency : Environment.ProcessorCount) - _taskRunners.Count;
_logger.LogDebug("Spawn {NumberRunners} new runners.", parallelism);
for (int i = 0; i < parallelism; i++)
var operationFanout = Math.Max(0, CalculateScanConcurrencyLimit() - _taskRunners.Count);
_logger.LogDebug("Spawn {NumberRunners} new runners.", operationFanout);
for (int i = 0; i < operationFanout; i++)
{
var stopToken = new CancellationTokenSource();
var combinedSource = CancellationTokenSource.CreateLinkedTokenSource(stopToken.Token, _hostApplicationLifetime.ApplicationStopping);
@@ -223,7 +242,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
};
}).ToArray();
if (_serverConfigurationManager.Configuration.LibraryScanFanoutConcurrency == 1)
if (ShouldForceSequentialOperation())
{
_logger.LogDebug("Process sequentially.");
try

View File

@@ -230,10 +230,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var hwType = encodingOptions.HardwareAccelerationType;
// Only Intel has VA-API MJPEG encoder
// Only enable VA-API MJPEG encoder on Intel iHD driver.
// Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
if (hwType == HardwareAccelerationType.vaapi
&& !(_mediaEncoder.IsVaapiDeviceInteliHD
|| _mediaEncoder.IsVaapiDeviceInteli965))
&& !_mediaEncoder.IsVaapiDeviceInteliHD)
{
return _defaultMjpegEncoder;
}
@@ -2390,6 +2390,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
|| (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
{
// If the video stream is in a static HDR format, don't allow copy if the client does not support HDR10 or HLG.
if (videoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG)
{
return false;
}
// Check complicated cases where we need to remove dynamic metadata
// Conservatively refuse to copy if the encoder can't remove dynamic metadata,
// but a removal is required for compatability reasons.
@@ -4442,6 +4448,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var swapOutputWandH = doVppTranspose && swapWAndH;
var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
// d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
// to prevent encoder async and bframes from exhausting the decoder pool.
if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
{
hwScaleFilter += ":passthrough=0";
}
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
{
hwScaleFilter += $":transpose={transposeDir}";
@@ -7138,7 +7151,8 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier += " -async " + state.InputAudioSync;
}
if (!string.IsNullOrEmpty(state.InputVideoSync))
// The -fps_mode option cannot be applied to input
if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
{
inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
}

View File

@@ -235,11 +235,11 @@ namespace MediaBrowser.LocalMetadata.Savers
{
if (item is Person)
{
await writer.WriteElementStringAsync(null, "DeathDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "DeathDate", null, item.EndDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
else if (item is not Episode)
{
await writer.WriteElementStringAsync(null, "EndDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "EndDate", null, item.EndDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
}

View File

@@ -569,7 +569,7 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets the trickplay manifest.
/// </summary>
/// <value>The trickplay manifest.</value>
public Dictionary<string, Dictionary<int, TrickplayInfo>> Trickplay { get; set; }
public Dictionary<string, Dictionary<int, TrickplayInfoDto>> Trickplay { get; set; }
/// <summary>
/// Gets or sets the type of the location.

View File

@@ -0,0 +1,62 @@
using System;
using Jellyfin.Database.Implementations.Entities;
namespace MediaBrowser.Model.Dto;
/// <summary>
/// The trickplay api model.
/// </summary>
public record TrickplayInfoDto
{
/// <summary>
/// Initializes a new instance of the <see cref="TrickplayInfoDto"/> class.
/// </summary>
/// <param name="info">The trickplay info.</param>
public TrickplayInfoDto(TrickplayInfo info)
{
ArgumentNullException.ThrowIfNull(info);
Width = info.Width;
Height = info.Height;
TileWidth = info.TileWidth;
TileHeight = info.TileHeight;
ThumbnailCount = info.ThumbnailCount;
Interval = info.Interval;
Bandwidth = info.Bandwidth;
}
/// <summary>
/// Gets the width of an individual thumbnail.
/// </summary>
public int Width { get; init; }
/// <summary>
/// Gets the height of an individual thumbnail.
/// </summary>
public int Height { get; init; }
/// <summary>
/// Gets the amount of thumbnails per row.
/// </summary>
public int TileWidth { get; init; }
/// <summary>
/// Gets the amount of thumbnails per column.
/// </summary>
public int TileHeight { get; init; }
/// <summary>
/// Gets the total amount of non-black thumbnails.
/// </summary>
public int ThumbnailCount { get; init; }
/// <summary>
/// Gets the interval in milliseconds between each trickplay thumbnail.
/// </summary>
public int Interval { get; init; }
/// <summary>
/// Gets the peak bandwidth usage in bits per second.
/// </summary>
public int Bandwidth { get; init; }
}

View File

@@ -8,21 +8,28 @@ public class LyricLineCue
/// <summary>
/// Initializes a new instance of the <see cref="LyricLineCue"/> class.
/// </summary>
/// <param name="position">The start of the character index of the lyric.</param>
/// <param name="position">The start character index of the cue.</param>
/// <param name="endPosition">The end character index of the cue.</param>
/// <param name="start">The start of the timestamp the lyric is synced to in ticks.</param>
/// <param name="end">The end of the timestamp the lyric is synced to in ticks.</param>
public LyricLineCue(int position, long start, long? end)
public LyricLineCue(int position, int endPosition, long start, long? end)
{
Position = position;
EndPosition = endPosition;
Start = start;
End = end;
}
/// <summary>
/// Gets the character index of the lyric.
/// Gets the start character index of the cue.
/// </summary>
public int Position { get; }
/// <summary>
/// Gets the end character index of the cue.
/// </summary>
public int EndPosition { get; }
/// <summary>
/// Gets the timestamp the lyric is synced to in ticks.
/// </summary>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Jellyfin.Extensions;
using LrcParser.Model;
@@ -66,47 +67,56 @@ public partial class LrcLyricParser : ILyricParser
}
List<LyricLine> lyricList = [];
for (var l = 0; l < sortedLyricData.Count; l++)
for (var lineIndex = 0; lineIndex < sortedLyricData.Count; lineIndex++)
{
var cues = new List<LyricLineCue>();
var lyric = sortedLyricData[l];
var lyric = sortedLyricData[lineIndex];
if (lyric.TimeTags.Count != 0)
// Extract cues from time tags
var cues = new List<LyricLineCue>();
if (lyric.TimeTags.Count > 0)
{
var keys = lyric.TimeTags.Keys.ToList();
int current = 0, next = 1;
while (next < keys.Count)
for (var tagIndex = 0; tagIndex < keys.Count - 1; tagIndex++)
{
var currentKey = keys[current];
var currentKey = keys[tagIndex];
var nextKey = keys[tagIndex + 1];
var currentPos = currentKey.State == IndexState.End ? currentKey.Index + 1 : currentKey.Index;
var nextPos = nextKey.State == IndexState.End ? nextKey.Index + 1 : nextKey.Index;
var currentMs = lyric.TimeTags[currentKey] ?? 0;
var nextMs = lyric.TimeTags[keys[next]] ?? 0;
cues.Add(new LyricLineCue(
position: Math.Max(currentKey.Index, 0),
start: TimeSpan.FromMilliseconds(currentMs).Ticks,
end: TimeSpan.FromMilliseconds(nextMs).Ticks));
current++;
next++;
var nextMs = lyric.TimeTags[keys[tagIndex + 1]] ?? 0;
var currentSlice = lyric.Text[currentPos..nextPos];
var currentSliceTrimmed = currentSlice.Trim();
if (currentSliceTrimmed.Length > 0)
{
cues.Add(new LyricLineCue(
position: currentPos,
endPosition: nextPos,
start: TimeSpan.FromMilliseconds(currentMs).Ticks,
end: TimeSpan.FromMilliseconds(nextMs).Ticks));
}
}
var lastKey = keys[current];
var lastKey = keys[^1];
var lastPos = lastKey.State == IndexState.End ? lastKey.Index + 1 : lastKey.Index;
var lastMs = lyric.TimeTags[lastKey] ?? 0;
var lastSlice = lyric.Text[lastPos..];
var lastSliceTrimmed = lastSlice.Trim();
cues.Add(new LyricLineCue(
position: Math.Max(lastKey.Index, 0),
start: TimeSpan.FromMilliseconds(lastMs).Ticks,
end: l + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[l + 1].StartTime).Ticks : null));
if (lastSliceTrimmed.Length > 0)
{
cues.Add(new LyricLineCue(
position: lastPos,
endPosition: lyric.Text.Length,
start: TimeSpan.FromMilliseconds(lastMs).Ticks,
end: lineIndex + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[lineIndex + 1].StartTime).Ticks : null));
}
}
long lyricStartTicks = TimeSpan.FromMilliseconds(lyric.StartTime).Ticks;
lyricList.Add(new LyricLine(WhitespaceRegex().Replace(lyric.Text.Trim(), " "), lyricStartTicks, cues));
lyricList.Add(new LyricLine(lyric.Text, lyricStartTicks, cues));
}
return new LyricDto { Lyrics = lyricList };
}
// Replacement is required until https://github.com/karaoke-dev/LrcParser/issues/83 is resolved.
[GeneratedRegex(@"\s+")]
private static partial Regex WhitespaceRegex();
}

View File

@@ -73,11 +73,11 @@ namespace MediaBrowser.Providers.Manager
public virtual int Order => 0;
private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService)
private FileSystemMetadata TryGetFileSystemMetadata(string path, IDirectoryService directoryService)
{
try
{
return directoryService.GetFile(path);
return directoryService.GetFileSystemEntry(path);
}
catch (Exception ex)
{
@@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Manager
var updateType = ItemUpdateType.None;
var libraryOptions = LibraryManager.GetLibraryOptions(item);
var isFirstRefresh = item.DateLastRefreshed.Date == DateTime.MinValue.Date;
var isFirstRefresh = item.DateLastRefreshed == DateTime.MinValue;
var hasRefreshedMetadata = true;
var hasRefreshedImages = true;
@@ -225,7 +225,7 @@ namespace MediaBrowser.Providers.Manager
{
if (item.IsFileProtocol)
{
var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
var file = TryGetFileSystemMetadata(item.Path, refreshOptions.DirectoryService);
if (file is not null)
{
item.DateModified = file.LastWriteTimeUtc;
@@ -1180,12 +1180,12 @@ namespace MediaBrowser.Providers.Manager
target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
}
if (source.DateCreated != default)
if (source.DateCreated != DateTime.MinValue)
{
target.DateCreated = source.DateCreated;
}
if (replaceData || source.DateModified != default)
if (replaceData || source.DateModified != DateTime.MinValue)
{
target.DateModified = source.DateModified;
}

View File

@@ -669,8 +669,13 @@ namespace MediaBrowser.Providers.Manager
private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
{
var libraryOptions = _libraryManager.GetLibraryOptions(item);
var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).ToList();
if (applicableSavers.Count == 0)
{
return;
}
foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
foreach (var saver in applicableSavers)
{
_logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
@@ -714,6 +719,8 @@ namespace MediaBrowser.Providers.Manager
}
}
}
_libraryManager.CreateItem(item, null);
}
/// <summary>

View File

@@ -192,7 +192,20 @@ namespace MediaBrowser.Providers.MediaInfo
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{
var people = new List<PersonInfo>();
var albumArtists = string.IsNullOrEmpty(trackAlbumArtist) ? [] : trackAlbumArtist.Split(InternalValueSeparator);
string[]? albumArtists = null;
if (libraryOptions.PreferNonstandardArtistsTag)
{
TryGetSanitizedAdditionalFields(track, "ALBUMARTISTS", out var albumArtistsTagString);
if (albumArtistsTagString is not null)
{
albumArtists = albumArtistsTagString.Split(InternalValueSeparator);
}
}
if (albumArtists is null || albumArtists.Length == 0)
{
albumArtists = string.IsNullOrEmpty(trackAlbumArtist) ? [] : trackAlbumArtist.Split(InternalValueSeparator);
}
if (libraryOptions.UseCustomTagDelimiters)
{
@@ -205,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = albumArtist.Trim(),
Name = albumArtist,
Type = PersonKind.AlbumArtist
});
}
@@ -237,7 +250,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = performer.Trim(),
Name = performer,
Type = PersonKind.Artist
});
}
@@ -251,7 +264,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = composer.Trim(),
Name = composer,
Type = PersonKind.Composer
});
}
@@ -340,9 +353,10 @@ namespace MediaBrowser.Providers.MediaInfo
genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
? genres
: audio.Genres;
if (options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0 || audio.Genres.All(string.IsNullOrWhiteSpace))
{
audio.Genres = genres;
}
}
TryGetSanitizedAdditionalFields(track, "REPLAYGAIN_TRACK_GAIN", out var trackGainTag);
@@ -435,7 +449,11 @@ namespace MediaBrowser.Providers.MediaInfo
// Save extracted lyrics if they exist,
// and if the audio doesn't yet have lyrics.
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
// ATL supports both SRT and LRC formats as synchronized lyrics, but we only want to save LRC format.
var supportedLyrics = track.Lyrics.Where(l => l.Format != LyricsInfo.LyricsFormat.SRT).ToList();
var candidateSynchronizedLyric = supportedLyrics.FirstOrDefault(l => l.Format is not LyricsInfo.LyricsFormat.UNSYNCHRONIZED and not LyricsInfo.LyricsFormat.OTHER && l.SynchronizedLyrics is not null);
var candidateUnsynchronizedLyric = supportedLyrics.FirstOrDefault(l => l.Format is LyricsInfo.LyricsFormat.UNSYNCHRONIZED or LyricsInfo.LyricsFormat.OTHER && l.UnsynchronizedLyrics is not null);
var lyrics = candidateSynchronizedLyric is not null ? candidateSynchronizedLyric.FormatSynch() : candidateUnsynchronizedLyric?.UnsynchronizedLyrics;
if (!string.IsNullOrWhiteSpace(lyrics)
&& tryExtractEmbeddedLyrics)
{

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Chapters;
@@ -516,12 +517,15 @@ namespace MediaBrowser.Providers.MediaInfo
foreach (var person in data.People)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrWhiteSpace(person.Name))
{
Name = person.Name.Trim(),
Type = person.Type,
Role = person.Role.Trim()
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = person.Name,
Type = person.Type,
Role = person.Role.Trim()
});
}
}
_libraryManager.UpdatePeople(video, people);

View File

@@ -200,20 +200,26 @@ public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo>
foreach (var albumArtist in item.AlbumArtists)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrWhiteSpace(albumArtist))
{
Name = albumArtist.Trim(),
Type = PersonKind.AlbumArtist
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = albumArtist,
Type = PersonKind.AlbumArtist
});
}
}
foreach (var artist in item.Artists)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrWhiteSpace(artist))
{
Name = artist.Trim(),
Type = PersonKind.Artist
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = artist,
Type = PersonKind.Artist
});
}
}
LibraryManager.UpdatePeople(item, people);

View File

@@ -38,6 +38,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
public int MaxCastMembers { get; set; } = 15;
/// <summary>
/// Gets or sets a value indicating the maximum number of crew members to fetch for an item.
/// </summary>
public int MaxCrewMembers { get; set; } = 15;
/// <summary>
/// Gets or sets a value indicating whether to hide cast members without profile images.
/// </summary>
public bool HideMissingCastMembers { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to hide crew members without profile images.
/// </summary>
public bool HideMissingCrewMembers { get; set; }
/// <summary>
/// Gets or sets a value indicating the poster image size to fetch.
/// </summary>

View File

@@ -25,9 +25,24 @@
<input is="emby-checkbox" type="checkbox" id="importSeasonName" />
<span>Import season name from metadata fetched for series.</span>
</label>
<div class="inputContainer">
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
<div class="verticalSection">
<h2>Cast & Crew Settings</h2>
<div class="inputContainer">
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="maxCrewMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Crew Members" />
<div class="fieldDescription">The maximum number of crew members to fetch for an item.</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="hideMissingCastMembers" />
<span>Hide cast members without profile images.</span>
</label>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="hideMissingCrewMembers" />
<span>Hide crew members without profile images.</span>
</label>
</div>
<div class="verticalSection verticalSection-extrabottompadding">
<h2>Image Scaling</h2>
@@ -129,6 +144,8 @@
document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries;
document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies;
document.querySelector('#importSeasonName').checked = config.ImportSeasonName;
document.querySelector('#hideMissingCastMembers').checked = config.HideMissingCastMembers;
document.querySelector('#hideMissingCrewMembers').checked = config.HideMissingCrewMembers;
var maxCastMembers = document.querySelector('#maxCastMembers');
maxCastMembers.value = config.MaxCastMembers;
@@ -137,12 +154,18 @@
cancelable: false
}));
var maxCrewMembers = document.querySelector('#maxCrewMembers');
maxCrewMembers.value = config.MaxCrewMembers;
maxCrewMembers.dispatchEvent(new Event('change', {
bubbles: true,
cancelable: false
}));
pluginConfig = config;
configureImageScaling();
});
});
document.querySelector('.configForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg();
@@ -153,6 +176,9 @@
config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked;
config.ImportSeasonName = document.querySelector('#importSeasonName').checked;
config.MaxCastMembers = document.querySelector('#maxCastMembers').value;
config.MaxCrewMembers = document.querySelector('#maxCrewMembers').value;
config.HideMissingCastMembers = document.querySelector('#hideMissingCastMembers').checked;
config.HideMissingCrewMembers = document.querySelector('#hideMissingCrewMembers').checked;
config.PosterSize = document.querySelector('#selectPosterSize').value;
config.BackdropSize = document.querySelector('#selectBackdropSize').value;
config.LogoSize = document.querySelector('#selectLogoSize').value;

View File

@@ -144,6 +144,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
var config = Plugin.Instance.Configuration;
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
{
@@ -249,12 +250,26 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Credits?.Cast is not null)
{
foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
var castQuery = movieResult.Credits.Cast.AsEnumerable();
if (config.HideMissingCastMembers)
{
castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
}
castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers);
foreach (var actor in castQuery)
{
if (string.IsNullOrWhiteSpace(actor.Name))
{
continue;
}
var personInfo = new PersonInfo
{
Name = actor.Name.Trim(),
Role = actor.Character.Trim(),
Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
SortOrder = actor.Order
};
@@ -275,32 +290,47 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Credits?.Crew is not null)
{
foreach (var person in movieResult.Credits.Crew)
{
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
var crewQuery = movieResult.Credits.Crew
.Select(crewMember => new
{
CrewMember = crewMember,
PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
})
.Where(entry =>
TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) ||
TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase));
if (!TmdbUtils.WantedCrewKinds.Contains(type)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
if (config.HideMissingCrewMembers)
{
crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
}
crewQuery = crewQuery.Take(config.MaxCrewMembers);
foreach (var entry in crewQuery)
{
var crewMember = entry.CrewMember;
if (string.IsNullOrWhiteSpace(crewMember.Name))
{
continue;
}
var personInfo = new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job?.Trim(),
Type = type
Name = crewMember.Name.Trim(),
Role = crewMember.Job?.Trim() ?? string.Empty,
Type = entry.PersonType
};
if (!string.IsNullOrWhiteSpace(person.ProfilePath))
if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath))
{
personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath);
personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath);
}
if (person.Id > 0)
if (crewMember.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
}
metadataResult.AddPerson(personInfo);

View File

@@ -81,6 +81,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{
var metadataResult = new MetadataResult<Episode>();
var config = Plugin.Instance.Configuration;
// Allowing this will dramatically increase scan times
if (info.IsMissingEpisode)
@@ -206,52 +207,106 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (credits?.Cast is not null)
{
foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
var castQuery = config.HideMissingCastMembers
? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order)
: credits.Cast.OrderBy(a => a.Order);
foreach (var actor in castQuery.Take(config.MaxCastMembers))
{
metadataResult.AddPerson(new PersonInfo
if (string.IsNullOrWhiteSpace(actor.Name))
{
continue;
}
var personInfo = new PersonInfo
{
Name = actor.Name.Trim(),
Role = actor.Character.Trim(),
Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
SortOrder = actor.Order
});
SortOrder = actor.Order,
ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath)
};
if (actor.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
}
metadataResult.AddPerson(personInfo);
}
}
if (credits?.GuestStars is not null)
{
foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
{
metadataResult.AddPerson(new PersonInfo
{
Name = guest.Name.Trim(),
Role = guest.Character.Trim(),
Type = PersonKind.GuestStar,
SortOrder = guest.Order
});
}
}
var guestQuery = config.HideMissingCastMembers
? credits.GuestStars.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order)
: credits.GuestStars.OrderBy(a => a.Order);
// and the rest from crew
if (credits?.Crew is not null)
{
foreach (var person in credits.Crew)
foreach (var guest in guestQuery.Take(config.MaxCastMembers))
{
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
if (!TmdbUtils.WantedCrewKinds.Contains(type)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrWhiteSpace(guest.Name))
{
continue;
}
metadataResult.AddPerson(new PersonInfo
var personInfo = new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job?.Trim(),
Type = type
});
Name = guest.Name.Trim(),
Role = guest.Character?.Trim() ?? string.Empty,
Type = PersonKind.GuestStar,
SortOrder = guest.Order,
ImageUrl = _tmdbClientManager.GetProfileUrl(guest.ProfilePath)
};
if (guest.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, guest.Id.ToString(CultureInfo.InvariantCulture));
}
metadataResult.AddPerson(personInfo);
}
}
if (credits?.Crew is not null)
{
var crewQuery = credits.Crew
.Select(crewMember => new
{
CrewMember = crewMember,
PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
})
.Where(entry =>
TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) ||
TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase));
if (config.HideMissingCrewMembers)
{
crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
}
foreach (var entry in crewQuery.Take(config.MaxCrewMembers))
{
var crewMember = entry.CrewMember;
if (string.IsNullOrWhiteSpace(crewMember.Name))
{
continue;
}
var personInfo = new PersonInfo
{
Name = crewMember.Name.Trim(),
Role = crewMember.Job?.Trim() ?? string.Empty,
Type = entry.PersonType,
ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath)
};
if (crewMember.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
}
metadataResult.AddPerson(personInfo);
}
}

View File

@@ -42,6 +42,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Season>();
var config = Plugin.Instance.Configuration;
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId);
@@ -65,10 +66,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.Item = new Season
{
IndexNumber = seasonNumber,
Overview = seasonResult.Overview
Overview = seasonResult.Overview,
PremiereDate = seasonResult.AirDate,
ProductionYear = seasonResult.AirDate?.Year
};
if (Plugin.Instance.Configuration.ImportSeasonName)
if (config.ImportSeasonName)
{
result.Item.Name = seasonResult.Name;
}
@@ -77,47 +80,81 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// TODO why was this disabled?
var credits = seasonResult.Credits;
if (credits?.Cast is not null)
{
var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList();
for (var i = 0; i < cast.Count; i++)
var castQuery = config.HideMissingCastMembers
? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order)
: credits.Cast.OrderBy(a => a.Order);
foreach (var actor in castQuery.Take(config.MaxCastMembers))
{
var member = cast[i];
result.AddPerson(new PersonInfo
if (string.IsNullOrWhiteSpace(actor.Name))
{
Name = member.Name.Trim(),
Role = member.Character.Trim(),
continue;
}
var personInfo = new PersonInfo
{
Name = actor.Name.Trim(),
Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
SortOrder = member.Order
});
SortOrder = actor.Order,
ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath)
};
if (actor.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
}
result.AddPerson(personInfo);
}
}
if (credits?.Crew is not null)
{
foreach (var person in credits.Crew)
{
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
var crewQuery = credits.Crew
.Select(crewMember => new
{
CrewMember = crewMember,
PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
})
.Where(entry =>
TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) ||
TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase));
if (!TmdbUtils.WantedCrewKinds.Contains(type)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
if (config.HideMissingCrewMembers)
{
crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
}
foreach (var entry in crewQuery.Take(config.MaxCrewMembers))
{
var crewMember = entry.CrewMember;
if (string.IsNullOrWhiteSpace(crewMember.Name))
{
continue;
}
result.AddPerson(new PersonInfo
var personInfo = new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job?.Trim(),
Type = type
});
Name = crewMember.Name.Trim(),
Role = crewMember.Job?.Trim() ?? string.Empty,
Type = entry.PersonType,
ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath)
};
if (crewMember.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
}
result.AddPerson(personInfo);
}
}
result.Item.PremiereDate = seasonResult.AirDate;
result.Item.ProductionYear = seasonResult.AirDate?.Year;
return result;
}

View File

@@ -323,17 +323,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult)
{
var config = Plugin.Instance.Configuration;
if (seriesResult.Credits?.Cast is not null)
{
foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
IEnumerable<Cast> castQuery = seriesResult.Credits.Cast.OrderBy(a => a.Order);
if (config.HideMissingCastMembers)
{
castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
}
foreach (var actor in castQuery.Take(config.MaxCastMembers))
{
if (string.IsNullOrWhiteSpace(actor.Name))
{
continue;
}
var personInfo = new PersonInfo
{
Name = actor.Name.Trim(),
Role = actor.Character.Trim(),
Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
SortOrder = actor.Order,
ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath)
ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath)
};
if (actor.Id > 0)
@@ -347,30 +361,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (seriesResult.Credits?.Crew is not null)
{
var keepTypes = new[]
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer
};
var crewQuery = seriesResult.Credits.Crew
.Select(crewMember => new
{
CrewMember = crewMember,
PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
})
.Where(entry =>
TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) ||
TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase));
foreach (var person in seriesResult.Credits.Crew)
if (config.HideMissingCrewMembers)
{
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
}
if (!TmdbUtils.WantedCrewKinds.Contains(type)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
foreach (var entry in crewQuery.Take(config.MaxCrewMembers))
{
var crewMember = entry.CrewMember;
if (string.IsNullOrWhiteSpace(crewMember.Name))
{
continue;
}
yield return new PersonInfo
var personInfo = new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job?.Trim(),
Type = type
Name = crewMember.Name.Trim(),
Role = crewMember.Job?.Trim() ?? string.Empty,
Type = entry.PersonType,
ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath)
};
if (crewMember.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
}
yield return personInfo;
}
}
}

View File

@@ -1,5 +1,6 @@
#pragma warning disable CS1591
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

View File

@@ -22,7 +22,7 @@ public class BaseItemImageInfo
/// <summary>
/// Gets or Sets the time the image was last modified.
/// </summary>
public DateTime DateModified { get; set; }
public DateTime? DateModified { get; set; }
/// <summary>
/// Gets or Sets the imagetype.

View File

@@ -82,7 +82,7 @@ public static class QueryPartitionHelpers
/// <typeparam name="TEntity">The entity to load.</typeparam>
/// <param name="partitionInfo">The source query.</param>
/// <param name="partitionSize">The number of elements to load per partition.</param>
/// <param name="cancellationToken">The cancelation token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A enumerable representing the whole of the query.</returns>
public static async IAsyncEnumerable<TEntity> PartitionAsync<TEntity>(this ProgressablePartitionReporting<TEntity> partitionInfo, int partitionSize, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
@@ -98,7 +98,7 @@ public static class QueryPartitionHelpers
/// <typeparam name="TEntity">The entity to load.</typeparam>
/// <param name="partitionInfo">The source query.</param>
/// <param name="partitionSize">The number of elements to load per partition.</param>
/// <param name="cancellationToken">The cancelation token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A enumerable representing the whole of the query.</returns>
public static async IAsyncEnumerable<TEntity> PartitionEagerAsync<TEntity>(this ProgressablePartitionReporting<TEntity> partitionInfo, int partitionSize, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
@@ -115,7 +115,7 @@ public static class QueryPartitionHelpers
/// <param name="query">The source query.</param>
/// <param name="partitionSize">The number of elements to load per partition.</param>
/// <param name="progressablePartition">Reporting helper.</param>
/// <param name="cancellationToken">The cancelation token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A enumerable representing the whole of the query.</returns>
public static async IAsyncEnumerable<TEntity> PartitionAsync<TEntity>(
this IOrderedQueryable<TEntity> query,
@@ -154,7 +154,7 @@ public static class QueryPartitionHelpers
/// <param name="query">The source query.</param>
/// <param name="partitionSize">The number of elements to load per partition.</param>
/// <param name="progressablePartition">Reporting helper.</param>
/// <param name="cancellationToken">The cancelation token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A enumerable representing the whole of the query.</returns>
public static async IAsyncEnumerable<TEntity> PartitionEagerAsync<TEntity>(
this IOrderedQueryable<TEntity> query,

View File

@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
/// <inheritdoc />
public partial class BaseItemImageInfoDateModifiedNullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "DateModified",
table: "BaseItemImageInfos",
type: "TEXT",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "TEXT");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "DateModified",
table: "BaseItemImageInfos",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldNullable: true);
}
}
}

View File

@@ -418,7 +418,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<byte[]>("Blurhash")
.HasColumnType("BLOB");
b.Property<DateTime>("DateModified")
b.Property<DateTime?>("DateModified")
.HasColumnType("TEXT");
b.Property<int>("Height")

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.IO;
using SkiaSharp;
namespace Jellyfin.Drawing.Skia;
@@ -27,12 +28,17 @@ public static class SkiaHelper
currentIndex = 0;
}
SKBitmap? bitmap = skiaEncoder.Decode(paths[currentIndex], false, null, out _);
var imagePath = paths[currentIndex];
imagesTested[currentIndex] = 0;
currentIndex++;
if (!Path.Exists(imagePath))
{
continue;
}
SKBitmap? bitmap = skiaEncoder.Decode(imagePath, false, null, out _);
if (bitmap is not null)
{
newIndex = currentIndex;

View File

@@ -1166,7 +1166,7 @@ namespace Jellyfin.LiveTv.Channels
}
}
if (isNew || forceUpdate || item.DateLastRefreshed == default)
if (isNew || forceUpdate || item.DateLastRefreshed == DateTime.MinValue)
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}

View File

@@ -75,6 +75,8 @@ public class KeyframeExtractionScheduledTask : IScheduledTask
var videos = _libraryManager.GetItemList(query);
foreach (var video in videos)
{
cancellationToken.ThrowIfCancellationRequested();
// Only local files supported
var path = video.Path;
if (File.Exists(path))

View File

@@ -20,22 +20,28 @@ public static class LrcLyricParserTests
var line1 = parsed.Lyrics[0];
Assert.Equal("Every night that goes between", line1.Text);
Assert.NotNull(line1.Cues);
Assert.Equal(9, line1.Cues.Count);
Assert.Equal(5, line1.Cues.Count);
Assert.Equal(68400000, line1.Cues[0].Start);
Assert.Equal(72000000, line1.Cues[0].End);
Assert.Equal(0, line1.Cues[0].Position);
Assert.Equal(5, line1.Cues[0].EndPosition);
Assert.Equal(6, line1.Cues[1].Position);
Assert.Equal(11, line1.Cues[1].EndPosition);
Assert.Equal(12, line1.Cues[2].Position);
var line5 = parsed.Lyrics[4];
Assert.Equal("Every night you do not come", line5.Text);
Assert.NotNull(line5.Cues);
Assert.Equal(11, line5.Cues.Count);
Assert.Equal(377300000, line5.Cues[5].Start);
Assert.Equal(380000000, line5.Cues[5].End);
Assert.Equal(6, line5.Cues.Count);
Assert.Equal(375200000, line5.Cues[2].Start);
Assert.Equal(377300000, line5.Cues[2].End);
var lastLine = parsed.Lyrics[^1];
Assert.Equal("I have always been a storm", lastLine.Text);
Assert.NotNull(lastLine.Cues);
Assert.Equal(11, lastLine.Cues.Count);
Assert.Equal(6, lastLine.Cues.Count);
Assert.Equal(2358000000, lastLine.Cues[^1].Start);
Assert.Equal(26, lastLine.Cues[^1].EndPosition);
Assert.Null(lastLine.Cues[^1].End);
}
}

View File

@@ -22,7 +22,7 @@ namespace Jellyfin.Providers.Tests.Manager
{
var newLocked = new[] { MetadataField.Genres, MetadataField.Cast };
var newString = "new";
var newDate = DateTime.Now;
var newDate = DateTime.UtcNow;
var oldLocked = new[] { MetadataField.Genres };
var oldString = "old";
@@ -39,6 +39,7 @@ namespace Jellyfin.Providers.Tests.Manager
DateCreated = newDate
}
};
if (defaultDate)
{
source.Item.DateCreated = default;
@@ -141,8 +142,8 @@ namespace Jellyfin.Providers.Tests.Manager
{ "ProductionYear", 1, 2 },
{ "CommunityRating", 1.0f, 2.0f },
{ "CriticRating", 1.0f, 2.0f },
{ "EndDate", DateTime.UnixEpoch, DateTime.Now },
{ "PremiereDate", DateTime.UnixEpoch, DateTime.Now },
{ "EndDate", DateTime.UnixEpoch, DateTime.UtcNow },
{ "PremiereDate", DateTime.UnixEpoch, DateTime.UtcNow },
{ "Video3DFormat", Video3DFormat.HalfSideBySide, Video3DFormat.FullSideBySide }
};

View File

@@ -185,7 +185,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
Description = packageInfo.Description,
Overview = packageInfo.Overview,
TargetAbi = packageInfo.Versions[0].TargetAbi!,
Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture),
Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime,
Changelog = packageInfo.Versions[0].Changelog!,
Version = new Version(1, 0).ToString(),
ImagePath = string.Empty
@@ -221,7 +221,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
Description = packageInfo.Description,
Overview = packageInfo.Overview,
TargetAbi = packageInfo.Versions[0].TargetAbi!,
Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture),
Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime,
Changelog = packageInfo.Versions[0].Changelog!,
Version = packageInfo.Versions[0].Version,
ImagePath = string.Empty