mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-10 07:53:57 +00:00
feat(export): add nvidia opt-in detection
This commit is contained in:
1 parent
50418ebe2d
commit
3335f88abd
9 files changed
+272
-24
No files matched your search
Vendored
+19
@@ -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;
|
||||
|
||||
@@ -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 }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,38 @@ export default function VideoEditor() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
void window.electronAPI?.getNativeExportCapabilities?.().then((result) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const available = result?.capabilities?.nvidiaCuda.available === true;
|
||||
setNvidiaCudaExportAvailable(available);
|
||||
if (!available) {
|
||||
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 +4169,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 +4214,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 +4527,8 @@ export default function VideoEditor() {
|
||||
exportEncodingMode,
|
||||
exportBackendPreference,
|
||||
exportPipelineModel,
|
||||
experimentalNvidiaCudaExport,
|
||||
nvidiaCudaExportAvailable,
|
||||
borderRadius,
|
||||
padding,
|
||||
cropRegion,
|
||||
@@ -5425,6 +5471,13 @@ export default function VideoEditor() {
|
||||
onMp4FrameRateChange={setMp4FrameRate}
|
||||
exportPipelineModel={exportPipelineModel}
|
||||
onExportPipelineModelChange={setExportPipelineModel}
|
||||
experimentalNvidiaCudaExport={
|
||||
experimentalNvidiaCudaExport && nvidiaCudaExportAvailable
|
||||
}
|
||||
onExperimentalNvidiaCudaExportChange={
|
||||
setExperimentalNvidiaCudaExport
|
||||
}
|
||||
nvidiaCudaExportAvailable={nvidiaCudaExportAvailable}
|
||||
exportQuality={exportQuality}
|
||||
onExportQualityChange={setExportQuality}
|
||||
gifFrameRate={gifFrameRate}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface ExportConfig {
|
||||
backendPreference?: ExportBackendPreference;
|
||||
preferredRenderBackend?: ExportRenderBackend;
|
||||
experimentalNativeExport?: boolean;
|
||||
experimentalNvidiaCudaExport?: boolean;
|
||||
maxEncodeQueue?: number;
|
||||
maxDecodeQueue?: number;
|
||||
maxPendingFrames?: number;
|
||||
|
||||
Reference in new issue
Block a user