diff --git a/packages/core/src/db/schemas.ts b/packages/core/src/db/schemas.ts index e8974427e..3d8112d75 100644 --- a/packages/core/src/db/schemas.ts +++ b/packages/core/src/db/schemas.ts @@ -72,6 +72,17 @@ const StreamProxyConfig = z.object({ export type StreamProxyConfig = z.infer; +const CleanRedirectOutputConfig = z.object({ + enabled: z.boolean().optional(), + redirectCode: z + .union([z.literal(302), z.literal(307), z.literal(308)]) + .optional(), +}); + +export type CleanRedirectOutputConfig = z.infer< + typeof CleanRedirectOutputConfig +>; + const ResultLimitOptions = z.object({ global: z.number().min(1).optional(), service: z.number().min(1).optional(), @@ -381,7 +392,7 @@ export const ParentConfigSchema = z.object({ proxy: BinaryMergeStrategy.default('inherit'), metadata: BinaryMergeStrategy.default('inherit'), misc: BinaryMergeStrategy.default('inherit'), - branding: BinaryMergeStrategy.default('inherit'), + branding: BinaryMergeStrategy.default('inherit'), fieldOverrides: z .record(z.string(), z.enum(['inherit', 'override', 'extend'])) .optional(), @@ -646,6 +657,10 @@ export const UserDataSchema = z.object({ usePosterServiceForMeta: z.boolean().optional(), formatter: Formatter, proxy: StreamProxyConfig.optional(), + cleanRedirectOutput: CleanRedirectOutputConfig.optional().default({ + enabled: false, + redirectCode: 307, + }), resultLimits: ResultLimitOptions.optional(), size: SizeFilterOptions.optional(), bitrate: BitrateFilterOptions.optional(), diff --git a/packages/core/src/utils/config.ts b/packages/core/src/utils/config.ts index d26eb7f4a..02dc5e770 100644 --- a/packages/core/src/utils/config.ts +++ b/packages/core/src/utils/config.ts @@ -1515,6 +1515,7 @@ const METADATA_FIELDS: (keyof UserData)[] = [ const MISC_FIELDS: (keyof UserData)[] = [ 'autoPlay', 'areYouStillThere', 'statistics', 'dynamicAddonFetching', 'nzbFailover', 'serviceWrap', 'cacheAndPlay', 'preloadStreams', 'precacheSelector', + 'cleanRedirectOutput', 'hideErrors', 'hideErrorsForResources', 'addonCategoryColors', 'catalogModifications', 'mergedCatalogs', 'addonPassword', 'externalDownloads', 'autoRemoveDownloads', 'checkOwned', 'showChanges', 'randomiseResults', 'enhanceResults', 'enhancePosters', diff --git a/packages/core/src/utils/fieldMeta.ts b/packages/core/src/utils/fieldMeta.ts index c465f7a1d..5b437772c 100644 --- a/packages/core/src/utils/fieldMeta.ts +++ b/packages/core/src/utils/fieldMeta.ts @@ -219,6 +219,7 @@ export const FIELD_META: Omit, IgnoredKeys> = usePosterServiceForMeta: { label: 'Use Poster Service for Meta', group: 'metadata', type: 'scalar', menu: 'services', subTab: 'posters' }, autoPlay: { label: 'Auto Play', group: 'misc', type: 'scalar', menu: 'miscellaneous', subTab: 'playback' }, + cleanRedirectOutput: { label: 'Clean Filename Redirect', group: 'misc', type: 'scalar', menu: 'miscellaneous', subTab: 'playback', keywords: ['infuse', 'subtitles', 'redirect'] }, areYouStillThere: { label: 'Are You Still There?', group: 'misc', type: 'scalar', menu: 'miscellaneous', subTab: 'playback' }, statistics: { label: 'Statistics', group: 'misc', type: 'scalar', menu: 'miscellaneous', subTab: 'display' }, hideErrors: { label: 'Hide Errors', group: 'misc', type: 'scalar', menu: 'miscellaneous', subTab: 'display' }, diff --git a/packages/docs/content/docs/guides/clean-filename-redirect.mdx b/packages/docs/content/docs/guides/clean-filename-redirect.mdx new file mode 100644 index 000000000..b36f21f3f --- /dev/null +++ b/packages/docs/content/docs/guides/clean-filename-redirect.mdx @@ -0,0 +1,16 @@ +--- +title: Clean Filename Redirect +description: Use a clean initial playback filename for external player subtitle matching. +--- + +# Clean Filename Redirect + +Clean Filename Redirect rewrites HTTP stream URLs through a lightweight AIOStreams endpoint that includes a cleaned filename in the URL path, then redirects to the original stream URL. + +This is useful for external players such as Infuse, where subtitle matching may rely on the initial playback filename. + +This feature does not proxy video traffic. AIOStreams only returns an HTTP redirect; the media is still fetched from the original provider, debrid service, or CDN URL. + +Recommended redirect code: 307. + +Default: disabled. diff --git a/packages/docs/content/docs/guides/meta.json b/packages/docs/content/docs/guides/meta.json index b182cdc6c..030234446 100644 --- a/packages/docs/content/docs/guides/meta.json +++ b/packages/docs/content/docs/guides/meta.json @@ -3,8 +3,9 @@ "pages": [ "groups", "usenet", + "clean-filename-redirect", "scored-sorting", "seanime", "development" ] -} \ No newline at end of file +} diff --git a/packages/frontend/src/components/menu/miscellaneous/_components/playback-behavior.tsx b/packages/frontend/src/components/menu/miscellaneous/_components/playback-behavior.tsx index f442d0ab8..3e28befb9 100644 --- a/packages/frontend/src/components/menu/miscellaneous/_components/playback-behavior.tsx +++ b/packages/frontend/src/components/menu/miscellaneous/_components/playback-behavior.tsx @@ -114,6 +114,49 @@ export function PlaybackBehavior() { )} + + { + setUserData((prev) => ({ + ...prev, + cleanRedirectOutput: { + ...prev.cleanRedirectOutput, + enabled: value, + redirectCode: prev.cleanRedirectOutput?.redirectCode ?? 307, + }, + })); + }} + /> +