draggable

This commit is contained in:
Sam Lavigne 2023-08-20 12:01:50 -04:00
parent 64473bd201
commit 99b4143dd6
5 changed files with 76 additions and 39 deletions

20
package-lock.json generated
View File

@ -15,6 +15,8 @@
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2", "@sveltejs/vite-plugin-svelte": "^2.4.2",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-dnd-action": "^0.9.25",
"uuid": "^9.0.0",
"vite": "^4.4.5" "vite": "^4.4.5"
} }
}, },
@ -819,6 +821,15 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/svelte-dnd-action": {
"version": "0.9.25",
"resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.25.tgz",
"integrity": "sha512-6cW+5b+xYn2w1KaYdzdMiWOn4wzFq9KHpWMyYwixN4M+94RFw9Sn8GiRI0EPFoT5r6/RVD3d3lZ/OjllZlpYbg==",
"dev": true,
"peerDependencies": {
"svelte": ">=3.23.0"
}
},
"node_modules/svelte-hmr": { "node_modules/svelte-hmr": {
"version": "0.15.3", "version": "0.15.3",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
@ -831,6 +842,15 @@
"svelte": "^3.19.0 || ^4.0.0" "svelte": "^3.19.0 || ^4.0.0"
} }
}, },
"node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "4.4.9", "version": "4.4.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",

View File

