Clarify export runtime copy
This commit is contained in:
1 parent
8d296c963e
commit
f469ab7444
4 files changed
+85
-32
No files matched your search
+5
-1
@@ -68,6 +68,7 @@ const welcomeOpen = ref(false)
|
|||||||
const fullscreenOpen = ref(false)
|
const fullscreenOpen = ref(false)
|
||||||
const aboutOpen = ref(false)
|
const aboutOpen = ref(false)
|
||||||
const helpOpen = ref(false)
|
const helpOpen = ref(false)
|
||||||
|
const isDesktop = computed(() => !!window.ruwioDesktop)
|
||||||
const TestOverlay = import.meta.env.DEV
|
const TestOverlay = import.meta.env.DEV
|
||||||
? defineAsyncComponent(() => import('./devtools/TestOverlay.vue'))
|
? defineAsyncComponent(() => import('./devtools/TestOverlay.vue'))
|
||||||
: null
|
: null
|
||||||
@@ -745,7 +746,10 @@ onBeforeUnmount(() => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="export-btn"
|
class="export-btn"
|
||||||
v-tooltip="{ label: t('app.export'), help: t('app.exportHelp') }"
|
v-tooltip="{
|
||||||
|
label: t('app.export'),
|
||||||
|
help: t(isDesktop ? 'app.exportHelpDesktop' : 'app.exportHelpServer'),
|
||||||
|
}"
|
||||||
@click="exportOpen = true"
|
@click="exportOpen = true"
|
||||||
>
|
>
|
||||||
<PhExport :size="14" weight="bold" />
|
<PhExport :size="14" weight="bold" />
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useProjectName } from '@/composables/useProjectName'
|
|||||||
import type { ButtonAsset, Layer } from '@/types/scene'
|
import type { ButtonAsset, Layer } from '@/types/scene'
|
||||||
import { Modal } from '@/components/ui'
|
import { Modal } from '@/components/ui'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { hasDesktopRender } from '@/lib/desktopRenderExport'
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: boolean }>()
|
const props = defineProps<{ modelValue: boolean }>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||||
@@ -45,6 +46,8 @@ function canRecordVideo(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const videoSupported = canRecordVideo()
|
const videoSupported = canRecordVideo()
|
||||||
|
const isDesktop = computed(() => !!window.ruwioDesktop)
|
||||||
|
const hasDesktopMp4 = computed(() => hasDesktopRender())
|
||||||
|
|
||||||
// Opt-in: strip layers that are hidden in the editor (`editorHidden`)
|
// Opt-in: strip layers that are hidden in the editor (`editorHidden`)
|
||||||
// from the exported scene. Default off — editor-hide is a workflow
|
// from the exported scene. Default off — editor-hide is a workflow
|
||||||
@@ -260,7 +263,7 @@ async function exportWebm() {
|
|||||||
progress.value = {
|
progress.value = {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
controller,
|
controller,
|
||||||
label: t('export.labelWebm'),
|
label: t(isDesktop.value ? 'export.labelWebmDesktop' : 'export.labelWebmBrowser'),
|
||||||
error: null,
|
error: null,
|
||||||
}
|
}
|
||||||
store.pause()
|
store.pause()
|
||||||
@@ -370,7 +373,7 @@ async function exportApngPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportMp4Server() {
|
async function exportMp4() {
|
||||||
if (progress.value) return
|
if (progress.value) return
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const base = (projectName.value ?? 'animation').toString()
|
const base = (projectName.value ?? 'animation').toString()
|
||||||
@@ -378,14 +381,14 @@ async function exportMp4Server() {
|
|||||||
progress.value = {
|
progress.value = {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
controller,
|
controller,
|
||||||
label: t('export.labelMp4'),
|
label: t(hasDesktopMp4.value ? 'export.labelMp4Desktop' : 'export.labelMp4Server'),
|
||||||
error: null,
|
error: null,
|
||||||
}
|
}
|
||||||
store.pause()
|
store.pause()
|
||||||
try {
|
try {
|
||||||
const scene = activeSceneClone()
|
const scene = activeSceneClone()
|
||||||
const { hasDesktopRender, renderMp4OnDesktop } = await import('@/lib/desktopRenderExport')
|
|
||||||
if (hasDesktopRender()) {
|
if (hasDesktopRender()) {
|
||||||
|
const { renderMp4OnDesktop } = await import('@/lib/desktopRenderExport')
|
||||||
const result = await renderMp4OnDesktop(scene, {
|
const result = await renderMp4OnDesktop(scene, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
fps: targetFps.value,
|
fps: targetFps.value,
|
||||||
@@ -593,14 +596,18 @@ function onUpdate(v: boolean) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="opt" @click="exportMp4Server">
|
<button class="opt" @click="exportMp4">
|
||||||
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
||||||
<div class="opt-text">
|
<div class="opt-text">
|
||||||
<div class="opt-title">
|
<div class="opt-title">
|
||||||
MP4 Video
|
MP4 Video
|
||||||
<span class="badge accent">{{ t('export.mp4Badge') }}</span>
|
<span class="badge accent">
|
||||||
|
{{ t(hasDesktopMp4 ? 'export.mp4DesktopBadge' : 'export.mp4ServerBadge') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="opt-desc">
|
||||||
|
{{ t(hasDesktopMp4 ? 'export.mp4DesktopDesc' : 'export.mp4ServerDesc') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="opt-desc">{{ t('export.mp4Desc') }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||||
</button>
|
</button>
|
||||||
@@ -610,10 +617,16 @@ function onUpdate(v: boolean) {
|
|||||||
<div class="opt-text">
|
<div class="opt-text">
|
||||||
<div class="opt-title">
|
<div class="opt-title">
|
||||||
WebM Video
|
WebM Video
|
||||||
<span class="badge">{{ t('export.webmBadge') }}</span>
|
<span class="badge">
|
||||||
|
{{ t(isDesktop ? 'export.webmDesktopBadge' : 'export.webmBrowserBadge') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="opt-desc">
|
<div class="opt-desc">
|
||||||
{{ videoSupported ? t('export.webmDesc') : t('export.webmNotSupported') }}
|
{{
|
||||||
|
videoSupported
|
||||||
|
? t(isDesktop ? 'export.webmDesktopDesc' : 'export.webmBrowserDesc')
|
||||||
|
: t('export.webmNotSupported')
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from '@phosphor-icons/vue'
|
} from '@phosphor-icons/vue'
|
||||||
import { Modal } from '@/components/ui'
|
import { Modal } from '@/components/ui'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { hasDesktopRender } from '@/lib/desktopRenderExport'
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: boolean }>()
|
const props = defineProps<{ modelValue: boolean }>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||||
@@ -63,6 +64,9 @@ interface Chapter {
|
|||||||
titleKey: string
|
titleKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDesktop = !!window.ruwioDesktop
|
||||||
|
const hasDesktopMp4 = hasDesktopRender()
|
||||||
|
|
||||||
const CHAPTERS: Chapter[] = [
|
const CHAPTERS: Chapter[] = [
|
||||||
{
|
{
|
||||||
id: 'getting-started',
|
id: 'getting-started',
|
||||||
@@ -509,8 +513,16 @@ const CHAPTERS: Chapter[] = [
|
|||||||
{ bodyKey: 'help.content.export.formats.runtime' },
|
{ bodyKey: 'help.content.export.formats.runtime' },
|
||||||
{ bodyKey: 'help.content.export.formats.scene' },
|
{ bodyKey: 'help.content.export.formats.scene' },
|
||||||
{ bodyKey: 'help.content.export.formats.html' },
|
{ bodyKey: 'help.content.export.formats.html' },
|
||||||
{ bodyKey: 'help.content.export.formats.mp4' },
|
{
|
||||||
{ bodyKey: 'help.content.export.formats.webm' },
|
bodyKey: hasDesktopMp4
|
||||||
|
? 'help.content.export.formats.mp4Desktop'
|
||||||
|
: 'help.content.export.formats.mp4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bodyKey: isDesktop
|
||||||
|
? 'help.content.export.formats.webmDesktop'
|
||||||
|
: 'help.content.export.formats.webm',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+44
-20
@@ -39,8 +39,10 @@ export const en = {
|
|||||||
untitledProject: 'Untitled project',
|
untitledProject: 'Untitled project',
|
||||||
saveProjectDirtyHelp:
|
saveProjectDirtyHelp:
|
||||||
'Your project has unsaved changes. Ctrl/Cmd+S writes them to browser storage.',
|
'Your project has unsaved changes. Ctrl/Cmd+S writes them to browser storage.',
|
||||||
exportHelp:
|
exportHelpServer:
|
||||||
'Save as runtime script, HTML, or video. MP4 needs the render server; WebM records in real time in the browser.',
|
'Save as runtime script, HTML, or video. MP4 uses the render server; WebM records in real time with MediaRecorder.',
|
||||||
|
exportHelpDesktop:
|
||||||
|
"Save as runtime script, HTML, or video. MP4 renders in the desktop app with bundled ffmpeg; WebM records in Electron's Chromium runtime.",
|
||||||
replaceDialog: {
|
replaceDialog: {
|
||||||
title: 'Replace project?',
|
title: 'Replace project?',
|
||||||
message:
|
message:
|
||||||
@@ -509,10 +511,14 @@ export const en = {
|
|||||||
sceneDesc: 'Scene data only — requires the runtime to be loaded',
|
sceneDesc: 'Scene data only — requires the runtime to be loaded',
|
||||||
exampleTitle: 'Example HTML',
|
exampleTitle: 'Example HTML',
|
||||||
exampleDesc: 'Standalone HTML — just open it in a browser',
|
exampleDesc: 'Standalone HTML — just open it in a browser',
|
||||||
mp4Badge: 'Server · Frame-accurate',
|
mp4ServerBadge: 'Server · Frame-by-frame',
|
||||||
mp4Desc: 'H.264, any resolution — requires render server',
|
mp4ServerDesc: 'H.264 export via render server',
|
||||||
webmBadge: 'Browser · Realtime',
|
mp4DesktopBadge: 'Desktop · Frame-by-frame',
|
||||||
webmDesc: 'No server — recorded in real time',
|
mp4DesktopDesc: 'H.264 export via bundled ffmpeg',
|
||||||
|
webmBrowserBadge: 'Browser · Realtime',
|
||||||
|
webmBrowserDesc: 'Recorded in real time with MediaRecorder',
|
||||||
|
webmDesktopBadge: 'Chromium · Realtime',
|
||||||
|
webmDesktopDesc: "Recorded in real time by Electron's Chromium runtime",
|
||||||
webmNotSupported: 'Browser does not support MediaRecorder',
|
webmNotSupported: 'Browser does not support MediaRecorder',
|
||||||
gifBadge: 'Browser · Frame-by-frame',
|
gifBadge: 'Browser · Frame-by-frame',
|
||||||
gifDesc: '256-colour palette, broadest compatibility',
|
gifDesc: '256-colour palette, broadest compatibility',
|
||||||
@@ -524,8 +530,10 @@ export const en = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
failed: 'Export failed',
|
failed: 'Export failed',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
labelWebm: 'Rendering WebM (browser, realtime)…',
|
labelWebmBrowser: 'Rendering WebM in browser realtime...',
|
||||||
labelMp4: 'Rendering MP4 (server)…',
|
labelWebmDesktop: 'Rendering WebM in Chromium realtime...',
|
||||||
|
labelMp4Server: 'Rendering MP4 with render server...',
|
||||||
|
labelMp4Desktop: 'Rendering MP4 in desktop app...',
|
||||||
labelGif: 'Rendering GIF (browser, frame-by-frame)…',
|
labelGif: 'Rendering GIF (browser, frame-by-frame)…',
|
||||||
labelApng: 'Rendering APNG (browser, frame-by-frame)…',
|
labelApng: 'Rendering APNG (browser, frame-by-frame)…',
|
||||||
labelApngPreview: 'Rendering APNG + preview page…',
|
labelApngPreview: 'Rendering APNG + preview page…',
|
||||||
@@ -1078,9 +1086,13 @@ play
|
|||||||
html:
|
html:
|
||||||
'<strong>HTML example:</strong> A quick standalone page with everything wired up.',
|
'<strong>HTML example:</strong> A quick standalone page with everything wired up.',
|
||||||
mp4:
|
mp4:
|
||||||
'<strong>MP4 video:</strong> Frame-accurate server render. This needs the render server configured in Settings.',
|
'<strong>MP4 video:</strong> Frame-by-frame H.264 export via the render server.',
|
||||||
|
mp4Desktop:
|
||||||
|
'<strong>MP4 video:</strong> Frame-by-frame H.264 export in the desktop app with bundled ffmpeg.',
|
||||||
webm:
|
webm:
|
||||||
'<strong>WebM video:</strong> Browser-side realtime recording. This depends on <code>MediaRecorder</code> support.',
|
'<strong>WebM video:</strong> Browser realtime recording with <code>MediaRecorder</code>.',
|
||||||
|
webmDesktop:
|
||||||
|
"<strong>WebM video:</strong> Realtime recording with <code>MediaRecorder</code> in Electron's Chromium runtime.",
|
||||||
},
|
},
|
||||||
skipHiddenTitle: 'Skip hidden layers',
|
skipHiddenTitle: 'Skip hidden layers',
|
||||||
skipHiddenBody:
|
skipHiddenBody:
|
||||||
@@ -1149,8 +1161,10 @@ export const de: DeepPartial<Messages> = {
|
|||||||
untitledProject: 'Unbenanntes Projekt',
|
untitledProject: 'Unbenanntes Projekt',
|
||||||
saveProjectDirtyHelp:
|
saveProjectDirtyHelp:
|
||||||
'Dein Projekt hat ungespeicherte Änderungen. Mit Ctrl/Cmd+S wird es im Browser gespeichert.',
|
'Dein Projekt hat ungespeicherte Änderungen. Mit Ctrl/Cmd+S wird es im Browser gespeichert.',
|
||||||
exportHelp:
|
exportHelpServer:
|
||||||
'Als Runtime, HTML oder Video exportieren. MP4 braucht den Render-Server. WebM wird direkt im Browser in Echtzeit aufgenommen.',
|
'Als Runtime, HTML oder Video exportieren. MP4 nutzt den Render-Server. WebM wird in Echtzeit mit MediaRecorder aufgenommen.',
|
||||||
|
exportHelpDesktop:
|
||||||
|
'Als Runtime, HTML oder Video exportieren. MP4 rendert in der Desktop-App mit gebündeltem ffmpeg. WebM wird in Electrons Chromium-Runtime aufgenommen.',
|
||||||
replaceDialog: {
|
replaceDialog: {
|
||||||
title: 'Projekt ersetzen?',
|
title: 'Projekt ersetzen?',
|
||||||
message:
|
message:
|
||||||
@@ -1363,10 +1377,14 @@ export const de: DeepPartial<Messages> = {
|
|||||||
sceneDesc: 'Nur Projektdaten: Die Runtime muss schon geladen sein.',
|
sceneDesc: 'Nur Projektdaten: Die Runtime muss schon geladen sein.',
|
||||||
exampleTitle: 'HTML-Beispiel',
|
exampleTitle: 'HTML-Beispiel',
|
||||||
exampleDesc: 'Eigenständige HTML-Datei: Einfach im Browser öffnen.',
|
exampleDesc: 'Eigenständige HTML-Datei: Einfach im Browser öffnen.',
|
||||||
mp4Badge: 'Server · Frame-genau',
|
mp4ServerBadge: 'Server · Frame-für-Frame',
|
||||||
mp4Desc: 'H.264 in jeder Auflösung: braucht den Render-Server.',
|
mp4ServerDesc: 'H.264-Export über den Render-Server',
|
||||||
webmBadge: 'Browser · Echtzeit',
|
mp4DesktopBadge: 'Desktop · Frame-für-Frame',
|
||||||
webmDesc: 'Ohne Server: direkt im Browser aufgenommen.',
|
mp4DesktopDesc: 'H.264-Export über gebündeltes ffmpeg',
|
||||||
|
webmBrowserBadge: 'Browser · Echtzeit',
|
||||||
|
webmBrowserDesc: 'Echtzeit-Aufnahme mit MediaRecorder',
|
||||||
|
webmDesktopBadge: 'Chromium · Echtzeit',
|
||||||
|
webmDesktopDesc: 'Echtzeit-Aufnahme durch Electrons Chromium-Runtime',
|
||||||
webmNotSupported: 'Dieser Browser unterstützt MediaRecorder nicht.',
|
webmNotSupported: 'Dieser Browser unterstützt MediaRecorder nicht.',
|
||||||
gifBadge: 'Browser · Frame-für-Frame',
|
gifBadge: 'Browser · Frame-für-Frame',
|
||||||
gifDesc: '256-Farben-Palette, maximale Kompatibilität.',
|
gifDesc: '256-Farben-Palette, maximale Kompatibilität.',
|
||||||
@@ -1378,8 +1396,10 @@ export const de: DeepPartial<Messages> = {
|
|||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
failed: 'Export fehlgeschlagen',
|
failed: 'Export fehlgeschlagen',
|
||||||
back: 'Zurück',
|
back: 'Zurück',
|
||||||
labelWebm: 'WebM wird gerendert (Browser, Echtzeit)…',
|
labelWebmBrowser: 'WebM wird in Browser-Echtzeit gerendert...',
|
||||||
labelMp4: 'MP4 wird gerendert (Server)…',
|
labelWebmDesktop: 'WebM wird in Chromium-Echtzeit gerendert...',
|
||||||
|
labelMp4Server: 'MP4 wird mit dem Render-Server gerendert...',
|
||||||
|
labelMp4Desktop: 'MP4 wird in der Desktop-App gerendert...',
|
||||||
labelGif: 'GIF wird gerendert (Browser, frame-für-frame)…',
|
labelGif: 'GIF wird gerendert (Browser, frame-für-frame)…',
|
||||||
labelApng: 'APNG wird gerendert (Browser, frame-für-frame)…',
|
labelApng: 'APNG wird gerendert (Browser, frame-für-frame)…',
|
||||||
labelApngPreview: 'APNG + Vorschau werden gerendert…',
|
labelApngPreview: 'APNG + Vorschau werden gerendert…',
|
||||||
@@ -1920,9 +1940,13 @@ play
|
|||||||
html:
|
html:
|
||||||
'<strong>HTML-Beispiel:</strong> Eine schnelle eigenständige Seite mit allem fertig verdrahtet.',
|
'<strong>HTML-Beispiel:</strong> Eine schnelle eigenständige Seite mit allem fertig verdrahtet.',
|
||||||
mp4:
|
mp4:
|
||||||
'<strong>MP4-Video:</strong> Frame-genauer Server-Render. Dafür muss der Render-Server in den Einstellungen konfiguriert sein.',
|
'<strong>MP4-Video:</strong> Frame-für-Frame-H.264-Export über den Render-Server.',
|
||||||
|
mp4Desktop:
|
||||||
|
'<strong>MP4-Video:</strong> Frame-für-Frame-H.264-Export in der Desktop-App mit gebündeltem ffmpeg.',
|
||||||
webm:
|
webm:
|
||||||
'<strong>WebM-Video:</strong> Browser-seitige Echtzeit-Aufnahme. Das hängt von <code>MediaRecorder</code> ab.',
|
'<strong>WebM-Video:</strong> Echtzeit-Aufnahme im Browser mit <code>MediaRecorder</code>.',
|
||||||
|
webmDesktop:
|
||||||
|
'<strong>WebM-Video:</strong> Echtzeit-Aufnahme mit <code>MediaRecorder</code> in Electrons Chromium-Runtime.',
|
||||||
},
|
},
|
||||||
skipHiddenTitle: 'Versteckte Ebenen auslassen',
|
skipHiddenTitle: 'Versteckte Ebenen auslassen',
|
||||||
skipHiddenBody:
|
skipHiddenBody:
|
||||||
|
|||||||
Reference in new issue
Block a user