Refactor playground page and init docs
This commit is contained in:
parent
9cd1f00ec7
commit
3f79cb5e81
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
slug: first-blog-post
|
|
||||||
title: First Blog Post
|
|
||||||
authors:
|
|
||||||
name: Gao Wei
|
|
||||||
title: Docusaurus Core Team
|
|
||||||
url: https://github.com/wgao19
|
|
||||||
image_url: https://github.com/wgao19.png
|
|
||||||
tags: [hola, docusaurus]
|
|
||||||
---
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
slug: long-blog-post
|
|
||||||
title: Long Blog Post
|
|
||||||
authors: endi
|
|
||||||
tags: [hello, docusaurus]
|
|
||||||
---
|
|
||||||
|
|
||||||
This is the summary of a very long blog post,
|
|
||||||
|
|
||||||
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
|
|
||||||
|
|
||||||
<!--truncate-->
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
|
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
slug: mdx-blog-post
|
|
||||||
title: MDX Blog Post
|
|
||||||
authors: [slorber]
|
|
||||||
tags: [docusaurus]
|
|
||||||
---
|
|
||||||
|
|
||||||
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
|
|
||||||
Use the power of React to create interactive blog posts.
|
|
||||||
|
|
||||||
```js
|
|
||||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
|
||||||
|
|
||||||
:::
|
|
Binary file not shown.
Before Width: | Height: | Size: 94 KiB |
@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
slug: welcome
|
|
||||||
title: Welcome
|
|
||||||
authors: [slorber, yangshun]
|
|
||||||
tags: [facebook, hello, docusaurus]
|
|
||||||
---
|
|
||||||
|
|
||||||
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
|
|
||||||
|
|
||||||
Simply add Markdown files (or folders) to the `blog` directory.
|
|
||||||
|
|
||||||
Regular blog authors can be added to `authors.yml`.
|
|
||||||
|
|
||||||
The blog post date can be extracted from filenames, such as:
|
|
||||||
|
|
||||||
- `2019-05-30-welcome.md`
|
|
||||||
- `2019-05-30-welcome/index.md`
|
|
||||||
|
|
||||||
A blog post folder can be convenient to co-locate blog post images:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The blog supports tags as well!
|
|
||||||
|
|
||||||
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
|
|
10
apps/website/blog/2022-10-8-release-ffmpeg.wasm-0.12.0.mdx
Normal file
10
apps/website/blog/2022-10-8-release-ffmpeg.wasm-0.12.0.mdx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
slug: release-ffmpeg.wasm-0.12.0
|
||||||
|
title: Release ffmpeg.wasm 0.12.0
|
||||||
|
authors: [jeromewu]
|
||||||
|
tags: [ffmpeg.wasm]
|
||||||
|
---
|
||||||
|
|
||||||
|
See what are the differences.
|
||||||
|
|
||||||
|
<!--truncate-->
|
@ -1,17 +1,5 @@
|
|||||||
endi:
|
jeromewu:
|
||||||
name: Endilie Yacop Sucipto
|
name: Jerome Wu
|
||||||
title: Maintainer of Docusaurus
|
title: Maintainer of ffmpeg.wasm
|
||||||
url: https://github.com/endiliey
|
url: https://github.com/jeromewu
|
||||||
image_url: https://github.com/endiliey.png
|
image_url: https://github.com/jeromewu.png
|
||||||
|
|
||||||
yangshun:
|
|
||||||
name: Yangshun Tay
|
|
||||||
title: Front End Engineer @ Facebook
|
|
||||||
url: https://github.com/yangshun
|
|
||||||
image_url: https://github.com/yangshun.png
|
|
||||||
|
|
||||||
slorber:
|
|
||||||
name: Sébastien Lorber
|
|
||||||
title: Docusaurus maintainer
|
|
||||||
url: https://sebastienlorber.com
|
|
||||||
image_url: https://github.com/slorber.png
|
|
||||||
|
1
apps/website/docs/contribution/core.md
Normal file
1
apps/website/docs/contribution/core.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# @ffmpeg/core
|
1
apps/website/docs/contribution/ffmpeg.md
Normal file
1
apps/website/docs/contribution/ffmpeg.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# @ffmpeg/ffmpeg
|
1
apps/website/docs/contribution/util.md
Normal file
1
apps/website/docs/contribution/util.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# @ffmpeg/util
|
1
apps/website/docs/faq.md
Normal file
1
apps/website/docs/faq.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# FAQ
|
1
apps/website/docs/getting-started/configuration.md
Normal file
1
apps/website/docs/getting-started/configuration.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Configuration
|
1
apps/website/docs/getting-started/installation.md
Normal file
1
apps/website/docs/getting-started/installation.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Installation
|
1
apps/website/docs/getting-started/lib-versions.md
Normal file
1
apps/website/docs/getting-started/lib-versions.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Library Versions
|
1
apps/website/docs/getting-started/multi-thread.md
Normal file
1
apps/website/docs/getting-started/multi-thread.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Use Mutlithreading
|
@ -1,8 +1,4 @@
|
|||||||
---
|
# Introduction
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Tutorial Intro
|
|
||||||
|
|
||||||
Let's discover **Docusaurus in less than 5 minutes**.
|
Let's discover **Docusaurus in less than 5 minutes**.
|
||||||
|
|
||||||
|
1
apps/website/docs/migration.md
Normal file
1
apps/website/docs/migration.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Migrating from 0.11.x to 0.12+
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Tutorial - Basics",
|
|
||||||
"position": 2,
|
|
||||||
"link": {
|
|
||||||
"type": "generated-index",
|
|
||||||
"description": "5 minutes to learn the most important Docusaurus concepts."
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 6
|
|
||||||
---
|
|
||||||
|
|
||||||
# Congratulations!
|
|
||||||
|
|
||||||
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
|
||||||
|
|
||||||
Docusaurus has **much more to offer**!
|
|
||||||
|
|
||||||
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
|
||||||
|
|
||||||
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
|
||||||
|
|
||||||
## What's next?
|
|
||||||
|
|
||||||
- Read the [official documentation](https://docusaurus.io/)
|
|
||||||
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
|
|
||||||
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
|
|
||||||
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
|
|
||||||
- Add a [search bar](https://docusaurus.io/docs/search)
|
|
||||||
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
|
|
||||||
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
|
|
@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Create a Blog Post
|
|
||||||
|
|
||||||
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
|
||||||
|
|
||||||
## Create your first Post
|
|
||||||
|
|
||||||
Create a file at `blog/2021-02-28-greetings.md`:
|
|
||||||
|
|
||||||
```md title="blog/2021-02-28-greetings.md"
|
|
||||||
---
|
|
||||||
slug: greetings
|
|
||||||
title: Greetings!
|
|
||||||
authors:
|
|
||||||
- name: Joel Marcey
|
|
||||||
title: Co-creator of Docusaurus 1
|
|
||||||
url: https://github.com/JoelMarcey
|
|
||||||
image_url: https://github.com/JoelMarcey.png
|
|
||||||
- name: Sébastien Lorber
|
|
||||||
title: Docusaurus maintainer
|
|
||||||
url: https://sebastienlorber.com
|
|
||||||
image_url: https://github.com/slorber.png
|
|
||||||
tags: [greetings]
|
|
||||||
---
|
|
||||||
|
|
||||||
Congratulations, you have made your first post!
|
|
||||||
|
|
||||||
Feel free to play around and edit this post as much you like.
|
|
||||||
```
|
|
||||||
|
|
||||||
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
|
|
@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 2
|
|
||||||
---
|
|
||||||
|
|
||||||
# Create a Document
|
|
||||||
|
|
||||||
Documents are **groups of pages** connected through:
|
|
||||||
|
|
||||||
- a **sidebar**
|
|
||||||
- **previous/next navigation**
|
|
||||||
- **versioning**
|
|
||||||
|
|
||||||
## Create your first Doc
|
|
||||||
|
|
||||||
Create a Markdown file at `docs/hello.md`:
|
|
||||||
|
|
||||||
```md title="docs/hello.md"
|
|
||||||
# Hello
|
|
||||||
|
|
||||||
This is my **first Docusaurus document**!
|
|
||||||
```
|
|
||||||
|
|
||||||
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
|
|
||||||
|
|
||||||
## Configure the Sidebar
|
|
||||||
|
|
||||||
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
|
||||||
|
|
||||||
Add metadata to customize the sidebar label and position:
|
|
||||||
|
|
||||||
```md title="docs/hello.md" {1-4}
|
|
||||||
---
|
|
||||||
sidebar_label: 'Hi!'
|
|
||||||
sidebar_position: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Hello
|
|
||||||
|
|
||||||
This is my **first Docusaurus document**!
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
|
||||||
|
|
||||||
```js title="sidebars.js"
|
|
||||||
module.exports = {
|
|
||||||
tutorialSidebar: [
|
|
||||||
'intro',
|
|
||||||
// highlight-next-line
|
|
||||||
'hello',
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Tutorial',
|
|
||||||
items: ['tutorial-basics/create-a-document'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
```
|
|
@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Create a Page
|
|
||||||
|
|
||||||
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
|
||||||
|
|
||||||
- `src/pages/index.js` → `localhost:3000/`
|
|
||||||
- `src/pages/foo.md` → `localhost:3000/foo`
|
|
||||||
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
|
|
||||||
|
|
||||||
## Create your first React Page
|
|
||||||
|
|
||||||
Create a file at `src/pages/my-react-page.js`:
|
|
||||||
|
|
||||||
```jsx title="src/pages/my-react-page.js"
|
|
||||||
import React from 'react';
|
|
||||||
import Layout from '@theme/Layout';
|
|
||||||
|
|
||||||
export default function MyReactPage() {
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<h1>My React page</h1>
|
|
||||||
<p>This is a React page</p>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
|
|
||||||
|
|
||||||
## Create your first Markdown Page
|
|
||||||
|
|
||||||
Create a file at `src/pages/my-markdown-page.md`:
|
|
||||||
|
|
||||||
```mdx title="src/pages/my-markdown-page.md"
|
|
||||||
# My Markdown page
|
|
||||||
|
|
||||||
This is a Markdown page
|
|
||||||
```
|
|
||||||
|
|
||||||
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
|
|
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 5
|
|
||||||
---
|
|
||||||
|
|
||||||
# Deploy your site
|
|
||||||
|
|
||||||
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
|
||||||
|
|
||||||
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
|
||||||
|
|
||||||
## Build your site
|
|
||||||
|
|
||||||
Build your site **for production**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
The static files are generated in the `build` folder.
|
|
||||||
|
|
||||||
## Deploy your site
|
|
||||||
|
|
||||||
Test your production build locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
|
|
||||||
|
|
||||||
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
|
|
@ -1,146 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 4
|
|
||||||
---
|
|
||||||
|
|
||||||
# Markdown Features
|
|
||||||
|
|
||||||
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
|
||||||
|
|
||||||
## Front Matter
|
|
||||||
|
|
||||||
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
|
||||||
|
|
||||||
```text title="my-doc.md"
|
|
||||||
// highlight-start
|
|
||||||
---
|
|
||||||
id: my-doc-id
|
|
||||||
title: My document title
|
|
||||||
description: My document description
|
|
||||||
slug: /my-custom-url
|
|
||||||
---
|
|
||||||
// highlight-end
|
|
||||||
|
|
||||||
## Markdown heading
|
|
||||||
|
|
||||||
Markdown text with [links](./hello.md)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
Regular Markdown links are supported, using url paths or relative file paths.
|
|
||||||
|
|
||||||
```md
|
|
||||||
Let's see how to [Create a page](/create-a-page).
|
|
||||||
```
|
|
||||||
|
|
||||||
```md
|
|
||||||
Let's see how to [Create a page](./create-a-page.md).
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:** Let's see how to [Create a page](./create-a-page.md).
|
|
||||||
|
|
||||||
## Images
|
|
||||||
|
|
||||||
Regular Markdown images are supported.
|
|
||||||
|
|
||||||
<!-- You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): -->
|
|
||||||
|
|
||||||
<!-- ```md -->
|
|
||||||
<!--  -->
|
|
||||||
<!-- ``` -->
|
|
||||||
|
|
||||||
<!--  -->
|
|
||||||
|
|
||||||
You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
|
|
||||||
|
|
||||||
## Code Blocks
|
|
||||||
|
|
||||||
Markdown code blocks are supported with Syntax highlighting.
|
|
||||||
|
|
||||||
```jsx title="src/components/HelloDocusaurus.js"
|
|
||||||
function HelloDocusaurus() {
|
|
||||||
return (
|
|
||||||
<h1>Hello, Docusaurus!</h1>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```jsx title="src/components/HelloDocusaurus.js"
|
|
||||||
function HelloDocusaurus() {
|
|
||||||
return <h1>Hello, Docusaurus!</h1>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Admonitions
|
|
||||||
|
|
||||||
Docusaurus has a special syntax to create admonitions and callouts:
|
|
||||||
|
|
||||||
:::tip My tip
|
|
||||||
|
|
||||||
Use this awesome feature option
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::danger Take care
|
|
||||||
|
|
||||||
This action is dangerous
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::tip My tip
|
|
||||||
|
|
||||||
Use this awesome feature option
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::danger Take care
|
|
||||||
|
|
||||||
This action is dangerous
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## MDX and React Components
|
|
||||||
|
|
||||||
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
export const Highlight = ({children, color}) => (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
backgroundColor: color,
|
|
||||||
borderRadius: '20px',
|
|
||||||
color: '#fff',
|
|
||||||
padding: '10px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
alert(`You clicked the color ${color} with label ${children}`)
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
|
||||||
|
|
||||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
|
||||||
```
|
|
||||||
|
|
||||||
export const Highlight = ({children, color}) => (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
backgroundColor: color,
|
|
||||||
borderRadius: '20px',
|
|
||||||
color: '#fff',
|
|
||||||
padding: '10px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
alert(`You clicked the color ${color} with label ${children}`);
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
|
||||||
|
|
||||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Tutorial - Extras",
|
|
||||||
"position": 3,
|
|
||||||
"link": {
|
|
||||||
"type": "generated-index"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Manage Docs Versions
|
|
||||||
|
|
||||||
Docusaurus can manage multiple versions of your docs.
|
|
||||||
|
|
||||||
## Create a docs version
|
|
||||||
|
|
||||||
Release a version 1.0 of your project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run docusaurus docs:version 1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
|
||||||
|
|
||||||
Your docs now have 2 versions:
|
|
||||||
|
|
||||||
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
|
|
||||||
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
|
|
||||||
|
|
||||||
## Add a Version Dropdown
|
|
||||||
|
|
||||||
To navigate seamlessly across versions, add a version dropdown.
|
|
||||||
|
|
||||||
Modify the `docusaurus.config.js` file:
|
|
||||||
|
|
||||||
```js title="docusaurus.config.js"
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
navbar: {
|
|
||||||
items: [
|
|
||||||
// highlight-start
|
|
||||||
{
|
|
||||||
type: 'docsVersionDropdown',
|
|
||||||
},
|
|
||||||
// highlight-end
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
The docs version dropdown appears in your navbar:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Update an existing version
|
|
||||||
|
|
||||||
It is possible to edit versioned docs in their respective folder:
|
|
||||||
|
|
||||||
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
|
|
||||||
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
|
|
@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 2
|
|
||||||
---
|
|
||||||
|
|
||||||
# Translate your site
|
|
||||||
|
|
||||||
Let's translate `docs/intro.md` to French.
|
|
||||||
|
|
||||||
## Configure i18n
|
|
||||||
|
|
||||||
Modify `docusaurus.config.js` to add support for the `fr` locale:
|
|
||||||
|
|
||||||
```js title="docusaurus.config.js"
|
|
||||||
module.exports = {
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Translate a doc
|
|
||||||
|
|
||||||
Copy the `docs/intro.md` file to the `i18n/fr` folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
|
||||||
|
|
||||||
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
|
|
||||||
|
|
||||||
## Start your localized site
|
|
||||||
|
|
||||||
Start your site on the French locale:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run start -- --locale fr
|
|
||||||
```
|
|
||||||
|
|
||||||
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
In development, you can only use one locale at a same time.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Add a Locale Dropdown
|
|
||||||
|
|
||||||
To navigate seamlessly across languages, add a locale dropdown.
|
|
||||||
|
|
||||||
Modify the `docusaurus.config.js` file:
|
|
||||||
|
|
||||||
```js title="docusaurus.config.js"
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
navbar: {
|
|
||||||
items: [
|
|
||||||
// highlight-start
|
|
||||||
{
|
|
||||||
type: 'localeDropdown',
|
|
||||||
},
|
|
||||||
// highlight-end
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
The locale dropdown now appears in your navbar:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Build your localized site
|
|
||||||
|
|
||||||
Build your site for a specific locale:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build -- --locale fr
|
|
||||||
```
|
|
||||||
|
|
||||||
Or build your site to include all the locales at once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
@ -76,8 +76,8 @@ const config = {
|
|||||||
position: "left",
|
position: "left",
|
||||||
label: "Docs",
|
label: "Docs",
|
||||||
},
|
},
|
||||||
{ to: "/blog", label: "Blog", position: "left" },
|
|
||||||
{ to: "/playground", label: "Playground", position: "left" },
|
{ to: "/playground", label: "Playground", position: "left" },
|
||||||
|
{ to: "/blog", label: "Blog", position: "left" },
|
||||||
{
|
{
|
||||||
href: "https://github.com/ffmpegwasm/ffmpeg.wasm",
|
href: "https://github.com/ffmpegwasm/ffmpeg.wasm",
|
||||||
label: "GitHub",
|
label: "GitHub",
|
||||||
|
@ -19,11 +19,28 @@ const sidebars = {
|
|||||||
// But you can create a sidebar manually
|
// But you can create a sidebar manually
|
||||||
tutorialSidebar: [
|
tutorialSidebar: [
|
||||||
"intro",
|
"intro",
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Getting Started",
|
||||||
|
items: [
|
||||||
|
"getting-started/installation",
|
||||||
|
"getting-started/configuration",
|
||||||
|
"getting-started/multi-thread",
|
||||||
|
"getting-started/lib-versions",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"migration",
|
||||||
|
"faq",
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "API",
|
label: "API",
|
||||||
items: ["api/classes/FFmpeg"],
|
items: ["api/classes/FFmpeg"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Contribution",
|
||||||
|
items: ["contribution/core", "contribution/ffmpeg", "contribution/util"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import LinearProgressWithLabel from "./LinearProgressWithLabel";
|
import LinearProgressWithLabel from "@site/src/components/common/LinearProgressWithLabel";
|
||||||
import { CORE_SIZE } from "./const";
|
import { CORE_SIZE } from "./const";
|
||||||
|
|
||||||
export default function CoreDownloader({ url, received }) {
|
export default function CoreDownloader({ url, received }) {
|
||||||
|
@ -19,12 +19,18 @@ export default function CoreSwitcher({ checked, onChange }: CoreSwitcherProps) {
|
|||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch checked={checked} onChange={onChange} />}
|
control={<Switch checked={checked} onChange={onChange} />}
|
||||||
label="Use Multi-thread"
|
label="Use Multithreading"
|
||||||
disabled={typeof SharedArrayBuffer !== "function"}
|
disabled={typeof SharedArrayBuffer !== "function"}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<Tooltip title="Multi-threaded core is faster, but unstable and not supported by all browsers. Click here for more details.">
|
<Tooltip title="Multi-threaded core is faster, but unstable and not supported by all browsers. Click here for more details.">
|
||||||
<IconButton aria-label="help" size="small">
|
<IconButton
|
||||||
|
aria-label="help"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
location.href = "/docs/getting-started/multi-thread";
|
||||||
|
}}
|
||||||
|
>
|
||||||
<HelpIcon fontSize="small" />
|
<HelpIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -1,387 +0,0 @@
|
|||||||
/// <reference types="ace" />
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Grid from "@mui/material/Grid";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import FolderIcon from "@mui/icons-material/Folder";
|
|
||||||
import List from "@mui/material/List";
|
|
||||||
import ListItem from "@mui/material/ListItem";
|
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
||||||
import ListItemText from "@mui/material/ListItemText";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
|
||||||
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
|
||||||
import UploadIcon from "@mui/icons-material/Upload";
|
|
||||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
|
||||||
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import Paper from "@mui/material/Paper";
|
|
||||||
import { useColorMode } from "@docusaurus/theme-common";
|
|
||||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
||||||
import { fetchFile } from "@ffmpeg/util";
|
|
||||||
import { downloadFile } from "@site/src/util";
|
|
||||||
import AceEditor from "react-ace";
|
|
||||||
import { getFFmpeg } from "./ffmpeg";
|
|
||||||
import { SAMPLE_FILES } from "./const";
|
|
||||||
import LinearProgressWithLabel from "./LinearProgressWithLabel";
|
|
||||||
import MoreButton from "./MoreButton";
|
|
||||||
import "ace-builds/src-noconflict/mode-json";
|
|
||||||
import "ace-builds/src-noconflict/mode-javascript";
|
|
||||||
import "ace-builds/src-noconflict/mode-text";
|
|
||||||
import "ace-builds/src-noconflict/theme-dracula";
|
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
|
||||||
import ListItemButton from "@mui/material/ListItemButton";
|
|
||||||
import Modal from "@mui/material/Modal";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
|
|
||||||
const defaultArgs = JSON.stringify(["-i", "video.avi", "video.mp4"], null, 2);
|
|
||||||
const options = [
|
|
||||||
{ text: "Download", key: "download" },
|
|
||||||
{ text: "Download as Text File", key: "download-text" },
|
|
||||||
{ text: "Delete", key: "delete" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const modalStyle = {
|
|
||||||
position: "absolute" as "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
width: 400,
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
p: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const genFFmpegText = (args: string) => {
|
|
||||||
let data: any = [];
|
|
||||||
try {
|
|
||||||
data = JSON.parse(args);
|
|
||||||
} catch (e) {}
|
|
||||||
return `// equivalent ffmpeg.wasm API call
|
|
||||||
ffmpeg.exec(${JSON.stringify(data)});
|
|
||||||
|
|
||||||
// equivalent ffmpeg command line
|
|
||||||
ffmpeg ${data.join(" ")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Editor() {
|
|
||||||
const { useState, useEffect } = React;
|
|
||||||
const [args, setArgs] = useState<string>(defaultArgs);
|
|
||||||
const [logs, setLogs] = useState<string[]>([]);
|
|
||||||
const [output, setOutput] = useState<Ace.Editor>();
|
|
||||||
const [path, setPath] = useState<string>("/");
|
|
||||||
const [nodes, setNodes] = useState([]);
|
|
||||||
const [progress, setProgress] = useState<number>(0);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [time, setTime] = useState(0);
|
|
||||||
const [folderName, setFolderName] = useState("");
|
|
||||||
const handleModalOpen = () => setOpen(true);
|
|
||||||
const handleModalClose = () => setOpen(false);
|
|
||||||
const { colorMode } = useColorMode();
|
|
||||||
|
|
||||||
const theme = colorMode === "dark" ? "github" : "dracula";
|
|
||||||
|
|
||||||
const scrollToEnd = () => {
|
|
||||||
output && output.renderer.scrollToLine(Number.POSITIVE_INFINITY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshDir = async (curPath: string) => {
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
if (ffmpeg.loaded) {
|
|
||||||
setNodes(
|
|
||||||
(await ffmpeg.listDir(curPath)).filter(({ name }) => name !== ".")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadSamples = async () => {
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
for (const name of Object.keys(SAMPLE_FILES)) {
|
|
||||||
await ffmpeg.writeFile(name, await fetchFile(SAMPLE_FILES[name]));
|
|
||||||
}
|
|
||||||
refreshDir(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exec = async () => {
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
setProgress(0);
|
|
||||||
setTime(0);
|
|
||||||
const logListener = ({ message }) => {
|
|
||||||
setLogs((_logs) => [..._logs, message]);
|
|
||||||
scrollToEnd();
|
|
||||||
};
|
|
||||||
const progListener = ({ progress: prog }) => {
|
|
||||||
setProgress(prog * 100);
|
|
||||||
};
|
|
||||||
ffmpeg.on(FFmpeg.LOG, logListener);
|
|
||||||
ffmpeg.on(FFmpeg.PROGRESS, progListener);
|
|
||||||
const start = performance.now();
|
|
||||||
await ffmpeg.exec(JSON.parse(args));
|
|
||||||
setTime(performance.now() - start);
|
|
||||||
ffmpeg.removeListener(FFmpeg.LOG, logListener);
|
|
||||||
ffmpeg.removeListener(FFmpeg.PROGRESS, progListener);
|
|
||||||
refreshDir(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cd = (name: string) => async () => {
|
|
||||||
let nextPath = path;
|
|
||||||
if (path === "/") {
|
|
||||||
if (name !== "..") nextPath = `/${name}`;
|
|
||||||
} else if (name === "..") {
|
|
||||||
const cols = path.split("/");
|
|
||||||
cols.pop();
|
|
||||||
nextPath = cols.length === 1 ? "/" : cols.join("/");
|
|
||||||
} else {
|
|
||||||
nextPath = `${path}/${name}`;
|
|
||||||
}
|
|
||||||
setPath(nextPath);
|
|
||||||
refreshDir(nextPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileUpload =
|
|
||||||
(isText: boolean = false) =>
|
|
||||||
async ({ target: { files } }: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const file = files[i];
|
|
||||||
let data: Uint8Array | string = await fetchFile(file);
|
|
||||||
if (isText) data = new TextDecoder().decode(data);
|
|
||||||
await ffmpeg.writeFile(`${path}/${file.name}`, data);
|
|
||||||
}
|
|
||||||
refreshDir(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleItemClick = (name: string) => async (option: string) => {
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
const fullPath = `${path}/${name}`;
|
|
||||||
switch (option) {
|
|
||||||
case "download":
|
|
||||||
downloadFile(
|
|
||||||
name,
|
|
||||||
((await ffmpeg.readFile(fullPath, "binary")) as Uint8Array).buffer
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "download-text":
|
|
||||||
downloadFile(name, await ffmpeg.readFile(fullPath, "utf8"));
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
await ffmpeg.deleteFile(fullPath);
|
|
||||||
refreshDir(path);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFolderCreate = async () => {
|
|
||||||
if (folderName !== "") {
|
|
||||||
await getFFmpeg().createDir(`${path}/${folderName}`);
|
|
||||||
}
|
|
||||||
refreshDir(path);
|
|
||||||
handleModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refreshDir(path);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
|
||||||
<Grid container spacing={{ xs: 1 }} columns={{ xs: 4, md: 12 }}>
|
|
||||||
<Grid item xs={4}>
|
|
||||||
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
|
||||||
<Stack justifyContent="space-between" style={{ height: "100%" }}>
|
|
||||||
<>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Typography>File System:</Typography>
|
|
||||||
<Box>
|
|
||||||
<Tooltip title="Upload a media file">
|
|
||||||
<IconButton
|
|
||||||
aria-label="upload-media-file"
|
|
||||||
component="label"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
hidden
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
onChange={handleFileUpload(false)}
|
|
||||||
/>
|
|
||||||
<UploadFileIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Upload a text file">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {}}
|
|
||||||
aria-label="upload-text"
|
|
||||||
component="label"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
hidden
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
onChange={handleFileUpload(true)}
|
|
||||||
/>
|
|
||||||
<UploadIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Create a new folder">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setFolderName("");
|
|
||||||
handleModalOpen();
|
|
||||||
}}
|
|
||||||
aria-label="create-a-new-folder"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<CreateNewFolderIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Refresh directory">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => refreshDir(path)}
|
|
||||||
aria-label="fresh"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<RefreshIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
<Typography>{`Path: ${path}`}</Typography>
|
|
||||||
<List style={{ height: 480, overflowX: "auto" }}>
|
|
||||||
{nodes.map(({ name, isDir }, index) =>
|
|
||||||
isDir ? (
|
|
||||||
<ListItemButton key={index} onClick={cd(name)}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<FolderIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={name} />
|
|
||||||
</ListItemButton>
|
|
||||||
) : (
|
|
||||||
<ListItem
|
|
||||||
key={index}
|
|
||||||
secondaryAction={
|
|
||||||
<MoreButton
|
|
||||||
options={options}
|
|
||||||
onItemClick={handleItemClick(name)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<InsertDriveFileIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={name} />
|
|
||||||
</ListItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</>
|
|
||||||
<Button onClick={loadSamples}>Load Sample Files</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={8}>
|
|
||||||
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
|
||||||
<Stack spacing={1}>
|
|
||||||
<Stack>
|
|
||||||
<Typography>Edit JSON below to update command:</Typography>
|
|
||||||
<AceEditor
|
|
||||||
mode="json"
|
|
||||||
theme={theme}
|
|
||||||
name="input-args"
|
|
||||||
fontSize={16}
|
|
||||||
showPrintMargin={true}
|
|
||||||
showGutter={true}
|
|
||||||
width="100%"
|
|
||||||
minLines={8}
|
|
||||||
maxLines={8}
|
|
||||||
highlightActiveLine={true}
|
|
||||||
value={args}
|
|
||||||
onChange={(value) => setArgs(value)}
|
|
||||||
setOptions={{ tabSize: 2 }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<AceEditor
|
|
||||||
mode="javascript"
|
|
||||||
theme={theme}
|
|
||||||
name="ffmpeg.wasm"
|
|
||||||
fontSize={16}
|
|
||||||
showGutter={false}
|
|
||||||
width="100%"
|
|
||||||
minLines={6}
|
|
||||||
maxLines={6}
|
|
||||||
readOnly
|
|
||||||
highlightActiveLine={false}
|
|
||||||
value={genFFmpegText(args)}
|
|
||||||
setOptions={{ tabSize: 2 }}
|
|
||||||
/>
|
|
||||||
<Typography>Console Output:</Typography>
|
|
||||||
<AceEditor
|
|
||||||
mode="text"
|
|
||||||
theme={theme}
|
|
||||||
name="console"
|
|
||||||
fontSize={16}
|
|
||||||
width="100%"
|
|
||||||
minLines={8}
|
|
||||||
maxLines={8}
|
|
||||||
readOnly
|
|
||||||
showPrintMargin={true}
|
|
||||||
highlightActiveLine={false}
|
|
||||||
value={logs.join("\n")}
|
|
||||||
setOptions={{ tabSize: 2 }}
|
|
||||||
onLoad={(editor) => setOutput(editor)}
|
|
||||||
/>
|
|
||||||
<Typography>Transcoding Progress:</Typography>
|
|
||||||
<LinearProgressWithLabel value={progress} />
|
|
||||||
<Stack direction="row" spacing={2} justifyContent="space-between">
|
|
||||||
<Typography>
|
|
||||||
{time === 0
|
|
||||||
? ""
|
|
||||||
: `Time Elapsed: ${(time / 1000).toFixed(2)} s`}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" onClick={exec}>
|
|
||||||
Run
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
onClose={handleModalClose}
|
|
||||||
aria-labelledby="new-folder-name"
|
|
||||||
aria-describedby="new-folder-name-description"
|
|
||||||
>
|
|
||||||
<Box sx={modalStyle}>
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
|
||||||
Folder Name:
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
id="outlined-basic"
|
|
||||||
label="my-folder"
|
|
||||||
variant="outlined"
|
|
||||||
value={folderName}
|
|
||||||
onChange={(event) => setFolderName(event.target.value)}
|
|
||||||
/>
|
|
||||||
<Stack direction="row" justifyContent="flex-end">
|
|
||||||
<Button onClick={handleModalClose}>Cancel</Button>
|
|
||||||
<Button variant="contained" onClick={handleFolderCreate}>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
121
apps/website/src/components/Playground/Workspace/Editor.tsx
Normal file
121
apps/website/src/components/Playground/Workspace/Editor.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/// <reference types="ace" />
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import LinearProgressWithLabel from "@site/src/components/common/LinearProgressWithLabel";
|
||||||
|
import { useColorMode } from "@docusaurus/theme-common";
|
||||||
|
import "ace-builds/src-noconflict/mode-json";
|
||||||
|
import "ace-builds/src-noconflict/mode-javascript";
|
||||||
|
import "ace-builds/src-noconflict/mode-text";
|
||||||
|
import "ace-builds/src-noconflict/theme-dracula";
|
||||||
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
|
||||||
|
const genFFmpegText = (args: string) => {
|
||||||
|
let data: any = [];
|
||||||
|
try {
|
||||||
|
data = JSON.parse(args);
|
||||||
|
} catch (e) {}
|
||||||
|
return `// equivalent ffmpeg.wasm API call
|
||||||
|
ffmpeg.exec(${JSON.stringify(data)});
|
||||||
|
|
||||||
|
// equivalent ffmpeg command line
|
||||||
|
ffmpeg ${data.join(" ")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EditorProps {
|
||||||
|
args: string;
|
||||||
|
logs: string[];
|
||||||
|
progress: number;
|
||||||
|
time: number;
|
||||||
|
onArgsUpdate: (args: string) => void;
|
||||||
|
onExec: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Editor({
|
||||||
|
args = "",
|
||||||
|
logs = [],
|
||||||
|
progress = 0,
|
||||||
|
time = 0,
|
||||||
|
onArgsUpdate,
|
||||||
|
onExec,
|
||||||
|
}: EditorProps) {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
const [output, setOutput] = useState<Ace.Editor>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// scroll logs to the end.
|
||||||
|
output && output.renderer.scrollToLine(Number.POSITIVE_INFINITY);
|
||||||
|
}, [logs]);
|
||||||
|
|
||||||
|
const theme = colorMode === "dark" ? "github" : "dracula";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Stack>
|
||||||
|
<Typography>Editor:</Typography>
|
||||||
|
<Typography>Edit arguments below to update command:</Typography>
|
||||||
|
<AceEditor
|
||||||
|
mode="json"
|
||||||
|
theme={theme}
|
||||||
|
name="input-args"
|
||||||
|
fontSize={16}
|
||||||
|
showPrintMargin={true}
|
||||||
|
showGutter={true}
|
||||||
|
width="100%"
|
||||||
|
minLines={8}
|
||||||
|
maxLines={8}
|
||||||
|
highlightActiveLine={true}
|
||||||
|
value={args}
|
||||||
|
onChange={onArgsUpdate}
|
||||||
|
setOptions={{ tabSize: 2 }}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<AceEditor
|
||||||
|
mode="javascript"
|
||||||
|
theme={theme}
|
||||||
|
name="ffmpeg.wasm"
|
||||||
|
fontSize={16}
|
||||||
|
showGutter={false}
|
||||||
|
width="100%"
|
||||||
|
minLines={6}
|
||||||
|
maxLines={6}
|
||||||
|
readOnly
|
||||||
|
highlightActiveLine={false}
|
||||||
|
value={genFFmpegText(args)}
|
||||||
|
setOptions={{ tabSize: 2 }}
|
||||||
|
/>
|
||||||
|
<Typography>Console Output:</Typography>
|
||||||
|
<AceEditor
|
||||||
|
mode="text"
|
||||||
|
theme={theme}
|
||||||
|
name="console"
|
||||||
|
fontSize={16}
|
||||||
|
width="100%"
|
||||||
|
minLines={8}
|
||||||
|
maxLines={8}
|
||||||
|
readOnly
|
||||||
|
showPrintMargin={true}
|
||||||
|
highlightActiveLine={false}
|
||||||
|
value={logs.join("\n")}
|
||||||
|
setOptions={{ tabSize: 2 }}
|
||||||
|
onLoad={(editor) => setOutput(editor)}
|
||||||
|
/>
|
||||||
|
<Typography>Transcoding Progress:</Typography>
|
||||||
|
<LinearProgressWithLabel value={progress} />
|
||||||
|
<Stack direction="row" spacing={2} justifyContent="space-between">
|
||||||
|
<Typography>
|
||||||
|
{time === 0 ? "" : `Time Elapsed: ${(time / 1000).toFixed(2)} s`}
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" onClick={onExec}>
|
||||||
|
Run
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
import React, { useState, ChangeEvent } from "react";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItem from "@mui/material/ListItem";
|
||||||
|
import ListItemButton from "@mui/material/ListItemButton";
|
||||||
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import Modal from "@mui/material/Modal";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import FolderIcon from "@mui/icons-material/Folder";
|
||||||
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
|
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
||||||
|
import UploadIcon from "@mui/icons-material/Upload";
|
||||||
|
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||||
|
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
||||||
|
import MoreButton from "./MoreButton";
|
||||||
|
import { Node } from "./types";
|
||||||
|
|
||||||
|
interface FileSystemManagerProps {
|
||||||
|
path: string;
|
||||||
|
nodes: Node[];
|
||||||
|
onFileUpload: (
|
||||||
|
isText: boolean
|
||||||
|
) => (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
||||||
|
onDirClick: (name: string) => () => Promise<void>;
|
||||||
|
onFileClick: (name: string) => (option: string) => Promise<void>;
|
||||||
|
onDirCreate: (name: string) => () => Promise<void>;
|
||||||
|
onRefresh: () => Promise<void>;
|
||||||
|
onLoadSamples: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalStyle = {
|
||||||
|
position: "absolute" as "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: 400,
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const options = [
|
||||||
|
{ text: "Download", key: "download" },
|
||||||
|
{ text: "Download as Text File", key: "download-text" },
|
||||||
|
{ text: "Delete", key: "delete" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function FileSystemManager({
|
||||||
|
path = "/",
|
||||||
|
nodes = [],
|
||||||
|
onFileUpload = () => () => Promise.resolve(),
|
||||||
|
onFileClick = () => () => Promise.resolve(),
|
||||||
|
onDirClick = () => () => Promise.resolve(),
|
||||||
|
onDirCreate = () => () => Promise.resolve(),
|
||||||
|
onRefresh = () => Promise.resolve(),
|
||||||
|
onLoadSamples = () => Promise.resolve(),
|
||||||
|
}: FileSystemManagerProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [dirName, setDirName] = useState("");
|
||||||
|
const handleModalOpen = () => setOpen(true);
|
||||||
|
const handleModalClose = () => setOpen(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
||||||
|
<Stack justifyContent="space-between" style={{ height: "100%" }}>
|
||||||
|
<>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Typography>File System:</Typography>
|
||||||
|
<Box>
|
||||||
|
<Tooltip title="Upload a media file">
|
||||||
|
<IconButton
|
||||||
|
aria-label="upload-media-file"
|
||||||
|
component="label"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
onChange={onFileUpload(false)}
|
||||||
|
/>
|
||||||
|
<UploadFileIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Upload a text file">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {}}
|
||||||
|
aria-label="upload-text"
|
||||||
|
component="label"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
onChange={onFileUpload(true)}
|
||||||
|
/>
|
||||||
|
<UploadIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Create a new folder">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDirName("");
|
||||||
|
handleModalOpen();
|
||||||
|
}}
|
||||||
|
aria-label="create-a-new-folder"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CreateNewFolderIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Refresh directory">
|
||||||
|
<IconButton
|
||||||
|
onClick={onRefresh}
|
||||||
|
aria-label="fresh"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<RefreshIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<Typography>{`Path: ${path}`}</Typography>
|
||||||
|
<List style={{ height: 480, overflowX: "auto" }}>
|
||||||
|
{nodes.map(({ name, isDir }, index) =>
|
||||||
|
isDir ? (
|
||||||
|
<ListItemButton key={index} onClick={onDirClick(name)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<FolderIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</ListItemButton>
|
||||||
|
) : (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
secondaryAction={
|
||||||
|
<MoreButton
|
||||||
|
options={options}
|
||||||
|
onItemClick={onFileClick(name)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<InsertDriveFileIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
<Button onClick={onLoadSamples}>Load Sample Files</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={handleModalClose}
|
||||||
|
aria-labelledby="new-folder-name"
|
||||||
|
aria-describedby="new-folder-name-description"
|
||||||
|
>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||||
|
Folder Name:
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
id="outlined-basic"
|
||||||
|
label="my-folder"
|
||||||
|
variant="outlined"
|
||||||
|
value={dirName}
|
||||||
|
onChange={(event) => setDirName(event.target.value)}
|
||||||
|
/>
|
||||||
|
<Stack direction="row" justifyContent="flex-end">
|
||||||
|
<Button onClick={handleModalClose}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
onDirCreate(dirName);
|
||||||
|
handleModalClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
154
apps/website/src/components/Playground/Workspace/index.tsx
Normal file
154
apps/website/src/components/Playground/Workspace/index.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
MutableRefObject,
|
||||||
|
} from "react";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||||
|
import { fetchFile } from "@ffmpeg/util";
|
||||||
|
import { downloadFile } from "@site/src/util";
|
||||||
|
import { Node } from "./types";
|
||||||
|
import FileSystemManager from "./FileSystemManager";
|
||||||
|
import { SAMPLE_FILES } from "../const";
|
||||||
|
import Editor from "./Editor";
|
||||||
|
|
||||||
|
const defaultArgs = JSON.stringify(["-i", "video.avi", "video.mp4"], null, 2);
|
||||||
|
|
||||||
|
interface WorkspaceProps {
|
||||||
|
ffmpeg: MutableRefObject<FFmpeg>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Workspace({ ffmpeg: _ffmpeg }: WorkspaceProps) {
|
||||||
|
const [path, setPath] = useState("/");
|
||||||
|
const [nodes, setNodes] = useState<Node[]>([]);
|
||||||
|
const [args, setArgs] = useState(defaultArgs);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [time, setTime] = useState(0);
|
||||||
|
const [logs, setLogs] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const ffmpeg = _ffmpeg.current;
|
||||||
|
|
||||||
|
const refreshDir = async (curPath: string) => {
|
||||||
|
if (ffmpeg.loaded) {
|
||||||
|
setNodes(
|
||||||
|
(await ffmpeg.listDir(curPath)).filter(({ name }) => name !== ".")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileUpload =
|
||||||
|
(isText: boolean) =>
|
||||||
|
async ({ target: { files } }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
let data: Uint8Array | string = await fetchFile(file);
|
||||||
|
if (isText) data = new TextDecoder().decode(data);
|
||||||
|
await ffmpeg.writeFile(`${path}/${file.name}`, data);
|
||||||
|
}
|
||||||
|
refreshDir(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileClick = (name: string) => async (option: string) => {
|
||||||
|
const fullPath = `${path}/${name}`;
|
||||||
|
switch (option) {
|
||||||
|
case "download":
|
||||||
|
downloadFile(
|
||||||
|
name,
|
||||||
|
((await ffmpeg.readFile(fullPath, "binary")) as Uint8Array).buffer
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "download-text":
|
||||||
|
downloadFile(name, await ffmpeg.readFile(fullPath, "utf8"));
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
await ffmpeg.deleteFile(fullPath);
|
||||||
|
refreshDir(path);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDirClick = (name: string) => async () => {
|
||||||
|
let nextPath = path;
|
||||||
|
if (path === "/") {
|
||||||
|
if (name !== "..") nextPath = `/${name}`;
|
||||||
|
} else if (name === "..") {
|
||||||
|
const cols = path.split("/");
|
||||||
|
cols.pop();
|
||||||
|
nextPath = cols.length === 1 ? "/" : cols.join("/");
|
||||||
|
} else {
|
||||||
|
nextPath = `${path}/${name}`;
|
||||||
|
}
|
||||||
|
setPath(nextPath);
|
||||||
|
refreshDir(nextPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDirCreate = (name: string) => async () => {
|
||||||
|
if (name !== "") {
|
||||||
|
await ffmpeg.createDir(`${path}/${name}`);
|
||||||
|
}
|
||||||
|
refreshDir(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLoadSamples = async () => {
|
||||||
|
for (const name of Object.keys(SAMPLE_FILES)) {
|
||||||
|
await ffmpeg.writeFile(name, await fetchFile(SAMPLE_FILES[name]));
|
||||||
|
}
|
||||||
|
refreshDir(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExec = async () => {
|
||||||
|
setProgress(0);
|
||||||
|
setTime(0);
|
||||||
|
const logListener = ({ message }) => {
|
||||||
|
setLogs((_logs) => [..._logs, message]);
|
||||||
|
};
|
||||||
|
const progListener = ({ progress: prog }) => {
|
||||||
|
setProgress(prog * 100);
|
||||||
|
};
|
||||||
|
ffmpeg.on(FFmpeg.LOG, logListener);
|
||||||
|
ffmpeg.on(FFmpeg.PROGRESS, progListener);
|
||||||
|
const start = performance.now();
|
||||||
|
await ffmpeg.exec(JSON.parse(args));
|
||||||
|
setTime(performance.now() - start);
|
||||||
|
ffmpeg.removeListener(FFmpeg.LOG, logListener);
|
||||||
|
ffmpeg.removeListener(FFmpeg.PROGRESS, progListener);
|
||||||
|
refreshDir(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshDir(path);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<Grid container spacing={{ xs: 1 }} columns={{ xs: 4, md: 12 }}>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<FileSystemManager
|
||||||
|
path={path}
|
||||||
|
nodes={nodes}
|
||||||
|
onFileUpload={onFileUpload}
|
||||||
|
onFileClick={onFileClick}
|
||||||
|
onDirClick={onDirClick}
|
||||||
|
onDirCreate={onDirCreate}
|
||||||
|
onLoadSamples={onLoadSamples}
|
||||||
|
onRefresh={() => refreshDir(path)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={8}>
|
||||||
|
<Editor
|
||||||
|
args={args}
|
||||||
|
logs={logs}
|
||||||
|
progress={progress}
|
||||||
|
time={time}
|
||||||
|
onArgsUpdate={(_args) => setArgs(_args)}
|
||||||
|
onExec={onExec}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface Node {
|
||||||
|
name: string;
|
||||||
|
isDir: boolean;
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
||||||
|
|
||||||
let ffmpeg = new FFmpeg();
|
|
||||||
|
|
||||||
export const getFFmpeg = () => ffmpeg;
|
|
@ -1,10 +1,9 @@
|
|||||||
import * as React from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import MuiThemeProvider from "@site/src/components/MuiThemeProvider";
|
import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider";
|
||||||
import CoreDownloader from "./CoreDownloader";
|
import CoreDownloader from "./CoreDownloader";
|
||||||
import Editor from "./Editor";
|
import Workspace from "./Workspace";
|
||||||
import { getFFmpeg } from "./ffmpeg";
|
|
||||||
import { CORE_URL, CORE_MT_URL } from "./const";
|
import { CORE_URL, CORE_MT_URL } from "./const";
|
||||||
import CoreSwitcher from "./CoreSwitcher";
|
import CoreSwitcher from "./CoreSwitcher";
|
||||||
|
|
||||||
@ -15,20 +14,20 @@ enum State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Playground() {
|
export default function Playground() {
|
||||||
const { useState, useEffect } = React;
|
|
||||||
const [state, setState] = useState(State.LOADED);
|
const [state, setState] = useState(State.LOADED);
|
||||||
const [isCoreMT, setIsCoreMT] = useState(false);
|
const [isCoreMT, setIsCoreMT] = useState(false);
|
||||||
const [url, setURL] = useState("");
|
const [url, setURL] = useState("");
|
||||||
const [received, setReceived] = useState(0);
|
const [received, setReceived] = useState(0);
|
||||||
|
const ffmpeg = useRef(new FFmpeg());
|
||||||
|
|
||||||
const load = async (mt: boolean = false) => {
|
const load = async (mt: boolean = false) => {
|
||||||
setState(State.LOADING);
|
setState(State.LOADING);
|
||||||
const ffmpeg = getFFmpeg();
|
ffmpeg.current.terminate();
|
||||||
ffmpeg.terminate();
|
ffmpeg.current.on(FFmpeg.DOWNLOAD, ({ url: _url, received: _received }) => {
|
||||||
ffmpeg.on(FFmpeg.DOWNLOAD, ({ url: _url, received: _received }) => {
|
|
||||||
setURL(_url as string);
|
setURL(_url as string);
|
||||||
setReceived(_received);
|
setReceived(_received);
|
||||||
});
|
});
|
||||||
await ffmpeg.load({
|
await ffmpeg.current.load({
|
||||||
coreURL: mt ? CORE_MT_URL : CORE_URL,
|
coreURL: mt ? CORE_MT_URL : CORE_URL,
|
||||||
thread: mt,
|
thread: mt,
|
||||||
});
|
});
|
||||||
@ -54,7 +53,7 @@ export default function Playground() {
|
|||||||
case State.LOADING:
|
case State.LOADING:
|
||||||
return <CoreDownloader url={url} received={received} />;
|
return <CoreDownloader url={url} received={received} />;
|
||||||
case State.LOADED:
|
case State.LOADED:
|
||||||
return <Editor />;
|
return <Workspace ffmpeg={ffmpeg} />;
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
11
apps/website/src/components/common/ThemedButton/index.tsx
Normal file
11
apps/website/src/components/common/ThemedButton/index.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MuiThemeProvider from "../MuiThemeProvider";
|
||||||
|
import Button, { ButtonProps } from "@mui/material/Button";
|
||||||
|
|
||||||
|
export default function ThemedButton(props: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<Button {...props} />
|
||||||
|
</MuiThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MuiThemeProvider from "../MuiThemeProvider";
|
||||||
|
import IconButton, { IconButtonProps } from "@mui/material/IconButton";
|
||||||
|
|
||||||
|
export default function ThemedIconButton(props: IconButtonProps) {
|
||||||
|
return (
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<IconButton {...props} />
|
||||||
|
</MuiThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -18,7 +18,7 @@ function HomepageHeader() {
|
|||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Link
|
<Link
|
||||||
className="button button--secondary button--lg"
|
className="button button--secondary button--lg"
|
||||||
to="/docs/intro"
|
to="/playground"
|
||||||
>
|
>
|
||||||
Try it Now!
|
Try it Now!
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -1,8 +1,104 @@
|
|||||||
import Playground from "@site/src/components/Playground";
|
import Playground from "@site/src/components/Playground";
|
||||||
|
import CoreSwitcher from "@site/src/components/Playground/CoreSwitcher";
|
||||||
|
import FileSystemManager from
|
||||||
|
"@site/src/components/Playground/Workspace/FileSystemManager";
|
||||||
|
import Editor from
|
||||||
|
"@site/src/components/Playground/Workspace/Editor";
|
||||||
|
import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider";
|
||||||
|
import ThemedButton from "@site/src/components/common/ThemedButton";
|
||||||
|
import ThemedIconButton from "@site/src/components/common/ThemedIconButton";
|
||||||
|
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
||||||
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
|
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
||||||
|
import UploadIcon from "@mui/icons-material/Upload";
|
||||||
|
|
||||||
# Playground
|
# Playground
|
||||||
|
|
||||||
Hi! Welcome to ffmpeg.wasm playground! Here you can try and test ffmpeg.wasm
|
Playground allows you to try ffmpeg.wasm without any installation and
|
||||||
with ease. :smile:
|
development!
|
||||||
|
|
||||||
|
:::tip Quick Start
|
||||||
|
|
||||||
|
1. Wait for assets (~32 MB) downloading.
|
||||||
|
2. Press <ThemedButton>Load Sample Files</ThemedButton> to download & add sample files.
|
||||||
|
3. Press <ThemedButton variant="contained">Run</ThemedButton> to convert an AVI file to MP4 file.
|
||||||
|
4. Download output files.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
<Playground />
|
<Playground />
|
||||||
|
|
||||||
|
<div style={{ height: 32 }} />
|
||||||
|
|
||||||
|
## How to Use :rocket:
|
||||||
|
|
||||||
|
> It is recommended to read [Introduction](/docs/intro) first to learn
|
||||||
|
ffmpeg.wasm fundamentals.
|
||||||
|
|
||||||
|
Demo Video:
|
||||||
|
<video width="100%" controls>
|
||||||
|
<source src="/video/playground-how-to.webm" type="video/webm" />
|
||||||
|
</video>
|
||||||
|
|
||||||
|
A typical flow to use ffmpeg.wasm is:
|
||||||
|
|
||||||
|
#### Download and load JavaScript & WebAssembly assets
|
||||||
|
|
||||||
|
The assets are downloaded automatically when you enter the Playground. You can
|
||||||
|
choose to use multithreading version instead by click on the switch:
|
||||||
|
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<CoreSwitcher />
|
||||||
|
</MuiThemeProvider>
|
||||||
|
|
||||||
|
#### Load files to in-memory File System
|
||||||
|
|
||||||
|
When ffmpeg.wasm is loaded and ready, you can upload files to its in-memory File
|
||||||
|
System to make sure these files can be consumed by the ffmpeg.wasm APIs:
|
||||||
|
|
||||||
|
<div style={{ maxWidth: 260 }}>
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<FileSystemManager
|
||||||
|
nodes={[
|
||||||
|
{name: "..", isDir: true},
|
||||||
|
{name: "tmp", isDir: true},
|
||||||
|
{name: "home", isDir: true},
|
||||||
|
{name: "dev", isDir: true},
|
||||||
|
{name: "proc", isDir: true},
|
||||||
|
{name: "video.avi", isDir: false},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</MuiThemeProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ height: 32 }} />
|
||||||
|
|
||||||
|
- <ThemedIconButton size="small"><UploadFileIcon fontSize="small"
|
||||||
|
/></ThemedIconButton>: Upload a media file.
|
||||||
|
- <ThemedIconButton size="small"><UploadIcon fontSize="small"
|
||||||
|
/></ThemedIconButton>: Upload a text file.
|
||||||
|
- <ThemedIconButton size="small"><CreateNewFolderIcon fontSize="small"
|
||||||
|
/></ThemedIconButton>: Create a new folder.
|
||||||
|
- <ThemedIconButton size="small"><RefreshIcon fontSize="small"
|
||||||
|
/></ThemedIconButton>: Refresh File System.
|
||||||
|
|
||||||
|
> Press <ThemedButton>Load Sample Files</ThemedButton> to load a set of samples
|
||||||
|
files.
|
||||||
|
|
||||||
|
#### Run a command
|
||||||
|
|
||||||
|
With files are ready in the File System, you can update arguments in the Editor
|
||||||
|
and hit <ThemedButton variant="contained">Run</ThemedButton> afterward:
|
||||||
|
|
||||||
|
<div style={{ maxWidth: 480 }}>
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<Editor args={JSON.stringify(["-i", "video.avi", "video.mp4"], null, 2)} />
|
||||||
|
</MuiThemeProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ height: 32 }} />
|
||||||
|
|
||||||
|
#### Download output files
|
||||||
|
|
||||||
|
Lastly you can download your files using File System panel and check the result.
|
||||||
|
:tada:
|
||||||
|
BIN
apps/website/static/video/playground-how-to.webm
Executable file
BIN
apps/website/static/video/playground-how-to.webm
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user