@ -11,6 +11,8 @@
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2", "@sveltejs/vite-plugin-svelte": "^2.4.2",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-dnd-action": "^0.9.25",
"uuid": "^9.0.0",
"vite": "^4.4.5" "vite": "^4.4.5"
}, },
"dependencies": { "dependencies": {

View File

@ -7,10 +7,10 @@
import FilterPicker from "./FilterPicker.svelte"; import FilterPicker from "./FilterPicker.svelte";
import { FFmpeg } from "@ffmpeg/ffmpeg"; import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile, toBlobURL } from "@ffmpeg/util"; import { fetchFile, toBlobURL } from "@ffmpeg/util";
import {dndzone} from "svelte-dnd-action";
const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.2/dist/esm"; const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.2/dist/esm";
// const baseURL = ""; // const baseURL = "";
// const videoURL = "https://ffmpegwasm.netlify.app/video/video-15s.avi";
const TIMEOUT = 40000; const TIMEOUT = 40000;
const ffmpeg = new FFmpeg(); const ffmpeg = new FFmpeg();
@ -38,31 +38,39 @@
} }
async function transcode() { async function transcode() {
// try {
message = "Start transcoding"; message = "Start transcoding";
videoValue = null; videoValue = null;
rendering = true; rendering = true;
for (let vid of $inputs) { for (let vid of $inputs) {
await ffmpeg.writeFile(vid, await fetchFile("/" + vid)); await ffmpeg.writeFile(vid, await fetchFile("/" + vid));
} }
// const infile = await ffmpeg.readFile("example.mp4");
// videoValue = URL.createObjectURL(new Blob([infile.buffer], { type: "video/mp4" }));
// console.log("VIDEO", videoValue);
const clist = commandList(); const clist = commandList();
console.log(clist); console.log(clist);
// await ffmpeg.exec(["-hide_banner", "-i", "example.mp4", "-vf", "negate", "out.mp4", "-y"]);
await ffmpeg.exec(clist, TIMEOUT); await ffmpeg.exec(clist, TIMEOUT);
// await ffmpeg.exec(["-f", "lavfi", "-i", "color=size=1280x720:rate=25:color=red", "-t", "5", "out.mp4"])
message = "Complete transcoding"; message = "Complete transcoding";
const data = await ffmpeg.readFile("out.mp4"); const data = await ffmpeg.readFile("out.mp4");
rendering = false; rendering = false;
videoValue = URL.createObjectURL(new Blob([data.buffer], { type: "video/mp4" })); videoValue = URL.createObjectURL(new Blob([data.buffer], { type: "video/mp4" }));
rendering = false; rendering = false;
// console.log("VIDEO", videoValue);
// } catch (error) {
// console.log(error);
// }
} }
async function loadFFmpeg() {
ffmpeg.on("log", ({ message: msg }) => {
console.log(msg);
log += msg + "\n";
logbox.scrollTop = logbox.scrollHeight;
// message = msg;
});
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
// workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, "text/javascript"),
});
console.log(ffmpeg);
ffmpegLoaded = true;
}
function updateCommand() { function updateCommand() {
const cInputs = $inputs.map((i) => `-i ${i}`).join(" "); const cInputs = $inputs.map((i) => `-i ${i}`).join(" ");
@ -116,29 +124,20 @@
command.push("-pix_fmt"); command.push("-pix_fmt");
command.push("yuv420p"); command.push("yuv420p");
command.push("out.mp4"); command.push("out.mp4");
return command; return command;
} }
function handleFilterSort(e) {
filters.set(e.detail.items);
}
inputs.subscribe(updateCommand); inputs.subscribe(updateCommand);
output.subscribe(updateCommand); output.subscribe(updateCommand);
filters.subscribe(updateCommand); filters.subscribe(updateCommand);
onMount(async () => { onMount(async () => {
ffmpeg.on("log", ({ message: msg }) => { loadFFmpeg();
console.log(msg);
log += msg + "\n";
logbox.scrollTop = logbox.scrollHeight;
// message = msg;
});
await ffmpeg.load({
// coreURL: `${baseURL}/ffmpeg-core.js`,
// wasmURL: `${baseURL}/ffmpeg-core.wasm`,
// workerURL: `${baseURL}/ffmpeg-core.worker.js`,
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
// workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, "text/javascript"),
});
ffmpegLoaded = true;
}); });
</script> </script>
@ -148,18 +147,16 @@
<p> <p>
A tool to help you explore FFmpeg filters and options. To use: select one or more input videos A tool to help you explore FFmpeg filters and options. To use: select one or more input videos
(there are currently two options), export and add some filters, and then hit "render" to (there are currently two options), export and add some filters, and then hit "render" to
preview the output in browser. preview the output in browser. Note: this is a work in progress, many things may still be
Note: this is a work in progress, many things may still be broken! By <a href="https://lav.io" broken! By <a href="https://lav.io">Sam Lavigne</a>.
>Sam Lavigne</a
>.
</p> </p>
</section> </section>
<!-- {message} --> <!-- {message} -->
<section class="command"> <section class="command">
<textarea class="actual-command" bind:this={commandRef}>{command}</textarea> <textarea readonly class="actual-command" bind:this={commandRef}>{command}</textarea>
<div> <div>
<button on:click={copyCommand}>Copy</button> <button on:click={copyCommand}>Copy</button>
<button on:click={render} disabled={!ffmpegLoaded}> <button on:click={render} disabled={!ffmpegLoaded || rendering}>
{#if ffmpegLoaded} {#if ffmpegLoaded}
{#if rendering} {#if rendering}
Rendering... Rendering...
@ -185,10 +182,13 @@
<section class="log"> <section class="log">
<h3>FFmpeg Log</h3> <h3>FFmpeg Log</h3>
<textarea class="the-log" bind:this={logbox}>{log}</textarea> <textarea readonly class="the-log" bind:this={logbox}>{log}</textarea>
</section> </section>
<section class="preview"> <section class="preview">
{#if rendering}
<div class="rendering-video"><span>Rendering...</span></div>
{/if}
<video controls src={videoValue} /> <video controls src={videoValue} />
</section> </section>
@ -203,10 +203,10 @@
<div class="filter-picker"> <div class="filter-picker">
<FilterPicker select={"video"} /> <FilterPicker select={"video"} />
</div> </div>
<div class="filters-holder"> <div class="filters-holder" use:dndzone={{items:$filters}} on:consider={handleFilterSort} on:finalize={handleFilterSort}>
{#each $filters as f, index} {#each $filters as f (f.id)}
<div class="filter"> <div class="filter">
<Filter bind:filter={f} {index} /> <Filter bind:filter={f} />
</div> </div>
{/each} {/each}
</div> </div>
@ -247,6 +247,7 @@
.preview { .preview {
grid-area: prv; grid-area: prv;
position: relative;
} }
.output { .output {
@ -326,4 +327,16 @@
resize: none; resize: none;
flex: 1; flex: 1;
} }
.rendering-video {
position: absolute;
width: 100%;
height: 100%;
z-index: 2;
background-color: rgba(255, 255, 255, 0.8);
top: 0;
left: 0;
display: grid;
align-items: center;
justify-content: center;
}
</style> </style>

View File

@ -1,5 +1,4 @@
<script> <script>
import FILTERS from "./filters.json";
import { filters } from "./stores.js"; import { filters } from "./stores.js";
export let filter = { export let filter = {
@ -8,10 +7,10 @@
description: "", description: "",
}; };
export let index;
let show = false; let show = false;
function remove() { function remove() {
const index = $filters.findIndex((f) => f.id === filter.id);
$filters.splice(index, 1); $filters.splice(index, 1);
$filters = $filters; $filters = $filters;
} }
@ -21,7 +20,9 @@
<div class="head"> <div class="head">
<div class="name"><h3>{filter.name}<h3></div> <div class="name"><h3>{filter.name}<h3></div>
<div> <div>
<button on:click={() => show = !show}>{show ? "Hide" : "Show"} Options</button> {#if filter.params}
<button on:click={() => show = !show}>{show ? "Hide" : "Show"} Options</button>
{/if}
<button on:click={remove}>X</button> <button on:click={remove}>X</button>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
<script> <script>
import uFuzzy from "@leeoniya/ufuzzy"; import uFuzzy from "@leeoniya/ufuzzy";
import { v4 as uuidv4 } from 'uuid';
import FILTERS from "./filters.json"; import FILTERS from "./filters.json";
import { filters } from "./stores.js"; import { filters } from "./stores.js";
@ -27,7 +28,7 @@
} }
function add(f) { function add(f) {
const newFilter = { ...f }; const newFilter = { ...f, filterId: f.id, id: uuidv4() };
if (f.params) { if (f.params) {
newFilter.params = f.params.map((p) => { newFilter.params = f.params.map((p) => {
p.value = null; p.value = null;