Merge pull request #562 from webadderallorg/feature/nvidia-export-opt-in

feat(export): add NVIDIA opt-in detection
This commit is contained in:
Phạm Thị Minh Hồng authored and GitHub committed 2026-05-22 20:57:52 +07:00
commit 3c9f3bbeee
9 files changed
+282 -24

No files matched your search

+19
View File
@@ -188,6 +188,19 @@ interface RendererNativeVideoMetadataProbe {
audioSampleRate?: number;
}
interface RendererNativeExportCapabilities {
platform: NodeJS.Platform;
nvidiaCuda: {
available: boolean;
skipReason: string | null;
hasNvidiaGpu: boolean | null;
hasWrapper: boolean;
explicitEnabled: boolean;
explicitDisabled: boolean;
userOptInRequired: boolean;
};
}
interface Window {
electronAPI: {
hudOverlaySetIgnoreMouse: (ignore: boolean) => void;
@@ -329,6 +342,11 @@ interface Window {
metadata?: RendererNativeVideoMetadataProbe;
error?: string;
}>;
getNativeExportCapabilities: () => Promise<{
success: boolean;
capabilities?: RendererNativeExportCapabilities;
error?: string;
}>;
nativeStaticLayoutExport: (options: {
sessionId?: string;
inputPath: string;
@@ -389,6 +407,7 @@ interface Window {
}>;
chunkDurationSec?: number;
experimentalWindowsGpuCompositor?: boolean;
experimentalNvidiaCudaExport?: boolean;
audioOptions?: {
audioMode?: "none" | "copy-source" | "trim-source" | "edited-track";
audioSourcePath?: string | null;
+40 -6
View File
@@ -55,6 +55,7 @@ import {
buildNativeVideoAudioMuxArgs,
canCopyAudioCodecIntoMp4,
getExperimentalNvidiaCudaExportSkipReason,
getNativeExportCapabilities,
getNativeGpuCompositorStallTimeoutMs,
getNativeStaticLayoutSourceProxyBitrate,
getNvidiaCudaAudioExportSkipReason,
@@ -307,7 +308,7 @@ describe("getNvidiaCudaAudioExportSkipReason", () => {
});
describe("getNvidiaCudaAutoStallTimeoutMs", () => {
it("only applies the stall guard to packaged auto candidates by default", () => {
it("only applies the stall guard to active validated CUDA candidates by default", () => {
expect(getNvidiaCudaAutoStallTimeoutMs(false)).toBeNull();
expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBe(120_000);
});
@@ -396,8 +397,21 @@ describe("hasNvidiaGpuDeviceInGpuInfo", () => {
});
});
describe("getNativeExportCapabilities", () => {
it("reports NVIDIA CUDA availability when the wrapper and an NVIDIA GPU are present", async () => {
const capabilities = await withPackagedCudaCandidate(
{ gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] },
() => getNativeExportCapabilities(),
);
expect(capabilities.nvidiaCuda.available).toBe(process.platform === "win32");
expect(capabilities.nvidiaCuda.hasWrapper).toBe(process.platform === "win32");
expect(capabilities.nvidiaCuda.hasNvidiaGpu).toBe(process.platform === "win32" ? true : null);
});
});
describe("getExperimentalNvidiaCudaExportSkipReason", () => {
it("auto-enables packaged CUDA candidates when the helper and an NVIDIA GPU are present", async () => {
it("requires user opt-in before packaged CUDA candidates run", async () => {
const reason = await withPackagedCudaCandidate(
{ gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] },
() =>
@@ -408,6 +422,21 @@ describe("getExperimentalNvidiaCudaExportSkipReason", () => {
),
);
expect(reason).toBe(process.platform === "win32" ? "env-disabled" : "not-windows");
});
it("allows user opt-in CUDA candidates when the helper and an NVIDIA GPU are present", async () => {
const reason = await withPackagedCudaCandidate(
{ gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] },
() =>
getExperimentalNvidiaCudaExportSkipReason(
createNvidiaCudaSkipOptions({
experimentalNvidiaCudaExport: true,
audioOptions: { audioMode: "copy-source", audioSourcePath: "input.mp4" },
}),
),
);
expect(reason).toBe(process.platform === "win32" ? null : "not-windows");
});
@@ -492,10 +521,13 @@ describe("getExperimentalNvidiaCudaExportSkipReason", () => {
}
});
it("skips packaged CUDA auto-candidates when Electron reports no NVIDIA GPU", async () => {
it("skips user opt-in CUDA candidates when Electron reports no NVIDIA GPU", async () => {
const reason = await withPackagedCudaCandidate(
{ gpuDevice: [{ vendorId: 0x8086, deviceString: "Intel UHD Graphics" }] },
() => getExperimentalNvidiaCudaExportSkipReason(createNvidiaCudaSkipOptions()),
() =>
getExperimentalNvidiaCudaExportSkipReason(
createNvidiaCudaSkipOptions({ experimentalNvidiaCudaExport: true }),
),
);
expect(reason).toBe(
@@ -503,12 +535,14 @@ describe("getExperimentalNvidiaCudaExportSkipReason", () => {
);
});
it("lets the packaged auto-candidate be explicitly disabled", async () => {
it("lets the user opt-in candidate be explicitly disabled", async () => {
const reason = await withPackagedCudaCandidate(
{ gpuDevice: [{ vendorId: 0x10de, deviceString: "NVIDIA GeForce GTX 1650" }] },
async () => {
process.env.RECORDLY_EXPERIMENTAL_NVIDIA_CUDA_EXPORT = "0";
return getExperimentalNvidiaCudaExportSkipReason(createNvidiaCudaSkipOptions());
return getExperimentalNvidiaCudaExportSkipReason(
createNvidiaCudaSkipOptions({ experimentalNvidiaCudaExport: true }),
);
},
);
+86 -18
View File
@@ -151,6 +151,7 @@ export interface NativeStaticLayoutExportOptions {
timelineMapPath?: string | null;
chunkDurationSec?: number;
experimentalWindowsGpuCompositor?: boolean;
experimentalNvidiaCudaExport?: boolean;
audioOptions?: NativeVideoExportFinishOptions;
nvidiaCudaForceVideoOnly?: boolean;
}
@@ -923,6 +924,19 @@ function setNativeStaticLayoutExportProcessPriority(pid: number | undefined, lab
export const nativeStaticLayoutExportSessions = new Map<string, NativeStaticLayoutExportSession>();
export interface NativeExportCapabilities {
platform: NodeJS.Platform;
nvidiaCuda: {
available: boolean;
skipReason: string | null;
hasNvidiaGpu: boolean | null;
hasWrapper: boolean;
explicitEnabled: boolean;
explicitDisabled: boolean;
userOptInRequired: boolean;
};
}
export function parseFfmpegDurationSeconds(value: string): number | null {
const parts = value.trim().split(":");
if (parts.length !== 3) {
@@ -1856,13 +1870,18 @@ export function hasNvidiaGpuDeviceInGpuInfo(gpuInfo: unknown) {
}
async function hasNvidiaGpuForCudaExportCandidate() {
const hasNvidiaGpu = await probeNvidiaGpuForCudaExportCandidate();
return hasNvidiaGpu ?? true;
}
async function probeNvidiaGpuForCudaExportCandidate(): Promise<boolean | null> {
const getGPUInfo = (
app as typeof app & {
getGPUInfo?: (infoType: "basic") => Promise<unknown>;
}
).getGPUInfo;
if (typeof getGPUInfo !== "function") {
return true;
return null;
}
try {
@@ -1872,7 +1891,7 @@ async function hasNvidiaGpuForCudaExportCandidate() {
"[native-static-layout-export] Unable to inspect GPU info before NVIDIA CUDA export; letting the helper decide",
error,
);
return true;
return null;
}
}
@@ -1959,12 +1978,12 @@ function isExplicitNvidiaCudaExportDisabled() {
return process.env[NVIDIA_CUDA_EXPORT_ENV] === "0";
}
function isPackagedNvidiaCudaExportAutoCandidateEnabled() {
return app.isPackaged && !isExplicitNvidiaCudaExportDisabled();
function isUserOptedInNvidiaCudaExport(options: NativeStaticLayoutExportOptions) {
return options.experimentalNvidiaCudaExport === true && !isExplicitNvidiaCudaExportDisabled();
}
function isPackagedNvidiaCudaExportAutoCandidateActive() {
return isPackagedNvidiaCudaExportAutoCandidateEnabled() && !isExplicitNvidiaCudaExportEnabled();
function isValidatedNvidiaCudaFallbackCandidate(options: NativeStaticLayoutExportOptions) {
return isUserOptedInNvidiaCudaExport(options) && !isExplicitNvidiaCudaExportEnabled();
}
function isNvidiaCudaForceVideoOnlyEnabled() {
@@ -1972,7 +1991,7 @@ function isNvidiaCudaForceVideoOnlyEnabled() {
}
export function getNvidiaCudaAutoStallTimeoutMs(
autoCandidateActive = isPackagedNvidiaCudaExportAutoCandidateActive(),
autoCandidateActive = false,
) {
if (!autoCandidateActive && !isExplicitNvidiaCudaExportEnabled()) {
return null;
@@ -2011,17 +2030,19 @@ export async function getExperimentalNvidiaCudaExportSkipReason(
if (process.platform !== "win32") {
return "not-windows";
}
if (isExplicitNvidiaCudaExportDisabled()) {
return "env-disabled";
}
const explicitCuda = isExplicitNvidiaCudaExportEnabled();
const packagedAutoCandidateEnabled = isPackagedNvidiaCudaExportAutoCandidateEnabled();
const packagedAutoCandidateActive = isPackagedNvidiaCudaExportAutoCandidateActive();
if (!explicitCuda && !packagedAutoCandidateEnabled) {
const userOptIn = isUserOptedInNvidiaCudaExport(options);
if (!explicitCuda && !userOptIn) {
return "env-disabled";
}
if (!options.experimentalWindowsGpuCompositor) {
return "windows-gpu-compositor-disabled";
}
if (packagedAutoCandidateActive) {
if (userOptIn) {
if (!(await resolveExperimentalNvidiaCudaExportScriptPath())) {
return "cuda-wrapper-unavailable";
}
@@ -2032,10 +2053,54 @@ export async function getExperimentalNvidiaCudaExportSkipReason(
return getNvidiaCudaAudioExportSkipReason(options.audioOptions?.audioMode, {
allowValidatedFallbackCandidate:
packagedAutoCandidateActive || isNvidiaCudaForceVideoOnlyEnabled(),
isValidatedNvidiaCudaFallbackCandidate(options) || isNvidiaCudaForceVideoOnlyEnabled(),
});
}
export async function getNativeExportCapabilities(): Promise<NativeExportCapabilities> {
if (process.platform !== "win32") {
return {
platform: process.platform,
nvidiaCuda: {
available: false,
skipReason: "not-windows",
hasNvidiaGpu: null,
hasWrapper: false,
explicitEnabled: isExplicitNvidiaCudaExportEnabled(),
explicitDisabled: isExplicitNvidiaCudaExportDisabled(),
userOptInRequired: true,
},
};
}
const explicitEnabled = isExplicitNvidiaCudaExportEnabled();
const explicitDisabled = isExplicitNvidiaCudaExportDisabled();
const wrapperPath = await resolveExperimentalNvidiaCudaExportScriptPath();
const hasNvidiaGpu = await probeNvidiaGpuForCudaExportCandidate();
const skipReason = explicitDisabled
? "env-disabled"
: !wrapperPath
? "cuda-wrapper-unavailable"
: hasNvidiaGpu === false
? "nvidia-gpu-unavailable"
: hasNvidiaGpu === null
? "nvidia-gpu-probe-unavailable"
: null;
return {
platform: process.platform,
nvidiaCuda: {
available: skipReason === null,
skipReason,
hasNvidiaGpu,
hasWrapper: Boolean(wrapperPath),
explicitEnabled,
explicitDisabled,
userOptInRequired: !explicitEnabled,
},
};
}
export async function resolveExperimentalNvidiaCudaExportScriptPath() {
if (process.platform !== "win32") {
return null;
@@ -2750,7 +2815,9 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
const startedAt = getNowMs();
const startedAtIso = new Date().toISOString();
const timeoutMs = Math.max(20 * 60 * 1000, options.durationSec * 2000);
const stallTimeoutMs = getNvidiaCudaAutoStallTimeoutMs();
const stallTimeoutMs = getNvidiaCudaAutoStallTimeoutMs(
isValidatedNvidiaCudaFallbackCandidate(options),
);
const ffmpegDirectory = path.dirname(ffmpegPath);
const pathKey = process.platform === "win32" ? "Path" : "PATH";
const env = {
@@ -3307,10 +3374,11 @@ export async function exportNativeStaticLayoutVideo(
const nvidiaCudaSkipReason =
await getExperimentalNvidiaCudaExportSkipReason(options);
let shouldTryNvidiaCuda = nvidiaCudaSkipReason === null;
const validatedCudaFallbackCandidate =
isValidatedNvidiaCudaFallbackCandidate(options);
if (
shouldTryNvidiaCuda &&
(isPackagedNvidiaCudaExportAutoCandidateActive() ||
isNvidiaCudaForceVideoOnlyEnabled()) &&
(validatedCudaFallbackCandidate || isNvidiaCudaForceVideoOnlyEnabled()) &&
(experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none") !== "none"
) {
experimentalNvidiaCudaOptions = {
@@ -3323,13 +3391,13 @@ export async function exportNativeStaticLayoutVideo(
audioMode:
experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none",
forcedByEnv: isNvidiaCudaForceVideoOnlyEnabled(),
packagedAutoCandidate: isPackagedNvidiaCudaExportAutoCandidateActive(),
userOptIn: options.experimentalNvidiaCudaExport === true,
},
);
}
const shouldLogNvidiaCudaSkip =
isExplicitNvidiaCudaExportEnabled() ||
(isPackagedNvidiaCudaExportAutoCandidateEnabled() &&
(options.experimentalNvidiaCudaExport === true &&
nvidiaCudaSkipReason !== "env-disabled");
if (
!shouldTryNvidiaCuda &&
@@ -3342,7 +3410,7 @@ export async function exportNativeStaticLayoutVideo(
reason: nvidiaCudaSkipReason,
audioMode: options.audioOptions?.audioMode ?? "none",
overrideEnv: NVIDIA_CUDA_ALLOW_AUDIO_EXPORT_ENV,
packagedAutoCandidate: isPackagedNvidiaCudaExportAutoCandidateEnabled(),
userOptIn: options.experimentalNvidiaCudaExport === true,
},
);
}
+16
View File
@@ -18,6 +18,7 @@ import {
enqueueNativeVideoExportFrameWrites,
exportNativeStaticLayoutVideo,
flushNativeVideoExportPendingWriteRequests,
getNativeExportCapabilities,
getNativeVideoExportMaxQueuedWriteBytes,
getNativeVideoExportSessionError,
isHardwareAcceleratedVideoEncoder,
@@ -413,6 +414,21 @@ export function registerExportHandlers() {
}
});
ipcMain.handle("get-native-export-capabilities", async () => {
try {
return {
success: true,
capabilities: await getNativeExportCapabilities(),
};
} catch (error) {
console.warn("[native-export-capabilities] Failed:", error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
});
ipcMain.handle(
"native-static-layout-export",
async (event, options: NativeStaticLayoutExportOptions) => {
+20
View File
@@ -90,6 +90,18 @@ type NativeVideoMetadataProbe = {
audioCodec?: string;
audioSampleRate?: number;
};
type NativeExportCapabilities = {
platform: NodeJS.Platform;
nvidiaCuda: {
available: boolean;
skipReason: string | null;
hasNvidiaGpu: boolean | null;
hasWrapper: boolean;
explicitEnabled: boolean;
explicitDisabled: boolean;
userOptInRequired: boolean;
};
};
const nativeVideoExportWriteRequests = new Map<
number,
@@ -195,6 +207,13 @@ contextBridge.exposeInMainWorld("electronAPI", {
error?: string;
}>;
},
getNativeExportCapabilities: () => {
return ipcRenderer.invoke("get-native-export-capabilities") as Promise<{
success: boolean;
capabilities?: NativeExportCapabilities;
error?: string;
}>;
},
nativeStaticLayoutExport: (options: {
sessionId?: string;
inputPath: string;
@@ -255,6 +274,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
}>;
chunkDurationSec?: number;
experimentalWindowsGpuCompositor?: boolean;
experimentalNvidiaCudaExport?: boolean;
audioOptions?: {
audioMode?: "none" | "copy-source" | "trim-source" | "edited-track";
audioSourcePath?: string | null;
@@ -26,6 +26,9 @@ interface ExportSettingsMenuProps {
onMp4FrameRateChange?: (frameRate: ExportMp4FrameRate) => void;
exportPipelineModel?: ExportPipelineModel;
onExportPipelineModelChange?: (pipelineModel: ExportPipelineModel) => void;
experimentalNvidiaCudaExport?: boolean;
onExperimentalNvidiaCudaExportChange?: (enabled: boolean) => void;
nvidiaCudaExportAvailable?: boolean;
mp4OutputDimensions?: Record<ExportQuality, { width: number; height: number }>;
gifFrameRate: GifFrameRate;
onGifFrameRateChange?: (rate: GifFrameRate) => void;
@@ -49,6 +52,9 @@ export function ExportSettingsMenu({
onMp4FrameRateChange,
exportPipelineModel = "modern",
onExportPipelineModelChange,
experimentalNvidiaCudaExport = false,
onExperimentalNvidiaCudaExportChange,
nvidiaCudaExportAvailable = false,
mp4OutputDimensions,
gifFrameRate,
onGifFrameRateChange,
@@ -330,6 +336,35 @@ export function ExportSettingsMenu({
"Lightning (Beta) automatically uses the fastest compatible backend and falls back when needed.",
)}
</p>
{!isLegacyModel && nvidiaCudaExportAvailable ? (
<div className="mb-3 flex min-h-12 items-center justify-between gap-3 rounded-lg border border-[#2563EB]/20 bg-[#2563EB]/5 px-3 py-2">
<div className="min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold text-foreground">
{tSettings("export.nvidiaCuda.title", "NVIDIA CUDA")}
</span>
<span className="rounded bg-[#2563EB]/10 px-1.5 py-0.5 text-[9px] font-semibold uppercase text-[#2563EB]">
{tSettings("export.nvidiaCuda.badge", "Experimental")}
</span>
</div>
<p className="mt-0.5 truncate text-[10px] text-muted-foreground/75">
{tSettings(
"export.nvidiaCuda.hint",
"Try GPU export on this Windows device.",
)}
</p>
</div>
<Switch
checked={experimentalNvidiaCudaExport}
onCheckedChange={onExperimentalNvidiaCudaExportChange}
aria-label={tSettings(
"export.nvidiaCuda.toggle",
"Enable experimental NVIDIA CUDA export",
)}
className="shrink-0 scale-75 data-[state=checked]:bg-[#2563EB]"
/>
</div>
) : null}
</LayoutGroup>
) : (
<div className="mb-3 space-y-2">
@@ -52,6 +52,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { Toaster } from "@/components/ui/sonner";
import { useI18n } from "@/contexts/I18nContext";
import { useShortcuts } from "@/contexts/ShortcutsContext";
import { loadAppSetting, saveAppSetting } from "@/lib/appSettings";
import {
calculateOutputDimensions,
DEFAULT_MP4_CODEC,
@@ -108,6 +109,8 @@ const PhSettings = (props: { className?: string; weight?: "fill" | "regular" })
<Gear weight={props.weight ?? "regular"} className={props.className} />
);
const NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY = "recordly.export.experimentalNvidiaCuda";
import type { SourceAudioTrackSettings } from "@/components/video-editor/audio/audioTypes";
import { extensionHost } from "@/lib/extensions";
import { useVideoEditorAudio } from "./audio/useVideoEditorAudio";
@@ -551,6 +554,10 @@ export default function VideoEditor() {
const [exportPipelineModel, setExportPipelineModel] = useState<ExportPipelineModel>(
initialEditorPreferences.exportPipelineModel,
);
const [nvidiaCudaExportAvailable, setNvidiaCudaExportAvailable] = useState(false);
const [experimentalNvidiaCudaExport, setExperimentalNvidiaCudaExportState] = useState(
() => loadAppSetting<boolean>(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY) === true,
);
const [mp4FrameRate, setMp4FrameRate] = useState<ExportMp4FrameRate>(
initialEditorPreferences.mp4FrameRate ?? DEFAULT_MP4_EXPORT_FRAME_RATE,
);
@@ -621,6 +628,48 @@ export default function VideoEditor() {
});
}, []);
useEffect(() => {
let cancelled = false;
void (async () => {
try {
const result = await window.electronAPI?.getNativeExportCapabilities?.();
if (cancelled) {
return;
}
const available = result?.capabilities?.nvidiaCuda.available === true;
setNvidiaCudaExportAvailable(available);
if (!available) {
setExperimentalNvidiaCudaExportState(false);
}
} catch (error) {
if (cancelled) {
return;
}
console.warn("[export] Failed to load native export capabilities", error);
setNvidiaCudaExportAvailable(false);
setExperimentalNvidiaCudaExportState(false);
}
})();
return () => {
cancelled = true;
};
}, []);
const setExperimentalNvidiaCudaExport = useCallback(
(enabled: boolean) => {
const nextEnabled = Boolean(enabled && nvidiaCudaExportAvailable);
setExperimentalNvidiaCudaExportState(nextEnabled);
saveAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY, nextEnabled);
if (nextEnabled) {
setExportPipelineModel("modern");
}
},
[nvidiaCudaExportAvailable],
);
useEffect(() => {
autoSuggestedVideoPathRef.current = null;
pendingFreshRecordingAutoSuggestTelemetryCountRef.current = 0;
@@ -4130,6 +4179,10 @@ export default function VideoEditor() {
const useExperimentalNativeExport =
pipelineModel === "modern" &&
(smokeExportConfig.enabled ? smokeExportConfig.useNativeExport : true);
const useExperimentalNvidiaCudaExport =
useExperimentalNativeExport &&
experimentalNvidiaCudaExport &&
nvidiaCudaExportAvailable;
const backendPreference =
pipelineModel === "legacy"
? "webcodecs"
@@ -4171,6 +4224,7 @@ export default function VideoEditor() {
preferredEncoderPath: supportedSourceDimensions.encoderPath,
preferredRenderBackend: smokeExportConfig.renderBackend,
experimentalNativeExport: useExperimentalNativeExport,
experimentalNvidiaCudaExport: useExperimentalNvidiaCudaExport,
maxEncodeQueue: smokeExportConfig.maxEncodeQueue,
maxDecodeQueue: smokeExportConfig.maxDecodeQueue,
maxPendingFrames: smokeExportConfig.maxPendingFrames,
@@ -4483,6 +4537,8 @@ export default function VideoEditor() {
exportEncodingMode,
exportBackendPreference,
exportPipelineModel,
experimentalNvidiaCudaExport,
nvidiaCudaExportAvailable,
borderRadius,
padding,
cropRegion,
@@ -5425,6 +5481,13 @@ export default function VideoEditor() {
onMp4FrameRateChange={setMp4FrameRate}
exportPipelineModel={exportPipelineModel}
onExportPipelineModelChange={setExportPipelineModel}
experimentalNvidiaCudaExport={
experimentalNvidiaCudaExport && nvidiaCudaExportAvailable
}
onExperimentalNvidiaCudaExportChange={
setExperimentalNvidiaCudaExport
}
nvidiaCudaExportAvailable={nvidiaCudaExportAvailable}
exportQuality={exportQuality}
onExportQualityChange={setExportQuality}
gifFrameRate={gifFrameRate}
+2
View File
@@ -2512,6 +2512,8 @@ export class ModernVideoExporter {
timelineSegments,
chunkDurationSec: STATIC_LAYOUT_CHUNK_DURATION_SEC,
experimentalWindowsGpuCompositor: this.config.experimentalNativeExport === true,
experimentalNvidiaCudaExport:
this.config.experimentalNvidiaCudaExport === true,
audioOptions: {
...audioOptions,
outputDurationSec: effectiveDuration,
+1
View File
@@ -8,6 +8,7 @@ export interface ExportConfig {
backendPreference?: ExportBackendPreference;
preferredRenderBackend?: ExportRenderBackend;
experimentalNativeExport?: boolean;
experimentalNvidiaCudaExport?: boolean;
maxEncodeQueue?: number;
maxDecodeQueue?: number;
maxPendingFrames?: number;