Skip to content

Commit

Permalink
Add render pane
Browse files Browse the repository at this point in the history
  • Loading branch information
nahkd123 committed Oct 4, 2024
1 parent dc729b5 commit dc4e3aa
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 33 deletions.
52 changes: 19 additions & 33 deletions nahara-motion-ui/src/ui/bar/TopBar.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import { concat, EncoderPipeline, MuxerPipeline, SceneRenderPipeline, type IPipeline } from "@nahara/motion-video";
import type { IObjectContainer, ISceneContainerObject } from "@nahara/motion";
import { app } from "../../appglobal";
import { openMenuAt } from "../menu/MenuHost.svelte";
import type { IScene } from "@nahara/motion";
import { openPopupAt } from "../popup/PopupHost.svelte";
import RenderPane from "../render/RenderPane.svelte";
const currentProject = app.currentProjectStore;
const currentScene = app.currentSceneStore;
Expand All @@ -11,40 +12,25 @@
openMenuAt(e.clientX, e.clientY, [
{
type: "simple",
name: "Quick render",
name: "Render current scene",
async click() {
const frameRate = 60;
const pipeline: IPipeline<IScene, any, [any, any, Blob]> = concat(
new SceneRenderPipeline(
$currentScene!.metadata.size.x,
$currentScene!.metadata.size.y,
frameRate, frameRate * 10
),
new EncoderPipeline({
codec: "avc1.4d0028",
width: $currentScene!.metadata.size.x,
height: $currentScene!.metadata.size.y,
bitrate: 10_000_000,
framerate: frameRate
}),
new MuxerPipeline({
codec: "avc",
width: $currentScene!.metadata.size.x,
height: $currentScene!.metadata.size.y,
frameRate: frameRate
})
);
let maxEndTime = 0;
await pipeline.initialize(console.log);
pipeline.consume($currentScene!);
function findMaxEndTime(container: IObjectContainer) {
for (const obj of container) {
if (maxEndTime < obj.timeEnd) maxEndTime = obj.timeEnd;
if ((obj.object as ISceneContainerObject).isContainer)
findMaxEndTime(obj.object as ISceneContainerObject);
}
}
const result = await pipeline.finalize();
const blob = result[2];
const downloadLink = document.createElement("a");
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = "video.mp4";
downloadLink.click();
URL.revokeObjectURL(downloadLink.href);
findMaxEndTime($currentScene!);
openPopupAt(e.clientX, e.clientY, "Render scene", RenderPane, {
saveAs: `${$currentScene!.metadata.name ?? 'scene'}.mp4`,
resWidth: $currentScene!.metadata.size.x,
resHeight: $currentScene!.metadata.size.y,
duration: maxEndTime / 1000
});
},
}
]);
Expand Down
10 changes: 10 additions & 0 deletions nahara-motion-ui/src/ui/properties/PropertyValuePart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
export let color = "#ffffff";
export let name = "X";
export let value = 0;
export let min: number | undefined = undefined;
export let max: number | undefined = undefined;
const dispatcher = createEventDispatcher();
let dragging = false;
Expand All @@ -21,6 +23,8 @@
function handleDrag(e: MouseEvent) {
if (!dragging) return;
value += e.movementX * (e.shiftKey ? 0.1 : 1) * (e.ctrlKey ? 10 : 1) * (e.altKey ? 100 : 1);
if (min != null && value < min) value = min;
if (max != null && value > max) value = max;
dispatcher("update", value);
}
Expand All @@ -31,6 +35,8 @@
function handleWheel(e: WheelEvent) {
value += e.deltaY > 0 ? 1 : -1;
if (min != null && value < min) value = min;
if (max != null && value > max) value = max;
dispatcher("update", value);
}
Expand All @@ -43,10 +49,14 @@
}
function handleInput() {
if (min != null && value < min) value = min;
if (max != null && value > max) value = max;
dispatcher("update", value);
}
function handleInputDone() {
if (min != null && value < min) value = min;
if (max != null && value > max) value = max;
dispatcher("update", value);
}
</script>
Expand Down
179 changes: 179 additions & 0 deletions nahara-motion-ui/src/ui/render/RenderPane.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<script lang="ts">
import { concat, EncoderPipeline, MuxerPipeline, SceneRenderPipeline, type IPipeline } from "@nahara/motion-video";
import { app } from "../../appglobal";
import Button from "../input/Button.svelte";
import Dropdown from "../input/Dropdown.svelte";
import { openMenuAt } from "../menu/MenuHost.svelte";
import Property from "../properties/Property.svelte";
import PropertyValuePart from "../properties/PropertyValuePart.svelte";
import type { IScene } from "@nahara/motion";
interface EncoderProfile {
profile: string;
name: string;
maximumKbps: number;
}
interface EncoderType {
type: string;
name: string;
profiles: EncoderProfile[];
}
const Encoders: EncoderType[] = [
{
type: "avc",
name: "Advanced Video Codec (H.264)",
profiles: [
{ profile: "avc1.4d0028", name: "Main Profile", maximumKbps: 20_000 }
]
}
];
export let scene: IScene = app.getCurrentScene()!;
export let saveAs = "scene.mp4";
export let resWidth = 1920;
export let resHeight = 1080;
export let fps = 60;
export let duration = 10;
export let encoder = Encoders[0];
export let encoderProfile = encoder.profiles[0];
export let kbitRate = 10_000;
let rendering = false;
export async function beginRender() {
const pipeline: IPipeline<IScene, any, [any, any, Blob]> = concat(
new SceneRenderPipeline(
resWidth,
resHeight,
fps, Math.max(fps * duration, 1)
),
new EncoderPipeline({
codec: encoderProfile.profile,
width: resWidth,
height: resHeight,
bitrate: kbitRate * 1000,
framerate: fps
}),
new MuxerPipeline({
codec: "avc",
width: resWidth,
height: resHeight,
frameRate: fps
})
);
rendering = true;
await pipeline.initialize(console.log);
pipeline.consume(scene);
const result = await pipeline.finalize();
const blob = result[2];
const downloadLink = document.createElement("a");
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = saveAs;
downloadLink.click();
URL.revokeObjectURL(downloadLink.href);
rendering = false;
}
</script>

