a bit better
This commit is contained in:
parent
0fcdfacda8
commit
f708f57b37
247
src/App.svelte
247
src/App.svelte
|
@ -5,43 +5,46 @@
|
||||||
import Output from "./Output.svelte";
|
import Output from "./Output.svelte";
|
||||||
import Filter from "./Filter.svelte";
|
import Filter from "./Filter.svelte";
|
||||||
import FilterPicker from "./FilterPicker.svelte";
|
import FilterPicker from "./FilterPicker.svelte";
|
||||||
import Modal from "./Modal.svelte";
|
|
||||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||||
import { fetchFile, toBlobURL } from "@ffmpeg/util";
|
import { fetchFile, toBlobURL } from "@ffmpeg/util";
|
||||||
|
|
||||||
const baseURL = "https://unpkg.com/@ffmpeg/core-mt@0.12.2/dist/esm";
|
const baseURL = "https://unpkg.com/@ffmpeg/core-mt@0.12.2/dist/esm";
|
||||||
// const baseURL = "";
|
// const baseURL = "";
|
||||||
// const videoURL = "https://ffmpegwasm.netlify.app/video/video-15s.avi";
|
// const videoURL = "https://ffmpegwasm.netlify.app/video/video-15s.avi";
|
||||||
const videoURL = "/example.mp4";
|
|
||||||
const TIMEOUT = 40000;
|
const TIMEOUT = 40000;
|
||||||
|
|
||||||
const ffmpeg = new FFmpeg();
|
const ffmpeg = new FFmpeg();
|
||||||
|
|
||||||
let showFilterModal = false;
|
|
||||||
let command = "";
|
let command = "";
|
||||||
let message = "";
|
let message = "";
|
||||||
let videoValue = null;
|
let videoValue = "/" + $inputs[0];
|
||||||
let ffmpegLoaded = false;
|
let ffmpegLoaded = false;
|
||||||
let rendering = false;
|
let rendering = false;
|
||||||
|
let log = "";
|
||||||
|
let logbox;
|
||||||
|
let commandRef;
|
||||||
|
|
||||||
function newInput() {
|
function newInput() {
|
||||||
$inputs = [...$inputs, ""];
|
$inputs = [...$inputs, "punch.mp4"];
|
||||||
}
|
|
||||||
|
|
||||||
function newFilter() {
|
|
||||||
showFilterModal = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
transcode();
|
transcode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyCommand() {
|
||||||
|
commandRef.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
}
|
||||||
|
|
||||||
async function transcode() {
|
async function transcode() {
|
||||||
// try {
|
// try {
|
||||||
message = "Start transcoding";
|
message = "Start transcoding";
|
||||||
videoValue = null;
|
videoValue = null;
|
||||||
rendering = true;
|
rendering = true;
|
||||||
await ffmpeg.writeFile("example.mp4", await fetchFile(videoURL));
|
for (let vid of $inputs) {
|
||||||
|
await ffmpeg.writeFile(vid, await fetchFile("/" + vid));
|
||||||
|
}
|
||||||
// const infile = await ffmpeg.readFile("example.mp4");
|
// const infile = await ffmpeg.readFile("example.mp4");
|
||||||
// videoValue = URL.createObjectURL(new Blob([infile.buffer], { type: "video/mp4" }));
|
// videoValue = URL.createObjectURL(new Blob([infile.buffer], { type: "video/mp4" }));
|
||||||
// console.log("VIDEO", videoValue);
|
// console.log("VIDEO", videoValue);
|
||||||
|
@ -65,22 +68,7 @@
|
||||||
|
|
||||||
const cOutput = $output;
|
const cOutput = $output;
|
||||||
|
|
||||||
const cFilters = $filters
|
const cFilters = $filters.map(makeFilterArgs).join(",");
|
||||||
.map((f) => {
|
|
||||||
let fCommand = f.name;
|
|
||||||
if (f.params && f.params.length > 0) {
|
|
||||||
let params = f.params
|
|
||||||
.map((p) => {
|
|
||||||
if (p.value === "" || p.value === null) return null;
|
|
||||||
return `${p.name}=${p.value}`;
|
|
||||||
})
|
|
||||||
.filter((p) => p !== null)
|
|
||||||
.join(":");
|
|
||||||
fCommand += "=" + params;
|
|
||||||
}
|
|
||||||
return fCommand;
|
|
||||||
})
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
let out = `ffmpeg ${cInputs}`;
|
let out = `ffmpeg ${cInputs}`;
|
||||||
|
|
||||||
|
@ -102,13 +90,18 @@
|
||||||
})
|
})
|
||||||
.filter((p) => p !== null)
|
.filter((p) => p !== null)
|
||||||
.join(":");
|
.join(":");
|
||||||
fCommand += "=" + params;
|
if (params) fCommand += "=" + params;
|
||||||
}
|
}
|
||||||
return fCommand;
|
return fCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
function commandList() {
|
function commandList() {
|
||||||
let command = ["-i", "example.mp4"];
|
let command = [];
|
||||||
|
for (let vid of $inputs) {
|
||||||
|
command.push("-i");
|
||||||
|
command.push(vid);
|
||||||
|
}
|
||||||
|
// let command = ["-i", "example.mp4"];
|
||||||
|
|
||||||
// const audioFilters = $filters.filter(f => f.type[0] === "A").map(makeFilterArgs);
|
// const audioFilters = $filters.filter(f => f.type[0] === "A").map(makeFilterArgs);
|
||||||
// const videoFilters = $filters.filter(f => f.type[0] === "V").map(makeFilterArgs);
|
// const videoFilters = $filters.filter(f => f.type[0] === "V").map(makeFilterArgs);
|
||||||
|
@ -133,6 +126,8 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
ffmpeg.on("log", ({ message: msg }) => {
|
ffmpeg.on("log", ({ message: msg }) => {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
|
log += msg + "\n";
|
||||||
|
logbox.scrollTop = logbox.scrollHeight;
|
||||||
// message = msg;
|
// message = msg;
|
||||||
});
|
});
|
||||||
await ffmpeg.load({
|
await ffmpeg.load({
|
||||||
|
@ -143,45 +138,27 @@
|
||||||
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
|
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
|
||||||
workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, "text/javascript"),
|
workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, "text/javascript"),
|
||||||
});
|
});
|
||||||
console.log(ffmpeg);
|
|
||||||
ffmpegLoaded = true;
|
ffmpegLoaded = true;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{message}
|
<section class="header">
|
||||||
<section class="command">{command}</section>
|
<h1>FFmpeg Explorer</h1>
|
||||||
<section class="inputs">
|
<p>
|
||||||
<h3>Inputs</h3>
|
A tool to help you explore FFmpeg filters and options. To use: select one or more input videos
|
||||||
{#each $inputs as inp, index}
|
(there are currently two options), export and add some filters, and then hit "render" to
|
||||||
<Input bind:filename={inp} {index} />
|
preview the output in browser.
|
||||||
{/each}
|
Note: this is a work in progress, many things may still be broken! By <a href="https://lav.io"
|
||||||
<button on:click={newInput}>New Input</button>
|
>Sam Lavigne</a
|
||||||
|
>.
|
||||||
{#each $inputs as inp}
|
</p>
|
||||||
<p>{inp}</p>
|
|
||||||
{/each}
|
|
||||||
</section>
|
</section>
|
||||||
|
<!-- {message} -->
|
||||||
<section class="filters">
|
<section class="command">
|
||||||
<!-- {JSON.stringify($filters)} -->
|
<textarea class="actual-command" bind:this={commandRef}>{command}</textarea>
|
||||||
<h3>Filters</h3>
|
<div>
|
||||||
<button on:click={newFilter}>Add Filter</button>
|
<button on:click={copyCommand}>Copy</button>
|
||||||
<Modal bind:showModal={showFilterModal}>
|
|
||||||
<FilterPicker bind:showFilterModal />
|
|
||||||
</Modal>
|
|
||||||
<div class="filters-holder">
|
|
||||||
{#each $filters as f, index}
|
|
||||||
<div class="filter">
|
|
||||||
<Filter bind:filter={f} {index} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="output">
|
|
||||||
<h3>Output</h3>
|
|
||||||
<Output bind:filename={$output} />
|
|
||||||
<button on:click={render} disabled={!ffmpegLoaded}>
|
<button on:click={render} disabled={!ffmpegLoaded}>
|
||||||
{#if ffmpegLoaded}
|
{#if ffmpegLoaded}
|
||||||
{#if rendering}
|
{#if rendering}
|
||||||
|
@ -193,18 +170,160 @@
|
||||||
Loading ffmpeg
|
Loading ffmpeg
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{#if videoValue}
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inputs">
|
||||||
|
<div class="section-header">
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<button on:click={newInput}>Add Input</button>
|
||||||
|
</div>
|
||||||
|
{#each $inputs as inp, index}
|
||||||
|
<Input bind:filename={inp} {index} />
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="log">
|
||||||
|
<h3>FFmpeg Log</h3>
|
||||||
|
<textarea class="the-log" bind:this={logbox}>{log}</textarea>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="preview">
|
||||||
<video controls src={videoValue} />
|
<video controls src={videoValue} />
|
||||||
{/if}
|
</section>
|
||||||
|
|
||||||
|
<section class="output">
|
||||||
|
<h3>Output</h3>
|
||||||
|
<Output bind:filename={$output} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="filters">
|
||||||
|
<h3>Filters</h3>
|
||||||
|
<div class="inner-filters">
|
||||||
|
<div class="filter-picker">
|
||||||
|
<FilterPicker select={"video"} />
|
||||||
|
</div>
|
||||||
|
<div class="filters-holder">
|
||||||
|
{#each $filters as f, index}
|
||||||
|
<div class="filter">
|
||||||
|
<Filter bind:filter={f} {index} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-areas:
|
||||||
|
"hdr hdr hdr"
|
||||||
|
"cmd cmd cmd"
|
||||||
|
"inp log prv"
|
||||||
|
"out log prv"
|
||||||
|
"flt flt flt";
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border: 1px solid #999;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: rgb(245, 245, 245);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-area: hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command {
|
||||||
|
grid-area: cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
grid-area: inp;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
grid-area: prv;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output {
|
||||||
|
grid-area: out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
grid-area: log;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-filters {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
grid-area: flt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-picker {
|
||||||
|
max-height: 500px;
|
||||||
|
max-width: 400px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.filters-holder {
|
.filters-holder {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
flex: 1;
|
||||||
|
align-content: start;
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
margin: 10px;
|
/* width: 33%; */
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actual-command {
|
||||||
|
border: none;
|
||||||
|
margin-right: 10px;
|
||||||
|
resize: none;
|
||||||
|
flex: 1;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.section-header h3 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.the-log {
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
export let index;
|
export let index;
|
||||||
|
let show = false;
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
$filters.splice(index, 1);
|
$filters.splice(index, 1);
|
||||||
|
@ -18,13 +19,16 @@
|
||||||
|
|
||||||
<div class="filter-holder">
|
<div class="filter-holder">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<div class="name">{filter.name}</div>
|
<div class="name"><h3>{filter.name}<h3></div>
|
||||||
|
<div>
|
||||||
|
<button on:click={() => show = !show}>{show ? "Hide" : "Show"} Options</button>
|
||||||
<button on:click={remove}>X</button>
|
<button on:click={remove}>X</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="description">{filter.description}</div>
|
<div class="description">{filter.description}</div>
|
||||||
|
|
||||||
{#if filter.params.length > 0}
|
{#if filter.params && filter.params.length > 0 && show}
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{#each filter.params as p}
|
{#each filter.params as p}
|
||||||
<div class="param-holder">
|
<div class="param-holder">
|
||||||
|
@ -41,7 +45,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{:else if p.type == "float" || p.type == "double" || p.type == "long" || p.type == "int"}
|
{:else if p.type == "float" || p.type == "double" || p.type == "long" || p.type == "int"}
|
||||||
<input type="range" min={p.min} max={p.max} bind:value={p.value} />
|
<input step={p.type == "int" ? 1 : 0.01 } type="range" min={p.min} max={p.max} bind:value={p.value} />
|
||||||
<input bind:value={p.value} />
|
<input bind:value={p.value} />
|
||||||
{:else}
|
{:else}
|
||||||
<input bind:value={p.value} />
|
<input bind:value={p.value} />
|
||||||
|
@ -56,6 +60,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
.filter-holder {
|
.filter-holder {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -3,37 +3,55 @@
|
||||||
import FILTERS from "./filters.json";
|
import FILTERS from "./filters.json";
|
||||||
import { filters } from "./stores.js";
|
import { filters } from "./stores.js";
|
||||||
|
|
||||||
let allfilters = [...FILTERS];
|
export let select = "video";
|
||||||
|
$: selectedFilters = selectFilters(select);
|
||||||
|
$: allfilters = [...selectedFilters];
|
||||||
let q = "";
|
let q = "";
|
||||||
export let showFilterModal;
|
|
||||||
|
|
||||||
const uf = new uFuzzy();
|
const uf = new uFuzzy();
|
||||||
|
|
||||||
|
function selectFilters(sel) {
|
||||||
|
if (sel == "video") {
|
||||||
|
return FILTERS.filter(f => f.type[0] === "V")
|
||||||
|
} else if (sel == "audio") {
|
||||||
|
return FILTERS.filter(f => f.type[0] === "A")
|
||||||
|
} else {
|
||||||
|
return [...FILTERS];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
console.log('ressetting', select)
|
||||||
|
selectedFilters = selectFilters(select);
|
||||||
|
q = "";
|
||||||
|
}
|
||||||
|
|
||||||
function add(f) {
|
function add(f) {
|
||||||
const newFilter = { ...f };
|
const newFilter = { ...f };
|
||||||
|
if (f.params) {
|
||||||
newFilter.params = f.params.map((p) => {
|
newFilter.params = f.params.map((p) => {
|
||||||
p.value = null;
|
p.value = null;
|
||||||
if (p.default != null) p.value = p.default;
|
if (p.default != null) p.value = p.default;
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
$filters = [...$filters, newFilter];
|
$filters = [...$filters, newFilter];
|
||||||
showFilterModal = false;
|
|
||||||
console.log(newFilter);
|
console.log(newFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
let newFilters = [];
|
let newFilters = [];
|
||||||
const [idxs, info, order] = uf.search(
|
const [idxs, info, order] = uf.search(
|
||||||
FILTERS.map((m) => m.name + " " + m.description),
|
selectedFilters.map((m) => m.name + " " + m.description),
|
||||||
q
|
q
|
||||||
);
|
);
|
||||||
if (idxs) {
|
if (idxs) {
|
||||||
for (let i of idxs) {
|
for (let i of idxs) {
|
||||||
newFilters.push(FILTERS[i]);
|
newFilters.push(selectedFilters[i]);
|
||||||
}
|
}
|
||||||
allfilters = newFilters;
|
allfilters = newFilters;
|
||||||
} else {
|
} else {
|
||||||
allfilters = FILTERS;
|
allfilters = [...selectedFilters];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,11 +59,15 @@
|
||||||
<div class="holder">
|
<div class="holder">
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<input placeholder="Search Filters" on:keyup={update} bind:value={q} />
|
<input placeholder="Search Filters" on:keyup={update} bind:value={q} />
|
||||||
|
<select on:change={reset} bind:value={select}>
|
||||||
|
<option value="video">Video Filters</option>
|
||||||
|
<option value="audio">Audio Filters</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="all-filters">
|
<div class="all-filters">
|
||||||
{#each allfilters as f}
|
{#each allfilters as f}
|
||||||
<div class="filter" on:click={() => add(f)}>
|
<div class="filter" on:click={() => add(f)}>
|
||||||
<div class="name">{f.name} <span class="type">{f.type}</span></div>
|
<div class="name">{f.name} <span class="type">{f.type.replace("->", "⇒")}</span></div>
|
||||||
<div class="desc">{f.description}</div>
|
<div class="desc">{f.description}</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -56,7 +78,12 @@
|
||||||
.holder {
|
.holder {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 400px;
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.type {
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { inputs } from './stores.js';
|
import { inputs } from "./stores.js";
|
||||||
export let filename="";
|
export let filename = "punch.mp4";
|
||||||
export let index;
|
export let index;
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
|
@ -10,6 +10,21 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input bind:value={filename} />
|
<select bind:value={filename}>
|
||||||
<button on:click={remove}>Remove</button>
|
<option value="punch.mp4">punch.mp4</option>
|
||||||
|
<option value="shoe.mp4">shoe.mp4</option>
|
||||||
|
</select>
|
||||||
|
<button on:click={remove}>X</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
11
src/app.css
11
src/app.css
|
@ -1,13 +1,24 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
:root {
|
:root {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
color: #000;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
font: 16px Times, serif;
|
||||||
|
}
|
||||||
|
textrea, select, input, button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export const inputs = writable(["example.mp4"]);
|
export const inputs = writable(["punch.mp4"]);
|
||||||
export const output = writable("out.mp4");
|
export const output = writable("out.mp4");
|
||||||
export const filters = writable([]);
|
export const filters = writable([]);
|
||||||
|
|
Loading…
Reference in New Issue