From 9ec84bdd42259e0caf4cd439a55e6a002a5a76c8 Mon Sep 17 00:00:00 2001 From: Fedor <55900130+fed1337@users.noreply.github.com> Date: Thu, 24 Sep 2026 06:58:55 +0300 Subject: [PATCH 01/11] chore: fix release workflow permissions --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2f5e70..bff186f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: - master permissions: + contents: write packages: write actions: write From 3c8c9675ff9ec77d4aa5382345367067df4723b9 Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Fri, 25 Sep 2026 02:14:02 +0300 Subject: [PATCH 02/11] chore: update deps --- go.mod | 2 +- go.sum | 2 + makefile | 11 +- openapi/swagger.json | 445 +++++++++------------------------- openapi/swagger.yaml | 285 +++++++++------------- web/package.json | 6 +- web/pnpm-lock.yaml | 214 ++++++++-------- web/src/client/schemas.gen.ts | 8 +- web/src/client/sdk.gen.ts | 6 +- web/src/client/types.gen.ts | 157 +++++------- web/src/client/zod.gen.ts | 174 +++---------- 11 files changed, 442 insertions(+), 868 deletions(-) diff --git a/go.mod b/go.mod index 0043b1e..6f8e3b9 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.22.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect + github.com/quic-go/quic-go v0.63.0 // indirect github.com/redis/rueidis v1.0.78 // indirect github.com/sethvargo/go-retry v0.4.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 1351e49..40555d1 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/rueidis v1.0.77 h1:ZR41bgJcm7oRFb3aSDPrRhC0eonDSrPzjvvZvHIlNjM= github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo= github.com/redis/rueidis v1.0.78 h1:hJXpEgC9IYfdwY4hCdaGYsfK+oUaAqvhI/GMy5akVJI= diff --git a/makefile b/makefile index 9fd6b98..cc25e90 100644 --- a/makefile +++ b/makefile @@ -57,7 +57,7 @@ test: test-db-up go tool cover -html=coverage.txt -o coverage.html openapi: - go run github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc5 init \ + go run github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc6 init \ --v3.1 --parseDependency --parseInternal \ --outputTypes json,yaml \ --output ./openapi -g cmd/server/main.go @@ -97,15 +97,8 @@ prod-up: prod-down: $(COMPOSE_PROD) down -build: - CGO_ENABLED=0 GOOS=linux GOARCH="amd64" go build -tags="embed nomsgpack sonic avx" \ - -trimpath \ - -ldflags="-s -w" \ - -o out/sudostream \ - ./cmd/server - docs: uv run mkdocs serve .PHONY: help check fix test test-db-up test-db-down openapi frontend-install frontend-typegen \ - frontend-check frontend-fix frontend-dev frontend-build docker-build dev-up dev-down prod-up prod-down build docs + frontend-check frontend-fix frontend-dev frontend-build docker-build dev-up dev-down prod-up prod-down docs diff --git a/openapi/swagger.json b/openapi/swagger.json index 4b9c3a9..002d9a9 100644 --- a/openapi/swagger.json +++ b/openapi/swagger.json @@ -500,7 +500,8 @@ "uniqueItems": false }, "series": { - "$ref": "#/components/schemas/internal_httpapi.PlaybackSeriesContext" + "$ref": "#/components/schemas/internal_httpapi.PlaybackSeriesContext", + "description": "Series is set only for paths in a series library with resolvable S/E identity." }, "status": { "$ref": "#/components/schemas/sudoStream_internal_transcode.Status" @@ -519,13 +520,13 @@ "type": "boolean" }, "userSubtitle": { - "$ref": "#/components/schemas/internal_httpapi.UserSubtitleTrack" + "$ref": "#/components/schemas/internal_httpapi.UserSubtitleTrack", + "description": "UserSubtitle is the current user's uploaded caption when present (TTL 1d)." } }, "type": "object" }, "internal_httpapi.PlaybackSeriesContext": { - "description": "Series is set only for paths in a series library with resolvable S/E identity.", "properties": { "episode": { "type": "integer" @@ -741,7 +742,6 @@ "type": "object" }, "internal_httpapi.UserSubtitleTrack": { - "description": "UserSubtitle is the current user's uploaded caption when present (TTL 1d).", "properties": { "label": { "type": "string" @@ -1984,7 +1984,7 @@ "info": { "description": "HTTP API for browsing and streaming media files.", "title": "sudoStream API", - "version": "0.35.0" + "version": "0.1.0" }, "externalDocs": { "description": "", @@ -2025,16 +2025,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_dlna.Settings", - "summary": "body", - "description": "DLNA settings" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_dlna.Settings", + "summary": "body", + "description": "DLNA settings" } } }, @@ -2174,16 +2167,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.CreateInviteRequest", - "summary": "body", - "description": "Invite" - } - ] + "$ref": "#/components/schemas/internal_httpapi.CreateInviteRequest", + "summary": "body", + "description": "Invite" } } }, @@ -2292,16 +2278,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.ResendInviteRequest", - "summary": "body", - "description": "Optional TTL override" - } - ] + "$ref": "#/components/schemas/internal_httpapi.ResendInviteRequest", + "summary": "body", + "description": "Optional TTL override" } } }, @@ -2384,16 +2363,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.CreateLibraryRequest", - "summary": "body", - "description": "Library fields" - } - ] + "$ref": "#/components/schemas/internal_httpapi.CreateLibraryRequest", + "summary": "body", + "description": "Library fields" } } }, @@ -2530,16 +2502,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PatchLibraryRequest", - "summary": "body", - "description": "Library fields" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PatchLibraryRequest", + "summary": "body", + "description": "Library fields" } } }, @@ -2660,16 +2625,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PatchMaintenanceRequest", - "summary": "body", - "description": "Schedules" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PatchMaintenanceRequest", + "summary": "body", + "description": "Schedules" } } }, @@ -2792,16 +2750,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PatchProviderSettingsRequest", - "summary": "body", - "description": "Provider settings" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PatchProviderSettingsRequest", + "summary": "body", + "description": "Provider settings" } } }, @@ -2946,16 +2897,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.AddLibraryRootRequest", - "summary": "body", - "description": "Folder" - } - ] + "$ref": "#/components/schemas/internal_httpapi.AddLibraryRootRequest", + "summary": "body", + "description": "Folder" } } }, @@ -3107,16 +3051,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PatchMaintenanceRequest", - "summary": "body", - "description": "Schedules" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PatchMaintenanceRequest", + "summary": "body", + "description": "Schedules" } } }, @@ -3284,16 +3221,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_network.Settings", - "summary": "body", - "description": "Network settings" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_network.Settings", + "summary": "body", + "description": "Network settings" } } }, @@ -3344,16 +3274,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_network.Settings", - "summary": "body", - "description": "Draft network settings" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_network.Settings", + "summary": "body", + "description": "Draft network settings" } } }, @@ -3466,16 +3389,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_auth.Settings", - "summary": "body", - "description": "Settings" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_auth.Settings", + "summary": "body", + "description": "Settings" } } }, @@ -3554,16 +3470,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_transcode.TranscodeSettings", - "summary": "body", - "description": "Transcode settings" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_transcode.TranscodeSettings", + "summary": "body", + "description": "Transcode settings" } } }, @@ -3693,16 +3602,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.TrashSettingsResponse", - "summary": "body", - "description": "Recycle bin settings" - } - ] + "$ref": "#/components/schemas/internal_httpapi.TrashSettingsResponse", + "summary": "body", + "description": "Recycle bin settings" } } }, @@ -3856,16 +3758,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.CreateUserRequest", - "summary": "body", - "description": "New user" - } - ] + "$ref": "#/components/schemas/internal_httpapi.CreateUserRequest", + "summary": "body", + "description": "New user" } } }, @@ -3999,16 +3894,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.UpdateUserRequest", - "summary": "body", - "description": "User patch" - } - ] + "$ref": "#/components/schemas/internal_httpapi.UpdateUserRequest", + "summary": "body", + "description": "User patch" } } }, @@ -4155,16 +4043,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PutGrantsRequest", - "summary": "body", - "description": "Grant list" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PutGrantsRequest", + "summary": "body", + "description": "Grant list" } } }, @@ -4344,16 +4225,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.DisableTwoFactorRequest", - "summary": "body", - "description": "Password and TOTP code" - } - ] + "$ref": "#/components/schemas/internal_httpapi.DisableTwoFactorRequest", + "summary": "body", + "description": "Password and TOTP code" } } }, @@ -4397,16 +4271,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.TwoFactorConfirmRequest", - "summary": "body", - "description": "TOTP code" - } - ] + "$ref": "#/components/schemas/internal_httpapi.TwoFactorConfirmRequest", + "summary": "body", + "description": "TOTP code" } } }, @@ -4457,16 +4324,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.TwoFactorSetupRequest", - "summary": "body", - "description": "Optional pending token" - } - ] + "$ref": "#/components/schemas/internal_httpapi.TwoFactorSetupRequest", + "summary": "body", + "description": "Optional pending token" } } }, @@ -4526,16 +4386,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.VerifyTwoFactorRequest", - "summary": "body", - "description": "Pending token and code" - } - ] + "$ref": "#/components/schemas/internal_httpapi.VerifyTwoFactorRequest", + "summary": "body", + "description": "Pending token and code" } } }, @@ -4586,16 +4439,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.AcceptInviteRequest", - "summary": "body", - "description": "Invite token" - } - ] + "$ref": "#/components/schemas/internal_httpapi.AcceptInviteRequest", + "summary": "body", + "description": "Invite token" } } }, @@ -4639,16 +4485,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.ChangeEmailRequest", - "summary": "body", - "description": "Email change" - } - ] + "$ref": "#/components/schemas/internal_httpapi.ChangeEmailRequest", + "summary": "body", + "description": "Email change" } } }, @@ -4712,16 +4551,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.ChangePasswordRequest", - "summary": "body", - "description": "Password change" - } - ] + "$ref": "#/components/schemas/internal_httpapi.ChangePasswordRequest", + "summary": "body", + "description": "Password change" } } }, @@ -4824,16 +4656,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.ForgotPasswordRequest", - "summary": "body", - "description": "Email" - } - ] + "$ref": "#/components/schemas/internal_httpapi.ForgotPasswordRequest", + "summary": "body", + "description": "Email" } } }, @@ -4868,16 +4693,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.LoginRequest", - "summary": "body", - "description": "Credentials" - } - ] + "$ref": "#/components/schemas/internal_httpapi.LoginRequest", + "summary": "body", + "description": "Credentials" } } }, @@ -5034,16 +4852,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.ResetPasswordRequest", - "summary": "body", - "description": "Reset token" - } - ] + "$ref": "#/components/schemas/internal_httpapi.ResetPasswordRequest", + "summary": "body", + "description": "Reset token" } } }, @@ -5691,16 +5502,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.FavoritePatchRequest", - "summary": "body", - "description": "Favorite flag" - } - ] + "$ref": "#/components/schemas/internal_httpapi.FavoritePatchRequest", + "summary": "body", + "description": "Favorite flag" } } }, @@ -6260,16 +6064,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/sudoStream_internal_metadata.PatchRequest", - "summary": "body", - "description": "Metadata patch" - } - ] + "$ref": "#/components/schemas/sudoStream_internal_metadata.PatchRequest", + "summary": "body", + "description": "Metadata patch" } } }, @@ -6350,7 +6147,7 @@ "content": { "application/x-www-form-urlencoded": { "schema": { - "type": "string" + "type": "object" } } } @@ -6579,16 +6376,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.PlaybackNegotiateRequest", - "summary": "body", - "description": "Device profile and quality selection" - } - ] + "$ref": "#/components/schemas/internal_httpapi.PlaybackNegotiateRequest", + "summary": "body", + "description": "Device profile and quality selection" } } }, @@ -7183,31 +6973,31 @@ ], "requestBody": { "content": { - "application/x-www-form-urlencoded": { + "multipart/form-data": { "schema": { - "oneOf": [ - { - "title": "lang", + "properties": { + "file": { + "description": "Subtitle file (.vtt or .srt)", + "format": "binary", "type": "string" }, - { - "title": "label", + "label": { + "description": "Display label", "type": "string" }, - { - "title": "file", - "type": "file" + "lang": { + "description": "ISO 639-1 language code", + "type": "string" } - ] - } - }, - "multipart/form-data": { - "schema": { + }, + "required": [ + "file" + ], "type": "object" } } }, - "description": "ISO 639-1 language code | Display label | Subtitle file (.vtt or .srt)", + "description": "ISO 639-1 language code", "required": true }, "responses": { @@ -7356,16 +7146,9 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "type": "object" - }, - { - "$ref": "#/components/schemas/internal_httpapi.WatchPatchRequest", - "summary": "body", - "description": "Watched flag and optional progress" - } - ] + "$ref": "#/components/schemas/internal_httpapi.WatchPatchRequest", + "summary": "body", + "description": "Watched flag and optional progress" } } }, diff --git a/openapi/swagger.yaml b/openapi/swagger.yaml index d1f30bf..0f45810 100644 --- a/openapi/swagger.yaml +++ b/openapi/swagger.yaml @@ -331,6 +331,8 @@ components: uniqueItems: false series: $ref: '#/components/schemas/internal_httpapi.PlaybackSeriesContext' + description: Series is set only for paths in a series library with resolvable + S/E identity. status: $ref: '#/components/schemas/sudoStream_internal_transcode.Status' streamUrl: @@ -344,10 +346,10 @@ components: type: boolean userSubtitle: $ref: '#/components/schemas/internal_httpapi.UserSubtitleTrack' + description: UserSubtitle is the current user's uploaded caption when present + (TTL 1d). type: object internal_httpapi.PlaybackSeriesContext: - description: Series is set only for paths in a series library with resolvable - S/E identity. properties: episode: type: integer @@ -488,8 +490,6 @@ components: $ref: '#/components/schemas/sudoStream_internal_auth.PublicUser' type: object internal_httpapi.UserSubtitleTrack: - description: UserSubtitle is the current user's uploaded caption when present - (TTL 1d). properties: label: type: string @@ -1327,7 +1327,7 @@ externalDocs: info: description: HTTP API for browsing and streaming media files. title: sudoStream API - version: 0.35.0 + version: 0.1.0 openapi: 3.1.0 paths: /api/admin/dlna/settings: @@ -1353,11 +1353,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_dlna.Settings' - description: DLNA settings - summary: body + $ref: '#/components/schemas/sudoStream_internal_dlna.Settings' + description: DLNA settings + summary: body description: DLNA settings required: true responses: @@ -1443,11 +1441,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.CreateInviteRequest' - description: Invite - summary: body + $ref: '#/components/schemas/internal_httpapi.CreateInviteRequest' + description: Invite + summary: body description: Invite required: true responses: @@ -1514,11 +1510,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.ResendInviteRequest' - description: Optional TTL override - summary: body + $ref: '#/components/schemas/internal_httpapi.ResendInviteRequest' + description: Optional TTL override + summary: body description: Optional TTL override responses: "204": @@ -1569,11 +1563,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.CreateLibraryRequest' - description: Library fields - summary: body + $ref: '#/components/schemas/internal_httpapi.CreateLibraryRequest' + description: Library fields + summary: body description: Library fields required: true responses: @@ -1641,11 +1633,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PatchLibraryRequest' - description: Library fields - summary: body + $ref: '#/components/schemas/internal_httpapi.PatchLibraryRequest' + description: Library fields + summary: body description: Library fields required: true responses: @@ -1719,11 +1709,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PatchMaintenanceRequest' - description: Schedules - summary: body + $ref: '#/components/schemas/internal_httpapi.PatchMaintenanceRequest' + description: Schedules + summary: body description: Schedules required: true responses: @@ -1801,11 +1789,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PatchProviderSettingsRequest' - description: Provider settings - summary: body + $ref: '#/components/schemas/internal_httpapi.PatchProviderSettingsRequest' + description: Provider settings + summary: body description: Provider settings required: true responses: @@ -1896,11 +1882,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.AddLibraryRootRequest' - description: Folder - summary: body + $ref: '#/components/schemas/internal_httpapi.AddLibraryRootRequest' + description: Folder + summary: body description: Folder required: true responses: @@ -2013,11 +1997,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PatchMaintenanceRequest' - description: Schedules - summary: body + $ref: '#/components/schemas/internal_httpapi.PatchMaintenanceRequest' + description: Schedules + summary: body description: Schedules required: true responses: @@ -2119,11 +2101,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_network.Settings' - description: Network settings - summary: body + $ref: '#/components/schemas/sudoStream_internal_network.Settings' + description: Network settings + summary: body description: Network settings required: true responses: @@ -2154,11 +2134,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_network.Settings' - description: Draft network settings - summary: body + $ref: '#/components/schemas/sudoStream_internal_network.Settings' + description: Draft network settings + summary: body description: Draft network settings required: true responses: @@ -2227,11 +2205,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_auth.Settings' - description: Settings - summary: body + $ref: '#/components/schemas/sudoStream_internal_auth.Settings' + description: Settings + summary: body description: Settings required: true responses: @@ -2279,11 +2255,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_transcode.TranscodeSettings' - description: Transcode settings - summary: body + $ref: '#/components/schemas/sudoStream_internal_transcode.TranscodeSettings' + description: Transcode settings + summary: body description: Transcode settings required: true responses: @@ -2414,11 +2388,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.TrashSettingsResponse' - description: Recycle bin settings - summary: body + $ref: '#/components/schemas/internal_httpapi.TrashSettingsResponse' + description: Recycle bin settings + summary: body description: Recycle bin settings required: true responses: @@ -2460,11 +2432,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.CreateUserRequest' - description: New user - summary: body + $ref: '#/components/schemas/internal_httpapi.CreateUserRequest' + description: New user + summary: body description: New user required: true responses: @@ -2546,11 +2516,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.UpdateUserRequest' - description: User patch - summary: body + $ref: '#/components/schemas/internal_httpapi.UpdateUserRequest' + description: User patch + summary: body description: User patch required: true responses: @@ -2641,11 +2609,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PutGrantsRequest' - description: Grant list - summary: body + $ref: '#/components/schemas/internal_httpapi.PutGrantsRequest' + description: Grant list + summary: body description: Grant list required: true responses: @@ -2755,11 +2721,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.DisableTwoFactorRequest' - description: Password and TOTP code - summary: body + $ref: '#/components/schemas/internal_httpapi.DisableTwoFactorRequest' + description: Password and TOTP code + summary: body description: Password and TOTP code required: true responses: @@ -2786,11 +2750,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.TwoFactorConfirmRequest' - description: TOTP code - summary: body + $ref: '#/components/schemas/internal_httpapi.TwoFactorConfirmRequest' + description: TOTP code + summary: body description: TOTP code required: true responses: @@ -2821,11 +2783,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.TwoFactorSetupRequest' - description: Optional pending token - summary: body + $ref: '#/components/schemas/internal_httpapi.TwoFactorSetupRequest' + description: Optional pending token + summary: body description: Optional pending token responses: "200": @@ -2861,11 +2821,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.VerifyTwoFactorRequest' - description: Pending token and code - summary: body + $ref: '#/components/schemas/internal_httpapi.VerifyTwoFactorRequest' + description: Pending token and code + summary: body description: Pending token and code required: true responses: @@ -2896,11 +2854,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.AcceptInviteRequest' - description: Invite token - summary: body + $ref: '#/components/schemas/internal_httpapi.AcceptInviteRequest' + description: Invite token + summary: body description: Invite token required: true responses: @@ -2927,11 +2883,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.ChangeEmailRequest' - description: Email change - summary: body + $ref: '#/components/schemas/internal_httpapi.ChangeEmailRequest' + description: Email change + summary: body description: Email change required: true responses: @@ -2970,11 +2924,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.ChangePasswordRequest' - description: Password change - summary: body + $ref: '#/components/schemas/internal_httpapi.ChangePasswordRequest' + description: Password change + summary: body description: Password change required: true responses: @@ -3037,11 +2989,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.ForgotPasswordRequest' - description: Email - summary: body + $ref: '#/components/schemas/internal_httpapi.ForgotPasswordRequest' + description: Email + summary: body description: Email required: true responses: @@ -3063,11 +3013,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.LoginRequest' - description: Credentials - summary: body + $ref: '#/components/schemas/internal_httpapi.LoginRequest' + description: Credentials + summary: body description: Credentials required: true responses: @@ -3163,11 +3111,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.ResetPasswordRequest' - description: Reset token - summary: body + $ref: '#/components/schemas/internal_httpapi.ResetPasswordRequest' + description: Reset token + summary: body description: Reset token required: true responses: @@ -3568,11 +3514,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.FavoritePatchRequest' - description: Favorite flag - summary: body + $ref: '#/components/schemas/internal_httpapi.FavoritePatchRequest' + description: Favorite flag + summary: body description: Favorite flag required: true responses: @@ -3915,11 +3859,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/sudoStream_internal_metadata.PatchRequest' - description: Metadata patch - summary: body + $ref: '#/components/schemas/sudoStream_internal_metadata.PatchRequest' + description: Metadata patch + summary: body description: Metadata patch required: true responses: @@ -3968,7 +3910,7 @@ paths: content: application/x-www-form-urlencoded: schema: - type: string + type: object responses: "200": content: @@ -4110,11 +4052,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.PlaybackNegotiateRequest' - description: Device profile and quality selection - summary: body + $ref: '#/components/schemas/internal_httpapi.PlaybackNegotiateRequest' + description: Device profile and quality selection + summary: body description: Device profile and quality selection responses: "200": @@ -4490,20 +4430,23 @@ paths: type: string requestBody: content: - application/x-www-form-urlencoded: - schema: - oneOf: - - title: lang - type: string - - title: label - type: string - - title: file - type: file multipart/form-data: schema: + properties: + file: + description: Subtitle file (.vtt or .srt) + format: binary + type: string + label: + description: Display label + type: string + lang: + description: ISO 639-1 language code + type: string + required: + - file type: object - description: ISO 639-1 language code | Display label | Subtitle file (.vtt - or .srt) + description: ISO 639-1 language code required: true responses: "200": @@ -4596,11 +4539,9 @@ paths: content: application/json: schema: - oneOf: - - type: object - - $ref: '#/components/schemas/internal_httpapi.WatchPatchRequest' - description: Watched flag and optional progress - summary: body + $ref: '#/components/schemas/internal_httpapi.WatchPatchRequest' + description: Watched flag and optional progress + summary: body description: Watched flag and optional progress required: true responses: diff --git a/web/package.json b/web/package.json index 5ad63e7..561315e 100644 --- a/web/package.json +++ b/web/package.json @@ -18,7 +18,7 @@ "@hookform/resolvers": "^5.9.1", "@radix-ui/themes": "^3.3.0", "@tailwindcss/vite": "^4.3.3", - "@tanstack/react-query": "^5.103.1", + "@tanstack/react-query": "^5.103.2", "@videojs/google-cast": "10.0.0-rc.2", "@videojs/hlsjs-video": "10.0.0-rc.2", "@videojs/react": "10.0.0-rc.2", @@ -36,7 +36,7 @@ "react": "^19.3.0", "react-dom": "^19.3.0", "react-hook-form": "^7.88.0", - "react-i18next": "^17.0.14", + "react-i18next": "^17.0.15", "react-router": "^8.4.0", "tailwind-merge": "^3.7.0", "tailwindcss": "^4.3.3", @@ -52,7 +52,7 @@ "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.1", "oxfmt": "^0.66.0", - "oxlint": "^1.83.0", + "oxlint": "^1.85.0", "oxlint-tsgolint": "^7.0.2002", "shadcn": "^4.21.0", "typescript": "~7.0.2", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 3bfc52a..2c3f492 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -179,8 +179,8 @@ importers: specifier: ^4.3.3 version: 4.3.3(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)) '@tanstack/react-query': - specifier: ^5.103.1 - version: 5.103.1(react@19.3.0) + specifier: ^5.103.2 + version: 5.103.2(react@19.3.0) '@videojs/google-cast': specifier: 10.0.0-rc.2 version: 10.0.0-rc.2 @@ -233,8 +233,8 @@ importers: specifier: ^7.88.0 version: 7.88.0(react@19.3.0) react-i18next: - specifier: ^17.0.14 - version: 17.0.14(i18next@26.4.2(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + specifier: ^17.0.15 + version: 17.0.15(i18next@26.4.2(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react-router: specifier: ^8.4.0 version: 8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -276,8 +276,8 @@ importers: specifier: ^0.66.0 version: 0.66.0 oxlint: - specifier: ^1.83.0 - version: 1.83.0(oxlint-tsgolint@7.0.2002) + specifier: ^1.85.0 + version: 1.85.0(oxlint-tsgolint@7.0.2002) oxlint-tsgolint: specifier: ^7.0.2002 version: 7.0.2002 @@ -768,124 +768,124 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.83.0': - resolution: {integrity: sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA==} + '@oxlint/binding-android-arm-eabi@1.85.0': + resolution: {integrity: sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.83.0': - resolution: {integrity: sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg==} + '@oxlint/binding-android-arm64@1.85.0': + resolution: {integrity: sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.83.0': - resolution: {integrity: sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ==} + '@oxlint/binding-darwin-arm64@1.85.0': + resolution: {integrity: sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.83.0': - resolution: {integrity: sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA==} + '@oxlint/binding-darwin-x64@1.85.0': + resolution: {integrity: sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.83.0': - resolution: {integrity: sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w==} + '@oxlint/binding-freebsd-x64@1.85.0': + resolution: {integrity: sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.83.0': - resolution: {integrity: sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw==} + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': + resolution: {integrity: sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.83.0': - resolution: {integrity: sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA==} + '@oxlint/binding-linux-arm-musleabihf@1.85.0': + resolution: {integrity: sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.83.0': - resolution: {integrity: sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg==} + '@oxlint/binding-linux-arm64-gnu@1.85.0': + resolution: {integrity: sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.83.0': - resolution: {integrity: sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew==} + '@oxlint/binding-linux-arm64-musl@1.85.0': + resolution: {integrity: sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.83.0': - resolution: {integrity: sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g==} + '@oxlint/binding-linux-ppc64-gnu@1.85.0': + resolution: {integrity: sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.83.0': - resolution: {integrity: sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw==} + '@oxlint/binding-linux-riscv64-gnu@1.85.0': + resolution: {integrity: sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.83.0': - resolution: {integrity: sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw==} + '@oxlint/binding-linux-riscv64-musl@1.85.0': + resolution: {integrity: sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.83.0': - resolution: {integrity: sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ==} + '@oxlint/binding-linux-s390x-gnu@1.85.0': + resolution: {integrity: sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.83.0': - resolution: {integrity: sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw==} + '@oxlint/binding-linux-x64-gnu@1.85.0': + resolution: {integrity: sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.83.0': - resolution: {integrity: sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ==} + '@oxlint/binding-linux-x64-musl@1.85.0': + resolution: {integrity: sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.83.0': - resolution: {integrity: sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA==} + '@oxlint/binding-openharmony-arm64@1.85.0': + resolution: {integrity: sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.83.0': - resolution: {integrity: sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg==} + '@oxlint/binding-win32-arm64-msvc@1.85.0': + resolution: {integrity: sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.83.0': - resolution: {integrity: sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA==} + '@oxlint/binding-win32-ia32-msvc@1.85.0': + resolution: {integrity: sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.83.0': - resolution: {integrity: sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q==} + '@oxlint/binding-win32-x64-msvc@1.85.0': + resolution: {integrity: sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1802,11 +1802,11 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 - '@tanstack/query-core@5.103.1': - resolution: {integrity: sha512-rms8HqTGp6zA00dM+cUQ2eBcgzNJefuu5CAMB37i/6MiGT1zulPOytCFu2a0qjLqVR2n1jENPj9woqFQNuCzWA==} + '@tanstack/query-core@5.103.2': + resolution: {integrity: sha512-I8DkFXls5jXLqtm8+QpOhEmG07hIblhSZFzafg9wHsMSEvMzULy9hK17wU1T/ahfhMbtITJhbxutwwCoihkR7A==} - '@tanstack/react-query@5.103.1': - resolution: {integrity: sha512-rmAPPApNEK17VXRJhtE1rja53n23VV5w30C0saCgJhhX+EpdUnVqMGV/s5nnzV43X75KTHKzuoiuxSzGTBIkag==} + '@tanstack/react-query@5.103.2': + resolution: {integrity: sha512-B+fWiYZBc+0uUD5zDZAeLw9dKj7XEsdnuu6zRZ+no6LNKpeE3P3bJ+N6HINjcIlWR6fW3vUoTneEaGa2V6ehqw==} peerDependencies: react: ^18 || ^19 @@ -2337,8 +2337,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.433: - resolution: {integrity: sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==} + electron-to-chromium@1.5.438: + resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==} embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} @@ -2478,8 +2478,8 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - fs-extra@11.4.0: - resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} + fs-extra@11.4.1: + resolution: {integrity: sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==} engines: {node: '>=14.14'} fsevents@2.3.3: @@ -2994,8 +2994,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - node-releases@2.0.56: - resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} engines: {node: '>=18'} npm-run-path@4.0.1: @@ -3069,8 +3069,8 @@ packages: resolution: {integrity: sha512-rQAj2RAQM57nCwTd3PhtnAkREGW2StqI47tzudNlJF8WwGommM7rVpeP4O1O8/ZiuDZ9f2GnTESzLHGTluwkvQ==} hasBin: true - oxlint@1.83.0: - resolution: {integrity: sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ==} + oxlint@1.85.0: + resolution: {integrity: sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3231,8 +3231,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-i18next@17.0.14: - resolution: {integrity: sha512-ZpMBfJL3BiXPuYHj5QMY1GwvKJNhE1vCxOJQ8BoMBdHP5XONTfi/Qss1nuRlnFdIl72PKo2jPmw+fxuLjRFgaQ==} + react-i18next@17.0.15: + resolution: {integrity: sha512-7uYpx/oBkesLto7cOzOmfITlUBaXHCAcFahmgETMfWr8nbbWRRnVGvz8wsH/xpbNcyoH+NpCcnOQEqWPMO6/hw==} peerDependencies: i18next: '>= 26.2.0' react: '>= 16.8.0' @@ -4102,61 +4102,61 @@ snapshots: '@oxlint-tsgolint/win32-x64@7.0.2002': optional: true - '@oxlint/binding-android-arm-eabi@1.83.0': + '@oxlint/binding-android-arm-eabi@1.85.0': optional: true - '@oxlint/binding-android-arm64@1.83.0': + '@oxlint/binding-android-arm64@1.85.0': optional: true - '@oxlint/binding-darwin-arm64@1.83.0': + '@oxlint/binding-darwin-arm64@1.85.0': optional: true - '@oxlint/binding-darwin-x64@1.83.0': + '@oxlint/binding-darwin-x64@1.85.0': optional: true - '@oxlint/binding-freebsd-x64@1.83.0': + '@oxlint/binding-freebsd-x64@1.85.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.83.0': + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.83.0': + '@oxlint/binding-linux-arm-musleabihf@1.85.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.83.0': + '@oxlint/binding-linux-arm64-gnu@1.85.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.83.0': + '@oxlint/binding-linux-arm64-musl@1.85.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.83.0': + '@oxlint/binding-linux-ppc64-gnu@1.85.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.83.0': + '@oxlint/binding-linux-riscv64-gnu@1.85.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.83.0': + '@oxlint/binding-linux-riscv64-musl@1.85.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.83.0': + '@oxlint/binding-linux-s390x-gnu@1.85.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.83.0': + '@oxlint/binding-linux-x64-gnu@1.85.0': optional: true - '@oxlint/binding-linux-x64-musl@1.83.0': + '@oxlint/binding-linux-x64-musl@1.85.0': optional: true - '@oxlint/binding-openharmony-arm64@1.83.0': + '@oxlint/binding-openharmony-arm64@1.85.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.83.0': + '@oxlint/binding-win32-arm64-msvc@1.85.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.83.0': + '@oxlint/binding-win32-ia32-msvc@1.85.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.83.0': + '@oxlint/binding-win32-x64-msvc@1.85.0': optional: true '@radix-ui/colors@3.0.0': {} @@ -5043,11 +5043,11 @@ snapshots: tailwindcss: 4.3.3 vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0) - '@tanstack/query-core@5.103.1': {} + '@tanstack/query-core@5.103.2': {} - '@tanstack/react-query@5.103.1(react@19.3.0)': + '@tanstack/react-query@5.103.2(react@19.3.0)': dependencies: - '@tanstack/query-core': 5.103.1 + '@tanstack/query-core': 5.103.2 react: 19.3.0 '@ts-morph/common@0.27.0': @@ -5265,8 +5265,8 @@ snapshots: dependencies: baseline-browser-mapping: 2.11.25 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.433 - node-releases: 2.0.56 + electron-to-chromium: 1.5.438 + node-releases: 2.0.57 update-browserslist-db: 1.3.3(browserslist@4.29.0) bundle-name@4.1.0: @@ -5438,7 +5438,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.433: {} + electron-to-chromium@1.5.438: {} embla-carousel-react@8.6.0(react@19.3.0): dependencies: @@ -5611,7 +5611,7 @@ snapshots: fresh@2.0.0: {} - fs-extra@11.4.0: + fs-extra@11.4.1: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -5983,7 +5983,7 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - node-releases@2.0.56: {} + node-releases@2.0.57: {} npm-run-path@4.0.1: dependencies: @@ -6087,27 +6087,27 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2002 '@oxlint-tsgolint/win32-x64': 7.0.2002 - oxlint@1.83.0(oxlint-tsgolint@7.0.2002): + oxlint@1.85.0(oxlint-tsgolint@7.0.2002): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.83.0 - '@oxlint/binding-android-arm64': 1.83.0 - '@oxlint/binding-darwin-arm64': 1.83.0 - '@oxlint/binding-darwin-x64': 1.83.0 - '@oxlint/binding-freebsd-x64': 1.83.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.83.0 - '@oxlint/binding-linux-arm-musleabihf': 1.83.0 - '@oxlint/binding-linux-arm64-gnu': 1.83.0 - '@oxlint/binding-linux-arm64-musl': 1.83.0 - '@oxlint/binding-linux-ppc64-gnu': 1.83.0 - '@oxlint/binding-linux-riscv64-gnu': 1.83.0 - '@oxlint/binding-linux-riscv64-musl': 1.83.0 - '@oxlint/binding-linux-s390x-gnu': 1.83.0 - '@oxlint/binding-linux-x64-gnu': 1.83.0 - '@oxlint/binding-linux-x64-musl': 1.83.0 - '@oxlint/binding-openharmony-arm64': 1.83.0 - '@oxlint/binding-win32-arm64-msvc': 1.83.0 - '@oxlint/binding-win32-ia32-msvc': 1.83.0 - '@oxlint/binding-win32-x64-msvc': 1.83.0 + '@oxlint/binding-android-arm-eabi': 1.85.0 + '@oxlint/binding-android-arm64': 1.85.0 + '@oxlint/binding-darwin-arm64': 1.85.0 + '@oxlint/binding-darwin-x64': 1.85.0 + '@oxlint/binding-freebsd-x64': 1.85.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.85.0 + '@oxlint/binding-linux-arm-musleabihf': 1.85.0 + '@oxlint/binding-linux-arm64-gnu': 1.85.0 + '@oxlint/binding-linux-arm64-musl': 1.85.0 + '@oxlint/binding-linux-ppc64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-musl': 1.85.0 + '@oxlint/binding-linux-s390x-gnu': 1.85.0 + '@oxlint/binding-linux-x64-gnu': 1.85.0 + '@oxlint/binding-linux-x64-musl': 1.85.0 + '@oxlint/binding-openharmony-arm64': 1.85.0 + '@oxlint/binding-win32-arm64-msvc': 1.85.0 + '@oxlint/binding-win32-ia32-msvc': 1.85.0 + '@oxlint/binding-win32-x64-msvc': 1.85.0 oxlint-tsgolint: 7.0.2002 p-limit@2.3.0: @@ -6293,7 +6293,7 @@ snapshots: dependencies: react: 19.3.0 - react-i18next@17.0.14(i18next@26.4.2(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2): + react-i18next@17.0.15(i18next@26.4.2(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2): dependencies: '@babel/runtime': 7.29.7 html-parse-stringify: 4.0.1 @@ -6454,7 +6454,7 @@ snapshots: diff: 8.0.4 execa: 9.6.1 fast-glob: 3.3.3 - fs-extra: 11.4.0 + fs-extra: 11.4.1 fuzzysort: 3.1.0 kleur: 4.1.5 open: 11.0.4 diff --git a/web/src/client/schemas.gen.ts b/web/src/client/schemas.gen.ts index 2ee179c..8fb1296 100644 --- a/web/src/client/schemas.gen.ts +++ b/web/src/client/schemas.gen.ts @@ -534,7 +534,8 @@ export const internal_httpapi_PlaybackResponseSchema = { uniqueItems: false }, series: { - $ref: '#/components/schemas/internal_httpapi.PlaybackSeriesContext' + $ref: '#/components/schemas/internal_httpapi.PlaybackSeriesContext', + description: 'Series is set only for paths in a series library with resolvable S/E identity.' }, status: { $ref: '#/components/schemas/sudoStream_internal_transcode.Status' @@ -553,14 +554,14 @@ export const internal_httpapi_PlaybackResponseSchema = { type: 'boolean' }, userSubtitle: { - $ref: '#/components/schemas/internal_httpapi.UserSubtitleTrack' + $ref: '#/components/schemas/internal_httpapi.UserSubtitleTrack', + description: 'UserSubtitle is the current user\'s uploaded caption when present (TTL 1d).' } }, type: 'object' } as const; export const internal_httpapi_PlaybackSeriesContextSchema = { - description: 'Series is set only for paths in a series library with resolvable S/E identity.', properties: { episode: { type: 'integer' @@ -793,7 +794,6 @@ export const internal_httpapi_UserResponseSchema = { } as const; export const internal_httpapi_UserSubtitleTrackSchema = { - description: 'UserSubtitle is the current user\'s uploaded caption when present (TTL 1d).', properties: { label: { type: 'string' diff --git a/web/src/client/sdk.gen.ts b/web/src/client/sdk.gen.ts index 37cc62f..4e21c15 100644 --- a/web/src/client/sdk.gen.ts +++ b/web/src/client/sdk.gen.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Client, type ClientMeta, type Options as Options2, type RequestResult, type TDataShape, urlSearchParamsBodySerializer } from './client'; +import { type Client, type ClientMeta, formDataBodySerializer, type Options as Options2, type RequestResult, type TDataShape, urlSearchParamsBodySerializer } from './client'; import { client } from './client.gen'; import type { DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesErrors, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsErrors, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashErrors, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersErrors, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeErrors, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsErrors, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathErrors, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseErrors, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathErrors, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthErrors, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesErrors, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueErrors, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesErrors, GetApiMeFavoritesResponses, GetApiMeStatsData, GetApiMeStatsErrors, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathErrors, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedErrors, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathErrors, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchErrors, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathErrors, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathErrors, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponses, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathErrors, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesErrors, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersErrors, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginErrors, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutErrors, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshErrors, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenErrors, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponses } from './types.gen'; @@ -791,11 +791,11 @@ export const getApiUserSubtitleByPath = (o * Accepts VTT or SRT (multipart field "file"); replaces existing upload. TTL 1d. */ export const putApiUserSubtitleByPath = (options: Options): RequestResult => (options.client ?? client).put({ - ...urlSearchParamsBodySerializer, + ...formDataBodySerializer, url: '/api/user-subtitle/{path}', ...options, headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': null, ...options.headers } }); diff --git a/web/src/client/types.gen.ts b/web/src/client/types.gen.ts index 982fbf0..6e2cee3 100644 --- a/web/src/client/types.gen.ts +++ b/web/src/client/types.gen.ts @@ -202,17 +202,20 @@ export type InternalHttpapiPlaybackResponse = { playMethod?: SudoStreamInternalPlaybackPlayMethod; providerSubtitleTracks?: Array; qualities?: Array; + /** + * Series is set only for paths in a series library with resolvable S/E identity. + */ series?: InternalHttpapiPlaybackSeriesContext; status?: SudoStreamInternalTranscodeStatus; streamUrl?: string; subtitleTracks?: Array; toneMapped?: boolean; + /** + * UserSubtitle is the current user's uploaded caption when present (TTL 1d). + */ userSubtitle?: InternalHttpapiUserSubtitleTrack; }; -/** - * Series is set only for paths in a series library with resolvable S/E identity. - */ export type InternalHttpapiPlaybackSeriesContext = { episode?: number; librarySlug?: string; @@ -298,9 +301,6 @@ export type InternalHttpapiUserResponse = { user?: SudoStreamInternalAuthPublicUser; }; -/** - * UserSubtitle is the current user's uploaded caption when present (TTL 1d). - */ export type InternalHttpapiUserSubtitleTrack = { label?: string; lang?: string; @@ -783,9 +783,7 @@ export type PatchApiAdminDlnaSettingsData = { /** * DLNA settings */ - body: { - [key: string]: unknown; - } | SudoStreamInternalDlnaSettings; + body: SudoStreamInternalDlnaSettings; path?: never; query?: never; url: '/api/admin/dlna/settings'; @@ -881,9 +879,7 @@ export type PostApiAdminInvitesData = { /** * Invite */ - body: { - [key: string]: unknown; - } | InternalHttpapiCreateInviteRequest; + body: InternalHttpapiCreateInviteRequest; path?: never; query?: never; url: '/api/admin/invites'; @@ -953,9 +949,7 @@ export type PostApiAdminInvitesByIdResendData = { /** * Optional TTL override */ - body?: { - [key: string]: unknown; - } | InternalHttpapiResendInviteRequest; + body?: InternalHttpapiResendInviteRequest; path: { /** * Invite ID @@ -1021,9 +1015,7 @@ export type PostApiAdminLibrariesData = { /** * Library fields */ - body: { - [key: string]: unknown; - } | InternalHttpapiCreateLibraryRequest; + body: InternalHttpapiCreateLibraryRequest; path?: never; query?: never; url: '/api/admin/libraries'; @@ -1114,9 +1106,7 @@ export type PatchApiAdminLibrariesByIdData = { /** * Library fields */ - body: { - [key: string]: unknown; - } | InternalHttpapiPatchLibraryRequest; + body: InternalHttpapiPatchLibraryRequest; path: { /** * Library ID @@ -1191,9 +1181,7 @@ export type PatchApiAdminLibrariesByIdMaintenanceData = { /** * Schedules */ - body: { - [key: string]: unknown; - } | InternalHttpapiPatchMaintenanceRequest; + body: InternalHttpapiPatchMaintenanceRequest; path: { /** * Library ID @@ -1268,9 +1256,7 @@ export type PatchApiAdminLibrariesByIdProvidersData = { /** * Provider settings */ - body: { - [key: string]: unknown; - } | InternalHttpapiPatchProviderSettingsRequest; + body: InternalHttpapiPatchProviderSettingsRequest; path: { /** * Library ID @@ -1358,9 +1344,7 @@ export type PostApiAdminLibrariesByIdRootsData = { /** * Folder */ - body: { - [key: string]: unknown; - } | InternalHttpapiAddLibraryRootRequest; + body: InternalHttpapiAddLibraryRootRequest; path: { /** * Library ID @@ -1464,9 +1448,7 @@ export type PatchApiAdminMaintenanceData = { /** * Schedules */ - body: { - [key: string]: unknown; - } | InternalHttpapiPatchMaintenanceRequest; + body: InternalHttpapiPatchMaintenanceRequest; path?: never; query?: never; url: '/api/admin/maintenance'; @@ -1583,9 +1565,7 @@ export type PatchApiAdminNetworkSettingsData = { /** * Network settings */ - body: { - [key: string]: unknown; - } | SudoStreamInternalNetworkSettings; + body: SudoStreamInternalNetworkSettings; path?: never; query?: never; url: '/api/admin/network/settings'; @@ -1617,9 +1597,7 @@ export type PostApiAdminNetworkSettingsTestData = { /** * Draft network settings */ - body: { - [key: string]: unknown; - } | SudoStreamInternalNetworkSettings; + body: SudoStreamInternalNetworkSettings; path?: never; query?: never; url: '/api/admin/network/settings/test'; @@ -1706,9 +1684,7 @@ export type PatchApiAdminSettingsData = { /** * Settings */ - body: { - [key: string]: unknown; - } | SudoStreamInternalAuthSettings; + body: SudoStreamInternalAuthSettings; path?: never; query?: never; url: '/api/admin/settings'; @@ -1765,9 +1741,7 @@ export type PatchApiAdminTranscodeSettingsData = { /** * Transcode settings */ - body: { - [key: string]: unknown; - } | SudoStreamInternalTranscodeTranscodeSettings; + body: SudoStreamInternalTranscodeTranscodeSettings; path?: never; query?: never; url: '/api/admin/transcode/settings'; @@ -1869,9 +1843,7 @@ export type PatchApiAdminTrashSettingsData = { /** * Recycle bin settings */ - body: { - [key: string]: unknown; - } | InternalHttpapiTrashSettingsResponse; + body: InternalHttpapiTrashSettingsResponse; path?: never; query?: never; url: '/api/admin/trash/settings'; @@ -1988,9 +1960,7 @@ export type PostApiAdminUsersData = { /** * New user */ - body: { - [key: string]: unknown; - } | InternalHttpapiCreateUserRequest; + body: InternalHttpapiCreateUserRequest; path?: never; query?: never; url: '/api/admin/users'; @@ -2068,9 +2038,7 @@ export type PatchApiAdminUsersByIdData = { /** * User patch */ - body: { - [key: string]: unknown; - } | InternalHttpapiUpdateUserRequest; + body: InternalHttpapiUpdateUserRequest; path: { /** * User ID @@ -2171,9 +2139,7 @@ export type PutApiAdminUsersByIdLibrariesData = { /** * Grant list */ - body: { - [key: string]: unknown; - } | InternalHttpapiPutGrantsRequest; + body: InternalHttpapiPutGrantsRequest; path: { /** * User ID @@ -2308,9 +2274,7 @@ export type DeleteApiAuth2FaData = { /** * Password and TOTP code */ - body: { - [key: string]: unknown; - } | InternalHttpapiDisableTwoFactorRequest; + body: InternalHttpapiDisableTwoFactorRequest; path?: never; query?: never; url: '/api/auth/2fa'; @@ -2342,9 +2306,7 @@ export type PostApiAuth2FaConfirmData = { /** * TOTP code */ - body: { - [key: string]: unknown; - } | InternalHttpapiTwoFactorConfirmRequest; + body: InternalHttpapiTwoFactorConfirmRequest; path?: never; query?: never; url: '/api/auth/2fa/confirm'; @@ -2376,9 +2338,7 @@ export type PostApiAuth2FaSetupData = { /** * Optional pending token */ - body?: { - [key: string]: unknown; - } | InternalHttpapiTwoFactorSetupRequest; + body?: InternalHttpapiTwoFactorSetupRequest; path?: never; query?: never; url: '/api/auth/2fa/setup'; @@ -2414,9 +2374,7 @@ export type PostApiAuth2FaVerifyData = { /** * Pending token and code */ - body: { - [key: string]: unknown; - } | InternalHttpapiVerifyTwoFactorRequest; + body: InternalHttpapiVerifyTwoFactorRequest; path?: never; query?: never; url: '/api/auth/2fa/verify'; @@ -2448,9 +2406,7 @@ export type PostApiAuthAcceptInviteData = { /** * Invite token */ - body: { - [key: string]: unknown; - } | InternalHttpapiAcceptInviteRequest; + body: InternalHttpapiAcceptInviteRequest; path?: never; query?: never; url: '/api/auth/accept-invite'; @@ -2482,9 +2438,7 @@ export type PostApiAuthChangeEmailData = { /** * Email change */ - body: { - [key: string]: unknown; - } | InternalHttpapiChangeEmailRequest; + body: InternalHttpapiChangeEmailRequest; path?: never; query?: never; url: '/api/auth/change-email'; @@ -2524,9 +2478,7 @@ export type PostApiAuthChangePasswordData = { /** * Password change */ - body: { - [key: string]: unknown; - } | InternalHttpapiChangePasswordRequest; + body: InternalHttpapiChangePasswordRequest; path?: never; query?: never; url: '/api/auth/change-password'; @@ -2592,9 +2544,7 @@ export type PostApiAuthForgotPasswordData = { /** * Email */ - body: { - [key: string]: unknown; - } | InternalHttpapiForgotPasswordRequest; + body: InternalHttpapiForgotPasswordRequest; path?: never; query?: never; url: '/api/auth/forgot-password'; @@ -2622,9 +2572,7 @@ export type PostApiAuthLoginData = { /** * Credentials */ - body: { - [key: string]: unknown; - } | InternalHttpapiLoginRequest; + body: InternalHttpapiLoginRequest; path?: never; query?: never; url: '/api/auth/login'; @@ -2739,9 +2687,7 @@ export type PostApiAuthResetPasswordData = { /** * Reset token */ - body: { - [key: string]: unknown; - } | InternalHttpapiResetPasswordRequest; + body: InternalHttpapiResetPasswordRequest; path?: never; query?: never; url: '/api/auth/reset-password'; @@ -3158,9 +3104,7 @@ export type PatchApiFavoriteByPathData = { /** * Favorite flag */ - body: { - [key: string]: unknown; - } | InternalHttpapiFavoritePatchRequest; + body: InternalHttpapiFavoritePatchRequest; path: { /** * Media file path @@ -3525,9 +3469,7 @@ export type PatchApiMetadataByPathData = { /** * Metadata patch */ - body: { - [key: string]: unknown; - } | SudoStreamInternalMetadataPatchRequest; + body: SudoStreamInternalMetadataPatchRequest; path: { /** * Path under media root @@ -3573,7 +3515,9 @@ export type PatchApiMetadataByPathResponses = { export type PatchApiMetadataByPathResponse = PatchApiMetadataByPathResponses[keyof PatchApiMetadataByPathResponses]; export type PostApiOauthTokenData = { - body?: string; + body?: { + [key: string]: unknown; + }; path?: never; query?: never; url: '/api/oauth/token'; @@ -3702,9 +3646,7 @@ export type PostApiPlaybackByPathData = { /** * Device profile and quality selection */ - body?: { - [key: string]: unknown; - } | InternalHttpapiPlaybackNegotiateRequest; + body?: InternalHttpapiPlaybackNegotiateRequest; path: { /** * Media file path @@ -4027,9 +3969,22 @@ export type GetApiUserSubtitleByPathResponse = GetApiUserSubtitleByPathResponses export type PutApiUserSubtitleByPathData = { /** - * ISO 639-1 language code | Display label | Subtitle file (.vtt or .srt) + * ISO 639-1 language code */ - body: string | unknown; + body: { + /** + * Subtitle file (.vtt or .srt) + */ + file: Blob | File; + /** + * Display label + */ + label?: string; + /** + * ISO 639-1 language code + */ + lang?: string; + }; path: { /** * Path under media root @@ -4116,9 +4071,7 @@ export type PatchApiWatchByPathData = { /** * Watched flag and optional progress */ - body: { - [key: string]: unknown; - } | InternalHttpapiWatchPatchRequest; + body: InternalHttpapiWatchPatchRequest; path: { /** * Media file path diff --git a/web/src/client/zod.gen.ts b/web/src/client/zod.gen.ts index 85cde51..f9cd42d 100644 --- a/web/src/client/zod.gen.ts +++ b/web/src/client/zod.gen.ts @@ -111,9 +111,6 @@ export const zInternalHttpapiPatchProviderSettingsRequest = z.object({ subtitleProvider: z.string().optional() }); -/** - * Series is set only for paths in a series library with resolvable S/E identity. - */ export const zInternalHttpapiPlaybackSeriesContext = z.object({ episode: z.int().optional(), librarySlug: z.string().optional(), @@ -166,9 +163,6 @@ export const zInternalHttpapiUpdateUserRequest = z.object({ role: z.string().optional() }); -/** - * UserSubtitle is the current user's uploaded caption when present (TTL 1d). - */ export const zInternalHttpapiUserSubtitleTrack = z.object({ label: z.string().optional(), lang: z.string().optional(), @@ -758,10 +752,7 @@ export const zGetApiAdminDlnaSettingsResponse = zSudoStreamInternalDlnaSettings; /** * DLNA settings */ -export const zPatchApiAdminDlnaSettingsBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalDlnaSettings -]); +export const zPatchApiAdminDlnaSettingsBody = zSudoStreamInternalDlnaSettings; /** * OK @@ -789,10 +780,7 @@ export const zGetApiAdminInvitesResponse = zInternalHttpapiInvitesResponse; /** * Invite */ -export const zPostApiAdminInvitesBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiCreateInviteRequest -]); +export const zPostApiAdminInvitesBody = zInternalHttpapiCreateInviteRequest; /** * No Content @@ -811,10 +799,7 @@ export const zDeleteApiAdminInvitesByIdResponse = z.void(); /** * Optional TTL override */ -export const zPostApiAdminInvitesByIdResendBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiResendInviteRequest -]); +export const zPostApiAdminInvitesByIdResendBody = zInternalHttpapiResendInviteRequest; export const zPostApiAdminInvitesByIdResendPath = z.object({ id: z.string() @@ -833,10 +818,7 @@ export const zGetApiAdminLibrariesResponse = zInternalHttpapiLibrariesResponse; /** * Library fields */ -export const zPostApiAdminLibrariesBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiCreateLibraryRequest -]); +export const zPostApiAdminLibrariesBody = zInternalHttpapiCreateLibraryRequest; /** * Created @@ -860,10 +842,7 @@ export const zDeleteApiAdminLibrariesByIdResponse = z.void(); /** * Library fields */ -export const zPatchApiAdminLibrariesByIdBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPatchLibraryRequest -]); +export const zPatchApiAdminLibrariesByIdBody = zInternalHttpapiPatchLibraryRequest; export const zPatchApiAdminLibrariesByIdPath = z.object({ id: z.string() @@ -886,10 +865,7 @@ export const zGetApiAdminLibrariesByIdMaintenanceResponse = zInternalHttpapiMain /** * Schedules */ -export const zPatchApiAdminLibrariesByIdMaintenanceBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPatchMaintenanceRequest -]); +export const zPatchApiAdminLibrariesByIdMaintenanceBody = zInternalHttpapiPatchMaintenanceRequest; export const zPatchApiAdminLibrariesByIdMaintenancePath = z.object({ id: z.string() @@ -912,10 +888,7 @@ export const zGetApiAdminLibrariesByIdProvidersResponse = zInternalHttpapiProvid /** * Provider settings */ -export const zPatchApiAdminLibrariesByIdProvidersBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPatchProviderSettingsRequest -]); +export const zPatchApiAdminLibrariesByIdProvidersBody = zInternalHttpapiPatchProviderSettingsRequest; export const zPatchApiAdminLibrariesByIdProvidersPath = z.object({ id: z.string() @@ -942,10 +915,7 @@ export const zDeleteApiAdminLibrariesByIdRootsResponse = z.union([ /** * Folder */ -export const zPostApiAdminLibrariesByIdRootsBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiAddLibraryRootRequest -]); +export const zPostApiAdminLibrariesByIdRootsBody = zInternalHttpapiAddLibraryRootRequest; export const zPostApiAdminLibrariesByIdRootsPath = z.object({ id: z.string() @@ -973,10 +943,7 @@ export const zGetApiAdminMaintenanceResponse = zInternalHttpapiMaintenanceStatus /** * Schedules */ -export const zPatchApiAdminMaintenanceBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPatchMaintenanceRequest -]); +export const zPatchApiAdminMaintenanceBody = zInternalHttpapiPatchMaintenanceRequest; /** * OK @@ -1013,10 +980,7 @@ export const zGetApiAdminNetworkSettingsResponse = zSudoStreamInternalNetworkSet /** * Network settings */ -export const zPatchApiAdminNetworkSettingsBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalNetworkSettings -]); +export const zPatchApiAdminNetworkSettingsBody = zSudoStreamInternalNetworkSettings; /** * OK @@ -1026,10 +990,7 @@ export const zPatchApiAdminNetworkSettingsResponse = zSudoStreamInternalNetworkS /** * Draft network settings */ -export const zPostApiAdminNetworkSettingsTestBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalNetworkSettings -]); +export const zPostApiAdminNetworkSettingsTestBody = zSudoStreamInternalNetworkSettings; /** * OK @@ -1053,10 +1014,7 @@ export const zGetApiAdminSettingsResponse = zSudoStreamInternalAuthSettings; /** * Settings */ -export const zPatchApiAdminSettingsBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalAuthSettings -]); +export const zPatchApiAdminSettingsBody = zSudoStreamInternalAuthSettings; /** * OK @@ -1071,10 +1029,7 @@ export const zGetApiAdminTranscodeSettingsResponse = zSudoStreamInternalTranscod /** * Transcode settings */ -export const zPatchApiAdminTranscodeSettingsBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalTranscodeTranscodeSettings -]); +export const zPatchApiAdminTranscodeSettingsBody = zSudoStreamInternalTranscodeTranscodeSettings; /** * OK @@ -1099,10 +1054,7 @@ export const zGetApiAdminTrashSettingsResponse = zInternalHttpapiTrashSettingsRe /** * Recycle bin settings */ -export const zPatchApiAdminTrashSettingsBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiTrashSettingsResponse -]); +export const zPatchApiAdminTrashSettingsBody = zInternalHttpapiTrashSettingsResponse; /** * OK @@ -1135,10 +1087,7 @@ export const zGetApiAdminUsersResponse = zInternalHttpapiUsersResponse; /** * New user */ -export const zPostApiAdminUsersBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiCreateUserRequest -]); +export const zPostApiAdminUsersBody = zInternalHttpapiCreateUserRequest; /** * Created @@ -1157,10 +1106,7 @@ export const zDeleteApiAdminUsersByIdResponse = z.void(); /** * User patch */ -export const zPatchApiAdminUsersByIdBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiUpdateUserRequest -]); +export const zPatchApiAdminUsersByIdBody = zInternalHttpapiUpdateUserRequest; export const zPatchApiAdminUsersByIdPath = z.object({ id: z.string() @@ -1192,10 +1138,7 @@ export const zGetApiAdminUsersByIdLibrariesResponse = zInternalHttpapiGrantsResp /** * Grant list */ -export const zPutApiAdminUsersByIdLibrariesBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPutGrantsRequest -]); +export const zPutApiAdminUsersByIdLibrariesBody = zInternalHttpapiPutGrantsRequest; export const zPutApiAdminUsersByIdLibrariesPath = z.object({ id: z.string() @@ -1236,10 +1179,7 @@ export const zPostApiAdminUsersByIdSessionsRevokeAllResponse = z.void(); /** * Password and TOTP code */ -export const zDeleteApiAuth2FaBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiDisableTwoFactorRequest -]); +export const zDeleteApiAuth2FaBody = zInternalHttpapiDisableTwoFactorRequest; /** * No Content @@ -1249,10 +1189,7 @@ export const zDeleteApiAuth2FaResponse = z.void(); /** * TOTP code */ -export const zPostApiAuth2FaConfirmBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiTwoFactorConfirmRequest -]); +export const zPostApiAuth2FaConfirmBody = zInternalHttpapiTwoFactorConfirmRequest; /** * OK @@ -1262,10 +1199,7 @@ export const zPostApiAuth2FaConfirmResponse = zSudoStreamInternalAuthTwoFactorCo /** * Optional pending token */ -export const zPostApiAuth2FaSetupBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiTwoFactorSetupRequest -]); +export const zPostApiAuth2FaSetupBody = zInternalHttpapiTwoFactorSetupRequest; /** * OK @@ -1275,10 +1209,7 @@ export const zPostApiAuth2FaSetupResponse = zSudoStreamInternalAuthTwoFactorSetu /** * Pending token and code */ -export const zPostApiAuth2FaVerifyBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiVerifyTwoFactorRequest -]); +export const zPostApiAuth2FaVerifyBody = zInternalHttpapiVerifyTwoFactorRequest; /** * OK @@ -1288,10 +1219,7 @@ export const zPostApiAuth2FaVerifyResponse = zInternalHttpapiAuthTokensResponse; /** * Invite token */ -export const zPostApiAuthAcceptInviteBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiAcceptInviteRequest -]); +export const zPostApiAuthAcceptInviteBody = zInternalHttpapiAcceptInviteRequest; /** * No Content @@ -1301,10 +1229,7 @@ export const zPostApiAuthAcceptInviteResponse = z.void(); /** * Email change */ -export const zPostApiAuthChangeEmailBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiChangeEmailRequest -]); +export const zPostApiAuthChangeEmailBody = zInternalHttpapiChangeEmailRequest; /** * No Content @@ -1314,10 +1239,7 @@ export const zPostApiAuthChangeEmailResponse = z.void(); /** * Password change */ -export const zPostApiAuthChangePasswordBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiChangePasswordRequest -]); +export const zPostApiAuthChangePasswordBody = zInternalHttpapiChangePasswordRequest; /** * OK @@ -1336,10 +1258,7 @@ export const zGetApiAuthConfirmEmailResponse = zInternalHttpapiStatusResponse; /** * Email */ -export const zPostApiAuthForgotPasswordBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiForgotPasswordRequest -]); +export const zPostApiAuthForgotPasswordBody = zInternalHttpapiForgotPasswordRequest; /** * No Content @@ -1349,10 +1268,7 @@ export const zPostApiAuthForgotPasswordResponse = z.void(); /** * Credentials */ -export const zPostApiAuthLoginBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiLoginRequest -]); +export const zPostApiAuthLoginBody = zInternalHttpapiLoginRequest; /** * OK @@ -1377,10 +1293,7 @@ export const zPostApiAuthRefreshResponse = zInternalHttpapiRefreshResponse; /** * Reset token */ -export const zPostApiAuthResetPasswordBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiResetPasswordRequest -]); +export const zPostApiAuthResetPasswordBody = zInternalHttpapiResetPasswordRequest; /** * No Content @@ -1491,10 +1404,7 @@ export const zGetApiFavoriteByPathResponse = zInternalHttpapiFavoriteResponse; /** * Favorite flag */ -export const zPatchApiFavoriteByPathBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiFavoritePatchRequest -]); +export const zPatchApiFavoriteByPathBody = zInternalHttpapiFavoritePatchRequest; export const zPatchApiFavoriteByPathPath = z.object({ path: z.string() @@ -1585,10 +1495,7 @@ export const zGetApiMetadataByPathResponse = zSudoStreamInternalMetadataMetadata /** * Metadata patch */ -export const zPatchApiMetadataByPathBody = z.union([ - z.record(z.string(), z.unknown()), - zSudoStreamInternalMetadataPatchRequest -]); +export const zPatchApiMetadataByPathBody = zSudoStreamInternalMetadataPatchRequest; export const zPatchApiMetadataByPathPath = z.object({ path: z.string() @@ -1599,7 +1506,7 @@ export const zPatchApiMetadataByPathPath = z.object({ */ export const zPatchApiMetadataByPathResponse = zSudoStreamInternalMetadataMetadataResponse; -export const zPostApiOauthTokenBody = z.string(); +export const zPostApiOauthTokenBody = z.record(z.string(), z.unknown()); /** * OK @@ -1632,10 +1539,7 @@ export const zGetApiPlaybackByPathResponse = zInternalHttpapiPlaybackResponse; /** * Device profile and quality selection */ -export const zPostApiPlaybackByPathBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiPlaybackNegotiateRequest -]); +export const zPostApiPlaybackByPathBody = zInternalHttpapiPlaybackNegotiateRequest; export const zPostApiPlaybackByPathPath = z.object({ path: z.string() @@ -1721,12 +1625,13 @@ export const zGetApiUserSubtitleByPathPath = z.object({ export const zGetApiUserSubtitleByPathResponse = z.string(); /** - * ISO 639-1 language code | Display label | Subtitle file (.vtt or .srt) + * ISO 639-1 language code */ -export const zPutApiUserSubtitleByPathBody = z.union([ - z.string(), - z.unknown() -]); +export const zPutApiUserSubtitleByPathBody = z.object({ + file: z.string(), + label: z.string().optional(), + lang: z.string().optional() +}); export const zPutApiUserSubtitleByPathPath = z.object({ path: z.string() @@ -1749,10 +1654,7 @@ export const zGetApiWatchByPathResponse = zInternalHttpapiWatchResponse; /** * Watched flag and optional progress */ -export const zPatchApiWatchByPathBody = z.union([ - z.record(z.string(), z.unknown()), - zInternalHttpapiWatchPatchRequest -]); +export const zPatchApiWatchByPathBody = zInternalHttpapiWatchPatchRequest; export const zPatchApiWatchByPathPath = z.object({ path: z.string() From c96810331e2ff202759cab810f8b749da1b7f054 Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Fri, 25 Sep 2026 02:30:59 +0300 Subject: [PATCH 03/11] feat: skip intro from chapters via player button --- internal/httpapi/play_handlers.go | 22 ++++ internal/httpapi/play_handlers_test.go | 46 ++++++++ internal/skipsegment/chapters.go | 94 ++++++++++++++++ internal/skipsegment/chapters_test.go | 104 ++++++++++++++++++ internal/skipsegment/types.go | 19 ++++ openapi/swagger.json | 18 +++ openapi/swagger.yaml | 13 +++ web/src/api/playback.ts | 1 + web/src/client/index.ts | 2 +- web/src/client/schemas.gen.ts | 19 ++++ web/src/client/types.gen.ts | 10 ++ web/src/client/zod.gen.ts | 7 ++ .../player/series-player-chrome.tsx | 30 +++-- .../components/player/skip-intro-action.tsx | 45 ++++++++ web/src/components/player/videojs-player.tsx | 10 ++ web/src/lib/skip-intro.ts | 29 +++++ web/src/locales/en/translation.json | 1 + web/src/locales/ru/translation.json | 1 + web/src/pages/player/index.tsx | 1 + 19 files changed, 462 insertions(+), 10 deletions(-) create mode 100644 internal/skipsegment/chapters.go create mode 100644 internal/skipsegment/chapters_test.go create mode 100644 internal/skipsegment/types.go create mode 100644 web/src/components/player/skip-intro-action.tsx create mode 100644 web/src/lib/skip-intro.ts diff --git a/internal/httpapi/play_handlers.go b/internal/httpapi/play_handlers.go index 8df9eca..c903608 100644 --- a/internal/httpapi/play_handlers.go +++ b/internal/httpapi/play_handlers.go @@ -13,6 +13,7 @@ import ( "sudoStream/internal/mediafs" "sudoStream/internal/observability" "sudoStream/internal/playback" + "sudoStream/internal/skipsegment" "sudoStream/internal/transcode" "github.com/gin-gonic/gin" @@ -44,6 +45,8 @@ type PlaybackResponse struct { UserSubtitle *UserSubtitleTrack `json:"userSubtitle,omitempty"` // Chapters are container chapter atoms on any video file (not limited to film/series). Chapters []transcode.ChapterInfo `json:"chapters,omitempty"` + // SkipIntro is set when chapter metadata identifies an opening intro segment (E-23). + SkipIntro *skipsegment.Intro `json:"skipIntro,omitempty"` // Series is set only for paths in a series library with resolvable S/E identity. Series *PlaybackSeriesContext `json:"series,omitempty"` } @@ -463,6 +466,7 @@ func (h *handler) respondHLSPlayback( ProviderSubtitleTracks: h.providerSubtitleTracks(c.Request.Context(), mediaPath), UserSubtitle: h.userSubtitleTrack(c, mediaPath), Chapters: chapters, + SkipIntro: skipIntroFromChapters(chapters, playbackInfo.DurationSeconds), Series: h.playbackSeriesContext(c, mediaPath), }) } @@ -523,10 +527,28 @@ func (h *handler) respondDirectPlay( ProviderSubtitleTracks: h.providerSubtitleTracks(c.Request.Context(), mediaPath), UserSubtitle: h.userSubtitleTrack(c, mediaPath), Chapters: chapters, + SkipIntro: skipIntroFromChapters(chapters, caps.DurationSeconds), Series: h.playbackSeriesContext(c, mediaPath), }) } +func skipIntroFromChapters(chapters []transcode.ChapterInfo, durationSeconds float64) *skipsegment.Intro { + if len(chapters) == 0 || durationSeconds <= 0 { + return nil + } + + cues := make([]skipsegment.ChapterCue, len(chapters)) + for index, chapter := range chapters { + cues[index] = skipsegment.ChapterCue{ + StartSeconds: chapter.StartSeconds, + EndSeconds: chapter.EndSeconds, + Title: chapter.Title, + } + } + + return skipsegment.IntroFromChapters(cues, durationSeconds) +} + func (h *handler) sourceCapsForPlayback( c *gin.Context, filePath, mediaPath, outDir string, diff --git a/internal/httpapi/play_handlers_test.go b/internal/httpapi/play_handlers_test.go index e22e0a3..e8aef1b 100644 --- a/internal/httpapi/play_handlers_test.go +++ b/internal/httpapi/play_handlers_test.go @@ -200,6 +200,52 @@ func TestIsVideoMediaFile(t *testing.T) { }) } +func TestPlaybackHandler_ReturnsSkipIntroFromChapterTitles(t *testing.T) { + t.Parallel() + + allure.Test(t, "GET /api/playback exposes skipIntro from opening chapter", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + + fixture := newPlayTestFixture(t, playTestSampleRel) + meta := fixture.sourceMeta() + meta.DurationSeconds = 3600 + meta.Chapters = []transcode.ChapterInfo{ + {StartSeconds: 0, EndSeconds: 30, Title: "Recap"}, + {StartSeconds: 30, EndSeconds: 120, Title: "Opening Theme"}, + } + fixture.writePublishedCacheWithMeta(t, meta) + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequestWithContext( + context.Background(), + http.MethodGet, + "/api/playback/"+fixture.rel, + nil, + ) + ctx.Params = gin.Params{{Key: playTestPathParamKey, Value: fixture.rel}} + + fixture.handler().playback(ctx) + + if recorder.Code != http.StatusOK { + t.Fatalf("status: got %d body=%s", recorder.Code, recorder.Body.String()) + } + + var body PlaybackResponse + err := json.Unmarshal(recorder.Body.Bytes(), &body) + if err != nil { + t.Fatalf("decode: %v", err) + } + if body.SkipIntro == nil { + t.Fatal("missing skipIntro") + } + if body.SkipIntro.StartMs != 30000 || body.SkipIntro.EndMs != 120000 { + t.Fatalf("skipIntro range: %+v", body.SkipIntro) + } + }) +} + func TestPlaybackHandler_ReturnsReadyFromSeededCache(t *testing.T) { t.Parallel() diff --git a/internal/skipsegment/chapters.go b/internal/skipsegment/chapters.go new file mode 100644 index 0000000..2cefa0e --- /dev/null +++ b/internal/skipsegment/chapters.go @@ -0,0 +1,94 @@ +package skipsegment + +import ( + "math" + "regexp" + "strings" +) + +const ( + minIntroSec = 10 + maxIntroSec = 150 + maxScanWindowSec = 600 // 10 minutes + maxScanRuntimeRatio = 0.30 +) + +var ( + recapTitleRe = regexp.MustCompile(`(?i)\b(recap|previously(\s+on)?|summary)\b`) + introTitleRe = regexp.MustCompile( + `(?i)\b(intro|opening|theme|title sequence|main titles)\b|` + + `(?i)(^|[\s\-—(])op([\s\-—)]|$)`, + ) +) + +// IntroFromChapters picks the first valid opening intro from embedded chapters. +// Returns nil when no qualifying chapter exists (prefer miss over bad skip). +func IntroFromChapters(chapters []ChapterCue, durationSeconds float64) *Intro { + if len(chapters) == 0 || durationSeconds <= 0 { + return nil + } + + scanWindow := introScanWindowSec(durationSeconds) + + for index, chapter := range chapters { + if !titleMatchesIntro(chapter.Title) || titleMatchesRecap(chapter.Title) { + continue + } + if index > 0 && titleMatchesIntro(chapters[index-1].Title) && + !titleMatchesRecap(chapters[index-1].Title) { + continue + } + + start := chapter.StartSeconds + end := chapter.EndSeconds + if end <= start { + continue + } + duration := end - start + if duration < minIntroSec || duration > maxIntroSec { + continue + } + if start < 0 || start > scanWindow { + continue + } + + return &Intro{ + StartMs: secondsToMs(start), + EndMs: secondsToMs(end), + Source: SourceChapter, + } + } + + return nil +} + +func introScanWindowSec(durationSeconds float64) float64 { + cap := durationSeconds * maxScanRuntimeRatio + if cap > maxScanWindowSec { + cap = maxScanWindowSec + } + return cap +} + +func titleMatchesIntro(title string) bool { + title = strings.TrimSpace(title) + if title == "" { + return false + } + return introTitleRe.MatchString(title) +} + +func titleMatchesRecap(title string) bool { + title = strings.TrimSpace(title) + if title == "" { + return false + } + return recapTitleRe.MatchString(title) +} + +func secondsToMs(seconds float64) int64 { + if seconds <= 0 { + return 0 + } + return int64(math.Round(seconds * 1000)) +} diff --git a/internal/skipsegment/chapters_test.go b/internal/skipsegment/chapters_test.go new file mode 100644 index 0000000..ce1d9ef --- /dev/null +++ b/internal/skipsegment/chapters_test.go @@ -0,0 +1,104 @@ +package skipsegment + +import ( + "testing" + + allure "github.com/allure-framework/allure-go/commons/gotest" +) + +func TestIntroFromChapters_MatchesOpeningTheme(t *testing.T) { + t.Parallel() + + allure.Test(t, "chapter intro title within duration and scan window", func(a *allure.Context) { + t := a.T() + intro := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 45, Title: "Cold open"}, + {StartSeconds: 45, EndSeconds: 135, Title: "Opening Theme"}, + }, 3600) + if intro == nil { + t.Fatal("expected intro segment") + } + if intro.StartMs != 45000 || intro.EndMs != 135000 { + t.Fatalf("range: %+v", intro) + } + if intro.Source != SourceChapter { + t.Fatalf("source: %q", intro.Source) + } + }) +} + +func TestIntroFromChapters_IgnoresRecapAndJunk(t *testing.T) { + t.Parallel() + + allure.Test(t, "recap chapters and generic titles produce no intro", func(a *allure.Context) { + t := a.T() + if got := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 60, Title: "Previously on"}, + }, 3600); got != nil { + t.Fatalf("recap: %+v", got) + } + if got := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 90, Title: "Chapter 01"}, + }, 3600); got != nil { + t.Fatalf("junk: %+v", got) + } + }) +} + +func TestIntroFromChapters_DurationAndWindowBounds(t *testing.T) { + t.Parallel() + + allure.Test(t, "intro must be 10-150s and start inside scan window", func(a *allure.Context) { + t := a.T() + if got := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 5, Title: "Intro"}, + }, 3600); got != nil { + t.Fatalf("too short: %+v", got) + } + if got := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 200, Title: "Intro"}, + }, 3600); got != nil { + t.Fatalf("too long: %+v", got) + } + // 30% of 1200s = 360s window; intro starting at 400s is rejected. + if got := IntroFromChapters([]ChapterCue{ + {StartSeconds: 400, EndSeconds: 490, Title: "Opening"}, + }, 1200); got != nil { + t.Fatalf("late start: %+v", got) + } + }) +} + +func TestIntroFromChapters_SkipsAdjacentIntroPair(t *testing.T) { + t.Parallel() + + allure.Test(t, "second consecutive intro chapter is ignored", func(a *allure.Context) { + t := a.T() + intro := IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 90, Title: "Opening"}, + {StartSeconds: 90, EndSeconds: 150, Title: "Main Titles"}, + }, 3600) + if intro == nil || intro.EndMs != 90000 { + t.Fatalf("want first intro only: %+v", intro) + } + }) +} + +func TestIntroFromChapters_OPWordBoundary(t *testing.T) { + t.Parallel() + + allure.Test(t, "OP token matches anime marker but not substring false positives", func(a *allure.Context) { + t := a.T() + intro := IntroFromChapters([]ChapterCue{ + {StartSeconds: 10, EndSeconds: 100, Title: "OP"}, + }, 1800) + if intro == nil { + t.Fatal("expected OP chapter") + } + if IntroFromChapters([]ChapterCue{ + {StartSeconds: 0, EndSeconds: 90, Title: "Stop motion"}, + }, 1800) != nil { + t.Fatal("stop should not match op") + } + }) +} diff --git a/internal/skipsegment/types.go b/internal/skipsegment/types.go new file mode 100644 index 0000000..c4a8ee5 --- /dev/null +++ b/internal/skipsegment/types.go @@ -0,0 +1,19 @@ +// Package skipsegment detects skippable playback ranges (E-23 skip intro). +package skipsegment + +// SourceChapter marks a segment derived from container chapter metadata. +const SourceChapter = "chapter" + +// Intro is an opening segment the player may offer to skip. +type Intro struct { + StartMs int64 `json:"startMs"` + EndMs int64 `json:"endMs"` + Source string `json:"source,omitempty"` +} + +// ChapterCue is one navigable chapter title and time range. +type ChapterCue struct { + StartSeconds float64 + EndSeconds float64 + Title string +} diff --git a/openapi/swagger.json b/openapi/swagger.json index 002d9a9..6e72b0e 100644 --- a/openapi/swagger.json +++ b/openapi/swagger.json @@ -503,6 +503,10 @@ "$ref": "#/components/schemas/internal_httpapi.PlaybackSeriesContext", "description": "Series is set only for paths in a series library with resolvable S/E identity." }, + "skipIntro": { + "$ref": "#/components/schemas/sudoStream_internal_skipsegment.Intro", + "description": "SkipIntro is set when chapter metadata identifies an opening intro segment (E-23)." + }, "status": { "$ref": "#/components/schemas/sudoStream_internal_transcode.Status" }, @@ -1834,6 +1838,20 @@ }, "type": "object" }, + "sudoStream_internal_skipsegment.Intro": { + "properties": { + "endMs": { + "type": "integer" + }, + "source": { + "type": "string" + }, + "startMs": { + "type": "integer" + } + }, + "type": "object" + }, "sudoStream_internal_transcode.ChapterInfo": { "properties": { "endSeconds": { diff --git a/openapi/swagger.yaml b/openapi/swagger.yaml index 0f45810..d5842a4 100644 --- a/openapi/swagger.yaml +++ b/openapi/swagger.yaml @@ -333,6 +333,10 @@ components: $ref: '#/components/schemas/internal_httpapi.PlaybackSeriesContext' description: Series is set only for paths in a series library with resolvable S/E identity. + skipIntro: + $ref: '#/components/schemas/sudoStream_internal_skipsegment.Intro' + description: SkipIntro is set when chapter metadata identifies an opening + intro segment (E-23). status: $ref: '#/components/schemas/sudoStream_internal_transcode.Status' streamUrl: @@ -1218,6 +1222,15 @@ components: updatedAt: type: string type: object + sudoStream_internal_skipsegment.Intro: + properties: + endMs: + type: integer + source: + type: string + startMs: + type: integer + type: object sudoStream_internal_transcode.ChapterInfo: properties: endSeconds: diff --git a/web/src/api/playback.ts b/web/src/api/playback.ts index 0846a68..222a176 100644 --- a/web/src/api/playback.ts +++ b/web/src/api/playback.ts @@ -39,6 +39,7 @@ export type PlaybackInfo = { userSubtitle?: { lang: string; label: string; url: string }; /** Container chapters for any video file (not series S/E identity). */ chapters?: Array<{ startSeconds: number; endSeconds: number; title: string }>; + skipIntro?: { startMs: number; endMs: number; source?: string }; series?: { librarySlug: string; showKey: string; diff --git a/web/src/client/index.ts b/web/src/client/index.ts index d373f24..59b981b 100644 --- a/web/src/client/index.ts +++ b/web/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export { deleteApiAdminInvitesById, deleteApiAdminLibrariesById, deleteApiAdminLibrariesByIdRoots, deleteApiAdminSessionsById, deleteApiAdminTrash, deleteApiAdminTrashById, deleteApiAdminUsersById, deleteApiAuth2Fa, deleteApiAuthSessionsById, deleteApiMediaByPath, deleteApiUserSubtitleByPath, getApiAdminDlnaSettings, getApiAdminFolderTree, getApiAdminInvites, getApiAdminLibraries, getApiAdminLibrariesByIdMaintenance, getApiAdminLibrariesByIdProviders, getApiAdminMaintenance, getApiAdminMaintenanceRuns, getApiAdminNetworkSettings, getApiAdminSettings, getApiAdminTranscodeSettings, getApiAdminTrash, getApiAdminTrashSettings, getApiAdminUsers, getApiAdminUsersByIdLibraries, getApiAdminUsersByIdSessions, getApiAuthConfirmEmail, getApiAuthMe, getApiAuthSessions, getApiBrowse, getApiBrowseByPath, getApiCatalogByLibrarySlug, getApiCatalogByLibrarySlugShowsByShowKey, getApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodes, getApiDownloadByPath, getApiFavoriteByPath, getApiHealth, getApiLibraries, getApiMeContinue, getApiMeFavorites, getApiMeStats, getApiMetadataByPath, getApiMeUnwatched, getApiMeWatched, getApiPlaybackByPath, getApiPlayByPath, getApiProviderPosterByPath, getApiProviderSubtitleByPath, getApiSearch, getApiStreamByPath, getApiThumbnailByPath, getApiUserSubtitleByPath, getApiWatchByPath, getMetrics, type Options, patchApiAdminDlnaSettings, patchApiAdminLibrariesById, patchApiAdminLibrariesByIdMaintenance, patchApiAdminLibrariesByIdProviders, patchApiAdminMaintenance, patchApiAdminNetworkSettings, patchApiAdminSettings, patchApiAdminTranscodeSettings, patchApiAdminTrashSettings, patchApiAdminUsersById, patchApiFavoriteByPath, patchApiMetadataByPath, patchApiWatchByPath, postApiAdminInvites, postApiAdminInvitesByIdResend, postApiAdminLibraries, postApiAdminLibrariesByIdRoots, postApiAdminLibrariesByIdSync, postApiAdminLibrariesSync, postApiAdminMaintenanceByActionRun, postApiAdminNetworkSettingsTest, postApiAdminTrashByIdRestore, postApiAdminUsers, postApiAdminUsersById2FaReset, postApiAdminUsersByIdResendConfirm, postApiAdminUsersByIdSessionsRevokeAll, postApiAuth2FaConfirm, postApiAuth2FaSetup, postApiAuth2FaVerify, postApiAuthAcceptInvite, postApiAuthChangeEmail, postApiAuthChangePassword, postApiAuthForgotPassword, postApiAuthLogin, postApiAuthLogout, postApiAuthRefresh, postApiAuthResetPassword, postApiAuthSessionsRevokeAll, postApiOauthToken, postApiPlaybackByPath, putApiAdminUsersByIdLibraries, putApiUserSubtitleByPath } from './sdk.gen'; -export type { ClientOptions, DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponse, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponse, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponse, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponse, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponse, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponse, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponse, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponse, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesErrors, GetApiAdminInvitesResponse, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponse, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponse, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsErrors, GetApiAdminSettingsResponse, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponse, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashErrors, GetApiAdminTrashResponse, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersErrors, GetApiAdminUsersResponse, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponse, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeErrors, GetApiAuthMeResponse, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsErrors, GetApiAuthSessionsResponse, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathErrors, GetApiBrowseByPathResponse, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseError, GetApiBrowseErrors, GetApiBrowseResponse, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathErrors, GetApiDownloadByPathResponse, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponse, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthError, GetApiHealthErrors, GetApiHealthResponse, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesErrors, GetApiLibrariesResponse, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueErrors, GetApiMeContinueResponse, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesErrors, GetApiMeFavoritesResponse, GetApiMeFavoritesResponses, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsErrors, GetApiMeStatsResponse, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathErrors, GetApiMetadataByPathResponse, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponse, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedErrors, GetApiMeWatchedResponse, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponse, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathErrors, GetApiPlayByPathResponse, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponse, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponse, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchError, GetApiSearchErrors, GetApiSearchResponse, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathErrors, GetApiStreamByPathResponse, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponse, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponse, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathErrors, GetApiWatchByPathResponse, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponse, GetMetricsResponses, InternalHttpapiAcceptInviteRequest, InternalHttpapiAddLibraryRootRequest, InternalHttpapiAdminUserResponse, InternalHttpapiAuthTokensResponse, InternalHttpapiChangeEmailRequest, InternalHttpapiChangePasswordRequest, InternalHttpapiCreateInviteRequest, InternalHttpapiCreateLibraryRequest, InternalHttpapiCreateUserRequest, InternalHttpapiDisableTwoFactorRequest, InternalHttpapiErrorResponse, InternalHttpapiFavoritePatchRequest, InternalHttpapiFavoriteResponse, InternalHttpapiFolderTreeEntry, InternalHttpapiFolderTreeResponse, InternalHttpapiForgotPasswordRequest, InternalHttpapiGrantsResponse, InternalHttpapiHealthResponse, InternalHttpapiHomeItemsResponse, InternalHttpapiHomeStatsResponse, InternalHttpapiInvitesResponse, InternalHttpapiLibrariesResponse, InternalHttpapiLibraryResponse, InternalHttpapiLoginRequest, InternalHttpapiLoginResponse, InternalHttpapiMaintenanceRunResponse, InternalHttpapiMaintenanceRunsResponse, InternalHttpapiMaintenanceStatusResponse, InternalHttpapiMeResponse, InternalHttpapiOAuthErrorResponse, InternalHttpapiOAuthTokenResponse, InternalHttpapiPatchLibraryRequest, InternalHttpapiPatchMaintenanceRequest, InternalHttpapiPatchProviderSettingsRequest, InternalHttpapiPlaybackNegotiateRequest, InternalHttpapiPlaybackResponse, InternalHttpapiPlaybackSeriesContext, InternalHttpapiProviderAvailability, InternalHttpapiProviderSettingsResponse, InternalHttpapiProviderSubtitleTrack, InternalHttpapiPutGrantsRequest, InternalHttpapiRefreshResponse, InternalHttpapiResendInviteRequest, InternalHttpapiResetPasswordRequest, InternalHttpapiSearchResponse, InternalHttpapiSessionsResponse, InternalHttpapiStatusResponse, InternalHttpapiTrashListResponse, InternalHttpapiTrashSettingsResponse, InternalHttpapiTwoFactorConfirmRequest, InternalHttpapiTwoFactorSetupRequest, InternalHttpapiUpdateUserRequest, InternalHttpapiUserResponse, InternalHttpapiUsersResponse, InternalHttpapiUserSubtitleTrack, InternalHttpapiVerifyTwoFactorRequest, InternalHttpapiWatchPatchRequest, InternalHttpapiWatchResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponse, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponse, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponse, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponse, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponse, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponse, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponse, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponse, PatchApiFavoriteByPathResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponse, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathErrors, PatchApiWatchByPathResponse, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesErrors, PostApiAdminInvitesResponse, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponse, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponse, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponse, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponse, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersErrors, PostApiAdminUsersResponse, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponse, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponse, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponse, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponse, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponse, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponse, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponse, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginErrors, PostApiAuthLoginResponse, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutErrors, PostApiAuthLogoutResponse, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshErrors, PostApiAuthRefreshResponse, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponse, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponse, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenErrors, PostApiOauthTokenResponse, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponse, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponse, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponse, PutApiUserSubtitleByPathResponses, SudoStreamInternalAccessGrantInput, SudoStreamInternalAccessLibrary, SudoStreamInternalAccessLibraryPermissions, SudoStreamInternalAccessLibraryType, SudoStreamInternalAccessUserGrantView, SudoStreamInternalAuthAdminUser, SudoStreamInternalAuthInviteView, SudoStreamInternalAuthPublicUser, SudoStreamInternalAuthSessionView, SudoStreamInternalAuthSettings, SudoStreamInternalAuthTwoFactorConfirmResult, SudoStreamInternalAuthTwoFactorSetup, SudoStreamInternalCatalogEpisode, SudoStreamInternalCatalogLibraryCatalog, SudoStreamInternalCatalogMovie, SudoStreamInternalCatalogSeasonEpisodes, SudoStreamInternalCatalogSeasonSummary, SudoStreamInternalCatalogShowDetail, SudoStreamInternalCatalogShowSummary, SudoStreamInternalDlnaSettings, SudoStreamInternalHomeshelfLibraryStat, SudoStreamInternalHomeshelfShelfItem, SudoStreamInternalMaintenanceActionStatus, SudoStreamInternalMaintenanceRun, SudoStreamInternalMaintenanceSchedule, SudoStreamInternalMaintenanceScheduleInput, SudoStreamInternalMediafsAction, SudoStreamInternalMediafsBreadcrumb, SudoStreamInternalMediafsBrowseResponse, SudoStreamInternalMediafsItem, SudoStreamInternalMetadataFilenameHint, SudoStreamInternalMetadataJsonValue, SudoStreamInternalMetadataMetadataResponse, SudoStreamInternalMetadataPatchRequest, SudoStreamInternalMetadataPatchTarget, SudoStreamInternalMetadataSearchHit, SudoStreamInternalMetadataSourceFields, SudoStreamInternalMetadataVideoFields, SudoStreamInternalNetworkProbeResult, SudoStreamInternalNetworkSettings, SudoStreamInternalPlaybackDeviceProfile, SudoStreamInternalPlaybackDirectPlayProfile, SudoStreamInternalPlaybackPlayMethod, SudoStreamInternalProviderSettings, SudoStreamInternalTranscodeChapterInfo, SudoStreamInternalTranscodeDownmixAlgorithm, SudoStreamInternalTranscodeHwAccel, SudoStreamInternalTranscodePackagingMode, SudoStreamInternalTranscodeQualityInfo, SudoStreamInternalTranscodeStatus, SudoStreamInternalTranscodeToneMappingAlgorithm, SudoStreamInternalTranscodeTrackInfo, SudoStreamInternalTranscodeTranscodeSettings, SudoStreamInternalTrashItem } from './types.gen'; +export type { ClientOptions, DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponse, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponse, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponse, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponse, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponse, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponse, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponse, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponse, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesErrors, GetApiAdminInvitesResponse, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponse, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponse, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsErrors, GetApiAdminSettingsResponse, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponse, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashErrors, GetApiAdminTrashResponse, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersErrors, GetApiAdminUsersResponse, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponse, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeErrors, GetApiAuthMeResponse, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsErrors, GetApiAuthSessionsResponse, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathErrors, GetApiBrowseByPathResponse, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseError, GetApiBrowseErrors, GetApiBrowseResponse, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathErrors, GetApiDownloadByPathResponse, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponse, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthError, GetApiHealthErrors, GetApiHealthResponse, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesErrors, GetApiLibrariesResponse, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueErrors, GetApiMeContinueResponse, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesErrors, GetApiMeFavoritesResponse, GetApiMeFavoritesResponses, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsErrors, GetApiMeStatsResponse, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathErrors, GetApiMetadataByPathResponse, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponse, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedErrors, GetApiMeWatchedResponse, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponse, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathErrors, GetApiPlayByPathResponse, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponse, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponse, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchError, GetApiSearchErrors, GetApiSearchResponse, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathErrors, GetApiStreamByPathResponse, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponse, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponse, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathErrors, GetApiWatchByPathResponse, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponse, GetMetricsResponses, InternalHttpapiAcceptInviteRequest, InternalHttpapiAddLibraryRootRequest, InternalHttpapiAdminUserResponse, InternalHttpapiAuthTokensResponse, InternalHttpapiChangeEmailRequest, InternalHttpapiChangePasswordRequest, InternalHttpapiCreateInviteRequest, InternalHttpapiCreateLibraryRequest, InternalHttpapiCreateUserRequest, InternalHttpapiDisableTwoFactorRequest, InternalHttpapiErrorResponse, InternalHttpapiFavoritePatchRequest, InternalHttpapiFavoriteResponse, InternalHttpapiFolderTreeEntry, InternalHttpapiFolderTreeResponse, InternalHttpapiForgotPasswordRequest, InternalHttpapiGrantsResponse, InternalHttpapiHealthResponse, InternalHttpapiHomeItemsResponse, InternalHttpapiHomeStatsResponse, InternalHttpapiInvitesResponse, InternalHttpapiLibrariesResponse, InternalHttpapiLibraryResponse, InternalHttpapiLoginRequest, InternalHttpapiLoginResponse, InternalHttpapiMaintenanceRunResponse, InternalHttpapiMaintenanceRunsResponse, InternalHttpapiMaintenanceStatusResponse, InternalHttpapiMeResponse, InternalHttpapiOAuthErrorResponse, InternalHttpapiOAuthTokenResponse, InternalHttpapiPatchLibraryRequest, InternalHttpapiPatchMaintenanceRequest, InternalHttpapiPatchProviderSettingsRequest, InternalHttpapiPlaybackNegotiateRequest, InternalHttpapiPlaybackResponse, InternalHttpapiPlaybackSeriesContext, InternalHttpapiProviderAvailability, InternalHttpapiProviderSettingsResponse, InternalHttpapiProviderSubtitleTrack, InternalHttpapiPutGrantsRequest, InternalHttpapiRefreshResponse, InternalHttpapiResendInviteRequest, InternalHttpapiResetPasswordRequest, InternalHttpapiSearchResponse, InternalHttpapiSessionsResponse, InternalHttpapiStatusResponse, InternalHttpapiTrashListResponse, InternalHttpapiTrashSettingsResponse, InternalHttpapiTwoFactorConfirmRequest, InternalHttpapiTwoFactorSetupRequest, InternalHttpapiUpdateUserRequest, InternalHttpapiUserResponse, InternalHttpapiUsersResponse, InternalHttpapiUserSubtitleTrack, InternalHttpapiVerifyTwoFactorRequest, InternalHttpapiWatchPatchRequest, InternalHttpapiWatchResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponse, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponse, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponse, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponse, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponse, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponse, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponse, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponse, PatchApiFavoriteByPathResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponse, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathErrors, PatchApiWatchByPathResponse, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesErrors, PostApiAdminInvitesResponse, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponse, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponse, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponse, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponse, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersErrors, PostApiAdminUsersResponse, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponse, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponse, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponse, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponse, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponse, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponse, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponse, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginErrors, PostApiAuthLoginResponse, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutErrors, PostApiAuthLogoutResponse, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshErrors, PostApiAuthRefreshResponse, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponse, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponse, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenErrors, PostApiOauthTokenResponse, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponse, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponse, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponse, PutApiUserSubtitleByPathResponses, SudoStreamInternalAccessGrantInput, SudoStreamInternalAccessLibrary, SudoStreamInternalAccessLibraryPermissions, SudoStreamInternalAccessLibraryType, SudoStreamInternalAccessUserGrantView, SudoStreamInternalAuthAdminUser, SudoStreamInternalAuthInviteView, SudoStreamInternalAuthPublicUser, SudoStreamInternalAuthSessionView, SudoStreamInternalAuthSettings, SudoStreamInternalAuthTwoFactorConfirmResult, SudoStreamInternalAuthTwoFactorSetup, SudoStreamInternalCatalogEpisode, SudoStreamInternalCatalogLibraryCatalog, SudoStreamInternalCatalogMovie, SudoStreamInternalCatalogSeasonEpisodes, SudoStreamInternalCatalogSeasonSummary, SudoStreamInternalCatalogShowDetail, SudoStreamInternalCatalogShowSummary, SudoStreamInternalDlnaSettings, SudoStreamInternalHomeshelfLibraryStat, SudoStreamInternalHomeshelfShelfItem, SudoStreamInternalMaintenanceActionStatus, SudoStreamInternalMaintenanceRun, SudoStreamInternalMaintenanceSchedule, SudoStreamInternalMaintenanceScheduleInput, SudoStreamInternalMediafsAction, SudoStreamInternalMediafsBreadcrumb, SudoStreamInternalMediafsBrowseResponse, SudoStreamInternalMediafsItem, SudoStreamInternalMetadataFilenameHint, SudoStreamInternalMetadataJsonValue, SudoStreamInternalMetadataMetadataResponse, SudoStreamInternalMetadataPatchRequest, SudoStreamInternalMetadataPatchTarget, SudoStreamInternalMetadataSearchHit, SudoStreamInternalMetadataSourceFields, SudoStreamInternalMetadataVideoFields, SudoStreamInternalNetworkProbeResult, SudoStreamInternalNetworkSettings, SudoStreamInternalPlaybackDeviceProfile, SudoStreamInternalPlaybackDirectPlayProfile, SudoStreamInternalPlaybackPlayMethod, SudoStreamInternalProviderSettings, SudoStreamInternalSkipsegmentIntro, SudoStreamInternalTranscodeChapterInfo, SudoStreamInternalTranscodeDownmixAlgorithm, SudoStreamInternalTranscodeHwAccel, SudoStreamInternalTranscodePackagingMode, SudoStreamInternalTranscodeQualityInfo, SudoStreamInternalTranscodeStatus, SudoStreamInternalTranscodeToneMappingAlgorithm, SudoStreamInternalTranscodeTrackInfo, SudoStreamInternalTranscodeTranscodeSettings, SudoStreamInternalTrashItem } from './types.gen'; diff --git a/web/src/client/schemas.gen.ts b/web/src/client/schemas.gen.ts index 8fb1296..a906765 100644 --- a/web/src/client/schemas.gen.ts +++ b/web/src/client/schemas.gen.ts @@ -537,6 +537,10 @@ export const internal_httpapi_PlaybackResponseSchema = { $ref: '#/components/schemas/internal_httpapi.PlaybackSeriesContext', description: 'Series is set only for paths in a series library with resolvable S/E identity.' }, + skipIntro: { + $ref: '#/components/schemas/sudoStream_internal_skipsegment.Intro', + description: 'SkipIntro is set when chapter metadata identifies an opening intro segment (E-23).' + }, status: { $ref: '#/components/schemas/sudoStream_internal_transcode.Status' }, @@ -1935,6 +1939,21 @@ export const sudoStream_internal_provider_SettingsSchema = { type: 'object' } as const; +export const sudoStream_internal_skipsegment_IntroSchema = { + properties: { + endMs: { + type: 'integer' + }, + source: { + type: 'string' + }, + startMs: { + type: 'integer' + } + }, + type: 'object' +} as const; + export const sudoStream_internal_transcode_ChapterInfoSchema = { properties: { endSeconds: { diff --git a/web/src/client/types.gen.ts b/web/src/client/types.gen.ts index 6e2cee3..7822407 100644 --- a/web/src/client/types.gen.ts +++ b/web/src/client/types.gen.ts @@ -206,6 +206,10 @@ export type InternalHttpapiPlaybackResponse = { * Series is set only for paths in a series library with resolvable S/E identity. */ series?: InternalHttpapiPlaybackSeriesContext; + /** + * SkipIntro is set when chapter metadata identifies an opening intro segment (E-23). + */ + skipIntro?: SudoStreamInternalSkipsegmentIntro; status?: SudoStreamInternalTranscodeStatus; streamUrl?: string; subtitleTracks?: Array; @@ -712,6 +716,12 @@ export type SudoStreamInternalProviderSettings = { updatedAt?: string; }; +export type SudoStreamInternalSkipsegmentIntro = { + endMs?: number; + source?: string; + startMs?: number; +}; + export type SudoStreamInternalTranscodeChapterInfo = { endSeconds?: number; startSeconds?: number; diff --git a/web/src/client/zod.gen.ts b/web/src/client/zod.gen.ts index f9cd42d..c71cc7f 100644 --- a/web/src/client/zod.gen.ts +++ b/web/src/client/zod.gen.ts @@ -663,6 +663,12 @@ export const zInternalHttpapiProviderSettingsResponse = z.object({ settings: zSudoStreamInternalProviderSettings.optional() }); +export const zSudoStreamInternalSkipsegmentIntro = z.object({ + endMs: z.int().optional(), + source: z.string().optional(), + startMs: z.int().optional() +}); + export const zSudoStreamInternalTranscodeChapterInfo = z.object({ endSeconds: z.number().optional(), startSeconds: z.number().optional(), @@ -717,6 +723,7 @@ export const zInternalHttpapiPlaybackResponse = z.object({ providerSubtitleTracks: z.array(zInternalHttpapiProviderSubtitleTrack).optional(), qualities: z.array(zSudoStreamInternalTranscodeQualityInfo).optional(), series: zInternalHttpapiPlaybackSeriesContext.optional(), + skipIntro: zSudoStreamInternalSkipsegmentIntro.optional(), status: zSudoStreamInternalTranscodeStatus.optional(), streamUrl: z.string().optional(), subtitleTracks: z.array(zSudoStreamInternalTranscodeTrackInfo).optional(), diff --git a/web/src/components/player/series-player-chrome.tsx b/web/src/components/player/series-player-chrome.tsx index b5f29a0..2c9ebbc 100644 --- a/web/src/components/player/series-player-chrome.tsx +++ b/web/src/components/player/series-player-chrome.tsx @@ -9,6 +9,8 @@ import { uniqueSeasons, type SeriesEpisodeOption, } from "@/lib/series-nav"; +import { SkipIntroAction } from "@/components/player/skip-intro-action"; +import { isInSkipIntroSegment, type SkipIntroSegment } from "@/lib/skip-intro"; import { cn } from "@/lib/utils"; type SeriesPlayerChromeProps = { @@ -21,6 +23,7 @@ type SeriesPlayerChromeProps = { onSelectEpisode: (path: string) => void; onUploadSubtitle?: (file: File) => Promise; uploadBusy?: boolean; + skipIntro?: SkipIntroSegment | null; }; type PlaybackEndedState = { @@ -49,6 +52,7 @@ export function SeriesPlayerChrome({ onSelectEpisode, onUploadSubtitle, uploadBusy, + skipIntro, }: SeriesPlayerChromeProps): ReactNode { const { t } = useTranslation(); const playback = usePlayer(selectPlayback) as PlaybackEndedState | undefined; @@ -77,7 +81,12 @@ export function SeriesPlayerChrome({ return currentTime / duration >= NEXT_EPISODE_RATIO; }, [ended, time?.currentTime, time?.duration]); - const showNext = Boolean(nav.next && nearEnd); + const showSkipIntro = useMemo( + () => isInSkipIntroSegment(skipIntro ?? undefined, time?.currentTime ?? 0), + [skipIntro, time?.currentTime], + ); + const showNext = Boolean(nav.next && nearEnd && !showSkipIntro); + const showActions = showSkipIntro || showNext; const congratsKind = ended && nav.seriesComplete @@ -207,15 +216,18 @@ export function SeriesPlayerChrome({ ) : null} - {showNext ? ( + {showActions ? (
- + {skipIntro ? : null} + {showNext ? ( + + ) : null}
) : null} diff --git a/web/src/components/player/skip-intro-action.tsx b/web/src/components/player/skip-intro-action.tsx new file mode 100644 index 0000000..40b18a0 --- /dev/null +++ b/web/src/components/player/skip-intro-action.tsx @@ -0,0 +1,45 @@ +import { selectTime, usePlayer } from "@videojs/react"; +import { useMemo, type ReactNode } from "react"; +import { useTranslation } from "react-i18next"; + +import { + isInSkipIntroSegment, + skipIntroSeekSeconds, + type SkipIntroSegment, +} from "@/lib/skip-intro"; + +type TimeState = { + currentTime: number; + seek: (time: number) => Promise; +}; + +type SkipIntroActionProps = { + segment: SkipIntroSegment; +}; + +/** Skip intro button in the series chrome actions slot (also used for non-series video). */ +export function SkipIntroAction({ segment }: SkipIntroActionProps): ReactNode { + const { t } = useTranslation(); + const time = usePlayer(selectTime) as TimeState | undefined; + const currentTime = time?.currentTime ?? 0; + + const visible = useMemo(() => isInSkipIntroSegment(segment, currentTime), [segment, currentTime]); + + if (!visible) { + return null; + } + + return ( + + ); +} diff --git a/web/src/components/player/videojs-player.tsx b/web/src/components/player/videojs-player.tsx index b6b2502..3f0f9c9 100644 --- a/web/src/components/player/videojs-player.tsx +++ b/web/src/components/player/videojs-player.tsx @@ -9,6 +9,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { SeriesPlayerChrome } from "@/components/player/series-player-chrome"; +import { SkipIntroAction } from "@/components/player/skip-intro-action"; import { usePlayerControlsVisible } from "@/components/player/use-player-controls-visible"; import { PlaybackResumeLoader } from "@/components/player/playback-resume-loader"; import { PlaybackProgressSaver } from "@/components/player/playback-progress-saver"; @@ -17,6 +18,7 @@ import { localeCode } from "@/lib/i18n"; import { chaptersToWebVTT } from "@/lib/chapters-vtt"; import { withAccessToken } from "@/lib/media-urls"; import type { SeriesEpisodeOption } from "@/lib/series-nav"; +import type { SkipIntroSegment } from "@/lib/skip-intro"; import { cn } from "@/lib/utils"; import "./videojs-player.css"; @@ -52,6 +54,7 @@ type VideoJSPlayerProps = { providerSubtitleTracks?: Array<{ lang: string; label: string; url: string }>; userSubtitleTrack?: { lang: string; label: string; url: string } | null; chapters?: ChapterCue[]; + skipIntro?: SkipIntroSegment | null; seriesChrome?: SeriesChrome | null; onUploadSubtitle?: (file: File) => Promise; uploadBusy?: boolean; @@ -73,6 +76,7 @@ export function VideoJSPlayer({ providerSubtitleTracks, userSubtitleTrack, chapters, + skipIntro, seriesChrome, onUploadSubtitle, uploadBusy, @@ -190,8 +194,14 @@ export function VideoJSPlayer({ onSelectEpisode={seriesChrome.onSelectEpisode} onUploadSubtitle={onUploadSubtitle} uploadBusy={uploadBusy} + skipIntro={skipIntro} /> ) : null} + {skipIntro && !seriesChrome ? ( +
+ +
+ ) : null} {showStandaloneUpload ? ( = startSec && currentTimeSec < endSec; +} + +export function skipIntroSeekSeconds(segment: SkipIntroSegment): number { + return segment.endMs / 1000 + SKIP_INTRO_SEEK_PAD_SEC; +} diff --git a/web/src/locales/en/translation.json b/web/src/locales/en/translation.json index f2ab12d..ca527e2 100644 --- a/web/src/locales/en/translation.json +++ b/web/src/locales/en/translation.json @@ -212,6 +212,7 @@ "seasonValue": "Season {{number}}", "episodeValue": "Episode {{number}}", "nextEpisode": "Next episode", + "skipIntro": "Skip intro", "uploadSubtitle": "Upload subtitle", "seasonCompleteTitle": "Season complete", "seasonCompleteBody": "You’ve finished season {{season}} of {{show}}.", diff --git a/web/src/locales/ru/translation.json b/web/src/locales/ru/translation.json index 25facbb..1a218e9 100644 --- a/web/src/locales/ru/translation.json +++ b/web/src/locales/ru/translation.json @@ -212,6 +212,7 @@ "seasonValue": "Сезон {{number}}", "episodeValue": "Серия {{number}}", "nextEpisode": "Следующая серия", + "skipIntro": "Пропустить заставку", "uploadSubtitle": "Загрузить субтитры", "seasonCompleteTitle": "Сезон завершён", "seasonCompleteBody": "Вы закончили сезон {{season}} сериала «{{show}}».", diff --git a/web/src/pages/player/index.tsx b/web/src/pages/player/index.tsx index 7188b03..8e9ad35 100644 --- a/web/src/pages/player/index.tsx +++ b/web/src/pages/player/index.tsx @@ -347,6 +347,7 @@ export default function PlayerPage() { : null } chapters={playback?.chapters} + skipIntro={playback?.skipIntro} seriesChrome={seriesChrome} onUploadSubtitle={async (file) => { await uploadMutation.mutateAsync(file); From 9bf804d4c6f2c63fbeb4993f2e412254063cd426 Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Fri, 25 Sep 2026 02:49:24 +0300 Subject: [PATCH 04/11] chore: update lint rules and make linter happy --- .golangci.yml | 10 +++--- CONTRIBUTING.md | 7 +++-- internal/httpapi/play_handlers.go | 1 - .../httpapi/provider_subtitle_handlers.go | 4 +-- internal/maintenance/runner.go | 10 +++--- internal/metadata/ffmpeg.go | 2 +- internal/metadata/ffprobe.go | 2 +- internal/provider/anidb/titles.go | 2 +- internal/skipsegment/chapters.go | 31 ++++++++++++++----- internal/thumbnail/video.go | 6 ++-- internal/transcode/hwencoder.go | 1 - internal/transcode/job.go | 1 - internal/transcode/keyframes.go | 3 +- internal/transcode/probe.go | 4 +-- internal/transcode/service.go | 1 - internal/transcode/session.go | 1 - internal/transcode/source_duration.go | 1 - internal/transcode/source_meta.go | 1 - internal/transcode/srt_vtt.go | 2 +- internal/transcode/subtitle_playlist.go | 1 - internal/usersub/service.go | 1 - 21 files changed, 51 insertions(+), 41 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7cd08c7..8774625 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,19 +38,21 @@ linters: cyclop: max-complexity: 10 funlen: - lines: 100 + lines: 90 exclusions: + presets: + - comments + - common-false-positives paths: - "openapi/" - "web/" - "cache/" - "logs/" + - "_test.go" formatters: enable: - gofumpt -# - goimports -# - golines -# - swaggo + - swaggo exclusions: paths: - "openapi/" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b482b1..65d97b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,11 @@ Run the stack via Docker Compose — no local Go binary required. To list all ta **Coverage:** project floor **≥ 60%**; touched packages / PR patch **≥ 70%** ([`codecov.yml`](codecov.yml)). -**Tests:** Go tests use [allure-go](https://github.com/allure-framework/allure-go) (`commons/gotest`). Match the `allure.Test` pattern in existing `*_test.go` -files. Do not put secrets in test titles or attachments — published reports are treated as public QA. +**Tests:** **All** Go tests must use [allure-go](https://github.com/allure-framework/allure-go) (`allure.Test` in every +scenario). +Prefer **table-driven** cases inside or per-row with Allure. +`*_test.go` is excluded from golangci **linters** (not formatters). +Do not put secrets in test titles or attachments — published reports are public. ## Pull requests diff --git a/internal/httpapi/play_handlers.go b/internal/httpapi/play_handlers.go index c903608..c48a9bd 100644 --- a/internal/httpapi/play_handlers.go +++ b/internal/httpapi/play_handlers.go @@ -283,7 +283,6 @@ func (h *handler) writeTranscodeResourceMissing(c *gin.Context, status transcode } func (h *handler) serveHLSPlaylist(c *gin.Context, diskPath string) { - //nolint:gosec // diskPath is under transcode cache directory raw, err := os.ReadFile(diskPath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{errorKey: "failed to read playlist"}) diff --git a/internal/httpapi/provider_subtitle_handlers.go b/internal/httpapi/provider_subtitle_handlers.go index 54f7a5d..0f79cd9 100644 --- a/internal/httpapi/provider_subtitle_handlers.go +++ b/internal/httpapi/provider_subtitle_handlers.go @@ -27,8 +27,8 @@ type ProviderSubtitleTrack struct { // @Description Serves a locally cached subtitle from providers.subtitles. Query lang is ISO 639-1. // @Tags media // @Produce text/vtt -// @Param path path string true "Path under media root" -// @Param lang query string true "ISO 639-1 language code" +// @Param path path string true "Path under media root" +// @Param lang query string true "ISO 639-1 language code" // @Success 200 // @Failure 400 {object} ErrorResponse // @Failure 404 {object} ErrorResponse diff --git a/internal/maintenance/runner.go b/internal/maintenance/runner.go index 6eb470e..6a5e7ee 100644 --- a/internal/maintenance/runner.go +++ b/internal/maintenance/runner.go @@ -96,11 +96,11 @@ func (s *Service) runThumbnailsWarm(ctx context.Context, libraryID string) (map[ generated += count if warmErr != nil { return map[string]any{ - "generated": generated, - }, fmt.Errorf( - "warm thumbnails: %w", - warmErr, - ) + "generated": generated, + }, fmt.Errorf( + "warm thumbnails: %w", + warmErr, + ) } } diff --git a/internal/metadata/ffmpeg.go b/internal/metadata/ffmpeg.go index b33b75e..180f0d3 100644 --- a/internal/metadata/ffmpeg.go +++ b/internal/metadata/ffmpeg.go @@ -113,7 +113,7 @@ func WriteFileTags(ctx context.Context, absPath string, tags map[string]string) } args = append(args, tempPath) - output, err := exec.CommandContext( //nolint:gosec // absPath resolved via mediafs + output, err := exec.CommandContext( ctx, "ffmpeg", args..., diff --git a/internal/metadata/ffprobe.go b/internal/metadata/ffprobe.go index f942d62..d162de7 100644 --- a/internal/metadata/ffprobe.go +++ b/internal/metadata/ffprobe.go @@ -42,7 +42,7 @@ func ProbePath(absPath string) (VideoFields, error) { // ProbePathContext runs ffprobe with cancellation support. func ProbePathContext(ctx context.Context, absPath string) (VideoFields, error) { - output, err := exec.CommandContext( //nolint:gosec // absPath resolved via mediafs + output, err := exec.CommandContext( ctx, "ffprobe", "-v", "quiet", diff --git a/internal/provider/anidb/titles.go b/internal/provider/anidb/titles.go index 3f6967b..fda5114 100644 --- a/internal/provider/anidb/titles.go +++ b/internal/provider/anidb/titles.go @@ -78,7 +78,7 @@ func (idx *titlesIndex) loadFromDisk(cachePath string) bool { if err != nil || time.Since(info.ModTime()) >= titlesCacheTTL { return false } - data, readErr := os.ReadFile(cachePath) //nolint:gosec // G304: path under controlled cacheDir + data, readErr := os.ReadFile(cachePath) if readErr != nil { return false } diff --git a/internal/skipsegment/chapters.go b/internal/skipsegment/chapters.go index 2cefa0e..01c7196 100644 --- a/internal/skipsegment/chapters.go +++ b/internal/skipsegment/chapters.go @@ -11,6 +11,7 @@ const ( maxIntroSec = 150 maxScanWindowSec = 600 // 10 minutes maxScanRuntimeRatio = 0.30 + msPerSecond = 1000 ) var ( @@ -23,32 +24,42 @@ var ( // IntroFromChapters picks the first valid opening intro from embedded chapters. // Returns nil when no qualifying chapter exists (prefer miss over bad skip). +// +//nolint:cyclop // single-pass scan with explicit guards; splitting would obscure rules func IntroFromChapters(chapters []ChapterCue, durationSeconds float64) *Intro { if len(chapters) == 0 || durationSeconds <= 0 { return nil } scanWindow := introScanWindowSec(durationSeconds) + prevTitleWasIntro := false - for index, chapter := range chapters { + for _, chapter := range chapters { if !titleMatchesIntro(chapter.Title) || titleMatchesRecap(chapter.Title) { + prevTitleWasIntro = false + continue } - if index > 0 && titleMatchesIntro(chapters[index-1].Title) && - !titleMatchesRecap(chapters[index-1].Title) { + if prevTitleWasIntro { continue } start := chapter.StartSeconds end := chapter.EndSeconds if end <= start { + prevTitleWasIntro = true + continue } duration := end - start if duration < minIntroSec || duration > maxIntroSec { + prevTitleWasIntro = true + continue } if start < 0 || start > scanWindow { + prevTitleWasIntro = true + continue } @@ -63,11 +74,12 @@ func IntroFromChapters(chapters []ChapterCue, durationSeconds float64) *Intro { } func introScanWindowSec(durationSeconds float64) float64 { - cap := durationSeconds * maxScanRuntimeRatio - if cap > maxScanWindowSec { - cap = maxScanWindowSec + windowSec := durationSeconds * maxScanRuntimeRatio + if windowSec > maxScanWindowSec { + windowSec = maxScanWindowSec } - return cap + + return windowSec } func titleMatchesIntro(title string) bool { @@ -75,6 +87,7 @@ func titleMatchesIntro(title string) bool { if title == "" { return false } + return introTitleRe.MatchString(title) } @@ -83,6 +96,7 @@ func titleMatchesRecap(title string) bool { if title == "" { return false } + return recapTitleRe.MatchString(title) } @@ -90,5 +104,6 @@ func secondsToMs(seconds float64) int64 { if seconds <= 0 { return 0 } - return int64(math.Round(seconds * 1000)) + + return int64(math.Round(seconds * msPerSecond)) } diff --git a/internal/thumbnail/video.go b/internal/thumbnail/video.go index 868b416..6abb1f8 100644 --- a/internal/thumbnail/video.go +++ b/internal/thumbnail/video.go @@ -333,7 +333,7 @@ type ffprobeStream struct { } func embeddedCoverStream(ctx context.Context, mediaPath string) (int, bool) { - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( ctx, "ffprobe", "-v", "quiet", @@ -375,7 +375,7 @@ func extractEmbeddedArtWebP( mediaPath, tempPath string, streamIndex int, ) error { - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( ctx, "ffmpeg", "-v", "quiet", @@ -416,7 +416,7 @@ func frameCaptureOutputOK(path string) bool { } func extractFrameAtWebP(ctx context.Context, mediaPath, tempPath, seek string) error { - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( ctx, "ffmpeg", "-v", "error", diff --git a/internal/transcode/hwencoder.go b/internal/transcode/hwencoder.go index bd9cadd..cc8145a 100644 --- a/internal/transcode/hwencoder.go +++ b/internal/transcode/hwencoder.go @@ -176,7 +176,6 @@ func primaryGPUVendor() gpuVendor { } func readPCIVendor(path string) gpuVendor { - //nolint:gosec // path from filepath.Glob("/sys/class/drm/card*/device/vendor") only raw, err := os.ReadFile(path) if err != nil { return gpuNone diff --git a/internal/transcode/job.go b/internal/transcode/job.go index d63f2be..77598d2 100644 --- a/internal/transcode/job.go +++ b/internal/transcode/job.go @@ -490,7 +490,6 @@ func convertEmbeddedSubtitleToWebVTT( ) error { _ = os.Remove(outputPath) - //nolint:gosec // mediaPath resolved via mediafs cmd := exec.CommandContext( ctx, "ffmpeg", diff --git a/internal/transcode/keyframes.go b/internal/transcode/keyframes.go index 2449491..629b1c3 100644 --- a/internal/transcode/keyframes.go +++ b/internal/transcode/keyframes.go @@ -29,7 +29,7 @@ func ProbeKeyframes(ctx context.Context, mediaPath string) ([]float64, error) { probeCtx, cancel := context.WithTimeout(ctx, keyframeProbeTimeout) defer cancel() - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( probeCtx, "ffprobe", "-v", "error", @@ -102,7 +102,6 @@ func WriteKeyframes(outDir string, keyframes []float64) error { // ReadKeyframes loads cached keyframe timestamps. func ReadKeyframes(outDir string) ([]float64, bool) { - //nolint:gosec // outDir is under transcode cache root raw, err := os.ReadFile(filepath.Join(outDir, keyframeMarker)) if err != nil { return nil, false diff --git a/internal/transcode/probe.go b/internal/transcode/probe.go index 77fdf82..e1cbf2f 100644 --- a/internal/transcode/probe.go +++ b/internal/transcode/probe.go @@ -131,7 +131,7 @@ func needsToneMap(transfer, primaries, pixFmt string) bool { // ProbeSource inspects a media file with ffprobe. func ProbeSource(ctx context.Context, mediaPath string) (SourceInfo, error) { - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( ctx, "ffprobe", "-v", "quiet", @@ -163,7 +163,7 @@ func ProbeSource(ctx context.Context, mediaPath string) (SourceInfo, error) { // ProbeChapters inspects chapter atoms only (fallback when cached source meta lacks them). func ProbeChapters(ctx context.Context, mediaPath string) ([]ChapterInfo, error) { - cmd := exec.CommandContext( //nolint:gosec // mediaPath resolved via mediafs + cmd := exec.CommandContext( ctx, "ffprobe", "-v", "quiet", diff --git a/internal/transcode/service.go b/internal/transcode/service.go index a8fe959..1693398 100644 --- a/internal/transcode/service.go +++ b/internal/transcode/service.go @@ -466,7 +466,6 @@ func isTranscodeComplete(outDir string) bool { } func readTranscodeError(outDir string) (string, bool) { - //nolint:gosec // outDir is under the transcode cache root raw, err := os.ReadFile(filepath.Join(outDir, errorMarker)) if err != nil { return "", false diff --git a/internal/transcode/session.go b/internal/transcode/session.go index deb9aa9..5ca9f9f 100644 --- a/internal/transcode/session.go +++ b/internal/transcode/session.go @@ -293,7 +293,6 @@ func (s *Service) startSession( downmix: settings.DownmixAlgorithm, }) - //nolint:gosec // mediaPath resolved via mediafs; args built internally cmd := exec.CommandContext(sessionCtx, "ffmpeg", args...) stderr := &tailBuffer{limit: stderrTailLimit} diff --git a/internal/transcode/source_duration.go b/internal/transcode/source_duration.go index a8ba5d0..dce193b 100644 --- a/internal/transcode/source_duration.go +++ b/internal/transcode/source_duration.go @@ -32,7 +32,6 @@ func ReadSourceDuration(outDir string) (float64, bool) { return 0, false } - //nolint:gosec // outDir is under the transcode cache root raw, err := os.ReadFile(filepath.Join(outDir, sourceDurationMarker)) if err != nil { return 0, false diff --git a/internal/transcode/source_meta.go b/internal/transcode/source_meta.go index cc58504..fe155aa 100644 --- a/internal/transcode/source_meta.go +++ b/internal/transcode/source_meta.go @@ -137,7 +137,6 @@ func WriteSourceMeta(outDir string, meta SourceMeta) error { // ReadSourceMeta loads the cached probe summary when present. func ReadSourceMeta(outDir string) (SourceMeta, bool) { - //nolint:gosec // outDir is under transcode cache root raw, err := os.ReadFile(filepath.Join(outDir, sourceMetaMarker)) if err != nil { return SourceMeta{}, false diff --git a/internal/transcode/srt_vtt.go b/internal/transcode/srt_vtt.go index 524fa53..2670718 100644 --- a/internal/transcode/srt_vtt.go +++ b/internal/transcode/srt_vtt.go @@ -186,7 +186,7 @@ func NormalizeSubtitleUpload(raw []byte, filename string) ([]byte, error) { func readSidecarSubtitleFile(inputPath string) ([]byte, error) { // inputPath is a resolved sidecar beside media. - //nolint:gosec // G304 + raw, err := os.ReadFile(inputPath) if err != nil { return nil, fmt.Errorf("read sidecar subtitle: %w", err) diff --git a/internal/transcode/subtitle_playlist.go b/internal/transcode/subtitle_playlist.go index afa5170..7ebbdc8 100644 --- a/internal/transcode/subtitle_playlist.go +++ b/internal/transcode/subtitle_playlist.go @@ -22,7 +22,6 @@ func hasPackagedSubtitleTracks(outDir string) bool { } func subtitleVTTHasCues(vttPath string) bool { - //nolint:gosec // vttPath is under transcode cache directory raw, err := os.ReadFile(vttPath) if err != nil || len(raw) < 12 { return false diff --git a/internal/usersub/service.go b/internal/usersub/service.go index 4e42c1f..dab3182 100644 --- a/internal/usersub/service.go +++ b/internal/usersub/service.go @@ -180,7 +180,6 @@ func (s *Service) readMeta(userID, relPath string) (metaFile, string, error) { metaPath := filepath.Join(dir, base+metaSuffix) vttPath := filepath.Join(dir, base+vttSuffix) - //nolint:gosec // paths under service cache root raw, err := os.ReadFile(metaPath) if err != nil { if os.IsNotExist(err) { From b124d25a5b9392a16f3ba0509782e514d828ca2d Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Fri, 25 Sep 2026 03:07:51 +0300 Subject: [PATCH 05/11] chore: fix style --- .golangci.yml | 13 +++++++++---- cmd/server/main.go | 1 - internal/access/postgres/store_test.go | 5 +++-- internal/auth/ginjwt/issuer.go | 4 ++-- internal/auth/ginjwt/issuer_test.go | 3 ++- internal/auth/integration_setup_test.go | 3 ++- internal/auth/postgres/refresh_store_test.go | 3 ++- internal/auth/postgres/store_admin_test.go | 3 ++- internal/auth/postgres/store_test.go | 3 ++- internal/httpapi/admin_auth_flows_test.go | 3 +-- internal/metadata/postgres/store_test.go | 5 +++-- 11 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 8774625..e55aae2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -44,18 +44,23 @@ linters: - comments - common-false-positives paths: - - "openapi/" - - "web/" - "cache/" + - "docs/" - "logs/" + - "openapi/" + - "site/" + - "web/" - "_test.go" formatters: enable: + - goimports - gofumpt - swaggo exclusions: paths: - - "openapi/" - - "web/" - "cache/" + - "docs/" - "logs/" + - "openapi/" + - "site/" + - "web/" diff --git a/cmd/server/main.go b/cmd/server/main.go index 6676a3c..e988c64 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -45,7 +45,6 @@ import ( metadatapostgres "sudoStream/internal/metadata/postgres" anilist "sudoStream/internal/provider/anilist" opensubtitles "sudoStream/internal/provider/opensubtitles" - providerpostgres "sudoStream/internal/provider/postgres" trashpostgres "sudoStream/internal/trash/postgres" watchpostgres "sudoStream/internal/watch/postgres" diff --git a/internal/access/postgres/store_test.go b/internal/access/postgres/store_test.go index 947d99a..d4cd158 100644 --- a/internal/access/postgres/store_test.go +++ b/internal/access/postgres/store_test.go @@ -7,13 +7,14 @@ import ( "errors" "os" "sudoStream/internal/access" - accesspostgres "sudoStream/internal/access/postgres" "sudoStream/internal/auth" - authpostgres "sudoStream/internal/auth/postgres" "sudoStream/internal/db" "testing" "time" + accesspostgres "sudoStream/internal/access/postgres" + authpostgres "sudoStream/internal/auth/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" ) diff --git a/internal/auth/ginjwt/issuer.go b/internal/auth/ginjwt/issuer.go index 5aa1b95..044f43c 100644 --- a/internal/auth/ginjwt/issuer.go +++ b/internal/auth/ginjwt/issuer.go @@ -9,11 +9,11 @@ import ( "sudoStream/internal/auth" "time" + authpostgres "sudoStream/internal/auth/postgres" + jwtmw "github.com/appleboy/gin-jwt/v3" "github.com/appleboy/gin-jwt/v3/core" "github.com/gin-gonic/gin" - - authpostgres "sudoStream/internal/auth/postgres" ) const ( diff --git a/internal/auth/ginjwt/issuer_test.go b/internal/auth/ginjwt/issuer_test.go index 0191183..1892a5b 100644 --- a/internal/auth/ginjwt/issuer_test.go +++ b/internal/auth/ginjwt/issuer_test.go @@ -9,11 +9,12 @@ import ( "os" "sudoStream/internal/auth" "sudoStream/internal/auth/ginjwt" - authpostgres "sudoStream/internal/auth/postgres" "sudoStream/internal/db" "testing" "time" + authpostgres "sudoStream/internal/auth/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" "github.com/gin-gonic/gin" ) diff --git a/internal/auth/integration_setup_test.go b/internal/auth/integration_setup_test.go index ea2478c..097f689 100644 --- a/internal/auth/integration_setup_test.go +++ b/internal/auth/integration_setup_test.go @@ -8,10 +8,11 @@ import ( "regexp" "sudoStream/internal/auth" "sudoStream/internal/auth/ginjwt" - authpostgres "sudoStream/internal/auth/postgres" "sudoStream/internal/db" "testing" "time" + + authpostgres "sudoStream/internal/auth/postgres" ) const ( diff --git a/internal/auth/postgres/refresh_store_test.go b/internal/auth/postgres/refresh_store_test.go index 032791d..c395780 100644 --- a/internal/auth/postgres/refresh_store_test.go +++ b/internal/auth/postgres/refresh_store_test.go @@ -5,10 +5,11 @@ import ( "errors" "os" "sudoStream/internal/auth" - authpostgres "sudoStream/internal/auth/postgres" "testing" "time" + authpostgres "sudoStream/internal/auth/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" "github.com/appleboy/gin-jwt/v3/core" ) diff --git a/internal/auth/postgres/store_admin_test.go b/internal/auth/postgres/store_admin_test.go index a258630..4610f48 100644 --- a/internal/auth/postgres/store_admin_test.go +++ b/internal/auth/postgres/store_admin_test.go @@ -4,10 +4,11 @@ import ( "context" "os" "sudoStream/internal/auth" - authpostgres "sudoStream/internal/auth/postgres" "testing" "time" + authpostgres "sudoStream/internal/auth/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" ) diff --git a/internal/auth/postgres/store_test.go b/internal/auth/postgres/store_test.go index 9aef9e4..d1d69d0 100644 --- a/internal/auth/postgres/store_test.go +++ b/internal/auth/postgres/store_test.go @@ -6,11 +6,12 @@ import ( "encoding/hex" "os" "sudoStream/internal/auth" - authpostgres "sudoStream/internal/auth/postgres" "sudoStream/internal/db" "testing" "time" + authpostgres "sudoStream/internal/auth/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" ) diff --git a/internal/httpapi/admin_auth_flows_test.go b/internal/httpapi/admin_auth_flows_test.go index b151e42..04956d2 100644 --- a/internal/httpapi/admin_auth_flows_test.go +++ b/internal/httpapi/admin_auth_flows_test.go @@ -13,9 +13,8 @@ import ( "testing" "time" - "github.com/disintegration/imaging" - allure "github.com/allure-framework/allure-go/commons/gotest" + "github.com/disintegration/imaging" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" ) diff --git a/internal/metadata/postgres/store_test.go b/internal/metadata/postgres/store_test.go index 05a0cf6..2c9ba3a 100644 --- a/internal/metadata/postgres/store_test.go +++ b/internal/metadata/postgres/store_test.go @@ -7,14 +7,15 @@ import ( "errors" "os" "sudoStream/internal/access" - accesspostgres "sudoStream/internal/access/postgres" "sudoStream/internal/db" "sudoStream/internal/metadata" "sudoStream/internal/watch" - watchpostgres "sudoStream/internal/watch/postgres" "testing" "time" + accesspostgres "sudoStream/internal/access/postgres" + watchpostgres "sudoStream/internal/watch/postgres" + allure "github.com/allure-framework/allure-go/commons/gotest" ) From 28d29b25520aa5d0a26322527195358775d9eac8 Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:10:00 +0300 Subject: [PATCH 06/11] chore: keeping linter happy --- internal/access/service.go | 6 +- internal/access/types_test.go | 2 +- internal/auth/admin.go | 6 +- internal/auth/ginjwt/issuer.go | 2 +- internal/auth/service.go | 2 +- internal/auth/tokens.go | 10 ++-- internal/auth/totp.go | 4 +- internal/auth/totp_crypto_internal_test.go | 2 +- internal/dlna/description.go | 2 +- internal/email/sender_test.go | 4 +- internal/metadata/ffprobe.go | 2 +- internal/observability/db_pool.go | 6 +- internal/observability/metrics.go | 68 +++++++++++----------- internal/transcode/job.go | 12 +++- 14 files changed, 67 insertions(+), 61 deletions(-) diff --git a/internal/access/service.go b/internal/access/service.go index 8895c25..6cf6fad 100644 --- a/internal/access/service.go +++ b/internal/access/service.go @@ -609,7 +609,7 @@ func (s *Service) permissionsForPath( return LibraryPermissions{}, false, err } if canonical == "" { - return LibraryPermissions{}, false, nil //nolint:exhaustruct // zero perms + return LibraryPermissions{}, false, nil } libraries, err := s.store.ListLibraries(ctx) @@ -619,7 +619,7 @@ func (s *Service) permissionsForPath( library, ok := MatchLibrary(libraries, canonical) if !ok { - return LibraryPermissions{}, false, nil //nolint:exhaustruct // zero perms + return LibraryPermissions{}, false, nil } grants, err := s.store.GetUserGrantMap(ctx, userID) @@ -629,7 +629,7 @@ func (s *Service) permissionsForPath( perms, found := grants[library.ID] if !found { - return LibraryPermissions{}, false, nil //nolint:exhaustruct // zero perms + return LibraryPermissions{}, false, nil } return perms, true, nil diff --git a/internal/access/types_test.go b/internal/access/types_test.go index 27e8c11..59d1001 100644 --- a/internal/access/types_test.go +++ b/internal/access/types_test.go @@ -49,7 +49,7 @@ func TestService_ListReadableLibraries_FiltersByReadGrant(t *testing.T) { }, } service := access.NewService(store) - user := auth.PublicUser{ //nolint:exhaustruct // test fixture + user := auth.PublicUser{ ID: testUserOne, Role: auth.RoleUser, } diff --git a/internal/auth/admin.go b/internal/auth/admin.go index 81e578a..4a53dcb 100644 --- a/internal/auth/admin.go +++ b/internal/auth/admin.go @@ -159,7 +159,7 @@ func (s *Service) UpdateUser( //nolint:cyclop // email/enabled/role patches ctx, userID, UserPatch{Role: patch.Role}, - ) //nolint:exhaustruct // role-only + ) if err != nil { return AdminUser{}, fmt.Errorf("update user: %w", err) } @@ -225,7 +225,7 @@ func (s *Service) DeleteUser(ctx context.Context, userID string) error { err = s.guardLastAdminMutation( ctx, *target, - UserPatch{Enabled: new(false)}, //nolint:exhaustruct // last-admin check + UserPatch{Enabled: new(false)}, ) if err != nil { return err @@ -273,7 +273,7 @@ func (s *Service) applyEnabledPatch(ctx context.Context, userID string, enabled err = s.store.UpdateUser( ctx, userID, - UserPatch{Enabled: &disabled}, //nolint:exhaustruct // partial patch + UserPatch{Enabled: &disabled}, ) if err != nil { return fmt.Errorf("disable user: %w", err) diff --git a/internal/auth/ginjwt/issuer.go b/internal/auth/ginjwt/issuer.go index 044f43c..e5eebe0 100644 --- a/internal/auth/ginjwt/issuer.go +++ b/internal/auth/ginjwt/issuer.go @@ -43,7 +43,7 @@ func New(store auth.Store, config auth.Config) (*Issuer, error) { refreshStore := authpostgres.NewRefreshTokenStore(store) jwtMiddleware, err := jwtmw.New( - &jwtmw.GinJWTMiddleware{ //nolint:exhaustruct // gin-jwt defaults + &jwtmw.GinJWTMiddleware{ Realm: auth.JWTRealm, Key: secret, Timeout: accessTTL, diff --git a/internal/auth/service.go b/internal/auth/service.go index db50be9..5dad02b 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -586,7 +586,7 @@ func (s *Service) createPendingLoginToken(ctx context.Context, user *User) (stri return "", fmt.Errorf("create pending login token: %w", err) } - err = s.store.CreateAuthToken(ctx, Token{ //nolint:exhaustruct // partial insert row + err = s.store.CreateAuthToken(ctx, Token{ UserID: user.ID, Email: user.Email, Purpose: TokenPurposeLogin2FA, diff --git a/internal/auth/tokens.go b/internal/auth/tokens.go index d7ae471..65017a5 100644 --- a/internal/auth/tokens.go +++ b/internal/auth/tokens.go @@ -43,7 +43,7 @@ func (s *Service) RequestPasswordReset(ctx context.Context, email string) error return fmt.Errorf("invalidate reset tokens: %w", err) } - err = s.store.CreateAuthToken(ctx, Token{ //nolint:exhaustruct // partial insert row + err = s.store.CreateAuthToken(ctx, Token{ ID: "", UserID: user.ID, Email: email, @@ -300,7 +300,7 @@ func (s *Service) issueChangeEmailToken( ttl := s.confirmEmailTTL() - err = s.store.CreateAuthToken(ctx, Token{ //nolint:exhaustruct // partial insert row + err = s.store.CreateAuthToken(ctx, Token{ ID: "", UserID: userID, Email: newEmail, @@ -419,7 +419,7 @@ func (s *Service) issueInviteToken( return fmt.Errorf("invalidate invite tokens: %w", err) } - err = s.store.CreateAuthToken(ctx, Token{ //nolint:exhaustruct // partial insert row + err = s.store.CreateAuthToken(ctx, Token{ ID: "", UserID: userID, Email: email, @@ -532,7 +532,7 @@ func (s *Service) loadOrCreateInviteUser( err = s.store.UpdateUser( ctx, existing.ID, - UserPatch{Role: &role}, //nolint:exhaustruct // role-only + UserPatch{Role: &role}, ) if err != nil { return nil, fmt.Errorf("set invited role: %w", err) @@ -625,7 +625,7 @@ func (s *Service) sendConfirmEmail( confirmTTL := s.confirmEmailTTL() - err = s.store.CreateAuthToken(ctx, Token{ //nolint:exhaustruct // partial insert row + err = s.store.CreateAuthToken(ctx, Token{ ID: "", UserID: userID, Email: email, diff --git a/internal/auth/totp.go b/internal/auth/totp.go index 65e9e6e..c2fdb3a 100644 --- a/internal/auth/totp.go +++ b/internal/auth/totp.go @@ -46,7 +46,7 @@ func (s *Service) StartTwoFactorSetup( } key, err := totp.Generate( - totp.GenerateOpts{ //nolint:exhaustruct // library defaults are sufficient + totp.GenerateOpts{ Issuer: "sudoStream", AccountName: email, Period: totpPeriodSeconds, @@ -376,7 +376,7 @@ func (s *Service) validateTOTPCode(secret, code string, leewaySeconds int) bool code, secret, time.Now().UTC(), - totp.ValidateOpts{ //nolint:exhaustruct // library defaults are sufficient + totp.ValidateOpts{ Period: totpPeriodSeconds, Skew: skew, Digits: otp.DigitsSix, diff --git a/internal/auth/totp_crypto_internal_test.go b/internal/auth/totp_crypto_internal_test.go index 56a6b8d..7296f4d 100644 --- a/internal/auth/totp_crypto_internal_test.go +++ b/internal/auth/totp_crypto_internal_test.go @@ -77,7 +77,7 @@ func TestValidateTOTPCode_LeewayClampAndRejection(t *testing.T) { allure.Test(t, "leeway clamps to bounds and invalid codes fail", func(a *allure.Context) { t := a.T() - service := &Service{} //nolint:exhaustruct // validateTOTPCode reads no service fields + service := &Service{} code, err := totp.GenerateCode(testTOTPSecret, time.Now()) if err != nil { diff --git a/internal/dlna/description.go b/internal/dlna/description.go index ab9ff19..ab3c179 100644 --- a/internal/dlna/description.go +++ b/internal/dlna/description.go @@ -81,7 +81,7 @@ func writeConnectionManagerSCPD(writer http.ResponseWriter) { // DetectLANIPv4 returns a non-loopback IPv4 suitable for LOCATION URLs. func DetectLANIPv4(ctx context.Context) (string, error) { - dialer := net.Dialer{Timeout: lanProbeTimeout} //nolint:exhaustruct // dial probe only + dialer := net.Dialer{Timeout: lanProbeTimeout} conn, err := dialer.DialContext(ctx, "udp", "8.8.8.8:80") if err != nil { return fallbackInterfaceIPv4() diff --git a/internal/email/sender_test.go b/internal/email/sender_test.go index 0ffaf93..9b28d9e 100644 --- a/internal/email/sender_test.go +++ b/internal/email/sender_test.go @@ -55,7 +55,7 @@ func TestSMTPSender_SendRejectsMissingHost(t *testing.T) { allure.Test(t, "send fails when SMTP host is not configured", func(a *allure.Context) { t := a.T() sender := email.NewSMTPSender( - email.SMTPConfig{ //nolint:exhaustruct // host intentionally empty + email.SMTPConfig{ Port: "587", From: "from@example.com", }, @@ -105,7 +105,7 @@ func TestSMTPSender_SendWithoutAuth(t *testing.T) { } sender := email.NewSMTPSender( - email.SMTPConfig{ //nolint:exhaustruct // auth intentionally omitted + email.SMTPConfig{ Host: host, Port: port, From: "sudostream@localhost", diff --git a/internal/metadata/ffprobe.go b/internal/metadata/ffprobe.go index d162de7..ed7e55e 100644 --- a/internal/metadata/ffprobe.go +++ b/internal/metadata/ffprobe.go @@ -78,7 +78,7 @@ func ParseFFProbeJSON(raw []byte) (VideoFields, error) { func mapFFProbe( //nolint:cyclop,gocognit // ffprobe tag mapping is intentionally explicit parsed ffprobeOutput, ) VideoFields { - fields := VideoFields{} //nolint:exhaustruct // populated below + fields := VideoFields{} // Editorial tags come from the container/format only. Stream titles are // track labels (e.g. "DD 2.0 @ 192 kbps") and must not become the file title. diff --git a/internal/observability/db_pool.go b/internal/observability/db_pool.go index 21ca132..2b35fa1 100644 --- a/internal/observability/db_pool.go +++ b/internal/observability/db_pool.go @@ -9,15 +9,15 @@ import ( //nolint:gochecknoglobals // Prometheus metrics are registered process-wide. var ( - dbSQLInUseConns = promauto.NewGauge(prometheus.GaugeOpts{ //nolint:exhaustruct + dbSQLInUseConns = promauto.NewGauge(prometheus.GaugeOpts{ Name: "db_sql_in_use_conns", Help: "Number of database connections currently in use.", }) - dbSQLIdleConns = promauto.NewGauge(prometheus.GaugeOpts{ //nolint:exhaustruct + dbSQLIdleConns = promauto.NewGauge(prometheus.GaugeOpts{ Name: "db_sql_idle_conns", Help: "Number of idle database connections in the pool.", }) - dbSQLOpenConns = promauto.NewGauge(prometheus.GaugeOpts{ //nolint:exhaustruct + dbSQLOpenConns = promauto.NewGauge(prometheus.GaugeOpts{ Name: "db_sql_open_conns", Help: "Number of open database connections in the pool.", }) diff --git a/internal/observability/metrics.go b/internal/observability/metrics.go index 84ceb38..785e04f 100644 --- a/internal/observability/metrics.go +++ b/internal/observability/metrics.go @@ -30,14 +30,14 @@ const ( //nolint:gochecknoglobals // Prometheus metrics are registered process-wide. var ( httpRequestsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests processed.", }, []string{metricLabelMethod, metricLabelRoute, metricLabelStatus}, ) httpRequestDuration = promauto.NewHistogramVec( - prometheus.HistogramOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request latency in seconds.", Buckets: prometheus.DefBuckets, @@ -45,14 +45,14 @@ var ( []string{metricLabelMethod, metricLabelRoute}, ) httpRequestsInFlight = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "http_requests_in_flight", Help: "Number of HTTP requests currently being processed.", }, ) videoPlayerOpensTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "video_player_opens_total", Help: "Total number of video stream requests treated as player opens.", }, @@ -60,21 +60,21 @@ var ( ) playbackDecisionsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "playback_decisions_total", Help: "Playback method decisions (directPlay / remux / transcode) by client.", }, []string{metricLabelMethod, metricLabelClient}, ) playbackStreamsActive = promauto.NewGaugeVec( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "playback_streams_active", Help: "Number of progressive media streams currently being served.", }, []string{metricLabelMethod, metricLabelClient}, ) playbackStreamBytesTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "playback_stream_bytes_total", Help: "Bytes written for progressive media streams.", }, @@ -82,21 +82,21 @@ var ( ) metadataProbeTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "metadata_probe_total", Help: "Total ffprobe executions during metadata indexing.", }, []string{metricLabelStatus}, ) metadataProbeDuration = promauto.NewHistogram( - prometheus.HistogramOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.HistogramOpts{ Name: "metadata_probe_duration_seconds", Help: "ffprobe latency during metadata indexing.", Buckets: prometheus.DefBuckets, }, ) metadataIndexFilesTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "metadata_index_files_total", Help: "Total video files indexed into metadata cache.", }, @@ -104,7 +104,7 @@ var ( ) videoPosterGenerationsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "video_poster_generations_total", Help: "Total video poster thumbnail generations.", }, @@ -112,40 +112,40 @@ var ( ) transcodeJobsActive = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "transcode_jobs_active", Help: "Number of active HLS timeline probe/publish jobs.", }, ) transcodeSegmentSessionsActive = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "transcode_segment_sessions_active", Help: "Number of active ffmpeg HLS segment encode sessions.", }, ) transcodeTimelineJobsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "transcode_timeline_jobs_total", Help: "Finished HLS timeline probe/publish jobs by outcome.", }, []string{metricLabelStatus}, ) transcodeSegmentJobsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "transcode_segment_jobs_total", Help: "Finished ffmpeg HLS segment encode sessions by outcome.", }, []string{metricLabelStatus}, ) transcodeTimelineDurationSeconds = promauto.NewHistogram( - prometheus.HistogramOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.HistogramOpts{ Name: "transcode_timeline_duration_seconds", Help: "HLS timeline probe/publish job duration in seconds.", Buckets: prometheus.DefBuckets, }, ) transcodeSegmentDurationSeconds = promauto.NewHistogram( - prometheus.HistogramOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.HistogramOpts{ Name: "transcode_segment_duration_seconds", Help: "ffmpeg HLS segment encode session duration in seconds.", Buckets: prometheus.DefBuckets, @@ -153,7 +153,7 @@ var ( ) ffmpegErrorsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "ffmpeg_errors_total", Help: "Total ffmpeg failures by stage.", }, @@ -161,26 +161,26 @@ var ( ) hlsCacheLookupsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "hls_cache_lookups_total", Help: "HLS timeline cache lookups by result (hit or miss).", }, []string{metricLabelResult}, ) hlsCacheBytes = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "hls_cache_bytes", Help: "Total bytes used by the on-disk HLS cache.", }, ) hlsCacheEntries = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "hls_cache_entries", Help: "Number of top-level HLS cache packages (cache keys).", }, ) hlsSegmentRequestsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "hls_segment_requests_total", Help: "Total HLS segment resolutions by how they were served.", }, @@ -188,14 +188,14 @@ var ( ) maintenanceRunsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "maintenance_runs_total", Help: "Total maintenance action runs by action, status, and trigger.", }, []string{metricLabelAction, metricLabelStatus, metricLabelTrigger}, ) maintenanceRunDuration = promauto.NewHistogramVec( - prometheus.HistogramOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.HistogramOpts{ Name: "maintenance_run_duration_seconds", Help: "Maintenance action run duration in seconds.", Buckets: prometheus.DefBuckets, @@ -203,7 +203,7 @@ var ( []string{metricLabelAction}, ) maintenanceRunning = promauto.NewGaugeVec( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "maintenance_running", Help: "Whether a maintenance action is currently running (0 or 1).", }, @@ -211,14 +211,14 @@ var ( ) dlnaSOAPRequestsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "dlna_soap_requests_total", Help: "DLNA SOAP control requests by action and outcome.", }, []string{metricLabelAction, metricLabelStatus}, ) dlnaStreamRequestsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "dlna_stream_requests_total", Help: "DLNA signed progressive stream requests by outcome.", }, @@ -226,34 +226,34 @@ var ( ) authLoginAttemptsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "auth_login_attempts_total", Help: "Login attempts by result (success, invalid_credentials, disabled, …).", }, []string{metricLabelResult}, ) aclPathCanonicalizedTotal = promauto.NewCounter( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "acl_path_canonicalized_total", Help: "ACL checks where the request path was rewritten (unescape/clean) before grant matching.", }, ) authSessionsActive = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "auth_sessions_active", Help: "Number of refresh sessions currently stored.", }, ) providerTaskItemsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "provider_task_items_total", Help: "Provider enrichment item outcomes by provider, kind, and result.", }, []string{metricLabelProvider, metricLabelKind, metricLabelResult}, ) providerHTTPRequestsTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.CounterOpts{ Name: "provider_http_requests_total", Help: "Outbound provider HTTP requests by provider and status class.", }, @@ -261,14 +261,14 @@ var ( ) libraryMediaFiles = promauto.NewGaugeVec( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "library_media_files", Help: "Indexed media files per library slug.", }, []string{metricLabelLibrarySlug}, ) trashItems = promauto.NewGauge( - prometheus.GaugeOpts{ //nolint:exhaustruct // optional Prometheus fields omitted + prometheus.GaugeOpts{ Name: "trash_items", Help: "Number of items currently in the recycle bin.", }, diff --git a/internal/transcode/job.go b/internal/transcode/job.go index 77598d2..afbdeda 100644 --- a/internal/transcode/job.go +++ b/internal/transcode/job.go @@ -163,12 +163,14 @@ func softwareScaleExpr(height int) string { func nvencVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMappingAlgorithm) string { scaleCUDA := fmt.Sprintf("scale_cuda=-2:min(%d\\,ih):format=nv12", rung.Height) - switch toneMap { //nolint:exhaustive // only CUDA/CPU apply on NVENC + switch toneMap { case ToneMapCUDA: return joinFilters(hwTonemapFilter("cuda", "nv12", algo), scaleCUDA) case ToneMapCPU: return joinFilters(cpuTonemapFilter(algo), fmt.Sprintf("scale=-2:min(%d\\,ih),format=nv12,hwupload_cuda", rung.Height)) + case ToneMapNone, ToneMapQSVVPP, ToneMapVulkan, ToneMapOpenCL: + return scaleCUDA default: return scaleCUDA } @@ -196,11 +198,13 @@ func qsvVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMappingAlgor func vaapiVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMappingAlgorithm) string { // Ladder scale via scale_vaapi (E-5 E1). Never VAAPI VPP tonemap. scaleVA := fmt.Sprintf("scale_vaapi=w=-2:h=min(%d\\,ih):format=nv12", rung.Height) - switch toneMap { //nolint:exhaustive // Vulkan/CPU/none only on VA-API + switch toneMap { case ToneMapVulkan: return joinFilters(vulkanTonemapFilter("nv12", algo), "hwupload", scaleVA) case ToneMapCPU: return joinFilters(cpuTonemapFilter(algo), "format=nv12,hwupload", scaleVA) + case ToneMapNone, ToneMapCUDA, ToneMapQSVVPP, ToneMapOpenCL: + return joinFilters("format=nv12,hwupload", scaleVA) default: return joinFilters("format=nv12,hwupload", scaleVA) } @@ -208,7 +212,7 @@ func vaapiVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMappingAlg func rockchipVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMappingAlgorithm) string { scaleExpr := softwareScaleExpr(rung.Height) - switch toneMap { //nolint:exhaustive // OpenCL/CPU/none only on RKMPP + switch toneMap { case ToneMapOpenCL: return joinFilters( "format=nv12,hwupload=derive_device=opencl", @@ -218,6 +222,8 @@ func rockchipVideoFilter(rung QualityRung, toneMap ToneMapMode, algo ToneMapping ) case ToneMapCPU: return joinFilters(cpuTonemapFilter(algo), scaleExpr) + case ToneMapNone, ToneMapCUDA, ToneMapQSVVPP, ToneMapVulkan: + return scaleExpr default: return scaleExpr } From 37eacf0d6c64efe016fd3b7a8ee1b0d77aa01129 Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:54:21 +0300 Subject: [PATCH 07/11] feat: downmix algos, admin boost, user audio language prefs --- internal/auth/playback_prefs.go | 75 +++++++++ internal/auth/playback_prefs_test.go | 34 +++++ internal/auth/postgres/models.go | 21 +-- internal/auth/postgres/store.go | 51 +++++++ internal/auth/service.go | 2 + .../00020_user_audio_language_prefs.sql | 6 + internal/httpapi/admin_handlers.go | 2 + internal/httpapi/me_playback_handlers.go | 111 ++++++++++++++ internal/httpapi/play_handlers.go | 32 +++- internal/httpapi/routes.go | 4 + internal/httpapi/swagger_test.go | 2 + internal/playback/decide_test.go | 28 ++++ internal/transcode/audio_select.go | 66 ++++++++ internal/transcode/audio_select_test.go | 37 +++++ internal/transcode/downmix.go | 104 ++++++++++--- internal/transcode/downmix_test.go | 77 ++++++---- internal/transcode/ffargs.go | 30 ++-- internal/transcode/playback.go | 18 ++- internal/transcode/playlist_vod.go | 37 +++++ internal/transcode/probe.go | 10 ++ internal/transcode/service.go | 4 +- internal/transcode/service_test.go | 11 ++ internal/transcode/session.go | 21 +-- internal/transcode/settings.go | 29 ++++ internal/transcode/settings_test.go | 6 +- internal/transcode/source_meta.go | 17 ++- openapi/swagger.json | 143 +++++++++++++++++- openapi/swagger.yaml | 92 +++++++++++ web/src/client/@tanstack/react-query.gen.ts | 43 +++++- web/src/client/index.ts | 4 +- web/src/client/schemas.gen.ts | 42 ++++- web/src/client/sdk.gen.ts | 23 ++- web/src/client/types.gen.ts | 77 +++++++++- web/src/client/zod.gen.ts | 33 +++- .../account/playback-preferences-section.tsx | 117 ++++++++++++++ web/src/locales/en/translation.json | 16 +- web/src/locales/ru/translation.json | 14 +- web/src/pages/admin/settings/index.tsx | 31 +++- web/src/routes/user/index.tsx | 3 + 39 files changed, 1366 insertions(+), 107 deletions(-) create mode 100644 internal/auth/playback_prefs.go create mode 100644 internal/auth/playback_prefs_test.go create mode 100644 internal/db/migrations/00020_user_audio_language_prefs.sql create mode 100644 internal/httpapi/me_playback_handlers.go create mode 100644 internal/transcode/audio_select.go create mode 100644 internal/transcode/audio_select_test.go create mode 100644 web/src/components/account/playback-preferences-section.tsx diff --git a/internal/auth/playback_prefs.go b/internal/auth/playback_prefs.go new file mode 100644 index 0000000..f1f8eca --- /dev/null +++ b/internal/auth/playback_prefs.go @@ -0,0 +1,75 @@ +package auth + +import ( + "context" + "errors" + "fmt" + "strings" +) + +const maxAudioLanguagePrefs = 3 + +var ( + // ErrInvalidAudioLanguagePrefs is returned when more than three languages are supplied. + ErrInvalidAudioLanguagePrefs = errors.New("audio language list exceeds maximum of 3") + errPlaybackStoreUnavailable = errors.New("auth store unavailable") +) + +// PlaybackPreferences holds per-user playback defaults (E-28). +type PlaybackPreferences struct { + AudioLanguages []string `json:"audioLanguages"` +} + +// PlaybackAudioLanguages returns normalized user audio language priority (may be empty). +func (s *Service) PlaybackAudioLanguages(ctx context.Context, userID string) ([]string, error) { + if s.store == nil { + return nil, nil + } + + prefs, err := s.store.GetPlaybackPreferences(ctx, userID) + if err != nil { + return nil, fmt.Errorf("get playback preferences: %w", err) + } + + return prefs.AudioLanguages, nil +} + +// UpdatePlaybackPreferences stores up to three ordered audio language codes. +func (s *Service) UpdatePlaybackPreferences( + ctx context.Context, + userID string, + prefs PlaybackPreferences, +) (PlaybackPreferences, error) { + normalized, err := NormalizePlaybackPreferences(prefs) + if err != nil { + return PlaybackPreferences{}, err + } + + if s.store == nil { + return PlaybackPreferences{}, errPlaybackStoreUnavailable + } + + err = s.store.SavePlaybackPreferences(ctx, userID, normalized) + if err != nil { + return PlaybackPreferences{}, fmt.Errorf("save playback preferences: %w", err) + } + + return normalized, nil +} + +// NormalizePlaybackPreferences validates and normalizes audio language codes. +func NormalizePlaybackPreferences(prefs PlaybackPreferences) (PlaybackPreferences, error) { + langs := make([]string, 0, len(prefs.AudioLanguages)) + for _, raw := range prefs.AudioLanguages { + lang := strings.ToLower(strings.TrimSpace(raw)) + if lang == "" { + continue + } + langs = append(langs, lang) + if len(langs) > maxAudioLanguagePrefs { + return PlaybackPreferences{}, ErrInvalidAudioLanguagePrefs + } + } + + return PlaybackPreferences{AudioLanguages: langs}, nil +} diff --git a/internal/auth/playback_prefs_test.go b/internal/auth/playback_prefs_test.go new file mode 100644 index 0000000..db70c0d --- /dev/null +++ b/internal/auth/playback_prefs_test.go @@ -0,0 +1,34 @@ +package auth + +import ( + "testing" + + allure "github.com/allure-framework/allure-go/commons/gotest" +) + +func TestNormalizePlaybackPreferences(t *testing.T) { + t.Parallel() + + allure.Test(t, "accepts up to three languages", func(a *allure.Context) { + t := a.T() + got, err := NormalizePlaybackPreferences(PlaybackPreferences{ + AudioLanguages: []string{" EN ", "ru", "jpn"}, + }) + if err != nil { + t.Fatalf("normalize: %v", err) + } + if len(got.AudioLanguages) != 3 || got.AudioLanguages[0] != "en" { + t.Fatalf("got %+v", got.AudioLanguages) + } + }) + + allure.Test(t, "rejects more than three languages", func(a *allure.Context) { + t := a.T() + _, err := NormalizePlaybackPreferences(PlaybackPreferences{ + AudioLanguages: []string{"en", "ru", "de", "fr"}, + }) + if err == nil { + t.Fatal("expected error") + } + }) +} diff --git a/internal/auth/postgres/models.go b/internal/auth/postgres/models.go index 2a6ad75..ad9c4a6 100644 --- a/internal/auth/postgres/models.go +++ b/internal/auth/postgres/models.go @@ -7,16 +7,17 @@ import ( ) type userModel struct { - ID string `gorm:"column:id;primaryKey;type:uuid"` - Email string `gorm:"column:email"` - PasswordHash string `gorm:"column:password_hash"` - Role string `gorm:"column:role"` - Enabled bool `gorm:"column:enabled"` - EmailVerifiedAt *time.Time `gorm:"column:email_verified_at"` - MustChangePassword bool `gorm:"column:must_change_password"` - CreatedAt time.Time `gorm:"column:created_at"` - UpdatedAt time.Time `gorm:"column:updated_at"` - LastLoginAt *time.Time `gorm:"column:last_login_at"` + ID string `gorm:"column:id;primaryKey;type:uuid"` + Email string `gorm:"column:email"` + PasswordHash string `gorm:"column:password_hash"` + Role string `gorm:"column:role"` + Enabled bool `gorm:"column:enabled"` + EmailVerifiedAt *time.Time `gorm:"column:email_verified_at"` + MustChangePassword bool `gorm:"column:must_change_password"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` + LastLoginAt *time.Time `gorm:"column:last_login_at"` + AudioLanguagePrefs datatypes.JSON `gorm:"column:audio_language_prefs"` } func (userModel) TableName() string { diff --git a/internal/auth/postgres/store.go b/internal/auth/postgres/store.go index 121b47b..a03decd 100644 --- a/internal/auth/postgres/store.go +++ b/internal/auth/postgres/store.go @@ -80,6 +80,7 @@ func (s *Store) CreateUser(ctx context.Context, user auth.User, passwordHash str Role: user.Role, Enabled: user.Enabled, MustChangePassword: user.MustChangePassword, + AudioLanguagePrefs: datatypes.JSON("[]"), } err := s.db.WithContext(ctx). @@ -897,6 +898,56 @@ func (s *Store) ConsumeBackupCode(ctx context.Context, userID string, index int) return nil } +// GetPlaybackPreferences loads ordered audio language codes for a user. +func (s *Store) GetPlaybackPreferences(ctx context.Context, userID string) (auth.PlaybackPreferences, error) { + var model userModel + err := s.db.WithContext(ctx).Select("audio_language_prefs").Where("id = ?", userID).First(&model).Error + if err != nil { + return auth.PlaybackPreferences{}, fmt.Errorf("get playback preferences: %w", err) + } + + return playbackPrefsFromJSON(model.AudioLanguagePrefs), nil +} + +// SavePlaybackPreferences stores ordered audio language codes for a user. +func (s *Store) SavePlaybackPreferences( + ctx context.Context, + userID string, + prefs auth.PlaybackPreferences, +) error { + raw, err := json.Marshal(prefs.AudioLanguages) + if err != nil { + return fmt.Errorf("encode playback preferences: %w", err) + } + + result := s.db.WithContext(ctx). + Model(&userModel{}). + Where("id = ?", userID). + Updates(map[string]any{ + "audio_language_prefs": datatypes.JSON(raw), + columnUpdatedAt: time.Now().UTC(), + }) + if result.Error != nil { + return fmt.Errorf("save playback preferences: %w", result.Error) + } + + return nil +} + +func playbackPrefsFromJSON(raw datatypes.JSON) auth.PlaybackPreferences { + if len(raw) == 0 { + return auth.PlaybackPreferences{AudioLanguages: []string{}} + } + + var langs []string + err := json.Unmarshal(raw, &langs) + if err != nil { + return auth.PlaybackPreferences{AudioLanguages: []string{}} + } + + return auth.PlaybackPreferences{AudioLanguages: langs} +} + func userFromModel(model userModel) *auth.User { return &auth.User{ ID: model.ID, diff --git a/internal/auth/service.go b/internal/auth/service.go index 5dad02b..0ec5faa 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -117,6 +117,8 @@ type Store interface { ) (*Token, error) GetSettings(ctx context.Context) (Settings, error) SaveSettings(ctx context.Context, settings Settings) error + GetPlaybackPreferences(ctx context.Context, userID string) (PlaybackPreferences, error) + SavePlaybackPreferences(ctx context.Context, userID string, prefs PlaybackPreferences) error CreateAuthToken(ctx context.Context, token Token) error GetAuthTokenByHash(ctx context.Context, tokenHash, purpose string) (*Token, error) MarkAuthTokenUsed(ctx context.Context, tokenID string) error diff --git a/internal/db/migrations/00020_user_audio_language_prefs.sql b/internal/db/migrations/00020_user_audio_language_prefs.sql new file mode 100644 index 0000000..9999ad1 --- /dev/null +++ b/internal/db/migrations/00020_user_audio_language_prefs.sql @@ -0,0 +1,6 @@ +-- +goose Up +ALTER TABLE users + ADD COLUMN audio_language_prefs JSONB NOT NULL DEFAULT '[]'::jsonb; + +-- +goose Down +ALTER TABLE users DROP COLUMN IF EXISTS audio_language_prefs; diff --git a/internal/httpapi/admin_handlers.go b/internal/httpapi/admin_handlers.go index a4ee7b9..28e66da 100644 --- a/internal/httpapi/admin_handlers.go +++ b/internal/httpapi/admin_handlers.go @@ -383,6 +383,8 @@ func (h *adminHandler) patchTranscodeSettings(c *gin.Context) { msg = "invalid hwAccel value" case errors.Is(err, transcode.ErrInvalidDownmix): msg = "invalid downmixAlgorithm value" + case errors.Is(err, transcode.ErrInvalidDownmixBoost): + msg = "invalid downmixBoost value" case errors.Is(err, transcode.ErrInvalidToneMappingAlgorithm): msg = "invalid toneMappingAlgorithm value" } diff --git a/internal/httpapi/me_playback_handlers.go b/internal/httpapi/me_playback_handlers.go new file mode 100644 index 0000000..87adbd4 --- /dev/null +++ b/internal/httpapi/me_playback_handlers.go @@ -0,0 +1,111 @@ +package httpapi + +import ( + "errors" + "net/http" + "sudoStream/internal/auth" + + "github.com/gin-gonic/gin" +) + +// PlaybackPreferencesResponse is returned by GET /api/me/playback-preferences. +type PlaybackPreferencesResponse struct { + AudioLanguages []string `json:"audioLanguages"` +} + +// PatchPlaybackPreferencesRequest updates user audio language priority. +type PatchPlaybackPreferencesRequest struct { + AudioLanguages []string `json:"audioLanguages"` +} + +// getPlaybackPreferences returns the current user's audio language priority list. +// +// @Summary Get playback preferences +// @Description Returns ordered audio language codes for default track selection (max 3). +// @Tags me +// @Produce json +// @Success 200 {object} PlaybackPreferencesResponse +// @Failure 401 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/me/playback-preferences [get] +func (h *handler) getPlaybackPreferences(c *gin.Context) { + user, ok := currentUser(c) + if !ok { + c.JSON(http.StatusUnauthorized, ErrorResponse{Error: authRequiredMessage}) + + return + } + + if h.auth == nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "auth unavailable"}) + + return + } + + langs, err := h.auth.PlaybackAudioLanguages(c.Request.Context(), user.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "load playback preferences failed"}) + + return + } + + if langs == nil { + langs = []string{} + } + + c.JSON(http.StatusOK, PlaybackPreferencesResponse{AudioLanguages: langs}) +} + +// patchPlaybackPreferences stores up to three ordered audio language codes. +// +// @Summary Update playback preferences +// @Description Sets ordered audio language codes for default track selection (max 3). +// @Tags me +// @Accept json +// @Produce json +// @Param body body PatchPlaybackPreferencesRequest true "Playback preferences" +// @Success 200 {object} PlaybackPreferencesResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/me/playback-preferences [patch] +func (h *handler) patchPlaybackPreferences(c *gin.Context) { + user, ok := currentUser(c) + if !ok { + c.JSON(http.StatusUnauthorized, ErrorResponse{Error: authRequiredMessage}) + + return + } + + if h.auth == nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "auth unavailable"}) + + return + } + + var body PatchPlaybackPreferencesRequest + err := c.ShouldBindJSON(&body) + if err != nil { + c.JSON(http.StatusBadRequest, ErrorResponse{Error: invalidRequestBody}) + + return + } + + saved, err := h.auth.UpdatePlaybackPreferences( + c.Request.Context(), + user.ID, + auth.PlaybackPreferences{AudioLanguages: body.AudioLanguages}, + ) + if err != nil { + switch { + case errors.Is(err, auth.ErrInvalidAudioLanguagePrefs): + c.JSON(http.StatusBadRequest, ErrorResponse{Error: "audio language list exceeds maximum of 3"}) + default: + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "save playback preferences failed"}) + } + + return + } + + c.JSON(http.StatusOK, PlaybackPreferencesResponse{AudioLanguages: saved.AudioLanguages}) +} diff --git a/internal/httpapi/play_handlers.go b/internal/httpapi/play_handlers.go index c48a9bd..5c2d663 100644 --- a/internal/httpapi/play_handlers.go +++ b/internal/httpapi/play_handlers.go @@ -293,6 +293,16 @@ func (h *handler) serveHLSPlaylist(c *gin.Context, diskPath string) { c.Header("Cache-Control", transcode.PlaylistCacheControl(raw)) body := transcode.NormalizePlaylistForClient(raw) + if strings.HasSuffix(filepath.Base(diskPath), hlsMasterPlaylist) { + outDir := filepath.Dir(diskPath) + if meta, ok := transcode.ReadSourceMeta(outDir); ok { + defaultIndex := transcode.SelectDefaultAudioStreamIndex( + meta.AudioStreams, + h.userAudioLanguages(c), + ) + body = transcode.ApplyMasterPlaylistDefaultAudio(body, defaultIndex) + } + } body = transcode.RewritePlaylistTokens(body, accessToken(c)) _, err = c.Writer.Write(body) if err != nil { @@ -426,8 +436,12 @@ func (h *handler) respondHLSPlayback( capsOK bool, ) { jobStatus := h.transcode.Status(cacheKey) - playbackInfo := transcode.BuildPlaybackInfoWithSettings( - outDir, masterURL, jobStatus, h.transcode.CurrentSettings(), + playbackInfo := transcode.BuildPlaybackInfoWithUserPrefs( + outDir, + masterURL, + jobStatus, + h.transcode.CurrentSettings(), + h.userAudioLanguages(c), ) playMethod := resolvePlayMethod(decision, playbackInfo.PackagingMode, capsOK) @@ -898,6 +912,20 @@ func setTestCacheRoot(root string) func() { } } +func (h *handler) userAudioLanguages(c *gin.Context) []string { + user, ok := currentUser(c) + if !ok || h.auth == nil { + return nil + } + + langs, err := h.auth.PlaybackAudioLanguages(c.Request.Context(), user.ID) + if err != nil { + return nil + } + + return langs +} + func cacheSubdir(subdir string) string { root := defaultCacheRoot if testCacheRoot != "" { diff --git a/internal/httpapi/routes.go b/internal/httpapi/routes.go index 48c9834..b6c3f14 100644 --- a/internal/httpapi/routes.go +++ b/internal/httpapi/routes.go @@ -48,6 +48,7 @@ type ErrorResponse struct { } type handler struct { + auth *auth.Service media *mediafs.Service access *access.Service metadata *metadata.Service @@ -107,6 +108,7 @@ func RegisterRoutes( registerInventoryMetrics(transcodeSvc, accessService, metadataService, authService, cfg.Trash) mediaHandler := &handler{ + auth: authService, media: media, access: accessService, metadata: metadataService, @@ -201,6 +203,8 @@ func RegisterRoutes( api.GET("/me/watched", mediaHandler.getHomeWatched) api.GET("/me/unwatched", mediaHandler.getHomeUnwatched) api.GET("/me/stats", mediaHandler.getHomeStats) + api.GET("/me/playback-preferences", mediaHandler.getPlaybackPreferences) + api.PATCH("/me/playback-preferences", mediaHandler.patchPlaybackPreferences) api.POST("/auth/change-password", authRoutes.changePassword) api.POST("/auth/change-email", authRoutes.changeEmail) api.GET("/auth/sessions", authRoutes.listSessions) diff --git a/internal/httpapi/swagger_test.go b/internal/httpapi/swagger_test.go index a8989d6..c6802ff 100644 --- a/internal/httpapi/swagger_test.go +++ b/internal/httpapi/swagger_test.go @@ -52,6 +52,8 @@ func apiRouteCatalog() []routeKey { {http.MethodPost, "/api/auth/accept-invite"}, {http.MethodPost, "/api/auth/2fa/verify"}, {http.MethodGet, "/api/auth/me"}, + {http.MethodGet, "/api/me/playback-preferences"}, + {http.MethodPatch, "/api/me/playback-preferences"}, {http.MethodGet, "/api/me/continue"}, {http.MethodGet, "/api/me/favorites"}, {http.MethodGet, "/api/me/watched"}, diff --git a/internal/playback/decide_test.go b/internal/playback/decide_test.go index ff0af16..5de78a8 100644 --- a/internal/playback/decide_test.go +++ b/internal/playback/decide_test.go @@ -8,6 +8,7 @@ import ( const ( testContainerMP4 = "mp4" + testContainerMKV = "mkv" testCodecH264 = "h264" testCodecAAC = "aac" testContainerWebM = "webm" @@ -155,6 +156,33 @@ func TestDecide_BitrateOverMaxBlocksDirectPlay(t *testing.T) { }) } +func TestDecide_MultiChannelAC3DirectPlay(t *testing.T) { + t.Parallel() + + allure.Test(t, "AC-3 5.1 stays Direct Play when profile lists ac3", func(a *allure.Context) { + t := a.T() + profile := DeviceProfile{ + DirectPlay: []DirectPlayProfile{{ + Container: testContainerMKV, + VideoCodec: testCodecH264, + AudioCodec: "ac3", + }}, + } + source := SourceCaps{ + Container: testContainerMKV, + VideoCodec: testCodecH264, + AudioCodec: "ac3", + Height: 1080, + RemuxOK: true, + } + + got := Decide(profile, source, 0) + if got.Method != MethodDirectPlay { + t.Fatalf("method: got %q want %q", got.Method, MethodDirectPlay) + } + }) +} + func TestContainerFromPath(t *testing.T) { t.Parallel() diff --git a/internal/transcode/audio_select.go b/internal/transcode/audio_select.go new file mode 100644 index 0000000..8b171f2 --- /dev/null +++ b/internal/transcode/audio_select.go @@ -0,0 +1,66 @@ +package transcode + +import ( + "strings" +) + +const maxUserAudioLanguagePrefs = 3 + +// SelectDefaultAudioStreamIndex picks the default encode/HLS audio track index using user +// language priority, skipping commentary/descriptive tracks when alternatives exist. +func SelectDefaultAudioStreamIndex(streams []AudioStream, userLanguages []string) int { + if len(streams) == 0 { + return -1 + } + + prefs := normalizeLanguagePrefs(userLanguages) + if len(prefs) > 0 { + for _, pref := range prefs { + for index, stream := range streams { + if stream.Commentary { + continue + } + if languageMatches(pref, stream.Language) { + return index + } + } + } + } + + for index, stream := range streams { + if !stream.Commentary { + return index + } + } + + return 0 +} + +func normalizeLanguagePrefs(langs []string) []string { + out := make([]string, 0, len(langs)) + for _, lang := range langs { + lang = strings.ToLower(strings.TrimSpace(lang)) + if lang == "" { + continue + } + out = append(out, lang) + if len(out) >= maxUserAudioLanguagePrefs { + break + } + } + + return out +} + +func languageMatches(pref, trackLang string) bool { + trackLang = strings.ToLower(strings.TrimSpace(trackLang)) + if trackLang == "" { + return false + } + if trackLang == pref { + return true + } + + // Match ISO 639-2 (eng) with 639-1 (en) prefixes. + return strings.HasPrefix(trackLang, pref) || strings.HasPrefix(pref, trackLang) +} diff --git a/internal/transcode/audio_select_test.go b/internal/transcode/audio_select_test.go new file mode 100644 index 0000000..61edace --- /dev/null +++ b/internal/transcode/audio_select_test.go @@ -0,0 +1,37 @@ +package transcode + +import ( + "testing" + + allure "github.com/allure-framework/allure-go/commons/gotest" +) + +func TestSelectDefaultAudioStreamIndex(t *testing.T) { + t.Parallel() + + streams := []AudioStream{ + {Index: 0, Language: "jpn", Commentary: false}, + {Index: 1, Language: "eng", Commentary: false}, + {Index: 2, Language: "eng", Commentary: true}, + } + + allure.Test(t, "user language preference wins over track order", func(a *allure.Context) { + t := a.T() + got := SelectDefaultAudioStreamIndex(streams, []string{"eng"}) + if got != 1 { + t.Fatalf("got %d want 1", got) + } + }) + + allure.Test(t, "commentary skipped when non-commentary exists", func(a *allure.Context) { + t := a.T() + onlyCommentary := []AudioStream{ + {Language: "eng", Commentary: true}, + {Language: "jpn", Commentary: false}, + } + got := SelectDefaultAudioStreamIndex(onlyCommentary, nil) + if got != 1 { + t.Fatalf("got %d want 1", got) + } + }) +} diff --git a/internal/transcode/downmix.go b/internal/transcode/downmix.go index a22e563..417ea5a 100644 --- a/internal/transcode/downmix.go +++ b/internal/transcode/downmix.go @@ -1,6 +1,7 @@ package transcode import ( + "fmt" "strings" ) @@ -10,31 +11,62 @@ import ( type DownmixAlgorithm string const ( - // DownmixNone uses ffmpeg's built-in channel matrix (-ac 2 only). No volume boost. + // DownmixNone uses ffmpeg's built-in channel matrix (-ac 2 only). DownmixNone DownmixAlgorithm = "none" // DownmixAC4 uses Jellyfin AC-4 pan filters (ETSI TS 103 190-1 defaults). DownmixAC4 DownmixAlgorithm = "ac4" + // DownmixDave750 uses Jellyfin Dave750 pan filters. + DownmixDave750 DownmixAlgorithm = "dave750" + // DownmixNightmodeDialogue uses Jellyfin NightmodeDialogue pan filters. + DownmixNightmodeDialogue DownmixAlgorithm = "nightmodeDialogue" + // DownmixRFC7845 uses Jellyfin RFC7845 pan filters. + DownmixRFC7845 DownmixAlgorithm = "rfc7845" layoutMono = "mono" layoutStereo = "stereo" + layout21 = "2.1" layout30 = "3.0" + layout40 = "4.0" + layoutQuad = "quad" layout50 = "5.0" layout51 = "5.1" + layout61 = "6.1" layout70 = "7.0" layout71 = "7.1" + + minDownmixBoost = 0.5 + maxDownmixBoost = 3.0 + defaultNoneBoost = 2.0 + defaultCustomPanBoost = 1.0 ) -// AC-4 pan filters copied from Jellyfin -// MediaBrowser.Controller/MediaEncoding/DownMixAlgorithmsHelper.cs (Ac4 entries). +type downmixKey struct { + algo DownmixAlgorithm + layout string +} + +// Pan strings copied from Jellyfin MediaBrowser.Controller/MediaEncoding/DownMixAlgorithmsHelper.cs. // -//nolint:gochecknoglobals // layout→filter map mirrors Jellyfin constants -var ac4PanFilters = map[string]string{ - layout30: "pan=stereo|c0=c0+0.707*c2|c1=c1+0.707*c2", - layout50: "pan=stereo|c0=c0+0.707*c2+0.707*c3|c1=c1+0.707*c2+0.707*c4", - layout51: "pan=stereo|c0=c0+0.707*c2+0.707*c4|c1=c1+0.707*c2+0.707*c5", - layout70: "pan=5.0(side)|c0=c0|c1=c1|c2=c2|c3=0.707*c3+0.707*c5|c4=0.707*c4+0.707*c6," + +//nolint:gochecknoglobals,lll // mirrors upstream AlgorithmFilterStrings verbatim +var downmixPanFilters = map[downmixKey]string{ + {DownmixDave750, layout51}: "pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3", + {DownmixDave750, layout71}: "pan=5.1(side)|c0=c0|c1=c1|c2=c2|c3=c3|c4=0.707*c4+0.707*c6|c5=0.707*c5+0.707*c7," + + "pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3", + {DownmixNightmodeDialogue, layout51}: "pan=stereo|c0=c2+0.30*c0+0.30*c4|c1=c2+0.30*c1+0.30*c5", + {DownmixNightmodeDialogue, layout71}: "pan=5.1(side)|c0=c0|c1=c1|c2=c2|c3=c3|c4=0.707*c4+0.707*c6|c5=0.707*c5+0.707*c7," + + "pan=stereo|c0=c2+0.30*c0+0.30*c4|c1=c2+0.30*c1+0.30*c5", + {DownmixRFC7845, layout30}: "pan=stereo|c0=0.414214*c2+0.585786*c0|c1=0.414214*c2+0.585786*c1", + {DownmixRFC7845, layoutQuad}: "pan=stereo|c0=0.422650*c0+0.366025*c2+0.211325*c3|c1=0.422650*c1+0.366025*c3+0.211325*c2", + {DownmixRFC7845, layout50}: "pan=stereo|c0=0.460186*c2+0.650802*c0+0.563611*c3+0.325401*c4|c1=0.460186*c2+0.650802*c1+0.563611*c4+0.325401*c3", + {DownmixRFC7845, layout51}: "pan=stereo|c0=0.374107*c2+0.529067*c0+0.458186*c4+0.264534*c5+0.374107*c3|c1=0.374107*c2+0.529067*c1+0.458186*c5+0.264534*c4+0.374107*c3", + {DownmixRFC7845, layout61}: "pan=stereo|c0=0.321953*c2+0.455310*c0+0.394310*c5+0.227655*c6+0.278819*c4+0.321953*c3|c1=0.321953*c2+0.455310*c1+0.394310*c6+0.227655*c5+0.278819*c4+0.321953*c3", + {DownmixRFC7845, layout71}: "pan=stereo|c0=0.274804*c2+0.388631*c0+0.336565*c6+0.194316*c7+0.336565*c4+0.194316*c5+0.274804*c3|c1=0.274804*c2+0.388631*c1+0.336565*c7+0.194316*c6+0.336565*c5+0.194316*c4+0.274804*c3", + {DownmixAC4, layout30}: "pan=stereo|c0=c0+0.707*c2|c1=c1+0.707*c2", + {DownmixAC4, layout50}: "pan=stereo|c0=c0+0.707*c2+0.707*c3|c1=c1+0.707*c2+0.707*c4", + {DownmixAC4, layout51}: "pan=stereo|c0=c0+0.707*c2+0.707*c4|c1=c1+0.707*c2+0.707*c5", + {DownmixAC4, layout70}: "pan=5.0(side)|c0=c0|c1=c1|c2=c2|c3=0.707*c3+0.707*c5|c4=0.707*c4+0.707*c6," + "pan=stereo|c0=c0+0.707*c2+0.707*c3|c1=c1+0.707*c2+0.707*c4", - layout71: "pan=5.1(side)|c0=c0|c1=c1|c2=c2|c3=c3|c4=0.707*c4+0.707*c6|c5=0.707*c5+0.707*c7," + + {DownmixAC4, layout71}: "pan=5.1(side)|c0=c0|c1=c1|c2=c2|c3=c3|c4=0.707*c4+0.707*c6|c5=0.707*c5+0.707*c7," + "pan=stereo|c0=c0+0.707*c2+0.707*c4|c1=c1+0.707*c2+0.707*c5", } @@ -42,11 +74,11 @@ var ac4PanFilters = map[string]string{ var channelCountLayouts = map[int]string{ 1: layoutMono, 2: layoutStereo, - 3: "2.1", - 4: "4.0", + 3: layout21, + 4: layout40, 5: layout50, 6: layout51, - 7: "6.1", + 7: layout61, 8: layout71, } @@ -60,14 +92,52 @@ func InferChannelLayout(channels int, layout string) string { return channelCountLayouts[channels] } -// StereoDownmixFilter returns an ffmpeg -af pan string for AC-4, or "" for none / ≤2ch / -// unsupported layouts (caller still applies -ac 2). +// DefaultDownmixBoost returns the Jellyfin-aligned default boost for an algorithm. +func DefaultDownmixBoost(algo DownmixAlgorithm) float64 { + if algo == DownmixNone { + return defaultNoneBoost + } + + return defaultCustomPanBoost +} + +// StereoDownmixFilter returns an ffmpeg pan filter for the algorithm and layout, or "" when +// ffmpeg should use its built-in matrix (-ac 2 only). func StereoDownmixFilter(algo DownmixAlgorithm, channels int, layout string) string { - if algo != DownmixAC4 || channels <= 2 { + if algo == DownmixNone || channels <= 2 { return "" } inferred := InferChannelLayout(channels, layout) - return ac4PanFilters[inferred] + return downmixPanFilters[downmixKey{algo: algo, layout: inferred}] +} + +// ComposeAudioFilter builds the -af chain (pan + volume) for encode/remux audio renditions. +func ComposeAudioFilter(algo DownmixAlgorithm, channels int, layout string, boost float64) string { + if boost <= 0 { + boost = DefaultDownmixBoost(algo) + } + + pan := StereoDownmixFilter(algo, channels, layout) + volume := volumeFilterValue(boost) + if pan == "" && volume == "" { + return "" + } + if pan == "" { + return volume + } + if volume == "" { + return pan + } + + return pan + "," + volume +} + +func volumeFilterValue(boost float64) string { + if boost == 1.0 { + return "" + } + + return fmt.Sprintf("volume=%g", boost) } diff --git a/internal/transcode/downmix_test.go b/internal/transcode/downmix_test.go index bff614e..d75ebe8 100644 --- a/internal/transcode/downmix_test.go +++ b/internal/transcode/downmix_test.go @@ -7,40 +7,55 @@ import ( allure "github.com/allure-framework/allure-go/commons/gotest" ) -func TestStereoDownmixFilter_AC4Layouts(t *testing.T) { +func TestStereoDownmixFilter_JellyfinLayouts(t *testing.T) { t.Parallel() - allure.Test( - t, - "AC-4 pan filters match Jellyfin DownMixAlgorithmsHelper", - func(a *allure.Context) { - t := a.T() - cases := []struct { - channels int - layout string - want string - }{ - {channels: 2, layout: layoutStereo, want: ""}, - {channels: 6, layout: "", want: ac4PanFilters[layout51]}, - {channels: 8, layout: layout71, want: ac4PanFilters[layout71]}, - {channels: 6, layout: layout51, want: ac4PanFilters[layout51]}, - {channels: 3, layout: layout30, want: ac4PanFilters[layout30]}, - } + allure.Test(t, "pan filters match Jellyfin DownMixAlgorithmsHelper", func(a *allure.Context) { + t := a.T() - for _, testCase := range cases { - got := StereoDownmixFilter(DownmixAC4, testCase.channels, testCase.layout) - if got != testCase.want { - t.Fatalf("channels=%d layout=%q: got %q want %q", - testCase.channels, testCase.layout, got, testCase.want) - } - } + cases := []struct { + algo DownmixAlgorithm + channels int + layout string + want string + }{ + {algo: DownmixAC4, channels: 2, layout: layoutStereo, want: ""}, + {algo: DownmixAC4, channels: 6, layout: layout51, want: downmixPanFilters[downmixKey{DownmixAC4, layout51}]}, + {algo: DownmixDave750, channels: 6, layout: layout51, want: downmixPanFilters[downmixKey{DownmixDave750, layout51}]}, + {algo: DownmixRFC7845, channels: 4, layout: layoutQuad, want: downmixPanFilters[downmixKey{DownmixRFC7845, layoutQuad}]}, + {algo: DownmixNone, channels: 6, layout: layout51, want: ""}, + } - if StereoDownmixFilter(DownmixNone, 6, layout51) != "" { - t.Fatal("none must not emit a pan filter") - } - if strings.Contains(StereoDownmixFilter(DownmixNone, 6, layout51), "volume=") { - t.Fatal("none must not boost volume") + for _, testCase := range cases { + got := StereoDownmixFilter(testCase.algo, testCase.channels, testCase.layout) + if got != testCase.want { + t.Fatalf("algo=%s channels=%d layout=%q: got %q want %q", + testCase.algo, testCase.channels, testCase.layout, got, testCase.want) } - }, - ) + } + }) +} + +func TestComposeAudioFilter_BoostAndPan(t *testing.T) { + t.Parallel() + + allure.Test(t, "composeAudioFilter orders pan then volume", func(a *allure.Context) { + t := a.T() + + filter := ComposeAudioFilter(DownmixAC4, 6, layout51, 1.5) + if !strings.Contains(filter, "pan=stereo") { + t.Fatalf("expected pan filter, got %q", filter) + } + if !strings.HasSuffix(filter, "volume=1.5") { + t.Fatalf("expected volume suffix, got %q", filter) + } + + noneOnly := ComposeAudioFilter(DownmixNone, 6, layout51, 2) + if strings.Contains(noneOnly, "pan=") { + t.Fatalf("none must not emit pan: %q", noneOnly) + } + if noneOnly != "volume=2" { + t.Fatalf("none boost: got %q", noneOnly) + } + }) } diff --git a/internal/transcode/ffargs.go b/internal/transcode/ffargs.go index 2f95805..916e07b 100644 --- a/internal/transcode/ffargs.go +++ b/internal/transcode/ffargs.go @@ -14,16 +14,17 @@ const maxRunSegments = 12 // segmentRun describes one on-demand ffmpeg invocation producing a contiguous run of segments. type segmentRun struct { - mediaPath string - variantDir string - meta SourceMeta - variant Variant - startIndex int - encoder VideoEncoder - render string - toneMap ToneMapMode - toneAlgo ToneMappingAlgorithm - downmix DownmixAlgorithm + mediaPath string + variantDir string + meta SourceMeta + variant Variant + startIndex int + encoder VideoEncoder + render string + toneMap ToneMapMode + toneAlgo ToneMappingAlgorithm + downmix DownmixAlgorithm + downmixBoost float64 } // startSeconds is the timeline offset of the first segment this run produces. @@ -151,8 +152,13 @@ func appendAudioRenditionArgs(args []string, run segmentRun) []string { "-vn", "-sn", "-dn", ) - if pan := StereoDownmixFilter(run.downmix, stream.Channels, stream.ChannelLayout); pan != "" { - args = append(args, "-af", pan) + if filter := ComposeAudioFilter( + run.downmix, + stream.Channels, + stream.ChannelLayout, + run.downmixBoost, + ); filter != "" { + args = append(args, "-af", filter) } return append(args, diff --git a/internal/transcode/playback.go b/internal/transcode/playback.go index 759387c..6b0fdec 100644 --- a/internal/transcode/playback.go +++ b/internal/transcode/playback.go @@ -8,8 +8,9 @@ import ( // TrackInfo describes an audio or subtitle rendition exposed to clients. type TrackInfo struct { - ID string `json:"id"` - Label string `json:"label"` + ID string `json:"id"` + Label string `json:"label"` + Default bool `json:"default,omitempty"` } // QualityInfo describes one HLS video variant. @@ -47,6 +48,16 @@ func BuildPlaybackInfoWithSettings( outDir, masterURL string, jobStatus JobStatus, settings TranscodeSettings, +) PlaybackInfo { + return BuildPlaybackInfoWithUserPrefs(outDir, masterURL, jobStatus, settings, nil) +} + +// BuildPlaybackInfoWithUserPrefs applies optional user audio language priority to track defaults. +func BuildPlaybackInfoWithUserPrefs( + outDir, masterURL string, + jobStatus JobStatus, + settings TranscodeSettings, + userLanguages []string, ) PlaybackInfo { info := PlaybackInfo{Status: jobStatus.Status, Error: jobStatus.Error} if jobStatus.Status == StatusReady { @@ -65,7 +76,8 @@ func BuildPlaybackInfoWithSettings( info.DurationSeconds = meta.DurationSeconds info.PackagingMode = meta.PackagingMode info.Qualities = meta.Qualities() - info.AudioTracks = meta.AudioTracks() + defaultAudio := SelectDefaultAudioStreamIndex(meta.AudioStreams, userLanguages) + info.AudioTracks = meta.AudioTracksWithDefault(defaultAudio) info.SubtitleTracks = packagedSubtitleTrackInfos(outDir) info.ToneMapped = meta.NeedsToneMap() && settings.ToneMappingEnabled if meta.Chapters != nil { diff --git a/internal/transcode/playlist_vod.go b/internal/transcode/playlist_vod.go index 385fd47..b6e2f24 100644 --- a/internal/transcode/playlist_vod.go +++ b/internal/transcode/playlist_vod.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "path" + "regexp" "sort" "strconv" "strings" @@ -331,6 +332,42 @@ func escapeAttr(value string) string { return strings.ReplaceAll(strings.TrimSpace(value), `"`, "'") } +// ApplyMasterPlaylistDefaultAudio rewrites DEFAULT= on TYPE=AUDIO lines for user language policy. +func ApplyMasterPlaylistDefaultAudio(playlist []byte, defaultIndex int) []byte { + if defaultIndex <= 0 { + return playlist + } + + lines := strings.Split(string(playlist), "\n") + audioLine := 0 + for index, line := range lines { + if !strings.Contains(line, "TYPE=AUDIO") { + continue + } + + if audioLine == defaultIndex { + lines[index] = replacePlaylistDefaultFlag(line, "YES") + } else { + lines[index] = replacePlaylistDefaultFlag(line, "NO") + } + + audioLine++ + } + + return []byte(strings.Join(lines, "\n")) +} + +func replacePlaylistDefaultFlag(line, value string) string { + if strings.Contains(line, "DEFAULT=") { + return defaultFlagPattern.ReplaceAllString(line, "DEFAULT="+value) + } + + return line +} + +//nolint:gochecknoglobals // compiled once for master playlist rewrite +var defaultFlagPattern = regexp.MustCompile(`DEFAULT=(YES|NO)`) + func bitrateToInt(bitrate string) int { value := strings.TrimSuffix(strings.ToLower(strings.TrimSpace(bitrate)), "k") if value == "" { diff --git a/internal/transcode/probe.go b/internal/transcode/probe.go index e1cbf2f..b97f6da 100644 --- a/internal/transcode/probe.go +++ b/internal/transcode/probe.go @@ -100,6 +100,7 @@ type AudioStream struct { Language string `json:"language,omitempty"` Channels int `json:"channels,omitempty"` ChannelLayout string `json:"channelLayout,omitempty"` + Commentary bool `json:"commentary,omitempty"` } // NeedsToneMap reports whether HDR/wide-gamut/10-bit video must be converted for SDR HLS. @@ -299,6 +300,7 @@ func appendAudioStream(info *SourceInfo, stream probeStream, defaultAudioIndex * Language: strings.TrimSpace(stream.Tags["language"]), Channels: stream.Channels, ChannelLayout: strings.TrimSpace(stream.ChannelLayout), + Commentary: isCommentaryAudioStream(stream), }) } @@ -317,6 +319,14 @@ func promoteDefaultAudioStream(info *SourceInfo, defaultAudioIndex int) { info.AudioStreams = append([]AudioStream{def}, streams...) } +func isCommentaryAudioStream(stream probeStream) bool { + if stream.Disposition == nil { + return false + } + + return stream.Disposition["comment"] == 1 || stream.Disposition["description"] == 1 +} + func audioLabel(stream probeStream) string { if stream.Tags != nil { if title := strings.TrimSpace(stream.Tags["title"]); title != "" { diff --git a/internal/transcode/service.go b/internal/transcode/service.go index 1693398..8c7bcfc 100644 --- a/internal/transcode/service.go +++ b/internal/transcode/service.go @@ -150,12 +150,14 @@ func (s *Service) CacheKey(mediaPath string, mtime int64, size int64) string { tmFlag = "1" } hashData := fmt.Sprintf( - "%s:%d:%d:v24:tm=%s:%s", + "%s:%d:%d:v25:tm=%s:%s:dm=%s:%g", mediaPath, mtime, size, tmFlag, settings.ToneMappingAlgorithm, + settings.DownmixAlgorithm, + settings.DownmixBoost, ) hash := sha256.Sum256([]byte(hashData)) diff --git a/internal/transcode/service_test.go b/internal/transcode/service_test.go index ef44b4f..ebaa232 100644 --- a/internal/transcode/service_test.go +++ b/internal/transcode/service_test.go @@ -338,6 +338,17 @@ func TestCacheKey_IncludesVersionSuffix(t *testing.T) { if key == service.CacheKey("/media/a.mp4", 1, 2) { t.Fatal("expected different key when tonemap algo changes") } + + service.SetSettingsProvider(func() TranscodeSettings { + settings := DefaultTranscodeSettings() + settings.DownmixAlgorithm = DownmixNone + settings.DownmixBoost = 2 + + return settings + }) + if key == service.CacheKey("/media/a.mp4", 1, 2) { + t.Fatal("expected different key when downmix settings change") + } }) } diff --git a/internal/transcode/session.go b/internal/transcode/session.go index 5ca9f9f..2a69a6b 100644 --- a/internal/transcode/session.go +++ b/internal/transcode/session.go @@ -281,16 +281,17 @@ func (s *Service) startSession( meta.NeedsToneMap() && settings.ToneMappingEnabled, ) args := buildSegmentArgs(segmentRun{ - mediaPath: mediaPath, - variantDir: variantDir, - meta: meta, - variant: variant, - startIndex: index, - encoder: encoder, - render: render, - toneMap: toneMap, - toneAlgo: settings.ToneMappingAlgorithm, - downmix: settings.DownmixAlgorithm, + mediaPath: mediaPath, + variantDir: variantDir, + meta: meta, + variant: variant, + startIndex: index, + encoder: encoder, + render: render, + toneMap: toneMap, + toneAlgo: settings.ToneMappingAlgorithm, + downmix: settings.DownmixAlgorithm, + downmixBoost: settings.DownmixBoost, }) cmd := exec.CommandContext(sessionCtx, "ffmpeg", args...) diff --git a/internal/transcode/settings.go b/internal/transcode/settings.go index baa00a8..188785a 100644 --- a/internal/transcode/settings.go +++ b/internal/transcode/settings.go @@ -42,6 +42,8 @@ var ( ErrInvalidHwAccel = errors.New("invalid hwAccel value") // ErrInvalidDownmix is returned when a settings patch has an unknown downmixAlgorithm. ErrInvalidDownmix = errors.New("invalid downmixAlgorithm value") + // ErrInvalidDownmixBoost is returned when downmixBoost is outside 0.5–3.0. + ErrInvalidDownmixBoost = errors.New("invalid downmixBoost value") // ErrInvalidToneMappingAlgorithm is returned for an unknown toneMappingAlgorithm. ErrInvalidToneMappingAlgorithm = errors.New("invalid toneMappingAlgorithm value") // ErrSettingsUnavailable is returned when the settings store is not configured. @@ -55,6 +57,7 @@ var ( type TranscodeSettings struct { HwAccel HwAccel `json:"hwAccel"` DownmixAlgorithm DownmixAlgorithm `json:"downmixAlgorithm"` + DownmixBoost float64 `json:"downmixBoost,omitempty"` ToneMappingEnabled bool `json:"toneMappingEnabled"` ToneMappingAlgorithm ToneMappingAlgorithm `json:"toneMappingAlgorithm"` } @@ -64,6 +67,7 @@ func (s *TranscodeSettings) UnmarshalJSON(data []byte) error { type rawSettings struct { HwAccel HwAccel `json:"hwAccel"` DownmixAlgorithm DownmixAlgorithm `json:"downmixAlgorithm"` + DownmixBoost float64 `json:"downmixBoost"` ToneMappingEnabled *bool `json:"toneMappingEnabled"` ToneMappingAlgorithm ToneMappingAlgorithm `json:"toneMappingAlgorithm"` } @@ -76,6 +80,7 @@ func (s *TranscodeSettings) UnmarshalJSON(data []byte) error { s.HwAccel = raw.HwAccel s.DownmixAlgorithm = raw.DownmixAlgorithm + s.DownmixBoost = raw.DownmixBoost s.ToneMappingAlgorithm = raw.ToneMappingAlgorithm if raw.ToneMappingEnabled == nil { s.ToneMappingEnabled = true @@ -127,11 +132,29 @@ func ParseDownmixAlgorithm(raw string) (DownmixAlgorithm, error) { return DownmixNone, nil case DownmixAC4, "": return DownmixAC4, nil + case DownmixDave750: + return DownmixDave750, nil + case DownmixNightmodeDialogue: + return DownmixNightmodeDialogue, nil + case DownmixRFC7845: + return DownmixRFC7845, nil default: return "", ErrInvalidDownmix } } +// ParseDownmixBoost validates admin downmixBoost (0 means algorithm default). +func ParseDownmixBoost(raw float64, algo DownmixAlgorithm) (float64, error) { + if raw == 0 { + return DefaultDownmixBoost(algo), nil + } + if raw < minDownmixBoost || raw > maxDownmixBoost { + return 0, ErrInvalidDownmixBoost + } + + return raw, nil +} + // ParseToneMappingAlgorithm validates and normalizes a toneMappingAlgorithm string. func ParseToneMappingAlgorithm(raw string) (ToneMappingAlgorithm, error) { switch ToneMappingAlgorithm(strings.ToLower(strings.TrimSpace(raw))) { @@ -160,6 +183,11 @@ func NormalizeTranscodeSettings(settings TranscodeSettings) (TranscodeSettings, return TranscodeSettings{}, err } + boost, err := ParseDownmixBoost(settings.DownmixBoost, downmix) + if err != nil { + return TranscodeSettings{}, err + } + algo, err := ParseToneMappingAlgorithm(string(settings.ToneMappingAlgorithm)) if err != nil { return TranscodeSettings{}, err @@ -168,6 +196,7 @@ func NormalizeTranscodeSettings(settings TranscodeSettings) (TranscodeSettings, return TranscodeSettings{ HwAccel: hwAccel, DownmixAlgorithm: downmix, + DownmixBoost: boost, ToneMappingEnabled: settings.ToneMappingEnabled, ToneMappingAlgorithm: algo, }, nil diff --git a/internal/transcode/settings_test.go b/internal/transcode/settings_test.go index 239e0cd..d9cf9c2 100644 --- a/internal/transcode/settings_test.go +++ b/internal/transcode/settings_test.go @@ -130,9 +130,9 @@ func TestParseDownmixAlgorithm(t *testing.T) { if err != nil || got != DownmixNone { t.Fatalf("none: %q %v", got, err) } - _, err = ParseDownmixAlgorithm("dave750") - if err == nil { - t.Fatal("expected invalid downmix") + got, err = ParseDownmixAlgorithm("dave750") + if err != nil || got != DownmixDave750 { + t.Fatalf("dave750: got %q err=%v", got, err) } }) } diff --git a/internal/transcode/source_meta.go b/internal/transcode/source_meta.go index fe155aa..e987612 100644 --- a/internal/transcode/source_meta.go +++ b/internal/transcode/source_meta.go @@ -57,13 +57,26 @@ func (m SourceMeta) NeedsToneMap() bool { // AudioTracks maps probed audio streams to the client track list. func (m SourceMeta) AudioTracks() []TrackInfo { + return m.AudioTracksWithDefault(0) +} + +// AudioTracksWithDefault marks one track as the default for the player/HLS UI. +func (m SourceMeta) AudioTracksWithDefault(defaultIndex int) []TrackInfo { if len(m.AudioStreams) == 0 { - return []TrackInfo{{ID: "0", Label: defaultAudioTrackLabel}} + return []TrackInfo{{ID: "0", Label: defaultAudioTrackLabel, Default: true}} + } + + if defaultIndex < 0 || defaultIndex >= len(m.AudioStreams) { + defaultIndex = 0 } tracks := make([]TrackInfo, 0, len(m.AudioStreams)) for index, stream := range m.AudioStreams { - tracks = append(tracks, TrackInfo{ID: strconv.Itoa(index), Label: stream.Label}) + tracks = append(tracks, TrackInfo{ + ID: strconv.Itoa(index), + Label: stream.Label, + Default: index == defaultIndex, + }) } return tracks diff --git a/openapi/swagger.json b/openapi/swagger.json index 901819a..068797f 100644 --- a/openapi/swagger.json +++ b/openapi/swagger.json @@ -411,6 +411,18 @@ }, "type": "object" }, + "internal_httpapi.PatchPlaybackPreferencesRequest": { + "properties": { + "audioLanguages": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, "internal_httpapi.PatchProviderSettingsRequest": { "properties": { "allowOverrideUserMetadata": { @@ -459,6 +471,18 @@ }, "type": "object" }, + "internal_httpapi.PlaybackPreferencesResponse": { + "properties": { + "audioLanguages": { + "items": { + "type": "string" + }, + "type": "array", + "uniqueItems": false + } + }, + "type": "object" + }, "internal_httpapi.PlaybackResponse": { "properties": { "audioTracks": { @@ -1878,12 +1902,18 @@ "sudoStream_internal_transcode.DownmixAlgorithm": { "enum": [ "none", - "ac4" + "ac4", + "dave750", + "nightmodeDialogue", + "rfc7845" ], "type": "string", "x-enum-varnames": [ "DownmixNone", - "DownmixAC4" + "DownmixAC4", + "DownmixDave750", + "DownmixNightmodeDialogue", + "DownmixRFC7845" ] }, "sudoStream_internal_transcode.HwAccel": { @@ -1957,6 +1987,9 @@ }, "sudoStream_internal_transcode.TrackInfo": { "properties": { + "default": { + "type": "boolean" + }, "id": { "type": "string" }, @@ -1971,6 +2004,9 @@ "downmixAlgorithm": { "$ref": "#/components/schemas/sudoStream_internal_transcode.DownmixAlgorithm" }, + "downmixBoost": { + "type": "number" + }, "hwAccel": { "$ref": "#/components/schemas/sudoStream_internal_transcode.HwAccel" }, @@ -5764,6 +5800,109 @@ ] } }, + "/api/me/playback-preferences": { + "get": { + "description": "Returns ordered audio language codes for default track selection (max 3).", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.PlaybackPreferencesResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Get playback preferences", + "tags": [ + "me" + ] + }, + "patch": { + "description": "Sets ordered audio language codes for default track selection (max 3).", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.PatchPlaybackPreferencesRequest", + "summary": "body", + "description": "Playback preferences" + } + } + }, + "description": "Playback preferences", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.PlaybackPreferencesResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/internal_httpapi.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Update playback preferences", + "tags": [ + "me" + ] + } + }, "/api/me/stats": { "get": { "responses": { diff --git a/openapi/swagger.yaml b/openapi/swagger.yaml index 8114599..f322624 100644 --- a/openapi/swagger.yaml +++ b/openapi/swagger.yaml @@ -266,6 +266,14 @@ components: type: array uniqueItems: false type: object + internal_httpapi.PatchPlaybackPreferencesRequest: + properties: + audioLanguages: + items: + type: string + type: array + uniqueItems: false + type: object internal_httpapi.PatchProviderSettingsRequest: properties: allowOverrideUserMetadata: @@ -299,6 +307,14 @@ components: transcode: type: boolean type: object + internal_httpapi.PlaybackPreferencesResponse: + properties: + audioLanguages: + items: + type: string + type: array + uniqueItems: false + type: object internal_httpapi.PlaybackResponse: properties: audioTracks: @@ -1250,10 +1266,16 @@ components: enum: - none - ac4 + - dave750 + - nightmodeDialogue + - rfc7845 type: string x-enum-varnames: - DownmixNone - DownmixAC4 + - DownmixDave750 + - DownmixNightmodeDialogue + - DownmixRFC7845 sudoStream_internal_transcode.HwAccel: enum: - "off" @@ -1309,6 +1331,8 @@ components: - ToneMappingMobius sudoStream_internal_transcode.TrackInfo: properties: + default: + type: boolean id: type: string label: @@ -1318,6 +1342,8 @@ components: properties: downmixAlgorithm: $ref: '#/components/schemas/sudoStream_internal_transcode.DownmixAlgorithm' + downmixBoost: + type: number hwAccel: $ref: '#/components/schemas/sudoStream_internal_transcode.HwAccel' toneMappingAlgorithm: @@ -3674,6 +3700,72 @@ paths: summary: List home favorites tags: - home + /api/me/playback-preferences: + get: + description: Returns ordered audio language codes for default track selection + (max 3). + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.PlaybackPreferencesResponse' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.ErrorResponse' + description: Unauthorized + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.ErrorResponse' + description: Internal Server Error + summary: Get playback preferences + tags: + - me + patch: + description: Sets ordered audio language codes for default track selection (max + 3). + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.PatchPlaybackPreferencesRequest' + description: Playback preferences + summary: body + description: Playback preferences + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.PlaybackPreferencesResponse' + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.ErrorResponse' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.ErrorResponse' + description: Unauthorized + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_httpapi.ErrorResponse' + description: Internal Server Error + summary: Update playback preferences + tags: + - me /api/me/stats: get: responses: diff --git a/web/src/client/@tanstack/react-query.gen.ts b/web/src/client/@tanstack/react-query.gen.ts index d55e59b..ac717c3 100644 --- a/web/src/client/@tanstack/react-query.gen.ts +++ b/web/src/client/@tanstack/react-query.gen.ts @@ -3,8 +3,8 @@ import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { client } from '../client.gen'; -import { deleteApiAdminInvitesById, deleteApiAdminLibrariesById, deleteApiAdminLibrariesByIdRoots, deleteApiAdminSessionsById, deleteApiAdminTrash, deleteApiAdminTrashById, deleteApiAdminUsersById, deleteApiAuth2Fa, deleteApiAuthSessionsById, deleteApiMediaByPath, deleteApiUserSubtitleByPath, getApiAdminDlnaSettings, getApiAdminFolderTree, getApiAdminInvites, getApiAdminLibraries, getApiAdminLibrariesByIdMaintenance, getApiAdminLibrariesByIdProviders, getApiAdminMaintenance, getApiAdminMaintenanceRuns, getApiAdminNetworkSettings, getApiAdminSettings, getApiAdminTranscodeSettings, getApiAdminTrash, getApiAdminTrashSettings, getApiAdminUsers, getApiAdminUsersByIdLibraries, getApiAdminUsersByIdSessions, getApiAuthConfirmEmail, getApiAuthMe, getApiAuthSessions, getApiBrowse, getApiBrowseByPath, getApiCatalogByLibrarySlug, getApiCatalogByLibrarySlugShowsByShowKey, getApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodes, getApiDownloadByPath, getApiFavoriteByPath, getApiHealth, getApiLibraries, getApiMeContinue, getApiMeFavorites, getApiMeStats, getApiMetadataByPath, getApiMeUnwatched, getApiMeWatched, getApiPlaybackByPath, getApiPlayByPath, getApiProviderPosterByPath, getApiProviderSubtitleByPath, getApiSearch, getApiStreamByPath, getApiThumbnailByPath, getApiUserSubtitleByPath, getApiWatchByPath, getMetrics, type Options, patchApiAdminDlnaSettings, patchApiAdminLibrariesById, patchApiAdminLibrariesByIdMaintenance, patchApiAdminLibrariesByIdProviders, patchApiAdminMaintenance, patchApiAdminNetworkSettings, patchApiAdminSettings, patchApiAdminTranscodeSettings, patchApiAdminTrashSettings, patchApiAdminUsersById, patchApiFavoriteByPath, patchApiMetadataByPath, patchApiWatchByPath, postApiAdminInvites, postApiAdminInvitesByIdResend, postApiAdminLibraries, postApiAdminLibrariesByIdRoots, postApiAdminLibrariesByIdSync, postApiAdminLibrariesSync, postApiAdminMaintenanceByActionRun, postApiAdminNetworkSettingsTest, postApiAdminTrashByIdRestore, postApiAdminUsers, postApiAdminUsersById2FaReset, postApiAdminUsersByIdResendConfirm, postApiAdminUsersByIdSessionsRevokeAll, postApiAuth2FaConfirm, postApiAuth2FaSetup, postApiAuth2FaVerify, postApiAuthAcceptInvite, postApiAuthChangeEmail, postApiAuthChangePassword, postApiAuthForgotPassword, postApiAuthLogin, postApiAuthLogout, postApiAuthRefresh, postApiAuthResetPassword, postApiAuthSessionsRevokeAll, postApiOauthToken, postApiPlaybackByPath, putApiAdminUsersByIdLibraries, putApiUserSubtitleByPath } from '../sdk.gen'; -import type { DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashResponse, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdResponse, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaResponse, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdResponse, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathResponse, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathResponse, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsResponse, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeResponse, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesResponse, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesResponse, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsResponse, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsResponse, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsResponse, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashResponse, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersResponse, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailResponse, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeResponse, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsResponse, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathResponse, GetApiBrowseData, GetApiBrowseError, GetApiBrowseResponse, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathResponse, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathResponse, GetApiHealthData, GetApiHealthError, GetApiHealthResponse, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesResponse, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueResponse, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesResponse, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsResponse, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathResponse, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedResponse, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedResponse, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathResponse, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathResponse, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathResponse, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathResponse, GetApiSearchData, GetApiSearchError, GetApiSearchResponse, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathResponse, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathResponse, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathResponse, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathResponse, GetMetricsData, GetMetricsResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsResponse, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdResponse, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceResponse, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsResponse, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsResponse, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsResponse, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdResponse, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathResponse, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathResponse, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathResponse, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesResponse, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesResponse, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncResponse, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestResponse, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreResponse, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersResponse, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmResponse, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupResponse, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyResponse, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteResponse, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailResponse, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordResponse, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordResponse, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginResponse, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutResponse, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshResponse, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordResponse, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllResponse, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenResponse, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathResponse, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesResponse, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathResponse } from '../types.gen'; +import { deleteApiAdminInvitesById, deleteApiAdminLibrariesById, deleteApiAdminLibrariesByIdRoots, deleteApiAdminSessionsById, deleteApiAdminTrash, deleteApiAdminTrashById, deleteApiAdminUsersById, deleteApiAuth2Fa, deleteApiAuthSessionsById, deleteApiMediaByPath, deleteApiUserSubtitleByPath, getApiAdminDlnaSettings, getApiAdminFolderTree, getApiAdminInvites, getApiAdminLibraries, getApiAdminLibrariesByIdMaintenance, getApiAdminLibrariesByIdProviders, getApiAdminMaintenance, getApiAdminMaintenanceRuns, getApiAdminNetworkSettings, getApiAdminSettings, getApiAdminTranscodeSettings, getApiAdminTrash, getApiAdminTrashSettings, getApiAdminUsers, getApiAdminUsersByIdLibraries, getApiAdminUsersByIdSessions, getApiAuthConfirmEmail, getApiAuthMe, getApiAuthSessions, getApiBrowse, getApiBrowseByPath, getApiCatalogByLibrarySlug, getApiCatalogByLibrarySlugShowsByShowKey, getApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodes, getApiDownloadByPath, getApiFavoriteByPath, getApiHealth, getApiLibraries, getApiMeContinue, getApiMeFavorites, getApiMePlaybackPreferences, getApiMeStats, getApiMetadataByPath, getApiMeUnwatched, getApiMeWatched, getApiPlaybackByPath, getApiPlayByPath, getApiProviderPosterByPath, getApiProviderSubtitleByPath, getApiSearch, getApiStreamByPath, getApiThumbnailByPath, getApiUserSubtitleByPath, getApiWatchByPath, getMetrics, type Options, patchApiAdminDlnaSettings, patchApiAdminLibrariesById, patchApiAdminLibrariesByIdMaintenance, patchApiAdminLibrariesByIdProviders, patchApiAdminMaintenance, patchApiAdminNetworkSettings, patchApiAdminSettings, patchApiAdminTranscodeSettings, patchApiAdminTrashSettings, patchApiAdminUsersById, patchApiFavoriteByPath, patchApiMePlaybackPreferences, patchApiMetadataByPath, patchApiWatchByPath, postApiAdminInvites, postApiAdminInvitesByIdResend, postApiAdminLibraries, postApiAdminLibrariesByIdRoots, postApiAdminLibrariesByIdSync, postApiAdminLibrariesSync, postApiAdminMaintenanceByActionRun, postApiAdminNetworkSettingsTest, postApiAdminTrashByIdRestore, postApiAdminUsers, postApiAdminUsersById2FaReset, postApiAdminUsersByIdResendConfirm, postApiAdminUsersByIdSessionsRevokeAll, postApiAuth2FaConfirm, postApiAuth2FaSetup, postApiAuth2FaVerify, postApiAuthAcceptInvite, postApiAuthChangeEmail, postApiAuthChangePassword, postApiAuthForgotPassword, postApiAuthLogin, postApiAuthLogout, postApiAuthRefresh, postApiAuthResetPassword, postApiAuthSessionsRevokeAll, postApiOauthToken, postApiPlaybackByPath, putApiAdminUsersByIdLibraries, putApiUserSubtitleByPath } from '../sdk.gen'; +import type { DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashResponse, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdResponse, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaResponse, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdResponse, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathResponse, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathResponse, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsResponse, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeResponse, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesResponse, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesResponse, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsResponse, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsResponse, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsResponse, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashResponse, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersResponse, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailResponse, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeResponse, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsResponse, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathResponse, GetApiBrowseData, GetApiBrowseError, GetApiBrowseResponse, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathResponse, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathResponse, GetApiHealthData, GetApiHealthError, GetApiHealthResponse, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesResponse, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueResponse, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesResponse, GetApiMePlaybackPreferencesData, GetApiMePlaybackPreferencesError, GetApiMePlaybackPreferencesResponse, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsResponse, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathResponse, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedResponse, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedResponse, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathResponse, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathResponse, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathResponse, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathResponse, GetApiSearchData, GetApiSearchError, GetApiSearchResponse, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathResponse, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathResponse, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathResponse, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathResponse, GetMetricsData, GetMetricsResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsResponse, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdResponse, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceResponse, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsResponse, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsResponse, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsResponse, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdResponse, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathResponse, PatchApiMePlaybackPreferencesData, PatchApiMePlaybackPreferencesError, PatchApiMePlaybackPreferencesResponse, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathResponse, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathResponse, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesResponse, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesResponse, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncResponse, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestResponse, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreResponse, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersResponse, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmResponse, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupResponse, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyResponse, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteResponse, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailResponse, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordResponse, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordResponse, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginResponse, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutResponse, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshResponse, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordResponse, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllResponse, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenResponse, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathResponse, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesResponse, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathResponse } from '../types.gen'; export type QueryKey = [ Pick & { @@ -1636,6 +1636,45 @@ export const getApiMeFavoritesInfiniteOptions = (options?: Options; }; +export const getApiMePlaybackPreferencesQueryKey = (options?: Options) => createQueryKey('getApiMePlaybackPreferences', options); + +/** + * Get playback preferences + * + * Returns ordered audio language codes for default track selection (max 3). + */ +export const getApiMePlaybackPreferencesOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiMePlaybackPreferences({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiMePlaybackPreferencesQueryKey(options) +}); + +/** + * Update playback preferences + * + * Sets ordered audio language codes for default track selection (max 3). + */ +export const patchApiMePlaybackPreferencesMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await patchApiMePlaybackPreferences({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getApiMeStatsQueryKey = (options?: Options) => createQueryKey('getApiMeStats', options); /** diff --git a/web/src/client/index.ts b/web/src/client/index.ts index 59b981b..a4c9266 100644 --- a/web/src/client/index.ts +++ b/web/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { deleteApiAdminInvitesById, deleteApiAdminLibrariesById, deleteApiAdminLibrariesByIdRoots, deleteApiAdminSessionsById, deleteApiAdminTrash, deleteApiAdminTrashById, deleteApiAdminUsersById, deleteApiAuth2Fa, deleteApiAuthSessionsById, deleteApiMediaByPath, deleteApiUserSubtitleByPath, getApiAdminDlnaSettings, getApiAdminFolderTree, getApiAdminInvites, getApiAdminLibraries, getApiAdminLibrariesByIdMaintenance, getApiAdminLibrariesByIdProviders, getApiAdminMaintenance, getApiAdminMaintenanceRuns, getApiAdminNetworkSettings, getApiAdminSettings, getApiAdminTranscodeSettings, getApiAdminTrash, getApiAdminTrashSettings, getApiAdminUsers, getApiAdminUsersByIdLibraries, getApiAdminUsersByIdSessions, getApiAuthConfirmEmail, getApiAuthMe, getApiAuthSessions, getApiBrowse, getApiBrowseByPath, getApiCatalogByLibrarySlug, getApiCatalogByLibrarySlugShowsByShowKey, getApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodes, getApiDownloadByPath, getApiFavoriteByPath, getApiHealth, getApiLibraries, getApiMeContinue, getApiMeFavorites, getApiMeStats, getApiMetadataByPath, getApiMeUnwatched, getApiMeWatched, getApiPlaybackByPath, getApiPlayByPath, getApiProviderPosterByPath, getApiProviderSubtitleByPath, getApiSearch, getApiStreamByPath, getApiThumbnailByPath, getApiUserSubtitleByPath, getApiWatchByPath, getMetrics, type Options, patchApiAdminDlnaSettings, patchApiAdminLibrariesById, patchApiAdminLibrariesByIdMaintenance, patchApiAdminLibrariesByIdProviders, patchApiAdminMaintenance, patchApiAdminNetworkSettings, patchApiAdminSettings, patchApiAdminTranscodeSettings, patchApiAdminTrashSettings, patchApiAdminUsersById, patchApiFavoriteByPath, patchApiMetadataByPath, patchApiWatchByPath, postApiAdminInvites, postApiAdminInvitesByIdResend, postApiAdminLibraries, postApiAdminLibrariesByIdRoots, postApiAdminLibrariesByIdSync, postApiAdminLibrariesSync, postApiAdminMaintenanceByActionRun, postApiAdminNetworkSettingsTest, postApiAdminTrashByIdRestore, postApiAdminUsers, postApiAdminUsersById2FaReset, postApiAdminUsersByIdResendConfirm, postApiAdminUsersByIdSessionsRevokeAll, postApiAuth2FaConfirm, postApiAuth2FaSetup, postApiAuth2FaVerify, postApiAuthAcceptInvite, postApiAuthChangeEmail, postApiAuthChangePassword, postApiAuthForgotPassword, postApiAuthLogin, postApiAuthLogout, postApiAuthRefresh, postApiAuthResetPassword, postApiAuthSessionsRevokeAll, postApiOauthToken, postApiPlaybackByPath, putApiAdminUsersByIdLibraries, putApiUserSubtitleByPath } from './sdk.gen'; -export type { ClientOptions, DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponse, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponse, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponse, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponse, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponse, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponse, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponse, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponse, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesErrors, GetApiAdminInvitesResponse, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponse, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponse, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsErrors, GetApiAdminSettingsResponse, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponse, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashErrors, GetApiAdminTrashResponse, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersErrors, GetApiAdminUsersResponse, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponse, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeErrors, GetApiAuthMeResponse, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsErrors, GetApiAuthSessionsResponse, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathErrors, GetApiBrowseByPathResponse, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseError, GetApiBrowseErrors, GetApiBrowseResponse, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathErrors, GetApiDownloadByPathResponse, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponse, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthError, GetApiHealthErrors, GetApiHealthResponse, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesErrors, GetApiLibrariesResponse, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueErrors, GetApiMeContinueResponse, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesErrors, GetApiMeFavoritesResponse, GetApiMeFavoritesResponses, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsErrors, GetApiMeStatsResponse, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathErrors, GetApiMetadataByPathResponse, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponse, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedErrors, GetApiMeWatchedResponse, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponse, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathErrors, GetApiPlayByPathResponse, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponse, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponse, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchError, GetApiSearchErrors, GetApiSearchResponse, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathErrors, GetApiStreamByPathResponse, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponse, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponse, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathErrors, GetApiWatchByPathResponse, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponse, GetMetricsResponses, InternalHttpapiAcceptInviteRequest, InternalHttpapiAddLibraryRootRequest, InternalHttpapiAdminUserResponse, InternalHttpapiAuthTokensResponse, InternalHttpapiChangeEmailRequest, InternalHttpapiChangePasswordRequest, InternalHttpapiCreateInviteRequest, InternalHttpapiCreateLibraryRequest, InternalHttpapiCreateUserRequest, InternalHttpapiDisableTwoFactorRequest, InternalHttpapiErrorResponse, InternalHttpapiFavoritePatchRequest, InternalHttpapiFavoriteResponse, InternalHttpapiFolderTreeEntry, InternalHttpapiFolderTreeResponse, InternalHttpapiForgotPasswordRequest, InternalHttpapiGrantsResponse, InternalHttpapiHealthResponse, InternalHttpapiHomeItemsResponse, InternalHttpapiHomeStatsResponse, InternalHttpapiInvitesResponse, InternalHttpapiLibrariesResponse, InternalHttpapiLibraryResponse, InternalHttpapiLoginRequest, InternalHttpapiLoginResponse, InternalHttpapiMaintenanceRunResponse, InternalHttpapiMaintenanceRunsResponse, InternalHttpapiMaintenanceStatusResponse, InternalHttpapiMeResponse, InternalHttpapiOAuthErrorResponse, InternalHttpapiOAuthTokenResponse, InternalHttpapiPatchLibraryRequest, InternalHttpapiPatchMaintenanceRequest, InternalHttpapiPatchProviderSettingsRequest, InternalHttpapiPlaybackNegotiateRequest, InternalHttpapiPlaybackResponse, InternalHttpapiPlaybackSeriesContext, InternalHttpapiProviderAvailability, InternalHttpapiProviderSettingsResponse, InternalHttpapiProviderSubtitleTrack, InternalHttpapiPutGrantsRequest, InternalHttpapiRefreshResponse, InternalHttpapiResendInviteRequest, InternalHttpapiResetPasswordRequest, InternalHttpapiSearchResponse, InternalHttpapiSessionsResponse, InternalHttpapiStatusResponse, InternalHttpapiTrashListResponse, InternalHttpapiTrashSettingsResponse, InternalHttpapiTwoFactorConfirmRequest, InternalHttpapiTwoFactorSetupRequest, InternalHttpapiUpdateUserRequest, InternalHttpapiUserResponse, InternalHttpapiUsersResponse, InternalHttpapiUserSubtitleTrack, InternalHttpapiVerifyTwoFactorRequest, InternalHttpapiWatchPatchRequest, InternalHttpapiWatchResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponse, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponse, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponse, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponse, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponse, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponse, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponse, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponse, PatchApiFavoriteByPathResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponse, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathErrors, PatchApiWatchByPathResponse, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesErrors, PostApiAdminInvitesResponse, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponse, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponse, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponse, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponse, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersErrors, PostApiAdminUsersResponse, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponse, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponse, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponse, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponse, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponse, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponse, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponse, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginErrors, PostApiAuthLoginResponse, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutErrors, PostApiAuthLogoutResponse, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshErrors, PostApiAuthRefreshResponse, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponse, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponse, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenErrors, PostApiOauthTokenResponse, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponse, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponse, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponse, PutApiUserSubtitleByPathResponses, SudoStreamInternalAccessGrantInput, SudoStreamInternalAccessLibrary, SudoStreamInternalAccessLibraryPermissions, SudoStreamInternalAccessLibraryType, SudoStreamInternalAccessUserGrantView, SudoStreamInternalAuthAdminUser, SudoStreamInternalAuthInviteView, SudoStreamInternalAuthPublicUser, SudoStreamInternalAuthSessionView, SudoStreamInternalAuthSettings, SudoStreamInternalAuthTwoFactorConfirmResult, SudoStreamInternalAuthTwoFactorSetup, SudoStreamInternalCatalogEpisode, SudoStreamInternalCatalogLibraryCatalog, SudoStreamInternalCatalogMovie, SudoStreamInternalCatalogSeasonEpisodes, SudoStreamInternalCatalogSeasonSummary, SudoStreamInternalCatalogShowDetail, SudoStreamInternalCatalogShowSummary, SudoStreamInternalDlnaSettings, SudoStreamInternalHomeshelfLibraryStat, SudoStreamInternalHomeshelfShelfItem, SudoStreamInternalMaintenanceActionStatus, SudoStreamInternalMaintenanceRun, SudoStreamInternalMaintenanceSchedule, SudoStreamInternalMaintenanceScheduleInput, SudoStreamInternalMediafsAction, SudoStreamInternalMediafsBreadcrumb, SudoStreamInternalMediafsBrowseResponse, SudoStreamInternalMediafsItem, SudoStreamInternalMetadataFilenameHint, SudoStreamInternalMetadataJsonValue, SudoStreamInternalMetadataMetadataResponse, SudoStreamInternalMetadataPatchRequest, SudoStreamInternalMetadataPatchTarget, SudoStreamInternalMetadataSearchHit, SudoStreamInternalMetadataSourceFields, SudoStreamInternalMetadataVideoFields, SudoStreamInternalNetworkProbeResult, SudoStreamInternalNetworkSettings, SudoStreamInternalPlaybackDeviceProfile, SudoStreamInternalPlaybackDirectPlayProfile, SudoStreamInternalPlaybackPlayMethod, SudoStreamInternalProviderSettings, SudoStreamInternalSkipsegmentIntro, SudoStreamInternalTranscodeChapterInfo, SudoStreamInternalTranscodeDownmixAlgorithm, SudoStreamInternalTranscodeHwAccel, SudoStreamInternalTranscodePackagingMode, SudoStreamInternalTranscodeQualityInfo, SudoStreamInternalTranscodeStatus, SudoStreamInternalTranscodeToneMappingAlgorithm, SudoStreamInternalTranscodeTrackInfo, SudoStreamInternalTranscodeTranscodeSettings, SudoStreamInternalTrashItem } from './types.gen'; +export { deleteApiAdminInvitesById, deleteApiAdminLibrariesById, deleteApiAdminLibrariesByIdRoots, deleteApiAdminSessionsById, deleteApiAdminTrash, deleteApiAdminTrashById, deleteApiAdminUsersById, deleteApiAuth2Fa, deleteApiAuthSessionsById, deleteApiMediaByPath, deleteApiUserSubtitleByPath, getApiAdminDlnaSettings, getApiAdminFolderTree, getApiAdminInvites, getApiAdminLibraries, getApiAdminLibrariesByIdMaintenance, getApiAdminLibrariesByIdProviders, getApiAdminMaintenance, getApiAdminMaintenanceRuns, getApiAdminNetworkSettings, getApiAdminSettings, getApiAdminTranscodeSettings, getApiAdminTrash, getApiAdminTrashSettings, getApiAdminUsers, getApiAdminUsersByIdLibraries, getApiAdminUsersByIdSessions, getApiAuthConfirmEmail, getApiAuthMe, getApiAuthSessions, getApiBrowse, getApiBrowseByPath, getApiCatalogByLibrarySlug, getApiCatalogByLibrarySlugShowsByShowKey, getApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodes, getApiDownloadByPath, getApiFavoriteByPath, getApiHealth, getApiLibraries, getApiMeContinue, getApiMeFavorites, getApiMePlaybackPreferences, getApiMeStats, getApiMetadataByPath, getApiMeUnwatched, getApiMeWatched, getApiPlaybackByPath, getApiPlayByPath, getApiProviderPosterByPath, getApiProviderSubtitleByPath, getApiSearch, getApiStreamByPath, getApiThumbnailByPath, getApiUserSubtitleByPath, getApiWatchByPath, getMetrics, type Options, patchApiAdminDlnaSettings, patchApiAdminLibrariesById, patchApiAdminLibrariesByIdMaintenance, patchApiAdminLibrariesByIdProviders, patchApiAdminMaintenance, patchApiAdminNetworkSettings, patchApiAdminSettings, patchApiAdminTranscodeSettings, patchApiAdminTrashSettings, patchApiAdminUsersById, patchApiFavoriteByPath, patchApiMePlaybackPreferences, patchApiMetadataByPath, patchApiWatchByPath, postApiAdminInvites, postApiAdminInvitesByIdResend, postApiAdminLibraries, postApiAdminLibrariesByIdRoots, postApiAdminLibrariesByIdSync, postApiAdminLibrariesSync, postApiAdminMaintenanceByActionRun, postApiAdminNetworkSettingsTest, postApiAdminTrashByIdRestore, postApiAdminUsers, postApiAdminUsersById2FaReset, postApiAdminUsersByIdResendConfirm, postApiAdminUsersByIdSessionsRevokeAll, postApiAuth2FaConfirm, postApiAuth2FaSetup, postApiAuth2FaVerify, postApiAuthAcceptInvite, postApiAuthChangeEmail, postApiAuthChangePassword, postApiAuthForgotPassword, postApiAuthLogin, postApiAuthLogout, postApiAuthRefresh, postApiAuthResetPassword, postApiAuthSessionsRevokeAll, postApiOauthToken, postApiPlaybackByPath, putApiAdminUsersByIdLibraries, putApiUserSubtitleByPath } from './sdk.gen'; +export type { ClientOptions, DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdError, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponse, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdError, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponse, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsError, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponse, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdError, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponse, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdError, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponse, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashError, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponse, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdError, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponse, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaError, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponse, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdError, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponse, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathError, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponse, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathError, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponse, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsError, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponse, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeError, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponse, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesError, GetApiAdminInvitesErrors, GetApiAdminInvitesResponse, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceError, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponse, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersError, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponse, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesError, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponse, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceError, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponse, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponse, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsError, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponse, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsError, GetApiAdminSettingsErrors, GetApiAdminSettingsResponse, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsError, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponse, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashError, GetApiAdminTrashErrors, GetApiAdminTrashResponse, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponse, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesError, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponse, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsError, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponse, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersError, GetApiAdminUsersErrors, GetApiAdminUsersResponse, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailError, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponse, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeError, GetApiAuthMeErrors, GetApiAuthMeResponse, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsError, GetApiAuthSessionsErrors, GetApiAuthSessionsResponse, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathError, GetApiBrowseByPathErrors, GetApiBrowseByPathResponse, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseError, GetApiBrowseErrors, GetApiBrowseResponse, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugError, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponse, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyError, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponse, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesError, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponse, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathError, GetApiDownloadByPathErrors, GetApiDownloadByPathResponse, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathError, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponse, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthError, GetApiHealthErrors, GetApiHealthResponse, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesError, GetApiLibrariesErrors, GetApiLibrariesResponse, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueError, GetApiMeContinueErrors, GetApiMeContinueResponse, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesError, GetApiMeFavoritesErrors, GetApiMeFavoritesResponse, GetApiMeFavoritesResponses, GetApiMePlaybackPreferencesData, GetApiMePlaybackPreferencesError, GetApiMePlaybackPreferencesErrors, GetApiMePlaybackPreferencesResponse, GetApiMePlaybackPreferencesResponses, GetApiMeStatsData, GetApiMeStatsError, GetApiMeStatsErrors, GetApiMeStatsResponse, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathError, GetApiMetadataByPathErrors, GetApiMetadataByPathResponse, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedError, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponse, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedError, GetApiMeWatchedErrors, GetApiMeWatchedResponse, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathError, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponse, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathError, GetApiPlayByPathErrors, GetApiPlayByPathResponse, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathError, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponse, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathError, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponse, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchError, GetApiSearchErrors, GetApiSearchResponse, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathError, GetApiStreamByPathErrors, GetApiStreamByPathResponse, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathError, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponse, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathError, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponse, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathError, GetApiWatchByPathErrors, GetApiWatchByPathResponse, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponse, GetMetricsResponses, InternalHttpapiAcceptInviteRequest, InternalHttpapiAddLibraryRootRequest, InternalHttpapiAdminUserResponse, InternalHttpapiAuthTokensResponse, InternalHttpapiChangeEmailRequest, InternalHttpapiChangePasswordRequest, InternalHttpapiCreateInviteRequest, InternalHttpapiCreateLibraryRequest, InternalHttpapiCreateUserRequest, InternalHttpapiDisableTwoFactorRequest, InternalHttpapiErrorResponse, InternalHttpapiFavoritePatchRequest, InternalHttpapiFavoriteResponse, InternalHttpapiFolderTreeEntry, InternalHttpapiFolderTreeResponse, InternalHttpapiForgotPasswordRequest, InternalHttpapiGrantsResponse, InternalHttpapiHealthResponse, InternalHttpapiHomeItemsResponse, InternalHttpapiHomeStatsResponse, InternalHttpapiInvitesResponse, InternalHttpapiLibrariesResponse, InternalHttpapiLibraryResponse, InternalHttpapiLoginRequest, InternalHttpapiLoginResponse, InternalHttpapiMaintenanceRunResponse, InternalHttpapiMaintenanceRunsResponse, InternalHttpapiMaintenanceStatusResponse, InternalHttpapiMeResponse, InternalHttpapiOAuthErrorResponse, InternalHttpapiOAuthTokenResponse, InternalHttpapiPatchLibraryRequest, InternalHttpapiPatchMaintenanceRequest, InternalHttpapiPatchPlaybackPreferencesRequest, InternalHttpapiPatchProviderSettingsRequest, InternalHttpapiPlaybackNegotiateRequest, InternalHttpapiPlaybackPreferencesResponse, InternalHttpapiPlaybackResponse, InternalHttpapiPlaybackSeriesContext, InternalHttpapiProviderAvailability, InternalHttpapiProviderSettingsResponse, InternalHttpapiProviderSubtitleTrack, InternalHttpapiPutGrantsRequest, InternalHttpapiRefreshResponse, InternalHttpapiResendInviteRequest, InternalHttpapiResetPasswordRequest, InternalHttpapiSearchResponse, InternalHttpapiSessionsResponse, InternalHttpapiStatusResponse, InternalHttpapiTrashListResponse, InternalHttpapiTrashSettingsResponse, InternalHttpapiTwoFactorConfirmRequest, InternalHttpapiTwoFactorSetupRequest, InternalHttpapiUpdateUserRequest, InternalHttpapiUserResponse, InternalHttpapiUsersResponse, InternalHttpapiUserSubtitleTrack, InternalHttpapiVerifyTwoFactorRequest, InternalHttpapiWatchPatchRequest, InternalHttpapiWatchResponse, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsError, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponse, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdError, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceError, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponse, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersError, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponse, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponse, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceError, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponse, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsError, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponse, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsError, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponse, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsError, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponse, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsError, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponse, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdError, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponse, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathError, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponse, PatchApiFavoriteByPathResponses, PatchApiMePlaybackPreferencesData, PatchApiMePlaybackPreferencesError, PatchApiMePlaybackPreferencesErrors, PatchApiMePlaybackPreferencesResponse, PatchApiMePlaybackPreferencesResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathError, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponse, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathError, PatchApiWatchByPathErrors, PatchApiWatchByPathResponse, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendError, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponse, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesError, PostApiAdminInvitesErrors, PostApiAdminInvitesResponse, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsError, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponse, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncError, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponse, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesError, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponse, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncError, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponse, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunError, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponse, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestError, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponse, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreError, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponse, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetError, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponse, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmError, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponse, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllError, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponse, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersError, PostApiAdminUsersErrors, PostApiAdminUsersResponse, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmError, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponse, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupError, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponse, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyError, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponse, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteError, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponse, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailError, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponse, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordError, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponse, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordError, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponse, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginError, PostApiAuthLoginErrors, PostApiAuthLoginResponse, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutError, PostApiAuthLogoutErrors, PostApiAuthLogoutResponse, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshError, PostApiAuthRefreshErrors, PostApiAuthRefreshResponse, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordError, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponse, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllError, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponse, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenError, PostApiOauthTokenErrors, PostApiOauthTokenResponse, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathError, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponse, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesError, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponse, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathError, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponse, PutApiUserSubtitleByPathResponses, SudoStreamInternalAccessGrantInput, SudoStreamInternalAccessLibrary, SudoStreamInternalAccessLibraryPermissions, SudoStreamInternalAccessLibraryType, SudoStreamInternalAccessUserGrantView, SudoStreamInternalAuthAdminUser, SudoStreamInternalAuthInviteView, SudoStreamInternalAuthPublicUser, SudoStreamInternalAuthSessionView, SudoStreamInternalAuthSettings, SudoStreamInternalAuthTwoFactorConfirmResult, SudoStreamInternalAuthTwoFactorSetup, SudoStreamInternalCatalogEpisode, SudoStreamInternalCatalogLibraryCatalog, SudoStreamInternalCatalogMovie, SudoStreamInternalCatalogSeasonEpisodes, SudoStreamInternalCatalogSeasonSummary, SudoStreamInternalCatalogShowDetail, SudoStreamInternalCatalogShowSummary, SudoStreamInternalDlnaSettings, SudoStreamInternalHomeshelfLibraryStat, SudoStreamInternalHomeshelfShelfItem, SudoStreamInternalMaintenanceActionStatus, SudoStreamInternalMaintenanceRun, SudoStreamInternalMaintenanceSchedule, SudoStreamInternalMaintenanceScheduleInput, SudoStreamInternalMediafsAction, SudoStreamInternalMediafsBreadcrumb, SudoStreamInternalMediafsBrowseResponse, SudoStreamInternalMediafsItem, SudoStreamInternalMetadataFilenameHint, SudoStreamInternalMetadataJsonValue, SudoStreamInternalMetadataMetadataResponse, SudoStreamInternalMetadataPatchRequest, SudoStreamInternalMetadataPatchTarget, SudoStreamInternalMetadataSearchHit, SudoStreamInternalMetadataSourceFields, SudoStreamInternalMetadataVideoFields, SudoStreamInternalNetworkProbeResult, SudoStreamInternalNetworkSettings, SudoStreamInternalPlaybackDeviceProfile, SudoStreamInternalPlaybackDirectPlayProfile, SudoStreamInternalPlaybackPlayMethod, SudoStreamInternalProviderSettings, SudoStreamInternalSkipsegmentIntro, SudoStreamInternalTranscodeChapterInfo, SudoStreamInternalTranscodeDownmixAlgorithm, SudoStreamInternalTranscodeHwAccel, SudoStreamInternalTranscodePackagingMode, SudoStreamInternalTranscodeQualityInfo, SudoStreamInternalTranscodeStatus, SudoStreamInternalTranscodeToneMappingAlgorithm, SudoStreamInternalTranscodeTrackInfo, SudoStreamInternalTranscodeTranscodeSettings, SudoStreamInternalTrashItem } from './types.gen'; diff --git a/web/src/client/schemas.gen.ts b/web/src/client/schemas.gen.ts index fb234f1..2fce63b 100644 --- a/web/src/client/schemas.gen.ts +++ b/web/src/client/schemas.gen.ts @@ -443,6 +443,19 @@ export const internal_httpapi_PatchMaintenanceRequestSchema = { type: 'object' } as const; +export const internal_httpapi_PatchPlaybackPreferencesRequestSchema = { + properties: { + audioLanguages: { + items: { + type: 'string' + }, + type: 'array', + uniqueItems: false + } + }, + type: 'object' +} as const; + export const internal_httpapi_PatchProviderSettingsRequestSchema = { properties: { allowOverrideUserMetadata: { @@ -493,6 +506,19 @@ export const internal_httpapi_PlaybackNegotiateRequestSchema = { type: 'object' } as const; +export const internal_httpapi_PlaybackPreferencesResponseSchema = { + properties: { + audioLanguages: { + items: { + type: 'string' + }, + type: 'array', + uniqueItems: false + } + }, + type: 'object' +} as const; + export const internal_httpapi_PlaybackResponseSchema = { properties: { audioTracks: { @@ -1981,12 +2007,18 @@ export const sudoStream_internal_transcode_ChapterInfoSchema = { export const sudoStream_internal_transcode_DownmixAlgorithmSchema = { enum: [ 'none', - 'ac4' + 'ac4', + 'dave750', + 'nightmodeDialogue', + 'rfc7845' ], type: 'string', 'x-enum-varnames': [ 'DownmixNone', - 'DownmixAC4' + 'DownmixAC4', + 'DownmixDave750', + 'DownmixNightmodeDialogue', + 'DownmixRFC7845' ] } as const; @@ -2066,6 +2098,9 @@ export const sudoStream_internal_transcode_ToneMappingAlgorithmSchema = { export const sudoStream_internal_transcode_TrackInfoSchema = { properties: { + default: { + type: 'boolean' + }, id: { type: 'string' }, @@ -2081,6 +2116,9 @@ export const sudoStream_internal_transcode_TranscodeSettingsSchema = { downmixAlgorithm: { $ref: '#/components/schemas/sudoStream_internal_transcode.DownmixAlgorithm' }, + downmixBoost: { + type: 'number' + }, hwAccel: { $ref: '#/components/schemas/sudoStream_internal_transcode.HwAccel' }, diff --git a/web/src/client/sdk.gen.ts b/web/src/client/sdk.gen.ts index 4e21c15..3d17f46 100644 --- a/web/src/client/sdk.gen.ts +++ b/web/src/client/sdk.gen.ts @@ -2,7 +2,7 @@ import { type Client, type ClientMeta, formDataBodySerializer, type Options as Options2, type RequestResult, type TDataShape, urlSearchParamsBodySerializer } from './client'; import { client } from './client.gen'; -import type { DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesErrors, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsErrors, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashErrors, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersErrors, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeErrors, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsErrors, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathErrors, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseErrors, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathErrors, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthErrors, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesErrors, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueErrors, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesErrors, GetApiMeFavoritesResponses, GetApiMeStatsData, GetApiMeStatsErrors, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathErrors, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedErrors, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathErrors, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchErrors, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathErrors, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathErrors, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponses, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathErrors, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesErrors, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersErrors, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginErrors, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutErrors, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshErrors, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenErrors, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponses } from './types.gen'; +import type { DeleteApiAdminInvitesByIdData, DeleteApiAdminInvitesByIdErrors, DeleteApiAdminInvitesByIdResponses, DeleteApiAdminLibrariesByIdData, DeleteApiAdminLibrariesByIdErrors, DeleteApiAdminLibrariesByIdResponses, DeleteApiAdminLibrariesByIdRootsData, DeleteApiAdminLibrariesByIdRootsErrors, DeleteApiAdminLibrariesByIdRootsResponses, DeleteApiAdminSessionsByIdData, DeleteApiAdminSessionsByIdErrors, DeleteApiAdminSessionsByIdResponses, DeleteApiAdminTrashByIdData, DeleteApiAdminTrashByIdErrors, DeleteApiAdminTrashByIdResponses, DeleteApiAdminTrashData, DeleteApiAdminTrashErrors, DeleteApiAdminTrashResponses, DeleteApiAdminUsersByIdData, DeleteApiAdminUsersByIdErrors, DeleteApiAdminUsersByIdResponses, DeleteApiAuth2FaData, DeleteApiAuth2FaErrors, DeleteApiAuth2FaResponses, DeleteApiAuthSessionsByIdData, DeleteApiAuthSessionsByIdErrors, DeleteApiAuthSessionsByIdResponses, DeleteApiMediaByPathData, DeleteApiMediaByPathErrors, DeleteApiMediaByPathResponses, DeleteApiUserSubtitleByPathData, DeleteApiUserSubtitleByPathErrors, DeleteApiUserSubtitleByPathResponses, GetApiAdminDlnaSettingsData, GetApiAdminDlnaSettingsErrors, GetApiAdminDlnaSettingsResponses, GetApiAdminFolderTreeData, GetApiAdminFolderTreeErrors, GetApiAdminFolderTreeResponses, GetApiAdminInvitesData, GetApiAdminInvitesErrors, GetApiAdminInvitesResponses, GetApiAdminLibrariesByIdMaintenanceData, GetApiAdminLibrariesByIdMaintenanceErrors, GetApiAdminLibrariesByIdMaintenanceResponses, GetApiAdminLibrariesByIdProvidersData, GetApiAdminLibrariesByIdProvidersErrors, GetApiAdminLibrariesByIdProvidersResponses, GetApiAdminLibrariesData, GetApiAdminLibrariesErrors, GetApiAdminLibrariesResponses, GetApiAdminMaintenanceData, GetApiAdminMaintenanceErrors, GetApiAdminMaintenanceResponses, GetApiAdminMaintenanceRunsData, GetApiAdminMaintenanceRunsResponses, GetApiAdminNetworkSettingsData, GetApiAdminNetworkSettingsErrors, GetApiAdminNetworkSettingsResponses, GetApiAdminSettingsData, GetApiAdminSettingsErrors, GetApiAdminSettingsResponses, GetApiAdminTranscodeSettingsData, GetApiAdminTranscodeSettingsErrors, GetApiAdminTranscodeSettingsResponses, GetApiAdminTrashData, GetApiAdminTrashErrors, GetApiAdminTrashResponses, GetApiAdminTrashSettingsData, GetApiAdminTrashSettingsResponses, GetApiAdminUsersByIdLibrariesData, GetApiAdminUsersByIdLibrariesErrors, GetApiAdminUsersByIdLibrariesResponses, GetApiAdminUsersByIdSessionsData, GetApiAdminUsersByIdSessionsErrors, GetApiAdminUsersByIdSessionsResponses, GetApiAdminUsersData, GetApiAdminUsersErrors, GetApiAdminUsersResponses, GetApiAuthConfirmEmailData, GetApiAuthConfirmEmailErrors, GetApiAuthConfirmEmailResponses, GetApiAuthMeData, GetApiAuthMeErrors, GetApiAuthMeResponses, GetApiAuthSessionsData, GetApiAuthSessionsErrors, GetApiAuthSessionsResponses, GetApiBrowseByPathData, GetApiBrowseByPathErrors, GetApiBrowseByPathResponses, GetApiBrowseData, GetApiBrowseErrors, GetApiBrowseResponses, GetApiCatalogByLibrarySlugData, GetApiCatalogByLibrarySlugErrors, GetApiCatalogByLibrarySlugResponses, GetApiCatalogByLibrarySlugShowsByShowKeyData, GetApiCatalogByLibrarySlugShowsByShowKeyErrors, GetApiCatalogByLibrarySlugShowsByShowKeyResponses, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesData, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesErrors, GetApiCatalogByLibrarySlugShowsByShowKeySeasonsBySeasonEpisodesResponses, GetApiDownloadByPathData, GetApiDownloadByPathErrors, GetApiDownloadByPathResponses, GetApiFavoriteByPathData, GetApiFavoriteByPathErrors, GetApiFavoriteByPathResponses, GetApiHealthData, GetApiHealthErrors, GetApiHealthResponses, GetApiLibrariesData, GetApiLibrariesErrors, GetApiLibrariesResponses, GetApiMeContinueData, GetApiMeContinueErrors, GetApiMeContinueResponses, GetApiMeFavoritesData, GetApiMeFavoritesErrors, GetApiMeFavoritesResponses, GetApiMePlaybackPreferencesData, GetApiMePlaybackPreferencesErrors, GetApiMePlaybackPreferencesResponses, GetApiMeStatsData, GetApiMeStatsErrors, GetApiMeStatsResponses, GetApiMetadataByPathData, GetApiMetadataByPathErrors, GetApiMetadataByPathResponses, GetApiMeUnwatchedData, GetApiMeUnwatchedErrors, GetApiMeUnwatchedResponses, GetApiMeWatchedData, GetApiMeWatchedErrors, GetApiMeWatchedResponses, GetApiPlaybackByPathData, GetApiPlaybackByPathErrors, GetApiPlaybackByPathResponses, GetApiPlayByPathData, GetApiPlayByPathErrors, GetApiPlayByPathResponses, GetApiProviderPosterByPathData, GetApiProviderPosterByPathErrors, GetApiProviderPosterByPathResponses, GetApiProviderSubtitleByPathData, GetApiProviderSubtitleByPathErrors, GetApiProviderSubtitleByPathResponses, GetApiSearchData, GetApiSearchErrors, GetApiSearchResponses, GetApiStreamByPathData, GetApiStreamByPathErrors, GetApiStreamByPathResponses, GetApiThumbnailByPathData, GetApiThumbnailByPathErrors, GetApiThumbnailByPathResponses, GetApiUserSubtitleByPathData, GetApiUserSubtitleByPathErrors, GetApiUserSubtitleByPathResponses, GetApiWatchByPathData, GetApiWatchByPathErrors, GetApiWatchByPathResponses, GetMetricsData, GetMetricsResponses, PatchApiAdminDlnaSettingsData, PatchApiAdminDlnaSettingsErrors, PatchApiAdminDlnaSettingsResponses, PatchApiAdminLibrariesByIdData, PatchApiAdminLibrariesByIdErrors, PatchApiAdminLibrariesByIdMaintenanceData, PatchApiAdminLibrariesByIdMaintenanceErrors, PatchApiAdminLibrariesByIdMaintenanceResponses, PatchApiAdminLibrariesByIdProvidersData, PatchApiAdminLibrariesByIdProvidersErrors, PatchApiAdminLibrariesByIdProvidersResponses, PatchApiAdminLibrariesByIdResponses, PatchApiAdminMaintenanceData, PatchApiAdminMaintenanceErrors, PatchApiAdminMaintenanceResponses, PatchApiAdminNetworkSettingsData, PatchApiAdminNetworkSettingsErrors, PatchApiAdminNetworkSettingsResponses, PatchApiAdminSettingsData, PatchApiAdminSettingsErrors, PatchApiAdminSettingsResponses, PatchApiAdminTranscodeSettingsData, PatchApiAdminTranscodeSettingsErrors, PatchApiAdminTranscodeSettingsResponses, PatchApiAdminTrashSettingsData, PatchApiAdminTrashSettingsErrors, PatchApiAdminTrashSettingsResponses, PatchApiAdminUsersByIdData, PatchApiAdminUsersByIdErrors, PatchApiAdminUsersByIdResponses, PatchApiFavoriteByPathData, PatchApiFavoriteByPathErrors, PatchApiFavoriteByPathResponses, PatchApiMePlaybackPreferencesData, PatchApiMePlaybackPreferencesErrors, PatchApiMePlaybackPreferencesResponses, PatchApiMetadataByPathData, PatchApiMetadataByPathErrors, PatchApiMetadataByPathResponses, PatchApiWatchByPathData, PatchApiWatchByPathErrors, PatchApiWatchByPathResponses, PostApiAdminInvitesByIdResendData, PostApiAdminInvitesByIdResendErrors, PostApiAdminInvitesByIdResendResponses, PostApiAdminInvitesData, PostApiAdminInvitesErrors, PostApiAdminInvitesResponses, PostApiAdminLibrariesByIdRootsData, PostApiAdminLibrariesByIdRootsErrors, PostApiAdminLibrariesByIdRootsResponses, PostApiAdminLibrariesByIdSyncData, PostApiAdminLibrariesByIdSyncErrors, PostApiAdminLibrariesByIdSyncResponses, PostApiAdminLibrariesData, PostApiAdminLibrariesErrors, PostApiAdminLibrariesResponses, PostApiAdminLibrariesSyncData, PostApiAdminLibrariesSyncErrors, PostApiAdminLibrariesSyncResponses, PostApiAdminMaintenanceByActionRunData, PostApiAdminMaintenanceByActionRunErrors, PostApiAdminMaintenanceByActionRunResponses, PostApiAdminNetworkSettingsTestData, PostApiAdminNetworkSettingsTestErrors, PostApiAdminNetworkSettingsTestResponses, PostApiAdminTrashByIdRestoreData, PostApiAdminTrashByIdRestoreErrors, PostApiAdminTrashByIdRestoreResponses, PostApiAdminUsersById2FaResetData, PostApiAdminUsersById2FaResetErrors, PostApiAdminUsersById2FaResetResponses, PostApiAdminUsersByIdResendConfirmData, PostApiAdminUsersByIdResendConfirmErrors, PostApiAdminUsersByIdResendConfirmResponses, PostApiAdminUsersByIdSessionsRevokeAllData, PostApiAdminUsersByIdSessionsRevokeAllErrors, PostApiAdminUsersByIdSessionsRevokeAllResponses, PostApiAdminUsersData, PostApiAdminUsersErrors, PostApiAdminUsersResponses, PostApiAuth2FaConfirmData, PostApiAuth2FaConfirmErrors, PostApiAuth2FaConfirmResponses, PostApiAuth2FaSetupData, PostApiAuth2FaSetupErrors, PostApiAuth2FaSetupResponses, PostApiAuth2FaVerifyData, PostApiAuth2FaVerifyErrors, PostApiAuth2FaVerifyResponses, PostApiAuthAcceptInviteData, PostApiAuthAcceptInviteErrors, PostApiAuthAcceptInviteResponses, PostApiAuthChangeEmailData, PostApiAuthChangeEmailErrors, PostApiAuthChangeEmailResponses, PostApiAuthChangePasswordData, PostApiAuthChangePasswordErrors, PostApiAuthChangePasswordResponses, PostApiAuthForgotPasswordData, PostApiAuthForgotPasswordErrors, PostApiAuthForgotPasswordResponses, PostApiAuthLoginData, PostApiAuthLoginErrors, PostApiAuthLoginResponses, PostApiAuthLogoutData, PostApiAuthLogoutErrors, PostApiAuthLogoutResponses, PostApiAuthRefreshData, PostApiAuthRefreshErrors, PostApiAuthRefreshResponses, PostApiAuthResetPasswordData, PostApiAuthResetPasswordErrors, PostApiAuthResetPasswordResponses, PostApiAuthSessionsRevokeAllData, PostApiAuthSessionsRevokeAllErrors, PostApiAuthSessionsRevokeAllResponses, PostApiOauthTokenData, PostApiOauthTokenErrors, PostApiOauthTokenResponses, PostApiPlaybackByPathData, PostApiPlaybackByPathErrors, PostApiPlaybackByPathResponses, PutApiAdminUsersByIdLibrariesData, PutApiAdminUsersByIdLibrariesErrors, PutApiAdminUsersByIdLibrariesResponses, PutApiUserSubtitleByPathData, PutApiUserSubtitleByPathErrors, PutApiUserSubtitleByPathResponses } from './types.gen'; export type Options = Options2 & { /** @@ -652,6 +652,27 @@ export const getApiMeContinue = (options?: */ export const getApiMeFavorites = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/me/favorites', ...options }); +/** + * Get playback preferences + * + * Returns ordered audio language codes for default track selection (max 3). + */ +export const getApiMePlaybackPreferences = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/me/playback-preferences', ...options }); + +/** + * Update playback preferences + * + * Sets ordered audio language codes for default track selection (max 3). + */ +export const patchApiMePlaybackPreferences = (options: Options): RequestResult => (options.client ?? client).patch({ + url: '/api/me/playback-preferences', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Get home library statistics */ diff --git a/web/src/client/types.gen.ts b/web/src/client/types.gen.ts index efdd292..9b90900 100644 --- a/web/src/client/types.gen.ts +++ b/web/src/client/types.gen.ts @@ -171,6 +171,10 @@ export type InternalHttpapiPatchMaintenanceRequest = { schedules?: Array; }; +export type InternalHttpapiPatchPlaybackPreferencesRequest = { + audioLanguages?: Array; +}; + export type InternalHttpapiPatchProviderSettingsRequest = { allowOverrideUserMetadata?: boolean; metadataApplyMode?: string; @@ -191,6 +195,10 @@ export type InternalHttpapiPlaybackNegotiateRequest = { transcode?: boolean; }; +export type InternalHttpapiPlaybackPreferencesResponse = { + audioLanguages?: Array; +}; + export type InternalHttpapiPlaybackResponse = { audioTracks?: Array; /** @@ -731,7 +739,7 @@ export type SudoStreamInternalTranscodeChapterInfo = { title?: string; }; -export type SudoStreamInternalTranscodeDownmixAlgorithm = 'none' | 'ac4'; +export type SudoStreamInternalTranscodeDownmixAlgorithm = 'none' | 'ac4' | 'dave750' | 'nightmodeDialogue' | 'rfc7845'; export type SudoStreamInternalTranscodeHwAccel = 'off' | 'qsv' | 'vaapi' | 'nvenc' | 'rockchip'; @@ -747,12 +755,14 @@ export type SudoStreamInternalTranscodeStatus = 'idle' | 'ready' | 'processing' export type SudoStreamInternalTranscodeToneMappingAlgorithm = 'bt2390' | 'hable' | 'reinhard' | 'mobius'; export type SudoStreamInternalTranscodeTrackInfo = { + default?: boolean; id?: string; label?: string; }; export type SudoStreamInternalTranscodeTranscodeSettings = { downmixAlgorithm?: SudoStreamInternalTranscodeDownmixAlgorithm; + downmixBoost?: number; hwAccel?: SudoStreamInternalTranscodeHwAccel; toneMappingAlgorithm?: SudoStreamInternalTranscodeToneMappingAlgorithm; toneMappingEnabled?: boolean; @@ -3280,6 +3290,71 @@ export type GetApiMeFavoritesResponses = { export type GetApiMeFavoritesResponse = GetApiMeFavoritesResponses[keyof GetApiMeFavoritesResponses]; +export type GetApiMePlaybackPreferencesData = { + body?: never; + path?: never; + query?: never; + url: '/api/me/playback-preferences'; +}; + +export type GetApiMePlaybackPreferencesErrors = { + /** + * Unauthorized + */ + 401: InternalHttpapiErrorResponse; + /** + * Internal Server Error + */ + 500: InternalHttpapiErrorResponse; +}; + +export type GetApiMePlaybackPreferencesError = GetApiMePlaybackPreferencesErrors[keyof GetApiMePlaybackPreferencesErrors]; + +export type GetApiMePlaybackPreferencesResponses = { + /** + * OK + */ + 200: InternalHttpapiPlaybackPreferencesResponse; +}; + +export type GetApiMePlaybackPreferencesResponse = GetApiMePlaybackPreferencesResponses[keyof GetApiMePlaybackPreferencesResponses]; + +export type PatchApiMePlaybackPreferencesData = { + /** + * Playback preferences + */ + body: InternalHttpapiPatchPlaybackPreferencesRequest; + path?: never; + query?: never; + url: '/api/me/playback-preferences'; +}; + +export type PatchApiMePlaybackPreferencesErrors = { + /** + * Bad Request + */ + 400: InternalHttpapiErrorResponse; + /** + * Unauthorized + */ + 401: InternalHttpapiErrorResponse; + /** + * Internal Server Error + */ + 500: InternalHttpapiErrorResponse; +}; + +export type PatchApiMePlaybackPreferencesError = PatchApiMePlaybackPreferencesErrors[keyof PatchApiMePlaybackPreferencesErrors]; + +export type PatchApiMePlaybackPreferencesResponses = { + /** + * OK + */ + 200: InternalHttpapiPlaybackPreferencesResponse; +}; + +export type PatchApiMePlaybackPreferencesResponse = PatchApiMePlaybackPreferencesResponses[keyof PatchApiMePlaybackPreferencesResponses]; + export type GetApiMeStatsData = { body?: never; path?: never; diff --git a/web/src/client/zod.gen.ts b/web/src/client/zod.gen.ts index 5d545b9..9694e04 100644 --- a/web/src/client/zod.gen.ts +++ b/web/src/client/zod.gen.ts @@ -104,6 +104,10 @@ export const zInternalHttpapiPatchLibraryRequest = z.object({ type: z.string().optional() }); +export const zInternalHttpapiPatchPlaybackPreferencesRequest = z.object({ + audioLanguages: z.array(z.string()).optional() +}); + export const zInternalHttpapiPatchProviderSettingsRequest = z.object({ allowOverrideUserMetadata: z.boolean().optional(), metadataApplyMode: z.string().optional(), @@ -114,6 +118,10 @@ export const zInternalHttpapiPatchProviderSettingsRequest = z.object({ subtitleProvider: z.string().optional() }); +export const zInternalHttpapiPlaybackPreferencesResponse = z.object({ + audioLanguages: z.array(z.string()).optional() +}); + export const zInternalHttpapiPlaybackSeriesContext = z.object({ episode: z.int().optional(), librarySlug: z.string().optional(), @@ -678,7 +686,13 @@ export const zSudoStreamInternalTranscodeChapterInfo = z.object({ title: z.string().optional() }); -export const zSudoStreamInternalTranscodeDownmixAlgorithm = z.enum(['none', 'ac4']); +export const zSudoStreamInternalTranscodeDownmixAlgorithm = z.enum([ + 'none', + 'ac4', + 'dave750', + 'nightmodeDialogue', + 'rfc7845' +]); export const zSudoStreamInternalTranscodeHwAccel = z.enum([ 'off', @@ -710,6 +724,7 @@ export const zSudoStreamInternalTranscodeToneMappingAlgorithm = z.enum([ ]); export const zSudoStreamInternalTranscodeTrackInfo = z.object({ + default: z.boolean().optional(), id: z.string().optional(), label: z.string().optional() }); @@ -736,6 +751,7 @@ export const zInternalHttpapiPlaybackResponse = z.object({ export const zSudoStreamInternalTranscodeTranscodeSettings = z.object({ downmixAlgorithm: zSudoStreamInternalTranscodeDownmixAlgorithm.optional(), + downmixBoost: z.number().optional(), hwAccel: zSudoStreamInternalTranscodeHwAccel.optional(), toneMappingAlgorithm: zSudoStreamInternalTranscodeToneMappingAlgorithm.optional(), toneMappingEnabled: z.boolean().optional() @@ -1455,6 +1471,21 @@ export const zGetApiMeFavoritesQuery = z.object({ */ export const zGetApiMeFavoritesResponse = zInternalHttpapiHomeItemsResponse; +/** + * OK + */ +export const zGetApiMePlaybackPreferencesResponse = zInternalHttpapiPlaybackPreferencesResponse; + +/** + * Playback preferences + */ +export const zPatchApiMePlaybackPreferencesBody = zInternalHttpapiPatchPlaybackPreferencesRequest; + +/** + * OK + */ +export const zPatchApiMePlaybackPreferencesResponse = zInternalHttpapiPlaybackPreferencesResponse; + /** * OK */ diff --git a/web/src/components/account/playback-preferences-section.tsx b/web/src/components/account/playback-preferences-section.tsx new file mode 100644 index 0000000..bc4d8d6 --- /dev/null +++ b/web/src/components/account/playback-preferences-section.tsx @@ -0,0 +1,117 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field"; +import { Input } from "@/components/ui/input"; +import { extractApiErrorMessage } from "@/lib/api-error"; +import { getApiMePlaybackPreferencesOptions } from "@/client/@tanstack/react-query.gen"; +import { patchApiMePlaybackPreferences } from "@/client/sdk.gen"; + +const MAX_LANGS = 3; + +export function PlaybackPreferencesSection() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const prefsQuery = useQuery({ + ...getApiMePlaybackPreferencesOptions(), + }); + + const saved = prefsQuery.data?.audioLanguages ?? []; + const [lang1, setLang1] = useState(""); + const [lang2, setLang2] = useState(""); + const [lang3, setLang3] = useState(""); + const seedKey = saved.join("|"); + const display1 = lang1 || saved[0] || ""; + const display2 = lang2 || saved[1] || ""; + const display3 = lang3 || saved[2] || ""; + + const saveMutation = useMutation({ + mutationFn: async () => { + const audioLanguages = [display1, display2, display3] + .map((value) => value.trim()) + .filter((value) => value.length > 0); + const response = await patchApiMePlaybackPreferences({ + body: { audioLanguages }, + }); + if (response.error || !response.data) { + throw new Error( + extractApiErrorMessage(response.error) ?? t("account.playbackPreferencesSaveFailed"), + ); + } + return response.data; + }, + onSuccess: () => { + setLang1(""); + setLang2(""); + setLang3(""); + void queryClient.invalidateQueries({ + queryKey: getApiMePlaybackPreferencesOptions().queryKey, + }); + }, + }); + + return ( + + + {t("account.playbackPreferences")} + {t("account.playbackPreferencesDescription")} + + +
{ + event.preventDefault(); + saveMutation.reset(); + saveMutation.mutate(); + }} + > + {saveMutation.isError ? ( + + {t("account.playbackPreferencesSaveFailed")} + + {saveMutation.error instanceof Error + ? saveMutation.error.message + : t("common.error")} + + + ) : null} + {saveMutation.isSuccess ? ( + + {t("account.playbackPreferencesSaveSuccess")} + + ) : null} + + {[ + { id: "audio-lang-1", value: display1, set: setLang1 }, + { id: "audio-lang-2", value: display2, set: setLang2 }, + { id: "audio-lang-3", value: display3, set: setLang3 }, + ].map((field, index) => ( + + + {t("account.audioLanguagePriority", { rank: index + 1 })} + + field.set(event.target.value)} + /> + + ))} + + {t("account.playbackPreferencesHint", { max: MAX_LANGS })} + + + +
+
+
+ ); +} diff --git a/web/src/locales/en/translation.json b/web/src/locales/en/translation.json index ca527e2..bec8bda 100644 --- a/web/src/locales/en/translation.json +++ b/web/src/locales/en/translation.json @@ -533,9 +533,14 @@ "downmixAlgorithm": "Stereo downmix", "downmixAlgorithmHelp": "How surround audio is mixed to stereo", "downmixAlgorithmDescription": "Applies only when the source has more than two channels.", - "downmixAlgorithmTooltip": "AC-4 — dialogue-aware surround→stereo (Jellyfin-style; default).\nNone — ffmpeg built-in -ac 2 matrix only (no gain boost).", + "downmixAlgorithmTooltip": "Surround→stereo on HLS/remux encode only — never on Direct Play.\nNone uses ffmpeg -ac 2; custom pans use boost 1.0 by default.", "downmixAc4": "AC-4 (dialogue-aware)", "downmixNone": "None (ffmpeg default)", + "downmixDave750": "Dave750", + "downmixNightmodeDialogue": "Nightmode dialogue", + "downmixRfc7845": "RFC7845", + "downmixBoost": "Downmix volume boost", + "downmixBoostDescription": "0.5–3.0 (leave empty for algorithm default: 2.0 for None, 1.0 for pan algorithms). Applied after pan as volume=.", "toneMappingEnabled": "HDR tone mapping", "toneMappingEnabledHelp": "Apply tone mapping when converting HDR/10-bit to SDR HLS", "toneMappingEnabledDescription": "Off skips the TMO; source still transcodes to 8-bit SDR. Colors may look washed, crushed, or oversaturated.", @@ -543,7 +548,7 @@ "toneMappingAlgorithm": "Tone-mapping algorithm", "toneMappingAlgorithmHelp": "Operator used when tone mapping is enabled", "toneMappingAlgorithmDescription": "Applies only when tone mapping is on and the source needs it.", - "toneMappingAlgorithmTooltip": "bt2390 — ITU-R BT.2390 (default; Jellyfin-aligned).\nhable — filmic Hable.\nreinhard — Reinhard.\nmobius — Mobius.", + "toneMappingAlgorithmTooltip": "bt2390 — ITU-R BT.2390.\nhable — filmic Hable.\nreinhard — Reinhard.\nmobius — Mobius.", "toneAlgoBt2390": "BT.2390", "toneAlgoHable": "Hable", "toneAlgoReinhard": "Reinhard", @@ -584,6 +589,13 @@ "account": { "title": "User", "profile": "Profile", + "playbackPreferences": "Playback", + "playbackPreferencesDescription": "Default audio track for HLS and remux (not Direct Play). Up to three language codes in priority order (e.g. en, ru, jpn).", + "playbackPreferencesHint": "Leave blank to use the file’s default track. Maximum {{max}} languages.", + "playbackPreferencesSaveSuccess": "Playback preferences saved", + "playbackPreferencesSaveFailed": "Could not save playback preferences", + "audioLanguagePriority": "Audio language {{rank}}", + "audioLanguagePlaceholder": "en", "role": "Role", "email": "Email", "changePassword": "Change password", diff --git a/web/src/locales/ru/translation.json b/web/src/locales/ru/translation.json index 1a218e9..dbfd150 100644 --- a/web/src/locales/ru/translation.json +++ b/web/src/locales/ru/translation.json @@ -533,9 +533,14 @@ "downmixAlgorithm": "Стерео downmix", "downmixAlgorithmHelp": "Как многоканальный звук сводится в стерео", "downmixAlgorithmDescription": "Применяется только если у источника больше двух каналов.", - "downmixAlgorithmTooltip": "AC-4 — диалогово-ориентированный surround→stereo (как в Jellyfin; по умолчанию).\nНет — встроенная матрица ffmpeg -ac 2 без усиления.", + "downmixAlgorithmTooltip": "Surround→stereo только при HLS/remux encode — не при Direct Play.\n«Нет» — ffmpeg -ac 2; для pan-алгоритмов по умолчанию boost 1.0.", "downmixAc4": "AC-4 (с учётом диалога)", "downmixNone": "Нет (по умолчанию ffmpeg)", + "downmixDave750": "Dave750", + "downmixNightmodeDialogue": "Nightmode dialogue", + "downmixRfc7845": "RFC7845", + "downmixBoost": "Усиление downmix", + "downmixBoostDescription": "0.5–3.0 (пусто — по умолчанию алгоритма: 2.0 для «Нет», 1.0 для pan). После pan как volume=.", "toneMappingEnabled": "Tone mapping HDR", "toneMappingEnabledHelp": "Применять tone mapping при конвертации HDR/10-bit в SDR HLS", "toneMappingEnabledDescription": "Выкл. пропускает TMO; источник всё равно транскодируется в 8-bit SDR. Цвета могут быть блёклыми, сжатыми или перенасыщенными.", @@ -543,7 +548,7 @@ "toneMappingAlgorithm": "Алгоритм tone mapping", "toneMappingAlgorithmHelp": "Оператор при включённом tone mapping", "toneMappingAlgorithmDescription": "Только если tone mapping включён и источнику он нужен.", - "toneMappingAlgorithmTooltip": "bt2390 — ITU-R BT.2390 (по умолчанию; как в Jellyfin).\nhable — плёночный Hable.\nreinhard — Reinhard.\nmobius — Mobius.", + "toneMappingAlgorithmTooltip": "bt2390 — ITU-R BT.2390.\nhable — плёночный Hable.\nreinhard — Reinhard.\nmobius — Mobius.", "toneAlgoBt2390": "BT.2390", "toneAlgoHable": "Hable", "toneAlgoReinhard": "Reinhard", @@ -591,6 +596,11 @@ "changePasswordTotpDescription": "Введите код из приложения-аутентификатора, чтобы завершить смену пароля.", "changePasswordSuccess": "Пароль обновлён", "changePasswordFailed": "Не удалось сменить пароль", + "playbackPreferences": "Воспроизведение", + "playbackPreferencesDescription": "Дорожка аудио по умолчанию для HLS и remux (не Direct Play). До трёх кодов языка по приоритету (напр. en, ru, jpn).", + "playbackPreferencesHint": "Оставьте пустым — будет дорожка по умолчанию в файле. Не более {{max}} языков.", + "playbackPreferencesSaveSuccess": "Настройки воспроизведения сохранены", + "playbackPreferencesSaveFailed": "Не удалось сохранить настройки воспроизведения", "invalidCurrentPassword": "Текущий пароль неверный.", "changingPassword": "Обновление…", "currentPassword": "Текущий пароль", diff --git a/web/src/pages/admin/settings/index.tsx b/web/src/pages/admin/settings/index.tsx index a6f40ec..5bdc32c 100644 --- a/web/src/pages/admin/settings/index.tsx +++ b/web/src/pages/admin/settings/index.tsx @@ -53,7 +53,7 @@ type DownmixValue = NonNullable; type ToneAlgoValue = NonNullable; const hwAccelOptions: HwAccelValue[] = ["off", "qsv", "vaapi", "nvenc", "rockchip"]; -const downmixOptions: DownmixValue[] = ["none", "ac4"]; +const downmixOptions: DownmixValue[] = ["none", "ac4", "dave750", "nightmodeDialogue", "rfc7845"]; const toneAlgoOptions: ToneAlgoValue[] = ["bt2390", "hable", "reinhard", "mobius"]; export default function AdminSettingsPage() { @@ -80,6 +80,7 @@ export default function AdminSettingsPage() { const [hwAccel, setHwAccel] = useState("off"); const [downmixAlgorithm, setDownmixAlgorithm] = useState("ac4"); + const [downmixBoost, setDownmixBoost] = useState("1"); const [toneMappingEnabled, setToneMappingEnabled] = useState(true); const [toneMappingAlgorithm, setToneMappingAlgorithm] = useState("bt2390"); const [prevTranscode, setPrevTranscode] = useState(transcodeQuery.data); @@ -104,6 +105,9 @@ export default function AdminSettingsPage() { if (transcodeQuery.data?.downmixAlgorithm) { setDownmixAlgorithm(transcodeQuery.data.downmixAlgorithm); } + if (typeof transcodeQuery.data?.downmixBoost === "number") { + setDownmixBoost(String(transcodeQuery.data.downmixBoost)); + } if (typeof transcodeQuery.data?.toneMappingEnabled === "boolean") { setToneMappingEnabled(transcodeQuery.data.toneMappingEnabled); } @@ -175,6 +179,9 @@ export default function AdminSettingsPage() { if (data.downmixAlgorithm) { setDownmixAlgorithm(data.downmixAlgorithm); } + if (typeof data.downmixBoost === "number") { + setDownmixBoost(String(data.downmixBoost)); + } if (typeof data.toneMappingEnabled === "boolean") { setToneMappingEnabled(data.toneMappingEnabled); } @@ -532,9 +539,11 @@ export default function AdminSettingsPage() { onSubmit={(event) => { event.preventDefault(); saveTranscodeMutation.reset(); + const boost = Number.parseFloat(downmixBoost); saveTranscodeMutation.mutate({ hwAccel, downmixAlgorithm, + downmixBoost: Number.isFinite(boost) ? boost : undefined, toneMappingEnabled, toneMappingAlgorithm, }); @@ -630,11 +639,31 @@ export default function AdminSettingsPage() { {t("admin.downmixAc4")} {t("admin.downmixNone")} + {t("admin.downmixDave750")} + + {t("admin.downmixNightmodeDialogue")} + + {t("admin.downmixRfc7845")} {t("admin.downmixAlgorithmDescription")} + + {t("admin.downmixBoost")} + setDownmixBoost(event.target.value)} + /> + {t("admin.downmixBoostDescription")} + +
+ +
From f204d938dc09d778b73d62454450666caa8ed4ab Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Sat, 26 Sep 2026 18:00:35 +0300 Subject: [PATCH 08/11] chore: update deps --- web/package.json | 12 +- web/pnpm-lock.yaml | 463 ++++++++++++++++++++-------------------- web/pnpm-workspace.yaml | 15 +- 3 files changed, 252 insertions(+), 238 deletions(-) diff --git a/web/package.json b/web/package.json index 561315e..c42e3a7 100644 --- a/web/package.json +++ b/web/package.json @@ -19,9 +19,9 @@ "@radix-ui/themes": "^3.3.0", "@tailwindcss/vite": "^4.3.3", "@tanstack/react-query": "^5.103.2", - "@videojs/google-cast": "10.0.0-rc.2", - "@videojs/hlsjs-video": "10.0.0-rc.2", - "@videojs/react": "10.0.0-rc.2", + "@videojs/google-cast": "10.0.0-rc.4", + "@videojs/hlsjs-video": "10.0.0-rc.4", + "@videojs/react": "10.0.0-rc.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "embla-carousel-react": "^8.6.0", @@ -29,7 +29,7 @@ "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.5.0", "ky": "^2.1.0", - "lucide-react": "^1.47.0", + "lucide-react": "^1.48.0", "next-themes": "^0.4.6", "qrcode.react": "^4.2.0", "radix-ui": "^1.6.7", @@ -53,10 +53,10 @@ "@vitejs/plugin-react": "^6.1.1", "oxfmt": "^0.66.0", "oxlint": "^1.85.0", - "oxlint-tsgolint": "^7.0.2002", + "oxlint-tsgolint": "^7.0.2003", "shadcn": "^4.21.0", "typescript": "~7.0.2", - "vite": "^8.3.0" + "vite": "^8.3.1" }, "packageManager": "pnpm@12.4.2" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 2c3f492..363b2bf 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -177,19 +177,19 @@ importers: version: 3.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tailwindcss/vite': specifier: ^4.3.3 - version: 4.3.3(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)) + version: 4.3.3(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0)) '@tanstack/react-query': specifier: ^5.103.2 version: 5.103.2(react@19.3.0) '@videojs/google-cast': - specifier: 10.0.0-rc.2 - version: 10.0.0-rc.2 + specifier: 10.0.0-rc.4 + version: 10.0.0-rc.4 '@videojs/hlsjs-video': - specifier: 10.0.0-rc.2 - version: 10.0.0-rc.2 + specifier: 10.0.0-rc.4 + version: 10.0.0-rc.4 '@videojs/react': - specifier: 10.0.0-rc.2 - version: 10.0.0-rc.2(@videojs/google-cast@10.0.0-rc.2)(@videojs/hlsjs-video@10.0.0-rc.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: 10.0.0-rc.4 + version: 10.0.0-rc.4(@videojs/google-cast@10.0.0-rc.4)(@videojs/hlsjs-video@10.0.0-rc.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -212,8 +212,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 lucide-react: - specifier: ^1.47.0 - version: 1.47.0(react@19.3.0) + specifier: ^1.48.0 + version: 1.48.0(react@19.3.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -259,7 +259,7 @@ importers: version: 0.0.0-next-20260824173136 '@hey-api/vite-plugin': specifier: 0.3.2 - version: 0.3.2(@hey-api/openapi-ts@0.0.0-next-20260824173136)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)) + version: 0.3.2(@hey-api/openapi-ts@0.0.0-next-20260824173136)(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0)) '@types/node': specifier: ^26.6.2 version: 26.6.2 @@ -271,16 +271,16 @@ importers: version: 19.3.0(@types/react@19.3.0) '@vitejs/plugin-react': specifier: ^6.1.1 - version: 6.1.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)) + version: 6.1.1(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0)) oxfmt: specifier: ^0.66.0 version: 0.66.0 oxlint: specifier: ^1.85.0 - version: 1.85.0(oxlint-tsgolint@7.0.2002) + version: 1.85.0(oxlint-tsgolint@7.0.2003) oxlint-tsgolint: - specifier: ^7.0.2002 - version: 7.0.2002 + specifier: ^7.0.2003 + version: 7.0.2003 shadcn: specifier: ^4.21.0 version: 4.21.0(typescript@7.0.2) @@ -288,8 +288,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: ^8.3.0 - version: 8.3.0(@types/node@26.6.2)(jiti@2.7.0) + specifier: ^8.3.1 + version: 8.3.1(@types/node@26.6.2)(jiti@2.7.0) packages: @@ -591,8 +591,8 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} - '@modelcontextprotocol/sdk@1.30.0': - resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} + '@modelcontextprotocol/sdk@1.30.1': + resolution: {integrity: sha512-H2HxLvC3HDNybePJaLdSrU1hhUK5iQw+WvV1b01myFyI7sdVGe1u/IPTE5D9fGCiJDVtgMV/lmFkQXLmQyIFYA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -613,8 +613,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.150.0': - resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} '@oxfmt/binding-android-arm-eabi@0.66.0': resolution: {integrity: sha512-2Me9eoptv6ERdEuI2P8AOlYdHHraXebJaM6SC0kc2Dfb+mLrep2db+fedBPKaYn673h/vBgvP4tkOdAbaudX6w==} @@ -738,33 +738,33 @@ packages: cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@7.0.2002': - resolution: {integrity: sha512-E3yYb/tI+5M3SqGgKwrwUD0nmjA2FMGD1xFbma46qi8Y7+nPTlGY6bZk/hqxijc9Vda/tdtUxQ2d5zGIEX9qlg==} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@7.0.2002': - resolution: {integrity: sha512-qEaMySdvzs8faaJwMO4dFtPFCa+degFCz+HbkFQ0REcIJqJ16KmC0smt30FAI9TeEHCoAtz6sU/6acJvi4uaxg==} + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@7.0.2002': - resolution: {integrity: sha512-bqPObCNIR6tDQSc0Xn9Nu7EfWRXFB+qFzADRl/q6YA8zT9YUg3pGUbmicV2hV91uLpiot74so9ocGjx35SaX6w==} + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@7.0.2002': - resolution: {integrity: sha512-E5CcRALAYdiQMbdYjEqlLOiYuEdt21RtXiyxIoD8bfypdfkpWppacijKOR4i0DtLXFRVwCGQggZBWk5bOIpU1A==} + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@7.0.2002': - resolution: {integrity: sha512-L+ul0ZFDiz/ZmTwCQ1J/qS7S6s45ktiaFqb+ZIzx+1sSdtdRxm8/H0+p4VDQYrCKBFzKNb6VjUlm48bNEe3BUg==} + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@7.0.2002': - resolution: {integrity: sha512-zkZeJfo4UmYiCzj77u85eCDpdxOiQiSAsV6j9Pvj5e7Vu5+MvwLtK1xmVutE77tJmUJ3eBOSXHR6WgY4AwHHGQ==} + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} cpu: [x64] os: [win32] @@ -1599,98 +1599,98 @@ packages: '@remix-run/route-pattern@0.22.1': resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==} - '@rolldown/binding-android-arm-eabi@1.2.9': - resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} + '@rolldown/binding-android-arm-eabi@1.2.11': + resolution: {integrity: sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.9': - resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==} + '@rolldown/binding-android-arm64@1.2.11': + resolution: {integrity: sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.9': - resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==} + '@rolldown/binding-darwin-arm64@1.2.11': + resolution: {integrity: sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.9': - resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==} + '@rolldown/binding-darwin-x64@1.2.11': + resolution: {integrity: sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.9': - resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==} + '@rolldown/binding-freebsd-x64@1.2.11': + resolution: {integrity: sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': - resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + resolution: {integrity: sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.9': - resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==} + '@rolldown/binding-linux-arm64-gnu@1.2.11': + resolution: {integrity: sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.9': - resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==} + '@rolldown/binding-linux-arm64-musl@1.2.11': + resolution: {integrity: sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.9': - resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + resolution: {integrity: sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.9': - resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.11': + resolution: {integrity: sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.9': - resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==} + '@rolldown/binding-linux-x64-gnu@1.2.11': + resolution: {integrity: sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.9': - resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==} + '@rolldown/binding-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.9': - resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==} + '@rolldown/binding-openharmony-arm64@1.2.11': + resolution: {integrity: sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.9': - resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==} + '@rolldown/binding-win32-arm64-msvc@1.2.11': + resolution: {integrity: sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.9': - resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==} + '@rolldown/binding-win32-x64-msvc@1.2.11': + resolution: {integrity: sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1950,38 +1950,38 @@ packages: cpu: [x64] os: [win32] - '@videojs/core@10.0.0-rc.2': - resolution: {integrity: sha512-yr6DA5CXFIrQ22xM5k0fEEoX64znex0NvMO5mcagkg8s5BaD0zOGuhCOGI0IHs+LFx29HZWZeY9Cf3BHpL5m6A==} + '@videojs/core@10.0.0-rc.4': + resolution: {integrity: sha512-wRiGG9c4rgap8X1yb2iWC/lF0GuhGGPkyEv0/bGcbiYFo0vHdXHiqtV/fjn+a+WoMLdgPhJHi77CTVxoP6xkOA==} - '@videojs/google-cast@10.0.0-rc.2': - resolution: {integrity: sha512-6WN9TL5NVygzLbcAK/XIKhEAxXI+Qr+GUbYWrjbNHXQdYaUDWWrvvVjvS59V/mWWAVIwBiiiLdf7Ku31IXE8xA==} + '@videojs/google-cast@10.0.0-rc.4': + resolution: {integrity: sha512-NtkvKtGZPcZApgFIFX/zLUAVgKZc+YxeCUWUubxhQqHS3TqPbRRcwfnyhSSa33PMx+DiUMir6EUklfQjd1JFfg==} - '@videojs/hlsjs-video@10.0.0-rc.2': - resolution: {integrity: sha512-JK5HhvTYASrhrSh7ye+ZFT+vyVAMOQt74M5eMBEZl3S04KdPWa31cGclCuDMbrWxkVlehtjB0A1Xc0XqJMiX7A==} + '@videojs/hlsjs-video@10.0.0-rc.4': + resolution: {integrity: sha512-L0IaPWNuAG5/7FMG2z3ECYg76wYw/zFyvY0lMwAGY69d/Al4q7VKWTGnKZbuxiGDqEG7OmbdNQIqIVaIJA5wmA==} - '@videojs/media@10.0.0-rc.2': - resolution: {integrity: sha512-hMZwCZQCtiPu7a1e2ZY/0ZXTqwPhy2T2VhqB+xQs7OgYx5LaCsVyjfSf7YNvIM8Fe4tTm7t/OJkehTAUrX7uSQ==} + '@videojs/media@10.0.0-rc.4': + resolution: {integrity: sha512-QrBpeF+uLr+RXgbezQSlAfswFKviKiSx4XFfsYeikikJ/y7FvgPLuJzNSJtIZIwvzWMfQEqZ2QBwNOZXeo7pFw==} - '@videojs/native-hls-video@10.0.0-rc.2': - resolution: {integrity: sha512-euVa3CGPdAHa5xPxsEM6L3dz4aD9HzK3AsO+ede4x9bExJlEmWWQVLJQ+CpAQdbotoPMeVTiXI4/4Y3X2HuZag==} + '@videojs/native-hls-video@10.0.0-rc.4': + resolution: {integrity: sha512-/tDI6p5yybuJ+Uksd89xNnZfpwpZ/ZXbhqDm299qPzC5vd4Em212zbabdIyj3Zf5Ca8UDOOB9RmYpcYOPODabg==} - '@videojs/react@10.0.0-rc.2': - resolution: {integrity: sha512-JJepyAnDs8oRWq1XUYk09d3DHfm5CDCUGnKyumq/euyN09SF5NXuAFd5/sFh8MtAXV25gLBL5tmYnLhwtcxazQ==} + '@videojs/react@10.0.0-rc.4': + resolution: {integrity: sha512-4sbU878boll6yMUpD9NHCgdKcHashIRXG5KQ/DdG4cA6Opc17xWFw/rM+H+G6VAJbArgux8nI5/9L8jFxGFkAw==} peerDependencies: - '@videojs/cloudflare-video': 10.0.0-rc.2 - '@videojs/dash-video': 10.0.0-rc.2 - '@videojs/google-cast': 10.0.0-rc.2 - '@videojs/hlsjs-video': 10.0.0-rc.2 - '@videojs/mux-audio': 10.0.0-rc.2 - '@videojs/mux-data': 10.0.0-rc.2 - '@videojs/mux-video': 10.0.0-rc.2 - '@videojs/shaka-video': 10.0.0-rc.2 - '@videojs/spotify-audio': 10.0.0-rc.2 - '@videojs/tiktok-video': 10.0.0-rc.2 - '@videojs/twitch-video': 10.0.0-rc.2 - '@videojs/vimeo-video': 10.0.0-rc.2 - '@videojs/wistia-video': 10.0.0-rc.2 - '@videojs/youtube-video': 10.0.0-rc.2 + '@videojs/cloudflare-video': 10.0.0-rc.4 + '@videojs/dash-video': 10.0.0-rc.4 + '@videojs/google-cast': 10.0.0-rc.4 + '@videojs/hlsjs-video': 10.0.0-rc.4 + '@videojs/mux-audio': 10.0.0-rc.4 + '@videojs/mux-data': 10.0.0-rc.4 + '@videojs/mux-video': 10.0.0-rc.4 + '@videojs/shaka-video': 10.0.0-rc.4 + '@videojs/spotify-audio': 10.0.0-rc.4 + '@videojs/tiktok-video': 10.0.0-rc.4 + '@videojs/twitch-video': 10.0.0-rc.4 + '@videojs/vimeo-video': 10.0.0-rc.4 + '@videojs/wistia-video': 10.0.0-rc.4 + '@videojs/youtube-video': 10.0.0-rc.4 react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -2014,13 +2014,13 @@ packages: '@videojs/youtube-video': optional: true - '@videojs/spf@10.0.0-rc.2': - resolution: {integrity: sha512-CLi/67WE7uluWYc27MxUIiYMYfAQS3erZUzLcQcn0ycuxlOo0+buNl2TgVaitZD99RS1vx0shB1eL5YoAeFTPg==} + '@videojs/spf@10.0.0-rc.4': + resolution: {integrity: sha512-7DDO2bH9bMFB6s9y7GpxRb4rZkzrJ+wVvcyUCdG6npg2Gu1vrwCDdKwic55oQwnp0Jr4HTFx9kC/rpT+CPCtXQ==} - '@videojs/store@10.0.0-rc.2': - resolution: {integrity: sha512-F4Rh8tWFhEODwxdssyk5ucXwEcxga2lR3dSTD0aP2gHPv2/hv5EXavsbICYXyWZnn2dDaHK+4uYJpAJGxv+xZQ==} + '@videojs/store@10.0.0-rc.4': + resolution: {integrity: sha512-Gv6RDSuwPDpbd/Qi5LZOWaW6ENiyn3Wyr382l0W1LLoCJi9PAxl1ZCAsfUdckrZuO7SLIgy9MNbueDpSo2jzHw==} peerDependencies: - '@videojs/element': 10.0.0-rc.2 + '@videojs/element': 10.0.0-rc.4 react: ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@videojs/element': @@ -2028,8 +2028,8 @@ packages: react: optional: true - '@videojs/utils@10.0.0-rc.2': - resolution: {integrity: sha512-4BKDIwxB6HWBp74nKBmP2ubOrPnCicC6CAaJBFRNQOrhFjOOy5wv74DV1ZKmNY4FFwaLxQH710+NEU5S3exGFg==} + '@videojs/utils@10.0.0-rc.4': + resolution: {integrity: sha512-61njRdinjIOveTz2Azzx1HcpyPlkHreGr0Jne9TPOyY6+x2XTljJ89To/flQBbcUSeaQ+9Nce9XJIGSVqZWN2g==} '@vitejs/plugin-react@6.1.1': resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} @@ -2101,8 +2101,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.25: - resolution: {integrity: sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==} + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -2118,8 +2118,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.29.0: - resolution: {integrity: sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==} + browserslist@4.29.1: + resolution: {integrity: sha512-AUdjuRyCNGUYtqpqfTmWyM4fXay8yIQhmLnvYe/THMGfT9B/34X7xQd3ifKxwyNPPpowVBjLb+64BN9Rn1mizw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2151,8 +2151,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001810: - resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001812: + resolution: {integrity: sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==} chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} @@ -2337,8 +2337,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.438: - resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==} + electron-to-chromium@1.5.439: + resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} @@ -2554,8 +2554,8 @@ packages: hls.js@1.6.7: resolution: {integrity: sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==} - hono@4.13.8: - resolution: {integrity: sha512-/Gng7NfoykZl2pjukW5Z6+8Yxm3BPRf86GTbQnt0SbySkvax4fyL4H3HhY1cCpBGmiW9XDRFzRV+CXK2W8QudQ==} + hono@4.13.9: + resolution: {integrity: sha512-7dMkQmZoC4E6F7AtaQSPhlWAdnBti+j7rreMZl8QB4jFiEhP9TWbGWUMi8WYzBCgmgulxuvLQupKqo+Co6Omyg==} engines: {node: '>=16.9.0'} html-parse-stringify@4.0.1: @@ -2918,8 +2918,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@1.47.0: - resolution: {integrity: sha512-o8C23aXpNQypRY73W7fW02EyvWJinEMXgKeGjFoKym0zj3Q73hD97A1IQps1g8C14HTGsOpZL0Am+xjfgFZAbg==} + lucide-react@1.48.0: + resolution: {integrity: sha512-R0CIKY/fXiC6y9xRBADgsK+VW2p/pcTJOMhLZf1T+uG+vJUvyUR02nGOgmIIC7MPkiNK4Ox8QDnbqF8rJYWPZQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3065,8 +3065,8 @@ packages: vite-plus: optional: true - oxlint-tsgolint@7.0.2002: - resolution: {integrity: sha512-rQAj2RAQM57nCwTd3PhtnAkREGW2StqI47tzudNlJF8WwGommM7rVpeP4O1O8/ZiuDZ9f2GnTESzLHGTluwkvQ==} + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} hasBin: true oxlint@1.85.0: @@ -3220,6 +3220,11 @@ packages: rc9@3.1.0: resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} + react-compiler-runtime@1.0.0: + resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental + react-dom@19.3.0: resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: @@ -3318,8 +3323,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.2.9: - resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} + rolldown@1.2.11: + resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3518,8 +3523,8 @@ packages: undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - undici@7.29.1: - resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} engines: {node: '>=20.18.1'} unicorn-magic@0.3.0: @@ -3580,8 +3585,8 @@ packages: resolution: {integrity: sha512-Njrh4U8UODGajoZ44QS2C/BsoEM9DTI/aCqY5swsizb+/ap0FamvnCMcZAxrR5+aoC0ZqkawEfpC/N2SBc+xeA==} engines: {node: '>=18.12.0'} - vite@8.3.0: - resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + vite@8.3.1: + resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3718,7 +3723,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.29.0 + browserslist: 4.29.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -3877,7 +3882,7 @@ snapshots: open: 8.4.2 picomatch: 4.0.7 systeminformation: 5.33.13 - undici: 7.29.1 + undici: 7.30.0 which: 4.0.0 yocto-spinner: 1.2.2 @@ -3950,14 +3955,14 @@ snapshots: '@hey-api/types@0.1.4': {} - '@hey-api/vite-plugin@0.3.2(@hey-api/openapi-ts@0.0.0-next-20260824173136)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))': + '@hey-api/vite-plugin@0.3.2(@hey-api/openapi-ts@0.0.0-next-20260824173136)(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0))': dependencies: '@hey-api/openapi-ts': 0.0.0-next-20260824173136 - vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0) + vite: 8.3.1(@types/node@26.6.2)(jiti@2.7.0) - '@hono/node-server@2.1.1(hono@4.13.8)': + '@hono/node-server@2.1.1(hono@4.13.9)': dependencies: - hono: 4.13.8 + hono: 4.13.9 '@hookform/resolvers@5.9.1(ajv-formats@2.1.1(ajv@8.20.0))(ajv@8.20.0)(react-hook-form@7.88.0(react@19.3.0))(zod@4.6.5)': dependencies: @@ -3991,9 +3996,9 @@ snapshots: '@lukeed/ms@2.0.2': {} - '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.30.1(zod@3.25.76)': dependencies: - '@hono/node-server': 2.1.1(hono@4.13.8) + '@hono/node-server': 2.1.1(hono@4.13.9) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -4003,7 +4008,7 @@ snapshots: eventsource-parser: 3.1.1 express: 5.2.1 express-rate-limit: 8.7.0(express@5.2.1) - hono: 4.13.8 + hono: 4.13.9 jose: 6.2.12 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -4025,7 +4030,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.3 - '@oxc-project/types@0.150.0': {} + '@oxc-project/types@0.151.0': {} '@oxfmt/binding-android-arm-eabi@0.66.0': optional: true @@ -4084,22 +4089,22 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.66.0': optional: true - '@oxlint-tsgolint/darwin-arm64@7.0.2002': + '@oxlint-tsgolint/darwin-arm64@7.0.2003': optional: true - '@oxlint-tsgolint/darwin-x64@7.0.2002': + '@oxlint-tsgolint/darwin-x64@7.0.2003': optional: true - '@oxlint-tsgolint/linux-arm64@7.0.2002': + '@oxlint-tsgolint/linux-arm64@7.0.2003': optional: true - '@oxlint-tsgolint/linux-x64@7.0.2002': + '@oxlint-tsgolint/linux-x64@7.0.2003': optional: true - '@oxlint-tsgolint/win32-arm64@7.0.2002': + '@oxlint-tsgolint/win32-arm64@7.0.2003': optional: true - '@oxlint-tsgolint/win32-x64@7.0.2002': + '@oxlint-tsgolint/win32-x64@7.0.2003': optional: true '@oxlint/binding-android-arm-eabi@1.85.0': @@ -4922,49 +4927,49 @@ snapshots: '@remix-run/route-pattern@0.22.1': {} - '@rolldown/binding-android-arm-eabi@1.2.9': + '@rolldown/binding-android-arm-eabi@1.2.11': optional: true - '@rolldown/binding-android-arm64@1.2.9': + '@rolldown/binding-android-arm64@1.2.11': optional: true - '@rolldown/binding-darwin-arm64@1.2.9': + '@rolldown/binding-darwin-arm64@1.2.11': optional: true - '@rolldown/binding-darwin-x64@1.2.9': + '@rolldown/binding-darwin-x64@1.2.11': optional: true - '@rolldown/binding-freebsd-x64@1.2.9': + '@rolldown/binding-freebsd-x64@1.2.11': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.9': + '@rolldown/binding-linux-arm64-gnu@1.2.11': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.9': + '@rolldown/binding-linux-arm64-musl@1.2.11': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.9': + '@rolldown/binding-linux-ppc64-gnu@1.2.11': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.9': + '@rolldown/binding-linux-s390x-gnu@1.2.11': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.9': + '@rolldown/binding-linux-x64-gnu@1.2.11': optional: true - '@rolldown/binding-linux-x64-musl@1.2.9': + '@rolldown/binding-linux-x64-musl@1.2.11': optional: true - '@rolldown/binding-openharmony-arm64@1.2.9': + '@rolldown/binding-openharmony-arm64@1.2.11': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.9': + '@rolldown/binding-win32-arm64-msvc@1.2.11': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.9': + '@rolldown/binding-win32-x64-msvc@1.2.11': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -5036,12 +5041,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))': + '@tailwindcss/vite@4.3.3(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0) + vite: 8.3.1(@types/node@26.6.2)(jiti@2.7.0) '@tanstack/query-core@5.103.2': {} @@ -5132,70 +5137,72 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@videojs/core@10.0.0-rc.2(react@19.3.0)': + '@videojs/core@10.0.0-rc.4(react@19.3.0)': dependencies: - '@videojs/media': 10.0.0-rc.2 - '@videojs/store': 10.0.0-rc.2(react@19.3.0) - '@videojs/utils': 10.0.0-rc.2 + '@videojs/media': 10.0.0-rc.4 + '@videojs/store': 10.0.0-rc.4(react@19.3.0) + '@videojs/utils': 10.0.0-rc.4 transitivePeerDependencies: - '@videojs/element' - react - '@videojs/google-cast@10.0.0-rc.2': + '@videojs/google-cast@10.0.0-rc.4': dependencies: - '@videojs/media': 10.0.0-rc.2 - '@videojs/utils': 10.0.0-rc.2 + '@videojs/media': 10.0.0-rc.4 + '@videojs/utils': 10.0.0-rc.4 - '@videojs/hlsjs-video@10.0.0-rc.2': + '@videojs/hlsjs-video@10.0.0-rc.4': dependencies: - '@videojs/media': 10.0.0-rc.2 - '@videojs/native-hls-video': 10.0.0-rc.2 - '@videojs/utils': 10.0.0-rc.2 + '@videojs/media': 10.0.0-rc.4 + '@videojs/native-hls-video': 10.0.0-rc.4 + '@videojs/utils': 10.0.0-rc.4 hls.js: 1.6.7 - '@videojs/media@10.0.0-rc.2': + '@videojs/media@10.0.0-rc.4': dependencies: - '@videojs/utils': 10.0.0-rc.2 + '@videojs/utils': 10.0.0-rc.4 - '@videojs/native-hls-video@10.0.0-rc.2': + '@videojs/native-hls-video@10.0.0-rc.4': dependencies: - '@videojs/media': 10.0.0-rc.2 - '@videojs/utils': 10.0.0-rc.2 + '@videojs/media': 10.0.0-rc.4 + '@videojs/utils': 10.0.0-rc.4 - '@videojs/react@10.0.0-rc.2(@videojs/google-cast@10.0.0-rc.2)(@videojs/hlsjs-video@10.0.0-rc.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@videojs/react@10.0.0-rc.4(@videojs/google-cast@10.0.0-rc.4)(@videojs/hlsjs-video@10.0.0-rc.4)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@videojs/core': 10.0.0-rc.2(react@19.3.0) - '@videojs/media': 10.0.0-rc.2 - '@videojs/native-hls-video': 10.0.0-rc.2 - '@videojs/spf': 10.0.0-rc.2 - '@videojs/store': 10.0.0-rc.2(react@19.3.0) - '@videojs/utils': 10.0.0-rc.2 + '@videojs/core': 10.0.0-rc.4(react@19.3.0) + '@videojs/media': 10.0.0-rc.4 + '@videojs/native-hls-video': 10.0.0-rc.4 + '@videojs/spf': 10.0.0-rc.4 + '@videojs/store': 10.0.0-rc.4(react@19.3.0) + '@videojs/utils': 10.0.0-rc.4 react: 19.3.0 + react-compiler-runtime: 1.0.0(react@19.3.0) react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@videojs/google-cast': 10.0.0-rc.2 - '@videojs/hlsjs-video': 10.0.0-rc.2 + '@videojs/google-cast': 10.0.0-rc.4 + '@videojs/hlsjs-video': 10.0.0-rc.4 transitivePeerDependencies: - '@videojs/element' - '@videojs/spf@10.0.0-rc.2': + '@videojs/spf@10.0.0-rc.4': dependencies: - '@videojs/media': 10.0.0-rc.2 - '@videojs/utils': 10.0.0-rc.2 + '@videojs/media': 10.0.0-rc.4 + '@videojs/utils': 10.0.0-rc.4 signal-polyfill: 0.2.2 - '@videojs/store@10.0.0-rc.2(react@19.3.0)': + '@videojs/store@10.0.0-rc.4(react@19.3.0)': dependencies: - '@videojs/utils': 10.0.0-rc.2 + '@videojs/utils': 10.0.0-rc.4 + react-compiler-runtime: 1.0.0(react@19.3.0) optionalDependencies: react: 19.3.0 - '@videojs/utils@10.0.0-rc.2': {} + '@videojs/utils@10.0.0-rc.4': {} - '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))': + '@vitejs/plugin-react@6.1.1(vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0) + vite: 8.3.1(@types/node@26.6.2)(jiti@2.7.0) accepts@2.0.0: dependencies: @@ -5237,7 +5244,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.25: {} + baseline-browser-mapping@2.11.26: {} body-parser@2.3.0: dependencies: @@ -5261,13 +5268,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.29.0: + browserslist@4.29.1: dependencies: - baseline-browser-mapping: 2.11.25 - caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.438 + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001812 + electron-to-chromium: 1.5.439 node-releases: 2.0.57 - update-browserslist-db: 1.3.3(browserslist@4.29.0) + update-browserslist-db: 1.3.3(browserslist@4.29.1) bundle-name@4.1.0: dependencies: @@ -5302,7 +5309,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001812: {} chalk@5.6.2: {} @@ -5438,7 +5445,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.438: {} + electron-to-chromium@1.5.439: {} embla-carousel-react@8.6.0(react@19.3.0): dependencies: @@ -5679,7 +5686,7 @@ snapshots: hls.js@1.6.7: {} - hono@4.13.8: {} + hono@4.13.9: {} html-parse-stringify@4.0.1: {} @@ -5929,7 +5936,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@1.47.0(react@19.3.0): + lucide-react@1.48.0(react@19.3.0): dependencies: react: 19.3.0 @@ -6078,16 +6085,16 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.66.0 '@oxfmt/binding-win32-x64-msvc': 0.66.0 - oxlint-tsgolint@7.0.2002: + oxlint-tsgolint@7.0.2003: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 7.0.2002 - '@oxlint-tsgolint/darwin-x64': 7.0.2002 - '@oxlint-tsgolint/linux-arm64': 7.0.2002 - '@oxlint-tsgolint/linux-x64': 7.0.2002 - '@oxlint-tsgolint/win32-arm64': 7.0.2002 - '@oxlint-tsgolint/win32-x64': 7.0.2002 - - oxlint@1.85.0(oxlint-tsgolint@7.0.2002): + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.85.0(oxlint-tsgolint@7.0.2003): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.85.0 '@oxlint/binding-android-arm64': 1.85.0 @@ -6108,7 +6115,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.85.0 '@oxlint/binding-win32-ia32-msvc': 1.85.0 '@oxlint/binding-win32-x64-msvc': 1.85.0 - oxlint-tsgolint: 7.0.2002 + oxlint-tsgolint: 7.0.2003 p-limit@2.3.0: dependencies: @@ -6284,6 +6291,10 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + react-compiler-runtime@1.0.0(react@19.3.0): + dependencies: + react: 19.3.0 + react-dom@19.3.0(react@19.3.0): dependencies: react: 19.3.0 @@ -6364,26 +6375,26 @@ snapshots: reusify@1.1.0: {} - rolldown@1.2.9: + rolldown@1.2.11: dependencies: - '@oxc-project/types': 0.150.0 + '@oxc-project/types': 0.151.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.9 - '@rolldown/binding-android-arm64': 1.2.9 - '@rolldown/binding-darwin-arm64': 1.2.9 - '@rolldown/binding-darwin-x64': 1.2.9 - '@rolldown/binding-freebsd-x64': 1.2.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.9 - '@rolldown/binding-linux-arm64-gnu': 1.2.9 - '@rolldown/binding-linux-arm64-musl': 1.2.9 - '@rolldown/binding-linux-ppc64-gnu': 1.2.9 - '@rolldown/binding-linux-s390x-gnu': 1.2.9 - '@rolldown/binding-linux-x64-gnu': 1.2.9 - '@rolldown/binding-linux-x64-musl': 1.2.9 - '@rolldown/binding-openharmony-arm64': 1.2.9 - '@rolldown/binding-win32-arm64-msvc': 1.2.9 - '@rolldown/binding-win32-x64-msvc': 1.2.9 + '@rolldown/binding-android-arm-eabi': 1.2.11 + '@rolldown/binding-android-arm64': 1.2.11 + '@rolldown/binding-darwin-arm64': 1.2.11 + '@rolldown/binding-darwin-x64': 1.2.11 + '@rolldown/binding-freebsd-x64': 1.2.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.11 + '@rolldown/binding-linux-arm64-gnu': 1.2.11 + '@rolldown/binding-linux-arm64-musl': 1.2.11 + '@rolldown/binding-linux-ppc64-gnu': 1.2.11 + '@rolldown/binding-linux-s390x-gnu': 1.2.11 + '@rolldown/binding-linux-x64-gnu': 1.2.11 + '@rolldown/binding-linux-x64-musl': 1.2.11 + '@rolldown/binding-openharmony-arm64': 1.2.11 + '@rolldown/binding-win32-arm64-msvc': 1.2.11 + '@rolldown/binding-win32-x64-msvc': 1.2.11 router@2.2.0: dependencies: @@ -6443,9 +6454,9 @@ snapshots: '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7) '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) '@dotenvx/dotenvx': 1.75.1 - '@modelcontextprotocol/sdk': 1.30.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.30.1(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.29.0 + browserslist: 4.29.1 cn: 0.2.6 commander: 14.0.3 cosmiconfig: 9.0.2(typescript@7.0.2) @@ -6467,7 +6478,7 @@ snapshots: stringify-object: 5.0.0 ts-morph: 26.0.0 tsconfig-paths: 4.2.0 - undici: 7.29.1 + undici: 7.30.0 validate-npm-package-name: 7.0.2 zod: 3.25.76 zod-to-json-schema: 3.25.2(zod@3.25.76) @@ -6631,7 +6642,7 @@ snapshots: undici-types@8.9.0: {} - undici@7.29.1: {} + undici@7.30.0: {} unicorn-magic@0.3.0: {} @@ -6639,9 +6650,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.3.3(browserslist@4.29.0): + update-browserslist-db@1.3.3(browserslist@4.29.1): dependencies: - browserslist: 4.29.0 + browserslist: 4.29.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -6672,12 +6683,12 @@ snapshots: verkit@0.3.0: {} - vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0): + vite@8.3.1(@types/node@26.6.2)(jiti@2.7.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 postcss: 8.5.28 - rolldown: 1.2.9 + rolldown: 1.2.11 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.6.2 diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml index 8213178..b497e47 100644 --- a/web/pnpm-workspace.yaml +++ b/web/pnpm-workspace.yaml @@ -1,7 +1,10 @@ minimumReleaseAgeExclude: - - "@videojs/core@10.0.0-beta.28" - - "@videojs/media@10.0.0-beta.28" - - "@videojs/react@10.0.0-beta.28" - - "@videojs/spf@10.0.0-beta.28" - - "@videojs/store@10.0.0-beta.28" - - "@videojs/utils@10.0.0-beta.28" + - '@videojs/core@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/media@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/react@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/spf@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/store@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/utils@10.0.0-beta.28 || 10.0.0-rc.4' + - '@videojs/google-cast@10.0.0-rc.4' + - '@videojs/hlsjs-video@10.0.0-rc.4' + - '@videojs/native-hls-video@10.0.0-rc.4' From b6f2a1ac30e228a389041b330fc305473a6c0c4e Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Sat, 26 Sep 2026 18:35:11 +0300 Subject: [PATCH 09/11] chore: update deps --- web/package.json | 2 +- web/pnpm-lock.yaml | 122 ++++++++++++++++++++-------------------- web/pnpm-workspace.yaml | 18 +++--- 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/web/package.json b/web/package.json index c42e3a7..c1c9891 100644 --- a/web/package.json +++ b/web/package.json @@ -58,5 +58,5 @@ "typescript": "~7.0.2", "vite": "^8.3.1" }, - "packageManager": "pnpm@12.4.2" + "packageManager": "pnpm@12.6.0" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 363b2bf..fce755b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.4.2 - version: 12.4.2 + specifier: 12.6.0 + version: 12.6.0 packages: - '@pnpm/exe.android-arm64@12.4.2': - resolution: {integrity: sha512-E255MbcQ0V1577M2BV0ajWuP7KmTPYA0jqZnk3rK6Lq3kTvZWulMMb7iqRmnLsQbyWTkMEB2CfyM70loVDA8xg==} + '@pnpm/exe.android-arm64@12.6.0': + resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.4.2': - resolution: {integrity: sha512-J1pSeCUwuKxMG70ZzpWn8JzElxiEa8NN/3N0SlZRtU2xbztChaJCKF3HaBNIj9yPfeofWzJo/lwNvDDjraQuZw==} + '@pnpm/exe.android-x64@12.6.0': + resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.4.2': - resolution: {integrity: sha512-A0WDo8iErfZBXgrLseQxw8i8Y9ctUpOEl/Uu+cubnTzpD8tT9ykIB548L8YTM2WD4OS+ZOHSxy8aGZcvKq8PaQ==} + '@pnpm/exe.darwin-arm64@12.6.0': + resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.4.2': - resolution: {integrity: sha512-MSgJdovBWHcb5DEOvfPH9yNi/T5O1Xa4ess+E1ESGo/5yuty7S4JoiIjami+fsNXfnoQMlwsMUqYU4zAvBcNCw==} + '@pnpm/exe.darwin-x64@12.6.0': + resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.4.2': - resolution: {integrity: sha512-h2YumlQSNvgbRPv+RXwABohX65f9bOBZn+jMIt7bFDISZPCzQ+Nvpt6Awbp4ip5PwQgYxbu5iREJ1fHE39Fm8A==} + '@pnpm/exe.freebsd-x64@12.6.0': + resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.4.2': - resolution: {integrity: sha512-LwSEtSEDTv6S51YLs3YvSkPyun/QmfMic1UGICUkPWFu6ByP43RdMlkKvmVkfGhAYCpnxO057vrmyJqtfZrPCA==} + '@pnpm/exe.linux-arm64-musl@12.6.0': + resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.4.2': - resolution: {integrity: sha512-2dSiDXyhx+RTHsewxex8f/jVjqQXWJ2oow4kCVHEWdZKeBpgMxvZ6fHkTAUBJXgqhbKIDHvuNlZBP7gJfUWL5A==} + '@pnpm/exe.linux-arm64@12.6.0': + resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.4.2': - resolution: {integrity: sha512-8Itc+jQk+MTz04LS9D1RcH+VctAWmzM4l1cJQ+Sx7pAJNo7EKHdRMbC0Tok1jyQ7eEHNJZD5EleYneZqWfZM+g==} + '@pnpm/exe.linux-ppc64@12.6.0': + resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.4.2': - resolution: {integrity: sha512-hleOeqhTVpH+z9RVMGnxvU4ZnrkBUClWjCbHD9u6kwyqhGSpevoU1wTGish+CBRhmIgMAy9pAfFpqhbAKOKNfw==} + '@pnpm/exe.linux-riscv64@12.6.0': + resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.4.2': - resolution: {integrity: sha512-LAsQRRdP9aToENR6dtcIJ9l+e1zMYOX0tQcLGpRyLPVBQcYRLlvAPcmDshsiIHQjp05SDa9FI0czX1ZQ1a7a/A==} + '@pnpm/exe.linux-s390x@12.6.0': + resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.4.2': - resolution: {integrity: sha512-kzfzH2/0BWdTABK14Yj5a1xsdkTEQUp2eXEPNakaD9jKL025lq3hyaKHIz/gIZaPDMe/1bFFK/En4ztFlbBJxw==} + '@pnpm/exe.linux-x64-musl@12.6.0': + resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.4.2': - resolution: {integrity: sha512-/pbt0UVTa8NMDhzOWLQRfZ6G9ROKXlJPZtx845BqyWfc7hCrWXhTLBd70yO2y8+E+IWN3oHM1s/JsIQNGk1yvg==} + '@pnpm/exe.linux-x64@12.6.0': + resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.4.2': - resolution: {integrity: sha512-PsW19e4dAUNpZ0cS9flaxFuAmpt2dKlH/Vvi8TZ4qyJjjQzue/CEsWm+6wKVP4CJ7IT8RdL4qh+soOPWIgF2Zw==} + '@pnpm/exe.win32-arm64@12.6.0': + resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.4.2': - resolution: {integrity: sha512-+xGoeE0g55ztWvl8i5QqdmNfW3nIrTVcoQrNshEOwxthm9Ag48oXton3uxOA3/SANyz5AXexEjj2KO20NnOrEw==} + '@pnpm/exe.win32-x64@12.6.0': + resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} cpu: [x64] os: [win32] - pnpm@12.4.2: - resolution: {integrity: sha512-CK3GYTGAJ1x8ntraOdzwjJxhrU5+rzMKTzRh8QKw+QdCNFTRF/mOctR/7wYWBwZE17/8lzpqV/UJCm18NosHyQ==} + pnpm@12.6.0: + resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.4.2': + '@pnpm/exe.android-arm64@12.6.0': optional: true - '@pnpm/exe.android-x64@12.4.2': + '@pnpm/exe.android-x64@12.6.0': optional: true - '@pnpm/exe.darwin-arm64@12.4.2': + '@pnpm/exe.darwin-arm64@12.6.0': optional: true - '@pnpm/exe.darwin-x64@12.4.2': + '@pnpm/exe.darwin-x64@12.6.0': optional: true - '@pnpm/exe.freebsd-x64@12.4.2': + '@pnpm/exe.freebsd-x64@12.6.0': optional: true - '@pnpm/exe.linux-arm64-musl@12.4.2': + '@pnpm/exe.linux-arm64-musl@12.6.0': optional: true - '@pnpm/exe.linux-arm64@12.4.2': + '@pnpm/exe.linux-arm64@12.6.0': optional: true - '@pnpm/exe.linux-ppc64@12.4.2': + '@pnpm/exe.linux-ppc64@12.6.0': optional: true - '@pnpm/exe.linux-riscv64@12.4.2': + '@pnpm/exe.linux-riscv64@12.6.0': optional: true - '@pnpm/exe.linux-s390x@12.4.2': + '@pnpm/exe.linux-s390x@12.6.0': optional: true - '@pnpm/exe.linux-x64-musl@12.4.2': + '@pnpm/exe.linux-x64-musl@12.6.0': optional: true - '@pnpm/exe.linux-x64@12.4.2': + '@pnpm/exe.linux-x64@12.6.0': optional: true - '@pnpm/exe.win32-arm64@12.4.2': + '@pnpm/exe.win32-arm64@12.6.0': optional: true - '@pnpm/exe.win32-x64@12.4.2': + '@pnpm/exe.win32-x64@12.6.0': optional: true - pnpm@12.4.2: + pnpm@12.6.0: optionalDependencies: - '@pnpm/exe.android-arm64': 12.4.2 - '@pnpm/exe.android-x64': 12.4.2 - '@pnpm/exe.darwin-arm64': 12.4.2 - '@pnpm/exe.darwin-x64': 12.4.2 - '@pnpm/exe.freebsd-x64': 12.4.2 - '@pnpm/exe.linux-arm64': 12.4.2 - '@pnpm/exe.linux-arm64-musl': 12.4.2 - '@pnpm/exe.linux-ppc64': 12.4.2 - '@pnpm/exe.linux-riscv64': 12.4.2 - '@pnpm/exe.linux-s390x': 12.4.2 - '@pnpm/exe.linux-x64': 12.4.2 - '@pnpm/exe.linux-x64-musl': 12.4.2 - '@pnpm/exe.win32-arm64': 12.4.2 - '@pnpm/exe.win32-x64': 12.4.2 + '@pnpm/exe.android-arm64': 12.6.0 + '@pnpm/exe.android-x64': 12.6.0 + '@pnpm/exe.darwin-arm64': 12.6.0 + '@pnpm/exe.darwin-x64': 12.6.0 + '@pnpm/exe.freebsd-x64': 12.6.0 + '@pnpm/exe.linux-arm64': 12.6.0 + '@pnpm/exe.linux-arm64-musl': 12.6.0 + '@pnpm/exe.linux-ppc64': 12.6.0 + '@pnpm/exe.linux-riscv64': 12.6.0 + '@pnpm/exe.linux-s390x': 12.6.0 + '@pnpm/exe.linux-x64': 12.6.0 + '@pnpm/exe.linux-x64-musl': 12.6.0 + '@pnpm/exe.win32-arm64': 12.6.0 + '@pnpm/exe.win32-x64': 12.6.0 --- lockfileVersion: '9.0' diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml index b497e47..c672141 100644 --- a/web/pnpm-workspace.yaml +++ b/web/pnpm-workspace.yaml @@ -1,10 +1,10 @@ minimumReleaseAgeExclude: - - '@videojs/core@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/media@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/react@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/spf@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/store@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/utils@10.0.0-beta.28 || 10.0.0-rc.4' - - '@videojs/google-cast@10.0.0-rc.4' - - '@videojs/hlsjs-video@10.0.0-rc.4' - - '@videojs/native-hls-video@10.0.0-rc.4' + - "@videojs/core@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/media@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/react@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/spf@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/store@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/utils@10.0.0-beta.28 || 10.0.0-rc.4" + - "@videojs/google-cast@10.0.0-rc.4" + - "@videojs/hlsjs-video@10.0.0-rc.4" + - "@videojs/native-hls-video@10.0.0-rc.4" From 2b826c60d00d03c6b0bb4983ca5ceb2df7de8d3f Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:31:32 +0300 Subject: [PATCH 10/11] chore: update codecov config --- codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 667dbed..13fafa2 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,7 +9,8 @@ coverage: threshold: 1% patch: default: - target: 70% + target: 65% + threshold: 1% comment: layout: "condensed_header, diff, flags, tree, condensed_footer" require_changes: true From e45e231ed520d204f2206994ffc1275f9cfbd12c Mon Sep 17 00:00:00 2001 From: Fedor S <55900130+fed1337@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:24:52 +0300 Subject: [PATCH 11/11] fix: nightmodeDialogue parsing --- .../auth/playback_prefs_integration_test.go | 57 +++++ internal/auth/playback_prefs_test.go | 42 ++++ internal/auth/postgres/store_test.go | 68 +++++ internal/httpapi/me_playback_handlers_test.go | 233 ++++++++++++++++++ internal/transcode/audio_select_test.go | 16 ++ internal/transcode/playlist_vod_test.go | 26 ++ internal/transcode/settings.go | 12 +- internal/transcode/settings_test.go | 40 +++ 8 files changed, 488 insertions(+), 6 deletions(-) create mode 100644 internal/auth/playback_prefs_integration_test.go create mode 100644 internal/httpapi/me_playback_handlers_test.go diff --git a/internal/auth/playback_prefs_integration_test.go b/internal/auth/playback_prefs_integration_test.go new file mode 100644 index 0000000..39c7e30 --- /dev/null +++ b/internal/auth/playback_prefs_integration_test.go @@ -0,0 +1,57 @@ +package auth_test + +import ( + "context" + "errors" + "sudoStream/internal/auth" + "testing" + + allure "github.com/allure-framework/allure-go/commons/gotest" +) + +func TestService_PlaybackPreferences(t *testing.T) { + allure.Test(t, "playback prefs round-trip via auth service", func(a *allure.Context) { + t := a.T() + ctx := context.Background() + service, _ := newAuthIntegration(ctx, t) + admin := seedAdminUser(ctx, t, service) + + empty, err := service.PlaybackAudioLanguages(ctx, admin.ID) + if err != nil { + t.Fatalf("get empty: %v", err) + } + if len(empty) != 0 { + t.Fatalf("expected no langs, got %v", empty) + } + + saved, err := service.UpdatePlaybackPreferences(ctx, admin.ID, auth.PlaybackPreferences{ + AudioLanguages: []string{" EN ", "ru"}, + }) + if err != nil { + t.Fatalf("update: %v", err) + } + if len(saved.AudioLanguages) != 2 || saved.AudioLanguages[0] != "en" { + t.Fatalf("saved: %+v", saved.AudioLanguages) + } + + langs, err := service.PlaybackAudioLanguages(ctx, admin.ID) + if err != nil { + t.Fatalf("reload: %v", err) + } + if len(langs) != 2 || langs[1] != "ru" { + t.Fatalf("reload: %v", langs) + } + + _, err = service.UpdatePlaybackPreferences(ctx, admin.ID, auth.PlaybackPreferences{ + AudioLanguages: []string{"en", "ru", "de", "fr"}, + }) + if !errors.Is(err, auth.ErrInvalidAudioLanguagePrefs) { + t.Fatalf("expected ErrInvalidAudioLanguagePrefs, got %v", err) + } + + _, err = service.PlaybackAudioLanguages(ctx, "00000000-0000-0000-0000-000000000099") + if err == nil { + t.Fatal("expected error for missing user") + } + }) +} diff --git a/internal/auth/playback_prefs_test.go b/internal/auth/playback_prefs_test.go index db70c0d..e9256bc 100644 --- a/internal/auth/playback_prefs_test.go +++ b/internal/auth/playback_prefs_test.go @@ -1,6 +1,8 @@ package auth import ( + "context" + "errors" "testing" allure "github.com/allure-framework/allure-go/commons/gotest" @@ -31,4 +33,44 @@ func TestNormalizePlaybackPreferences(t *testing.T) { t.Fatal("expected error") } }) + + allure.Test(t, "empty list normalizes to empty slice", func(a *allure.Context) { + t := a.T() + got, err := NormalizePlaybackPreferences(PlaybackPreferences{ + AudioLanguages: []string{" ", ""}, + }) + if err != nil { + t.Fatalf("normalize: %v", err) + } + if len(got.AudioLanguages) != 0 { + t.Fatalf("got %+v", got.AudioLanguages) + } + }) +} + +func TestPlaybackPreferences_StoreUnavailable(t *testing.T) { + t.Parallel() + + allure.Test(t, "update fails when auth store is unset", func(a *allure.Context) { + t := a.T() + service := &Service{} + _, err := service.UpdatePlaybackPreferences(context.Background(), "user-id", PlaybackPreferences{ + AudioLanguages: []string{"en"}, + }) + if !errors.Is(err, errPlaybackStoreUnavailable) { + t.Fatalf("expected errPlaybackStoreUnavailable, got %v", err) + } + }) + + allure.Test(t, "playback languages empty when store is unset", func(a *allure.Context) { + t := a.T() + service := &Service{} + langs, err := service.PlaybackAudioLanguages(context.Background(), "user-id") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if langs != nil { + t.Fatalf("expected nil langs, got %v", langs) + } + }) } diff --git a/internal/auth/postgres/store_test.go b/internal/auth/postgres/store_test.go index d1d69d0..de2bb00 100644 --- a/internal/auth/postgres/store_test.go +++ b/internal/auth/postgres/store_test.go @@ -200,3 +200,71 @@ func uniqueAuthStoreSchema() string { return "auth_store_it_" + hex.EncodeToString(buf) } + +func TestStore_PlaybackPreferences(t *testing.T) { + if os.Getenv("SUDOSTREAM_DATABASE_URL") == "" { + t.Skip("SUDOSTREAM_DATABASE_URL not set") + } + + allure.Test(t, "postgres store persists audio language prefs on user", func(a *allure.Context) { + t := a.T() + ctx := context.Background() + database := openAuthStoreDatabase(ctx, t) + store := authpostgres.NewStore(database.GORM) + + err := store.CreateUser(ctx, auth.User{ + Email: "playback-prefs@example.com", + Role: auth.RoleUser, + Enabled: true, + MustChangePassword: false, + }, "hash") + if err != nil { + t.Fatalf("create user: %v", err) + } + + user, _, err := store.GetUserByEmail(ctx, "playback-prefs@example.com") + if err != nil { + t.Fatalf("get user: %v", err) + } + + empty, err := store.GetPlaybackPreferences(ctx, user.ID) + if err != nil { + t.Fatalf("get empty prefs: %v", err) + } + if len(empty.AudioLanguages) != 0 { + t.Fatalf("expected empty prefs, got %v", empty.AudioLanguages) + } + + want := auth.PlaybackPreferences{AudioLanguages: []string{"en", "ru"}} + err = store.SavePlaybackPreferences(ctx, user.ID, want) + if err != nil { + t.Fatalf("save prefs: %v", err) + } + + got, err := store.GetPlaybackPreferences(ctx, user.ID) + if err != nil { + t.Fatalf("reload prefs: %v", err) + } + if len(got.AudioLanguages) != 2 || got.AudioLanguages[1] != "ru" { + t.Fatalf("got %+v", got.AudioLanguages) + } + + _, err = database.SQLDB().ExecContext( + ctx, + `UPDATE users SET audio_language_prefs = $1::jsonb WHERE id = $2`, + `{"not":"array"}`, + user.ID, + ) + if err != nil { + t.Fatalf("corrupt prefs column: %v", err) + } + + corrupt, err := store.GetPlaybackPreferences(ctx, user.ID) + if err != nil { + t.Fatalf("get corrupt prefs: %v", err) + } + if len(corrupt.AudioLanguages) != 0 { + t.Fatalf("invalid JSON should yield empty prefs, got %v", corrupt.AudioLanguages) + } + }) +} diff --git a/internal/httpapi/me_playback_handlers_test.go b/internal/httpapi/me_playback_handlers_test.go new file mode 100644 index 0000000..d216550 --- /dev/null +++ b/internal/httpapi/me_playback_handlers_test.go @@ -0,0 +1,233 @@ +package httpapi + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "sudoStream/internal/auth" + "testing" + + allure "github.com/allure-framework/allure-go/commons/gotest" + "github.com/gin-gonic/gin" +) + +func TestPlaybackPreferencesHandlers_Unauthorized(t *testing.T) { + t.Parallel() + + allure.Test(t, "playback prefs return 401 without login", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + router := newAuthTestRouter(t) + + getRecorder := httptest.NewRecorder() + getRequest := httptest.NewRequestWithContext( + context.Background(), + http.MethodGet, + "/api/me/playback-preferences", + nil, + ) + router.ServeHTTP(getRecorder, getRequest) + if getRecorder.Code != http.StatusUnauthorized { + t.Fatalf("GET: got %d", getRecorder.Code) + } + + patchRecorder := httptest.NewRecorder() + patchRequest := httptest.NewRequestWithContext( + context.Background(), + http.MethodPatch, + "/api/me/playback-preferences", + bytes.NewReader([]byte(`{"audioLanguages":["en"]}`)), + ) + patchRequest.Header.Set("Content-Type", "application/json") + router.ServeHTTP(patchRecorder, patchRequest) + if patchRecorder.Code != http.StatusUnauthorized { + t.Fatalf("PATCH: got %d", patchRecorder.Code) + } + }) +} + +func TestPlaybackPreferencesHandlers_GetPatchRoundTrip(t *testing.T) { + allure.Test(t, "GET and PATCH /api/me/playback-preferences persist languages", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + ctx := context.Background() + router := newAuthTestRouter(t) + token := adminAccessToken(ctx, t, router) + + getRecorder := httptest.NewRecorder() + getRequest := httptest.NewRequestWithContext( + ctx, + http.MethodGet, + "/api/me/playback-preferences", + nil, + ) + setBearerAuth(getRequest, token) + router.ServeHTTP(getRecorder, getRequest) + if getRecorder.Code != http.StatusOK { + t.Fatalf("GET empty: %d %s", getRecorder.Code, getRecorder.Body.String()) + } + + var empty PlaybackPreferencesResponse + err := json.NewDecoder(getRecorder.Body).Decode(&empty) + if err != nil { + t.Fatalf("decode GET: %v", err) + } + if empty.AudioLanguages == nil || len(empty.AudioLanguages) != 0 { + t.Fatalf("expected empty slice, got %v", empty.AudioLanguages) + } + + patchBody := []byte(`{"audioLanguages":["jpn","en"]}`) + patchRecorder := httptest.NewRecorder() + patchRequest := httptest.NewRequestWithContext( + ctx, + http.MethodPatch, + "/api/me/playback-preferences", + bytes.NewReader(patchBody), + ) + patchRequest.Header.Set("Content-Type", "application/json") + setBearerAuth(patchRequest, token) + router.ServeHTTP(patchRecorder, patchRequest) + if patchRecorder.Code != http.StatusOK { + t.Fatalf("PATCH: %d %s", patchRecorder.Code, patchRecorder.Body.String()) + } + + var saved PlaybackPreferencesResponse + err = json.NewDecoder(patchRecorder.Body).Decode(&saved) + if err != nil { + t.Fatalf("decode PATCH: %v", err) + } + if len(saved.AudioLanguages) != 2 || saved.AudioLanguages[0] != "jpn" { + t.Fatalf("saved: %+v", saved.AudioLanguages) + } + + getRecorder = httptest.NewRecorder() + getRequest = httptest.NewRequestWithContext( + ctx, + http.MethodGet, + "/api/me/playback-preferences", + nil, + ) + setBearerAuth(getRequest, token) + router.ServeHTTP(getRecorder, getRequest) + if getRecorder.Code != http.StatusOK { + t.Fatalf("GET after patch: %d", getRecorder.Code) + } + + err = json.NewDecoder(getRecorder.Body).Decode(&empty) + if err != nil { + t.Fatalf("decode GET after patch: %v", err) + } + if len(empty.AudioLanguages) != 2 || empty.AudioLanguages[1] != "en" { + t.Fatalf("reloaded: %+v", empty.AudioLanguages) + } + }) +} + +func TestPlaybackPreferencesHandlers_Validation(t *testing.T) { + allure.Test(t, "PATCH rejects more than three languages and bad JSON", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + ctx := context.Background() + router := newAuthTestRouter(t) + token := adminAccessToken(ctx, t, router) + + tooMany := httptest.NewRecorder() + tooManyRequest := httptest.NewRequestWithContext( + ctx, + http.MethodPatch, + "/api/me/playback-preferences", + bytes.NewReader([]byte(`{"audioLanguages":["a","b","c","d"]}`)), + ) + tooManyRequest.Header.Set("Content-Type", "application/json") + setBearerAuth(tooManyRequest, token) + router.ServeHTTP(tooMany, tooManyRequest) + if tooMany.Code != http.StatusBadRequest { + t.Fatalf("too many langs: got %d", tooMany.Code) + } + + badJSON := httptest.NewRecorder() + badRequest := httptest.NewRequestWithContext( + ctx, + http.MethodPatch, + "/api/me/playback-preferences", + bytes.NewReader([]byte(`not-json`)), + ) + badRequest.Header.Set("Content-Type", "application/json") + setBearerAuth(badRequest, token) + router.ServeHTTP(badJSON, badRequest) + if badJSON.Code != http.StatusBadRequest { + t.Fatalf("bad json: got %d", badJSON.Code) + } + }) +} + +func TestPlaybackPreferencesHandlers_LoadFailed(t *testing.T) { + allure.Test(t, "GET returns 500 when playback prefs cannot be loaded", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + ctx := context.Background() + pool := setupTestPool(ctx, t) + authService := prepareIntegrationAuth(ctx, t, pool) + h := &handler{auth: authService} + + recorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(recorder) + ginCtx.Request = httptest.NewRequestWithContext( + ctx, + http.MethodGet, + "/api/me/playback-preferences", + nil, + ) + setTestUser(ginCtx, auth.PublicUser{ + ID: "00000000-0000-0000-0000-000000000099", + Role: auth.RoleUser, + }) + h.getPlaybackPreferences(ginCtx) + + if recorder.Code != http.StatusInternalServerError { + t.Fatalf("GET: got %d %s", recorder.Code, recorder.Body.String()) + } + }) +} + +func TestPlaybackPreferencesHandlers_AuthUnavailable(t *testing.T) { + t.Parallel() + + allure.Test(t, "playback prefs return 500 when auth service unset", func(a *allure.Context) { + t := a.T() + gin.SetMode(gin.TestMode) + user := auth.PublicUser{ID: "user-id", Role: auth.RoleUser} + h := &handler{} + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequestWithContext( + context.Background(), + http.MethodGet, + "/api/me/playback-preferences", + nil, + ) + setTestUser(ctx, user) + h.getPlaybackPreferences(ctx) + if recorder.Code != http.StatusInternalServerError { + t.Fatalf("GET nil auth: got %d", recorder.Code) + } + + recorder = httptest.NewRecorder() + ctx, _ = gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequestWithContext( + context.Background(), + http.MethodPatch, + "/api/me/playback-preferences", + bytes.NewReader([]byte(`{"audioLanguages":["en"]}`)), + ) + ctx.Request.Header.Set("Content-Type", "application/json") + setTestUser(ctx, user) + h.patchPlaybackPreferences(ctx) + if recorder.Code != http.StatusInternalServerError { + t.Fatalf("PATCH nil auth: got %d", recorder.Code) + } + }) +} diff --git a/internal/transcode/audio_select_test.go b/internal/transcode/audio_select_test.go index 61edace..56f4e75 100644 --- a/internal/transcode/audio_select_test.go +++ b/internal/transcode/audio_select_test.go @@ -34,4 +34,20 @@ func TestSelectDefaultAudioStreamIndex(t *testing.T) { t.Fatalf("got %d want 1", got) } }) + + allure.Test(t, "matches eng preference to eng track language code", func(a *allure.Context) { + t := a.T() + got := SelectDefaultAudioStreamIndex(streams, []string{"en"}) + if got != 1 { + t.Fatalf("got %d want 1", got) + } + }) + + allure.Test(t, "empty stream list returns -1", func(a *allure.Context) { + t := a.T() + got := SelectDefaultAudioStreamIndex(nil, []string{"en"}) + if got != -1 { + t.Fatalf("got %d want -1", got) + } + }) } diff --git a/internal/transcode/playlist_vod_test.go b/internal/transcode/playlist_vod_test.go index 90c9972..2721d44 100644 --- a/internal/transcode/playlist_vod_test.go +++ b/internal/transcode/playlist_vod_test.go @@ -169,3 +169,29 @@ func TestLadderHeights_IncludesNativeAndNeverUpscales(t *testing.T) { }, ) } + +func TestApplyMasterPlaylistDefaultAudio(t *testing.T) { + t.Parallel() + + allure.Test(t, "rewrites DEFAULT= on TYPE=AUDIO lines for user track index", func(a *allure.Context) { + t := a.T() + playlist := []byte( + "#EXTM3U\n" + + `#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a",DEFAULT=YES,NAME="JPN",LANGUAGE="jpn"` + "\n" + + `#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a",DEFAULT=NO,NAME="ENG",LANGUAGE="eng"` + "\n", + ) + + unchanged := ApplyMasterPlaylistDefaultAudio(playlist, 0) + if string(unchanged) != string(playlist) { + t.Fatalf("index 0 should not rewrite playlist") + } + + rewritten := string(ApplyMasterPlaylistDefaultAudio(playlist, 1)) + if strings.Contains(rewritten, `DEFAULT=YES,NAME="JPN"`) { + t.Fatalf("first audio line should not stay DEFAULT=YES:\n%s", rewritten) + } + if !strings.Contains(rewritten, `DEFAULT=YES,NAME="ENG"`) { + t.Fatalf("second audio line should become DEFAULT=YES:\n%s", rewritten) + } + }) +} diff --git a/internal/transcode/settings.go b/internal/transcode/settings.go index 188785a..3b7bd01 100644 --- a/internal/transcode/settings.go +++ b/internal/transcode/settings.go @@ -127,16 +127,16 @@ func ParseHwAccel(raw string) (HwAccel, error) { // ParseDownmixAlgorithm validates and normalizes a downmixAlgorithm string. func ParseDownmixAlgorithm(raw string) (DownmixAlgorithm, error) { - switch DownmixAlgorithm(strings.ToLower(strings.TrimSpace(raw))) { - case DownmixNone: + switch strings.ToLower(strings.TrimSpace(raw)) { + case string(DownmixNone): return DownmixNone, nil - case DownmixAC4, "": + case string(DownmixAC4), "": return DownmixAC4, nil - case DownmixDave750: + case string(DownmixDave750): return DownmixDave750, nil - case DownmixNightmodeDialogue: + case strings.ToLower(string(DownmixNightmodeDialogue)): return DownmixNightmodeDialogue, nil - case DownmixRFC7845: + case string(DownmixRFC7845): return DownmixRFC7845, nil default: return "", ErrInvalidDownmix diff --git a/internal/transcode/settings_test.go b/internal/transcode/settings_test.go index d9cf9c2..16defc3 100644 --- a/internal/transcode/settings_test.go +++ b/internal/transcode/settings_test.go @@ -134,6 +134,46 @@ func TestParseDownmixAlgorithm(t *testing.T) { if err != nil || got != DownmixDave750 { t.Fatalf("dave750: got %q err=%v", got, err) } + got, err = ParseDownmixAlgorithm("ac4") + if err != nil || got != DownmixAC4 { + t.Fatalf("ac4: got %q err=%v", got, err) + } + got, err = ParseDownmixAlgorithm("nightmodeDialogue") + if err != nil || got != DownmixNightmodeDialogue { + t.Fatalf("nightmode: got %q err=%v", got, err) + } + got, err = ParseDownmixAlgorithm("rfc7845") + if err != nil || got != DownmixRFC7845 { + t.Fatalf("rfc7845: got %q err=%v", got, err) + } + _, err = ParseDownmixAlgorithm("bogus") + if !errors.Is(err, ErrInvalidDownmix) { + t.Fatalf("expected ErrInvalidDownmix, got %v", err) + } + }) +} + +func TestParseDownmixBoost(t *testing.T) { + t.Parallel() + + allure.Test(t, "parseDownmixBoost applies algorithm defaults and bounds", func(a *allure.Context) { + t := a.T() + got, err := ParseDownmixBoost(0, DownmixNone) + if err != nil || got != 2 { + t.Fatalf("none default: got %v err=%v", got, err) + } + got, err = ParseDownmixBoost(0, DownmixAC4) + if err != nil || got != 1 { + t.Fatalf("ac4 default: got %v err=%v", got, err) + } + got, err = ParseDownmixBoost(1.25, DownmixAC4) + if err != nil || got != 1.25 { + t.Fatalf("explicit boost: got %v err=%v", got, err) + } + _, err = ParseDownmixBoost(0.4, DownmixAC4) + if !errors.Is(err, ErrInvalidDownmixBoost) { + t.Fatalf("expected ErrInvalidDownmixBoost, got %v", err) + } }) }