<div>
<table>
<tr>
<th scope="row">General options</th>
</tr>
<Property name="Save as" value={saveAs} on:update={e => saveAs = e.detail} />
<tr>
<th scope="row">Renderer options</th>
</tr>
<tr>
<th scope="row">Resolution</th>
<td><PropertyValuePart
name="Width" value={resWidth} min={1} max={10_000_000}
on:update={e => resWidth = e.detail}
/></td>
</tr>
<tr>
<td></td>
<td><PropertyValuePart
name="Height" value={resHeight} min={1} max={10_000_000}
on:update={e => resHeight = e.detail}
/></td>
</tr>
<tr>
<th scope="row">Frames per second</th>
<td><PropertyValuePart
name="FPS" value={fps} min={1}
on:update={e => fps = e.detail}
/></td>
</tr>
<tr>
<th scope="row">Duration (seconds)</th>
<td><PropertyValuePart
name="Sec" value={duration} min={0.001}
on:update={e => duration = e.detail}
/></td>
</tr>
<tr>
<th scope="row">Encoder options</th>
</tr>
<tr>
<th scope="row">Encoder</th>
<td><Dropdown label={encoder.name} on:click={e => openMenuAt(e.clientX, e.clientY, Encoders.map(v => ({
type: "simple",
name: v.name,
click() {
if (encoder == v) return;
encoder = v;
encoderProfile = v.profiles[0];
},
})))} /></td>
</tr>
<tr>
<th scope="row">Profile</th>
<td><Dropdown label={encoderProfile.name} on:click={e => openMenuAt(e.clientX, e.clientY, encoder.profiles.map(v => ({
type: "simple",
description: `${v.maximumKbps} kbps max`,
name: v.name,
click() {
if (encoderProfile == v) return;
encoderProfile = v;
}
})))} /></td>
</tr>
<tr>
<th scope="row">Bitrate (kbps)</th>
<td><PropertyValuePart
name="Kbps" value={kbitRate} min={1} max={encoderProfile.maximumKbps}
on:update={e => kbitRate = e.detail}
/></td>
</tr>
<tr>
<th scope="row">Actions</th>
<td><Button label="Render and download" disabled={rendering} on:click={beginRender} /></td>
</tr>
</table>
</div>

<style lang="scss">
table {
margin: 0;
border-spacing: 0;
td {
padding: 0;
}
th[scope="row"] {
text-align: left;
min-width: 100px;
font-weight: normal;
padding: 0 16px 0 8px;
height: 24px;
}
}
</style>

0 comments on commit dc4e3aa

Please sign in to comment.