diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index 89d59e4c4a..473302ddef 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '9.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml index 702dd29b82..a8104a917d 100644 --- a/.github/workflows/ci-compat.yml +++ b/.github/workflows/ci-compat.yml @@ -115,7 +115,7 @@ jobs: } >> $GITHUB_OUTPUT - name: Find difference comment - uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} @@ -123,7 +123,7 @@ jobs: body-includes: abi-diff-workflow-comment - name: Reply or edit difference comment (changed) - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 if: ${{ steps.diff.outputs.body != '' }} with: issue-number: ${{ github.event.pull_request.number }} @@ -142,7 +142,7 @@ jobs: - name: Reply or edit difference comment (unchanged) - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} with: issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 262582dfc7..7cca2af274 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -120,14 +120,14 @@ jobs: echo "" >> openapi-changes-reply.md echo "" >> openapi-changes-reply.md - name: Find difference comment - uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} direction: last body-includes: openapi-diff-workflow-comment - name: Reply or edit difference comment (changed) - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 if: ${{ steps.read-diff.outputs.ApiChanged == '1' }} with: issue-number: ${{ github.event.pull_request.number }} @@ -135,7 +135,7 @@ jobs: edit-mode: replace body-path: openapi-changes-reply.md - name: Edit difference comment (unchanged) - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }} with: issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index f2cf967e93..846835491a 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -35,7 +35,7 @@ jobs: --verbosity minimal - name: Merge code coverage results - uses: danielpalme/ReportGenerator-GitHub-Action@1978db745da4a573ca4baa2d0f67175df51a148c # v5.4.16 + uses: danielpalme/ReportGenerator-GitHub-Action@9870ed167742d546b99962ff815fcc1098355ed8 # v5.4.17 with: reports: "**/coverage.cobertura.xml" targetdir: "merged/" diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 68715897df..ba12d47473 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify as seen - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: token: ${{ secrets.JF_BOT_TOKEN }} comment-id: ${{ github.event.comment.id }} @@ -46,7 +46,7 @@ jobs: - name: install python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.13' + python-version: '3.14' cache: 'pip' - name: install python packages run: pip install -r rename/requirements.txt diff --git a/.github/workflows/issue-stale.yml b/.github/workflows/issue-stale.yml index 46b56c7067..db22848c3f 100644 --- a/.github/workflows/issue-stale.yml +++ b/.github/workflows/issue-stale.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest if: ${{ contains(github.repository, 'jellyfin/') }} steps: - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: repo-token: ${{ secrets.JF_BOT_TOKEN }} ascending: true diff --git a/.github/workflows/issue-template-check.yml b/.github/workflows/issue-template-check.yml index 0c4115888f..b49647d337 100644 --- a/.github/workflows/issue-template-check.yml +++ b/.github/workflows/issue-template-check.yml @@ -16,7 +16,7 @@ jobs: - name: install python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.13' + python-version: '3.14' cache: 'pip' - name: install python packages run: pip install -r main-repo-triage/requirements.txt diff --git a/.github/workflows/pull-request-stale.yaml b/.github/workflows/pull-request-stale.yaml index f5598797c0..223ffc590b 100644 --- a/.github/workflows/pull-request-stale.yaml +++ b/.github/workflows/pull-request-stale.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest if: ${{ contains(github.repository, 'jellyfin/') }} steps: - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: repo-token: ${{ secrets.JF_BOT_TOKEN }} ascending: true diff --git a/Directory.Packages.props b/Directory.Packages.props index 35f8ed4cdc..857b1a7ef0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ - + @@ -52,7 +52,7 @@ - + diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 0eb387ffdd..a320a774c6 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -104,6 +104,8 @@ namespace Emby.Server.Implementations.Collections await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureAwait(false); + _libraryManager.RootFolder.Children = null; + return FindFolders(path).First(); } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index ef497726e2..a400cb0925 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2129,6 +2129,8 @@ namespace Emby.Server.Implementations.Library } } + item.ValidateImages(); + _itemRepository.SaveImages(item); RegisterItem(item); @@ -3051,10 +3053,10 @@ namespace Emby.Server.Implementations.Library } finally { + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); + if (refreshLibrary) { - await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); - StartScanInBackground(); } else diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 3b2bb70a95..30d38dde37 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -123,11 +123,11 @@ "External": "Väline", "HearingImpaired": "Kuulmispuudega", "TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.", - "TaskKeyframeExtractor": "Võtmekaadri ekstraktor", - "TaskRefreshTrickplayImages": "Loo eelvaate pildid", - "TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud.", - "TaskAudioNormalization": "Heli Normaliseerimine", - "TaskAudioNormalizationDescription": "Skaneerib faile heli normaliseerimise andmete jaoks.", + "TaskKeyframeExtractor": "Võtmekaadrite eraldamine", + "TaskRefreshTrickplayImages": "Loo trickplay pildid", + "TaskRefreshTrickplayImagesDescription": "Loob trickplay eelvaated videotele lubatud meediakogudes.", + "TaskAudioNormalization": "Normaliseeri heli", + "TaskAudioNormalizationDescription": "Otsib failidest helitugevuse normaliseerimise teavet.", "TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest asjad, mida enam ei eksisteeri.", "TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid", "TaskDownloadMissingLyrics": "Lae alla puuduolev lüürika", @@ -135,5 +135,7 @@ "TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.", "TaskExtractMediaSegments": "Meediasegmentide skaneerimine", "TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.", - "TaskMoveTrickplayImages": "Migreeri trickplay piltide asukoht" + "TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht", + "CleanupUserDataTask": "Kasutajaandmete puhastamise ülesanne", + "CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mis pole enam vähemalt 90 päeva saadaval olnud." } diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 0e7c9dc3a0..b3f137febd 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -1,74 +1,74 @@ { - "Albums": "Álbumes", + "Albums": "Álbums", "Collections": "Coleccións", "ChapterNameValue": "Capítulo {0}", "Channels": "Canles", - "CameraImageUploadedFrom": "Cargouse unha nova imaxe da cámara desde {0}", + "CameraImageUploadedFrom": "Cargouse unha nova imaxe de cámara dende {0}", "Books": "Libros", "AuthenticationSucceededWithUserName": "{0} autenticouse correctamente", "Artists": "Artistas", - "Application": "Aplicativo", - "NotificationOptionServerRestartRequired": "Necesario un reinicio do servidor", - "NotificationOptionPluginUpdateInstalled": "Actualización do Plugin instalada", + "Application": "Aplicación", + "NotificationOptionServerRestartRequired": "Necesario o reinicio do servidor", + "NotificationOptionPluginUpdateInstalled": "Actualización do plugin instalada", "NotificationOptionPluginUninstalled": "Plugin desinstalado", "NotificationOptionPluginInstalled": "Plugin instalado", - "NotificationOptionPluginError": "Fallo do Plugin", + "NotificationOptionPluginError": "Fallo do plugin", "NotificationOptionNewLibraryContent": "Novo contido engadido", "NotificationOptionInstallationFailed": "Fallo na instalación", - "NotificationOptionCameraImageUploaded": "Imaxe da cámara subida", - "NotificationOptionAudioPlaybackStopped": "Reproducción de audio parada", + "NotificationOptionCameraImageUploaded": "Imaxe da cámara cargada", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detida", "NotificationOptionAudioPlayback": "Reproducción de audio comezada", "NotificationOptionApplicationUpdateInstalled": "Actualización da aplicación instalada", "NotificationOptionApplicationUpdateAvailable": "Actualización da aplicación dispoñible", - "NewVersionIsAvailable": "Unha nova versión do Servidor Jellyfin está dispoñible para descarga.", + "NewVersionIsAvailable": "Nova versión do Servidor Jellyfin dispoñible para descargar.", "NameSeasonUnknown": "Tempada descoñecida", "NameSeasonNumber": "Tempada {0}", "NameInstallFailed": "{0} instalación fallida", - "MusicVideos": "Vídeos Musicais", + "MusicVideos": "Vídeos musicais", "Music": "Música", "Movies": "Películas", - "MixedContent": "Contido Mixto", - "MessageServerConfigurationUpdated": "A configuración do servidor foi actualizada", - "MessageNamedServerConfigurationUpdatedWithValue": "A sección de configuración {0} do servidor foi actualizada", - "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado a {0}", - "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado", + "MixedContent": "Contido mixto", + "MessageServerConfigurationUpdated": "Actualizouse a configuración do servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "Actualizouse a sección de configuración {0} do servidor", + "MessageApplicationUpdatedTo": "O servidor Jellyfin actualizouse a {0}", + "MessageApplicationUpdated": "O servidor Jellyfin actualizouse", "Latest": "Último", - "LabelRunningTimeValue": "Tempo de execución: {0}", + "LabelRunningTimeValue": "Tempo en execución: {0}", "LabelIpAddressValue": "Enderezo IP: {0}", - "ItemRemovedWithName": "{0} foi eliminado da biblioteca", - "ItemAddedWithName": "{0} foi engadido a biblioteca", + "ItemRemovedWithName": "{0} eliminouse da biblioteca", + "ItemAddedWithName": "{0} engadiuse á biblioteca", "Inherit": "Herdar", "HomeVideos": "Videos caseiros", - "HeaderRecordingGroups": "Grupos de Grabación", + "HeaderRecordingGroups": "Grupos de grabación", "HeaderNextUp": "De seguido", "HeaderLiveTV": "TV en directo", - "HeaderFavoriteSongs": "Cancións Favoritas", - "HeaderFavoriteShows": "Series de TV Favoritas", - "HeaderFavoriteEpisodes": "Episodios Favoritos", - "HeaderFavoriteArtists": "Artistas Favoritos", - "HeaderFavoriteAlbums": "Álbunes Favoritos", + "HeaderFavoriteSongs": "Cancións favoritas", + "HeaderFavoriteShows": "Series de TV favoritas", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbums favoritos", "HeaderContinueWatching": "Seguir vendo", - "HeaderAlbumArtists": "Artistas do Album", + "HeaderAlbumArtists": "Artistas do álbum", "Genres": "Xéneros", "Forced": "Forzado", "Folders": "Cartafoles", "Favorites": "Favoritos", - "FailedLoginAttemptWithUserName": "Intento de incio de sesión fallido {0}", + "FailedLoginAttemptWithUserName": "Fallo de intento de inicio de sesión dende {0}", "DeviceOnlineWithName": "{0} conectouse", "DeviceOfflineWithName": "{0} desconectouse", "Default": "Por defecto", "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", - "TaskCleanLogs": "Limpar Carpeta de Rexistros", - "TaskCleanActivityLog": "Limpar Rexistro de Actividade", - "TasksChannelsCategory": "Canáis de Internet", - "TaskUpdatePlugins": "Actualizar Plugins", + "TaskCleanLogs": "Limpar directorio de rexistros", + "TaskCleanActivityLog": "Limpar rexistro de actividade", + "TasksChannelsCategory": "Canles da Internet", + "TaskUpdatePlugins": "Actualizar plugins", "User": "Usuario", "Undefined": "Sen definir", "TvShows": "Programas de TV", "System": "Sistema", "Sync": "Sincronizar", "SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}", - "StartupEmbyServerIsLoading": "O Servidor Jellyfin está cargando. Por favor, reinténteo en breve.", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está cargando. Por favor, ténteo axiña outra vez.", "Songs": "Cancións", "Shows": "Programas", "ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado", @@ -85,57 +85,57 @@ "UserDeletedWithName": "O usuario {0} foi borrado", "UserCreatedWithName": "O usuario {0} foi creado", "Plugin": "Plugin", - "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo parada", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detida", "NotificationOptionVideoPlayback": "Reproducción de vídeo iniciada", "NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionTaskFailed": "Falla na tarefa axendada", - "TaskCleanTranscodeDescription": "Borra os arquivos de transcode anteriores a un día.", - "TaskCleanTranscode": "Limpar Directorio de Transcode", + "TaskCleanTranscodeDescription": "Borra os ficheiros de transcodificación de hai más dun día.", + "TaskCleanTranscode": "Limpar o directorio de transcodificación", "UserStoppedPlayingItemWithValues": "{0} rematou de reproducir {1} en {2}", - "UserStartedPlayingItemWithValues": "{0} está reproducindo {1} en {2}", - "TaskDownloadMissingSubtitlesDescription": "Busca en internet por subtítulos que faltan baseado na configuración de metadatos.", + "UserStartedPlayingItemWithValues": "{0} está a reproducir {1} en {2}", + "TaskDownloadMissingSubtitlesDescription": "Procura na internet os subtítulos que faltan segundo a configuración de metadatos.", "TaskDownloadMissingSubtitles": "Descargar subtítulos que faltan", - "TaskRefreshChannelsDescription": "Refresca a información do canle de internet.", - "TaskRefreshChannels": "Refrescar Canles", - "TaskUpdatePluginsDescription": "Descarga e instala actualizacións para plugins que están configurados para actualizarse automáticamente.", - "TaskRefreshPeopleDescription": "Actualiza os metadatos dos actores e directores na túa libraría multimedia.", - "TaskRefreshPeople": "Refrescar Persoas", - "TaskCleanLogsDescription": "Borra arquivos de rexistro que son mais antigos que {0} días.", - "TaskRefreshLibraryDescription": "Escanea a tua libraría multimedia buscando novos arquivos e refrescando os metadatos.", - "TaskRefreshLibrary": "Escanear Libraría Multimedia", - "TaskRefreshChapterImagesDescription": "Crea previsualizacións para videos que teñen capítulos.", - "TaskRefreshChapterImages": "Extraer Imaxes dos Capítulos", + "TaskRefreshChannelsDescription": "Refresca a información da canle de internet.", + "TaskRefreshChannels": "Refrescar canles", + "TaskUpdatePluginsDescription": "Descarga e instala actualizacións dos plugins configurados para actualizarse automáticamente.", + "TaskRefreshPeopleDescription": "Actualiza os metadatos dos actores e directores na túa biblioteca de medios.", + "TaskRefreshPeople": "Refrescar persoas", + "TaskCleanLogsDescription": "Borra ficheiros de rexistro con máis de {0} días de antigüidade.", + "TaskRefreshLibraryDescription": "Escanea a túa biblioteca de medios á procura de novos ficheiros e refresca os metadatos.", + "TaskRefreshLibrary": "Escanear a biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas dos vídeos que teñen capítulos.", + "TaskRefreshChapterImages": "Extraer imaxes dos capítulos", "TaskCleanCacheDescription": "Borra ficheiros da caché que xa non son necesarios para o sistema.", - "TaskCleanCache": "Limpa Directorio de Caché", - "TaskCleanActivityLogDescription": "Borra as entradas no rexistro de actividade anteriores á data configurada.", + "TaskCleanCache": "Limpar directorio de caché", + "TaskCleanActivityLogDescription": "Borra do rexistro de actividade as entradas anteriores á data configurada.", "TasksApplicationCategory": "Aplicación", "ValueSpecialEpisodeName": "Especial - {0}", - "ValueHasBeenAddedToLibrary": "{0} foi engadido a túa libraría multimedia", - "TasksLibraryCategory": "Libraría", + "ValueHasBeenAddedToLibrary": "{0} engadiuse á túa biblioteca de medios", + "TasksLibraryCategory": "Biblioteca", "TasksMaintenanceCategory": "Mantemento", "VersionNumber": "Versión {0}", "UserPolicyUpdatedWithName": "A política de usuario foi actualizada para {0}", "UserPasswordChangedWithName": "Cambiouse o contrasinal para o usuario {0}", "UserOnlineFromDevice": "{0} está en liña desde {1}", - "UserOfflineFromDevice": "{0} desconectouse desde {1}", - "TaskOptimizeDatabaseDescription": "Compacta e libera o espazo libre da base de datos. Executar esta tarefa logo de realizar mudanzas que impliquen modificacións da base de datos ou despois de escanear a biblioteca pode traer mellorías de desempeño.", + "UserOfflineFromDevice": "{0} desconectouse dende {1}", + "TaskOptimizeDatabaseDescription": "Compacta e libera espazo na base de datos. Executar esta tarefa logo de facer cambios que muden a base de datos ou despois de escanear a biblioteca pode mellorar o rendemento.", "TaskOptimizeDatabase": "Optimizar base de datos", - "TaskKeyframeExtractorDescription": "Extrae fragmentos do vídeo para crear listas de reprodución HLS máis precisas. Podería levarlle bastante tempo.", + "TaskKeyframeExtractorDescription": "Extrae fotogramas clave dos vídeos para crear listas de reprodución HLS máis precisas. Podería levar moito tempo.", "External": "Externo", "HearingImpaired": "Problemas de audición", - "TaskKeyframeExtractor": "Extractor de fragmentos", - "TaskAudioNormalization": "Normalización do audio", - "TaskRefreshTrickplayImagesDescription": "Crea vistas previas de reprodución con truco para vídeos en bibliotecas activadas.", + "TaskKeyframeExtractor": "Extractor de fotogramas clave", + "TaskAudioNormalization": "Normalización de volume", + "TaskRefreshTrickplayImagesDescription": "Crea miniaturas de previsualización para os vídeos nas bibliotecas habilitadas.", "TaskDownloadMissingLyrics": "Descargar letras que faltan", - "TaskDownloadMissingLyricsDescription": "Descargas de letras das cancións", + "TaskDownloadMissingLyricsDescription": "Descarga as letras das cancións", "TaskCleanCollectionsAndPlaylists": "Limpar coleccións e listas de reprodución", - "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de coleccións e listas de reprodución que xa non existen.", - "TaskExtractMediaSegmentsDescription": "Extrae ou obtén segmentos multimedia de complementos habilitados para o Segmento de medios.", - "TaskExtractMediaSegments": "Escaneo de segmentos multimedia", - "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.", - "CleanupUserDataTask": "Tarefa de limpeza de datos do usuario", - "CleanupUserDataTaskDescription": "Limpa todos os datos do usuario (Estado de visualización, estado de favorito, etc) da multimedia que leve non presente polo menos durante 90 días." + "TaskCleanCollectionsAndPlaylistsDescription": "Quita ítems que xa non existen das coleccións e listas de reprodución.", + "TaskExtractMediaSegmentsDescription": "Procura segmentos de medios cos plugins habilitados.", + "TaskExtractMediaSegments": "Escaneo de segmentos de medios", + "TaskMoveTrickplayImages": "Migrar as miniaturas de previsualización a outra ubicación", + "TaskMoveTrickplayImagesDescription": "Move as miniaturas de previsualización segundo a configuración da biblioteca.", + "TaskRefreshTrickplayImages": "Xerar miniaturas de previsualización", + "TaskAudioNormalizationDescription": "Escanea ficheiros á procura de datos de normalización de volume.", + "CleanupUserDataTask": "Tarefa de limpeza de datos dos usuarios", + "CleanupUserDataTaskDescription": "Limpa todos os datos do usuario (estado de visualización, de favorito etc.) dos medios ausentes polo menos 90 días." } diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index a56ef82fc8..67263d3b22 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -125,8 +125,8 @@ "TaskKeyframeExtractor": "Izvoditelj ključnog okvira", "TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.", "HearingImpaired": "Oštećen sluh", - "TaskRefreshTrickplayImages": "Generiraj Trickplay Slike", - "TaskRefreshTrickplayImagesDescription": "Kreira trickplay pretpreglede za videe u omogućenim knjižnicama.", + "TaskRefreshTrickplayImages": "Generiraj slike brzog pregledavanja", + "TaskRefreshTrickplayImagesDescription": "Stvara preglede brzog pregledavanja za videa u aktiviranim bibliotekama.", "TaskAudioNormalization": "Normalizacija zvuka", "TaskAudioNormalizationDescription": "Skenira datoteke u potrazi za podacima o normalizaciji zvuka.", "TaskCleanCollectionsAndPlaylistsDescription": "Uklanja stavke iz zbirki i popisa za reprodukciju koje više ne postoje.", @@ -135,8 +135,8 @@ "TaskDownloadMissingLyrics": "Preuzmi tekstove koji nedostaju", "TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama", "TaskExtractMediaSegmentsDescription": "Izvlači ili pribavlja dijelove medija iz omogućenih media pluginova.", - "TaskMoveTrickplayImages": "Preseli lokaciju Trickplay slika", - "TaskMoveTrickplayImagesDescription": "Preseli lokaciju Trickplay slika prema postavkama zbirke.", + "TaskMoveTrickplayImages": "Premjesti mjesto slika brzog pregledavanja", + "TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke brzog pregledavanja prema postavkama biblioteke.", "CleanupUserDataTask": "Zadatak čišćenja korisničkih podataka", "CleanupUserDataTaskDescription": "Briše sve korisničke podatke (stanje gledanja, status favorita itd.) s medija koji više nisu prisutni najmanje 90 dana." } diff --git a/Emby.Server.Implementations/Localization/Core/ur.json b/Emby.Server.Implementations/Localization/Core/ur.json index 3766830413..94d9c8541e 100644 --- a/Emby.Server.Implementations/Localization/Core/ur.json +++ b/Emby.Server.Implementations/Localization/Core/ur.json @@ -1,3 +1,16 @@ { - "Books": "کتابیں" + "Books": "کتابیں", + "AppDeviceValues": "ایپ: {0}، ڈیوائس: {1}", + "Albums": "البمز", + "Application": "ایپلی کیشن", + "Artists": "فنکار", + "AuthenticationSucceededWithUserName": "{0} کی کامیابی سے تصدیق ہو چکی ہے", + "CameraImageUploadedFrom": "ایک نئی کیمرے کی تصویر {0} سے اپ لوڈ کی گئی ہے", + "Channels": "چینلز", + "ChapterNameValue": "باب {0}", + "Collections": "مجموعے", + "Default": "ڈیفالٹ", + "DeviceOfflineWithName": "{0} نے رابطہ منقطع کر دیا ہے", + "DeviceOnlineWithName": "{0} منسلک ہے", + "External": "بیرونی" } diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json index 5d3f194329..f6539adff3 100644 --- a/Emby.Server.Implementations/Localization/Core/ur_PK.json +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -123,5 +123,9 @@ "TaskCleanActivityLogDescription": "تشکیل شدہ عمر سے زیادہ پرانی سرگرمی لاگ اندراجات کو حذف کرتا ہے۔", "External": "بیرونی", "HearingImpaired": "قوت سماعت سے محروم", - "TaskCleanActivityLog": "سرگرمی لاگ کو صاف کریں" + "TaskCleanActivityLog": "سرگرمی لاگ کو صاف کریں", + "TaskDownloadMissingLyrics": "غائب بول ڈاؤن لوڈ کریں", + "TaskDownloadMissingLyricsDescription": "گانے کے غائب بول ڈاؤن لوڈ کریں", + "TaskAudioNormalization": "آڈیو نارملائزیشن", + "TaskAudioNormalizationDescription": "آڈیو نارملائزیشن ڈیٹا کے لیے فائلوں کو سکین کرتا ہے۔" } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 4d17e37699..ef444b9302 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -75,6 +75,7 @@ public sealed class BaseItemRepository private static readonly IReadOnlyList _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist]; private static readonly IReadOnlyList _getStudiosValueTypes = [ItemValueType.Studios]; private static readonly IReadOnlyList _getGenreValueTypes = [ItemValueType.Genre]; + private static readonly IReadOnlyList SearchWildcardTerms = ['%', '_', '[', ']', '^']; /// /// Initializes a new instance of the class. @@ -266,12 +267,13 @@ public sealed class BaseItemRepository IQueryable dbQuery = PrepareItemQuery(context, filter); dbQuery = TranslateQuery(dbQuery, context, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); + if (filter.EnableTotalRecordCount) { result.TotalRecordCount = dbQuery.Count(); } - dbQuery = ApplyGroupingFilter(context, dbQuery, filter); dbQuery = ApplyQueryPaging(dbQuery, filter); result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray(); @@ -1693,7 +1695,15 @@ public sealed class BaseItemRepository if (!string.IsNullOrEmpty(filter.SearchTerm)) { var searchTerm = filter.SearchTerm.ToLower(); - baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(searchTerm))); + if (SearchWildcardTerms.Any(f => searchTerm.Contains(f))) + { + searchTerm = $"%{searchTerm.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!.ToLower(), searchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle.ToLower(), searchTerm))); + } + else + { + baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(searchTerm))); + } } if (filter.IsFolder.HasValue) @@ -1865,10 +1875,17 @@ public sealed class BaseItemRepository if (filter.PersonIds.Length > 0) { + var peopleEntityIds = context.BaseItems + .WhereOneOrMany(filter.PersonIds, b => b.Id) + .Join( + context.Peoples, + b => b.Name, + p => p.Name, + (b, p) => p.Id); + baseQuery = baseQuery - .Where(e => - context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).Any(f => f.Name == w.People.Name)) - .Any(f => f.ItemId == e.Id)); + .Where(e => context.PeopleBaseItemMap + .Any(m => m.ItemId == e.Id && peopleEntityIds.Contains(m.PeopleId))); } if (!string.IsNullOrWhiteSpace(filter.Person)) @@ -1904,9 +1921,17 @@ public sealed class BaseItemRepository var nameContains = filter.NameContains; if (!string.IsNullOrWhiteSpace(nameContains)) { - baseQuery = baseQuery.Where(e => - e.CleanName!.Contains(nameContains) - || e.OriginalTitle!.ToLower().Contains(nameContains!)); + if (SearchWildcardTerms.Any(f => nameContains.Contains(f))) + { + nameContains = $"%{nameContains.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName, nameContains) || EF.Functions.Like(e.OriginalTitle, nameContains)); + } + else + { + baseQuery = baseQuery.Where(e => + e.CleanName!.Contains(nameContains) + || e.OriginalTitle!.ToLower().Contains(nameContains!)); + } } if (!string.IsNullOrWhiteSpace(filter.NameStartsWith)) diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 6296881a9e..f20fb2d92d 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -108,7 +108,7 @@ namespace Jellyfin.Server.Implementations.Users UserName = user.Username }; - FileStream fileStream = AsyncFile.OpenWrite(filePath); + FileStream fileStream = AsyncFile.Create(filePath); await using (fileStream.ConfigureAwait(false)) { await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8d3977103b..c81e639a22 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6552,7 +6552,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (isD3d11Supported && isCodecAvailable) { return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stripRotationDataArgs : string.Empty) - + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); + + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" + (isAv1 ? " -c:v av1" : string.Empty); } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 587cb4092b..869e3f292e 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -533,14 +533,14 @@ namespace MediaBrowser.Providers.MediaInfo private bool TryGetSanitizedAdditionalFields(Track track, string field, out string? value) { - var hasField = track.AdditionalFields.TryGetValue(field, out value); + var hasField = TryGetAdditionalFieldWithFallback(track, field, out value); value = GetSanitizedStringTag(value, track.Path); return hasField; } private bool TryGetSanitizedUFIDFields(Track track, out string? owner, out string? identifier) { - var hasField = track.AdditionalFields.TryGetValue("UFID", out string? value); + var hasField = TryGetAdditionalFieldWithFallback(track, "UFID", out string? value); if (hasField && !string.IsNullOrEmpty(value)) { string[] parts = value.Split('\0'); @@ -556,5 +556,37 @@ namespace MediaBrowser.Providers.MediaInfo identifier = null; return false; } + + // Build the explicit mka-style fallback key (e.g., ARTISTS -> track.artists, "MusicBrainz Artist Id" -> track.musicbrainz_artist_id) + private static string GetMkaFallbackKey(string key) + { + if (string.IsNullOrWhiteSpace(key)) + { + return key; + } + + var normalized = key.Trim().Replace(' ', '_').ToLowerInvariant(); + return "track." + normalized; + } + + // First try the normal key exactly; if missing, try the mka-style fallback key. + private bool TryGetAdditionalFieldWithFallback(Track track, string key, out string? value) + { + // Prefer the normal key (as-is, case-sensitive) + if (track.AdditionalFields.TryGetValue(key, out value)) + { + return true; + } + + // Fallback to mka-style: "track." + lower-case(original key) + var fallbackKey = GetMkaFallbackKey(key); + if (track.AdditionalFields.TryGetValue(fallbackKey, out value)) + { + return true; + } + + value = null; + return false; + } } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs index 56b0d9bcb2..6c1fbbeb7b 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs @@ -22,7 +22,7 @@ public class AudioDbArtistExternalUrlProvider : IExternalUrlProvider var baseUrl = "https://www.theaudiodb.com/"; switch (item) { - case MusicAlbum: + case MusicArtist: case Person: yield return baseUrl + $"artist/{externalId}"; break; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs index ee5a597c62..398ec2d203 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs @@ -21,7 +21,7 @@ public class MusicBrainzArtistExternalUrlProvider : IExternalUrlProvider { switch (item) { - case MusicAlbum: + case MusicArtist: case Person: yield return Plugin.Instance!.Configuration.Server + $"/artist/{externalId}"; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index afbada3b30..2db8cae7e5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -185,7 +185,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return requestLanguage; } - return imageLanguage; + // TMDb now returns xx for no language instead of an empty string. + return string.Equals(imageLanguage, "xx", StringComparison.OrdinalIgnoreCase) + ? string.Empty + : imageLanguage; } /// diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs index 6fd48a044b..fb0a08c29c 100644 --- a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -29,6 +29,7 @@ namespace Jellyfin.Providers.Tests.Tmdb [InlineData("fr-CA", "fr-BE", "fr-CA")] [InlineData("fr-CA", "fr", "fr-CA")] [InlineData("de", "en-US", "de")] + [InlineData("", "en-US", "")] public static void AdjustImageLanguage_Valid_Success(string imageLanguage, string requestLanguage, string? expected) { Assert.Equal(expected, TmdbUtils.AdjustImageLanguage(imageLanguage, requestLanguage));