? Blobs { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/FSNode.cs:
--------------------------------------------------------------------------------
1 | namespace SpawnDev.BlazorJS.FFmpegWasm
2 | {
3 | public class FSNode
4 | {
5 | public string Name { get; set; }
6 | public bool IsDir { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/SpawnDev.BlazorJS.FFmpegWasm.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | 1.6.0
8 | True
9 | true
10 | true
11 | Embedded
12 | SpawnDev.BlazorJS.FFmpegWasm
13 | LostBeard
14 | SpawnDev.BlazorJS.FFmpegWasm is a Blazor WASM wrapper around ffmpeg.wasm and contains only the base ffmpeg.js and 814.ffmpeg.js files.
15 | https://github.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm
16 | README.md
17 | LICENSE.txt
18 | icon-128.png
19 | https://github.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm.git
20 | git
21 | Blazor;BlazorWebAssembly;FFmpegWasm;FFmpeg
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/WorkerFSBlobEntry.cs:
--------------------------------------------------------------------------------
1 | using SpawnDev.BlazorJS.JSObjects;
2 |
3 | namespace SpawnDev.BlazorJS.FFmpegWasm
4 | {
5 | public class WorkerFSBlobEntry
6 | {
7 | public string Name { get; set; }
8 | public Blob Data { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Components.Web
2 | @using SpawnDev.BlazorJS
3 | @using SpawnDev.BlazorJS.JSObjects
4 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/wwwroot/814.ffmpeg.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{var e={454:e=>{function t(e){return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}))}t.keys=()=>[],t.resolve=t,t.id=454,e.exports=t}},t={};function r(o){var s=t[o];if(void 0!==s)return s.exports;var a=t[o]={exports:{}};return e[o](a,a.exports,r),a.exports}return r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e="https://unpkg.com/@ffmpeg/core@0.12.9/dist/umd/ffmpeg-core.js";var t;!function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.FFPROBE="FFPROBE",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(t||(t={}));const o=new Error("unknown message type"),s=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),a=(new Error("called FFmpeg.terminate()"),new Error("failed to import ffmpeg-core.js"));let n;self.onmessage=async({data:{id:E,type:c,data:i}})=>{const p=[];let f;try{if(c!==t.LOAD&&!n)throw s;switch(c){case t.LOAD:f=await(async({coreURL:o,wasmURL:s,workerURL:E})=>{const c=!n;try{o||(o=e),importScripts(o)}catch{if(o&&o!==e||(o=e.replace("/umd/","/esm/")),self.createFFmpegCore=(await r(454)(o)).default,!self.createFFmpegCore)throw a}const i=o,p=s||o.replace(/.js$/g,".wasm"),f=E||o.replace(/.js$/g,".worker.js");return n=await self.createFFmpegCore({mainScriptUrlOrBlob:`${i}#${btoa(JSON.stringify({wasmURL:p,workerURL:f}))}`}),n.setLogger((e=>self.postMessage({type:t.LOG,data:e}))),n.setProgress((e=>self.postMessage({type:t.PROGRESS,data:e}))),c})(i);break;case t.EXEC:f=(({args:e,timeout:t=-1})=>{n.setTimeout(t),n.exec(...e);const r=n.ret;return n.reset(),r})(i);break;case t.FFPROBE:f=(({args:e,timeout:t=-1})=>{n.setTimeout(t),n.ffprobe(...e);const r=n.ret;return n.reset(),r})(i);break;case t.WRITE_FILE:f=(({path:e,data:t})=>(n.FS.writeFile(e,t),!0))(i);break;case t.READ_FILE:f=(({path:e,encoding:t})=>n.FS.readFile(e,{encoding:t}))(i);break;case t.DELETE_FILE:f=(({path:e})=>(n.FS.unlink(e),!0))(i);break;case t.RENAME:f=(({oldPath:e,newPath:t})=>(n.FS.rename(e,t),!0))(i);break;case t.CREATE_DIR:f=(({path:e})=>(n.FS.mkdir(e),!0))(i);break;case t.LIST_DIR:f=(({path:e})=>{const t=n.FS.readdir(e),r=[];for(const o of t){const t=n.FS.stat(`${e}/${o}`),s=n.FS.isDir(t.mode);r.push({name:o,isDir:s})}return r})(i);break;case t.DELETE_DIR:f=(({path:e})=>(n.FS.rmdir(e),!0))(i);break;case t.MOUNT:f=(({fsType:e,options:t,mountPoint:r})=>{const o=e,s=n.FS.filesystems[o];return!!s&&(n.FS.mount(s,t,r),!0)})(i);break;case t.UNMOUNT:f=(({mountPoint:e})=>(n.FS.unmount(e),!0))(i);break;default:throw o}}catch(e){return void self.postMessage({id:E,type:t.ERROR,data:e.toString()})}f instanceof Uint8Array&&p.push(f.buffer),self.postMessage({id:E,type:c,data:f},p)}})(),{}})()));
2 | //# sourceMappingURL=814.ffmpeg.js.map
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/wwwroot/Version 0.12.15.txt:
--------------------------------------------------------------------------------
1 | .
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasm/wwwroot/ffmpeg.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{"use strict";var e={m:{},d:(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})},u:e=>e+".ffmpeg.js"};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var t;e.g.importScripts&&(t=e.g.location+"");var s=e.g.document;if(!t&&s&&(s.currentScript&&(t=s.currentScript.src),!t)){var r=s.getElementsByTagName("script");if(r.length)for(var a=r.length-1;a>-1&&!t;)t=r[a--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=t})(),e.b=document.baseURI||self.location.href;var t,s={};e.r(s),e.d(s,{FFFSType:()=>n,FFmpeg:()=>i}),function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.FFPROBE="FFPROBE",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(t||(t={}));const r=(()=>{let e=0;return()=>e++})(),a=(new Error("unknown message type"),new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first")),o=new Error("called FFmpeg.terminate()");new Error("failed to import ffmpeg-core.js");class i{#e=null;#t={};#s={};#r=[];#a=[];loaded=!1;#o=()=>{this.#e&&(this.#e.onmessage=({data:{id:e,type:s,data:r}})=>{switch(s){case t.LOAD:this.loaded=!0,this.#t[e](r);break;case t.MOUNT:case t.UNMOUNT:case t.EXEC:case t.FFPROBE:case t.WRITE_FILE:case t.READ_FILE:case t.DELETE_FILE:case t.RENAME:case t.CREATE_DIR:case t.LIST_DIR:case t.DELETE_DIR:this.#t[e](r);break;case t.LOG:this.#r.forEach((e=>e(r)));break;case t.PROGRESS:this.#a.forEach((e=>e(r)));break;case t.ERROR:this.#s[e](r)}delete this.#t[e],delete this.#s[e]})};#i=({type:e,data:t},s=[],o)=>this.#e?new Promise(((a,i)=>{const n=r();this.#e&&this.#e.postMessage({id:n,type:e,data:t},s),this.#t[n]=a,this.#s[n]=i,o?.addEventListener("abort",(()=>{i(new DOMException(`Message # ${n} was aborted`,"AbortError"))}),{once:!0})})):Promise.reject(a);on(e,t){"log"===e?this.#r.push(t):"progress"===e&&this.#a.push(t)}off(e,t){"log"===e?this.#r=this.#r.filter((e=>e!==t)):"progress"===e&&(this.#a=this.#a.filter((e=>e!==t)))}load=({classWorkerURL:s,...r}={},{signal:a}={})=>(this.#e||(this.#e=s?new Worker(new URL(s,"file:///Users/focus/Projects/ffmpeg.wasm/packages/ffmpeg/dist/esm/classes.js"),{type:"module"}):new Worker(r.workerLoadURL),this.#o()),this.#i({type:t.LOAD,data:r},void 0,a));exec=(e,s=-1,{signal:r}={})=>this.#i({type:t.EXEC,data:{args:e,timeout:s}},void 0,r);ffprobe=(e,s=-1,{signal:r}={})=>this.#i({type:t.FFPROBE,data:{args:e,timeout:s}},void 0,r);terminate=()=>{const e=Object.keys(this.#s);for(const t of e)this.#s[t](o),delete this.#s[t],delete this.#t[t];this.#e&&(this.#e.terminate(),this.#e=null,this.loaded=!1)};writeFile=(e,s,{signal:r}={})=>{const a=[];return s instanceof Uint8Array&&a.push(s.buffer),this.#i({type:t.WRITE_FILE,data:{path:e,data:s}},a,r)};mount=(e,s,r)=>this.#i({type:t.MOUNT,data:{fsType:e,options:s,mountPoint:r}},[]);unmount=e=>this.#i({type:t.UNMOUNT,data:{mountPoint:e}},[]);readFile=(e,s="binary",{signal:r}={})=>this.#i({type:t.READ_FILE,data:{path:e,encoding:s}},void 0,r);deleteFile=(e,{signal:s}={})=>this.#i({type:t.DELETE_FILE,data:{path:e}},void 0,s);rename=(e,s,{signal:r}={})=>this.#i({type:t.RENAME,data:{oldPath:e,newPath:s}},void 0,r);createDir=(e,{signal:s}={})=>this.#i({type:t.CREATE_DIR,data:{path:e}},void 0,s);listDir=(e,{signal:s}={})=>this.#i({type:t.LIST_DIR,data:{path:e}},void 0,s);deleteDir=(e,{signal:s}={})=>this.#i({type:t.DELETE_DIR,data:{path:e}},void 0,s)}var n;return function(e){e.MEMFS="MEMFS",e.NODEFS="NODEFS",e.NODERAWFS="NODERAWFS",e.IDBFS="IDBFS",e.WORKERFS="WORKERFS",e.PROXYFS="PROXYFS"}(n||(n={})),s})()));
2 | //# sourceMappingURL=ffmpeg.js.map
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Not found
8 |
9 | Sorry, there's nothing at this address.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/AddSubtitles.razor:
--------------------------------------------------------------------------------
1 | @page "/AddSubtitles"
2 |
3 | Add Subtitles
4 | This basic demo takes an input video and an input subtitle file and merges them.
5 |
6 | AddSubtitles.razor
7 | AddSubtitles.razor.cs
8 |
9 |
10 |
11 | @outputFileName
12 |
13 |
14 |
15 |
16 | Load ffmpeg-core (~31 MB)
17 |
18 |
19 |
20 |
21 | Select a source video.
22 |
23 | Video File:
24 |
25 |
26 | Subtitle File:
27 |
28 |
29 |
Transcode
30 |
31 | Multithreading will be used: @FFmpegFactory.MultiThreadSupported
32 |
33 |
34 |
35 |
36 |
37 |
38 | @(Math.Round(percentComplete, 3)) %
39 |
40 |
41 |
42 | @logMessage
43 | @progressMessage
44 | Open Developer Tools (Ctrl+Shift+I) to View Logs
45 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/AddSubtitles.razor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components;
2 | using Microsoft.AspNetCore.Components.Forms;
3 | using SpawnDev.BlazorJS.FFmpegWasm;
4 | using SpawnDev.BlazorJS.JSObjects;
5 | using static System.Formats.Asn1.AsnWriter;
6 | using File = SpawnDev.BlazorJS.JSObjects.File;
7 |
8 | namespace SpawnDev.BlazorJS.FFmpegWasmDemo.Pages
9 | {
10 | public partial class AddSubtitles
11 | {
12 | [Inject]
13 | BlazorJSRuntime JS { get; set; }
14 |
15 | [Inject]
16 | FFmpegFactory FFmpegFactory { get; set; }
17 |
18 | bool loaded = false;
19 | bool busy = false;
20 | string logMessage = "";
21 | string progressMessage = "";
22 | FFmpeg? ffmpeg = null;
23 | ElementReference fileInputRef;
24 | ElementReference srtInputRef;
25 | HTMLInputElement? fileInput;
26 | HTMLInputElement? srtInput;
27 | ElementReference videoResult;
28 | HTMLVideoElement videoEl;
29 | bool beenInit = false;
30 | double percentComplete = 0;
31 | string outputURL = "";
32 | File? inputFileObj = null;
33 | File? srtFileObj = null;
34 |
35 | protected override void OnAfterRender(bool firstRender)
36 | {
37 | if (!beenInit)
38 | {
39 | beenInit = true;
40 | videoEl = new HTMLVideoElement(videoResult);
41 | fileInput = new HTMLInputElement(JS.ToJSRef(fileInputRef));
42 | fileInput.OnChange += FileInput_OnChange;
43 | srtInput = new HTMLInputElement(JS.ToJSRef(srtInputRef));
44 | srtInput.OnChange += SrtInput_OnChange;
45 | }
46 | }
47 |
48 | async Task Transcode()
49 | {
50 | if (inputFileObj == null || srtFileObj == null) return;
51 | await TranscodeLocalFile(inputFileObj, srtFileObj);
52 | }
53 |
54 | public void Dispose()
55 | {
56 | if (beenInit)
57 | {
58 | beenInit = false;
59 | fileInput.OnChange -= FileInput_OnChange;
60 | fileInput.Dispose();
61 | }
62 | if (ffmpeg != null)
63 | {
64 | ffmpeg.Terminate();
65 | ffmpeg.Dispose();
66 | ffmpeg = null;
67 | }
68 | }
69 |
70 | void FileInput_OnChange(Event ev)
71 | {
72 | using var files = fileInput!.Files;
73 | inputFileObj = files?.FirstOrDefault();
74 | StateHasChanged();
75 | }
76 | void SrtInput_OnChange(Event ev)
77 | {
78 | using var files = srtInput!.Files;
79 | srtFileObj = files?.FirstOrDefault();
80 | StateHasChanged();
81 | }
82 |
83 | async Task Load()
84 | {
85 | busy = true;
86 | StateHasChanged();
87 | await FFmpegFactory.Init();
88 | ffmpeg = new FFmpeg();
89 | ffmpeg.OnLog += FFmpeg_OnLog;
90 | ffmpeg.OnProgress += FFmpeg_OnProgress;
91 | // Use FFmpegFactory extension methods supplied by the Nuget packages
92 | // SpawnDev.BlazorJS.FFmpegWasm.Core
93 | // SpawnDev.BlazorJS.FFmpegWasm.CoreMT
94 | //
95 | // From SpawnDev.BlazorJS.FFmpegWasm.Core
96 | // - Contains the ffmpeg.wasm core for single thread files
97 | // - Adds CreateLoadCoreConfig to FFmpegFactory
98 | // From SpawnDev.BlazorJS.FFmpegWasm.CoreMT
99 | // - Contains the ffmpeg.wasm core for multi thread files
100 | // - Adds CreateLoadCoreMTConfig to FFmpegFactory
101 | // Single thread and multi thread versions acn be used independently of each other to lower resource packaging.
102 | var loadConfig = FFmpegFactory.MultiThreadSupported ? FFmpegFactory.CreateLoadCoreMTConfig() : FFmpegFactory.CreateLoadCoreConfig();
103 | await ffmpeg.Load(loadConfig);
104 | busy = false;
105 | loaded = true;
106 | StateHasChanged();
107 | }
108 |
109 | // https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg
110 | string outputFileName = "";
111 | string outputMimeType = "";
112 | async Task TranscodeLocalFile(File videoFile, File srtFile)
113 | {
114 | busy = true;
115 | StateHasChanged();
116 | // load input videoFile and srtFile
117 | var inputDir = "/input";
118 | await ffmpeg.CreateDir(inputDir);
119 | await ffmpeg.MountWorkerFS(inputDir, new FSMountWorkerFSOptions { Files = new[] { videoFile, srtFile } });
120 | var inputFile = $"{inputDir}/{videoFile.Name}";
121 | var srtPath = $"/{inputDir}/{srtFile.Name}";
122 | var pos = videoFile.Name.LastIndexOf(".");
123 | var inputFilenameBase = pos > -1 ? videoFile.Name.Substring(0, pos) : videoFile.Name;
124 | var outputFile = inputFilenameBase + ".mp4";
125 | // load font
126 | var fontFile = "/tmp/calibri-regular.ttf";
127 | var fontURL = "./fonts/calibri-regular.ttf";
128 | await ffmpeg.WriteFile(fontFile, await FFmpegFactory.FetchFile(fontURL));
129 | // transcode
130 | logMessage = "Transcoding source video";
131 | StateHasChanged();
132 | string font_name = "Calibri";
133 | string primary_colour = "&H8ffffff";
134 | string outline_colour = "&H00000000";
135 | string back_colour = "";
136 | string border_style = "0";
137 | string outline = "1";
138 | string shadow = "0";
139 | string marginv = "20";
140 | string font_size = "32";
141 | await ffmpeg.Exec(new string[] {
142 | "-i",
143 | inputFile,
144 | "-vf",
145 | $"subtitles={srtPath}:fontsdir=/tmp:force_style='Fontname='{font_name}',Fontsize={font_size},PrimaryColour={primary_colour},OutlineColour={outline_colour},BorderStyle={border_style},Outline={outline},Shadow={shadow},MarginV={marginv},BackColour={back_colour}',scale=1280:720",
146 | "-c:v",
147 | "libx264",
148 | "-preset",
149 | "ultrafast",
150 | "-c:a",
151 | "copy",
152 | "-y",
153 | outputFile
154 | });
155 | logMessage = "Source video transcoded";
156 | StateHasChanged();
157 | await ffmpeg.Unmount(inputDir);
158 | await ffmpeg.DeleteDir(inputDir);
159 | using var data = await ffmpeg.ReadFileUint8Array(outputFile);
160 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/mp4" });
161 | outputFileName = outputFile;
162 | var objSrc = URL.CreateObjectURL(blob);
163 | videoEl.Src = objSrc;
164 | outputURL = objSrc;
165 | busy = false;
166 | StateHasChanged();
167 | }
168 |
169 | void FFmpeg_OnLog(FFmpegLogEvent ev)
170 | {
171 | logMessage = ev.Message;
172 | JS.Log("FFmpeg_OnLog", ev.Message);
173 | StateHasChanged();
174 | }
175 |
176 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
177 | {
178 | var progress = ev.Progress;
179 | var time = ev.Time;
180 | progressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
181 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
182 | percentComplete = ev.Progress * 100d;
183 | StateHasChanged();
184 | }
185 |
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/BasicExample.razor:
--------------------------------------------------------------------------------
1 | @page "/AlternateSourceExample"
2 | @using System.Text
3 | @using SpawnDev.BlazorJS
4 | @using SpawnDev.BlazorJS.FFmpegWasm
5 |
6 | Basic Example of ffmpeg.wasm in Blazor
7 | A simple demo of using the official ffmpeg.wasm in Blazor using SpawnDev.BlazorJS.FFmpegWASM
8 |
9 | This example uses ffmpeg.wasm directly from unpkg.com CDN and transcodes a webm video file that is also remotely hosted.
10 | Only Nuget SpawnDev.BlazorJS.FFmpegWasm is needed.
11 |
12 |
13 |
14 | Load ffmpeg-core (~31 MB)
15 |
16 | Transcode webm to mp4
17 |
18 | @logMessage
19 | @progressMessage
20 | Open Developer Tools (Ctrl+Shift+I) to View Logs
21 |
22 | @code {
23 | [Inject]
24 | BlazorJSRuntime JS { get; set; }
25 |
26 | ElementReference videoResult;
27 | HTMLVideoElement? videoEl;
28 | bool loaded = false;
29 | bool busy = false;
30 | string logMessage = "";
31 | string progressMessage = "";
32 | // Unpkg urls for FFmpegWasm dist files
33 | // Current versions as of 2024-04-21
34 | // ffmpeg
35 | // https://unpkg.com/@ffmpeg/ffmpeg@0.12.10/dist/umd/ffmpeg.js
36 | // https://unpkg.com/@ffmpeg/ffmpeg@0.12.10/dist/umd/814.ffmpeg.js
37 | // core
38 | // https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd/ffmpeg-core.js
39 | // https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd/ffmpeg-core.wasm
40 | // core-mt
41 | // https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/umd/ffmpeg-core.js
42 | // https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/umd/ffmpeg-core.wasm
43 | // https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/umd/ffmpeg-core.worker.js
44 | static string Version = "0.12.10";
45 | static string CoreVersion = "0.12.6";
46 | string baseURLFFmpeg = $"https://unpkg.com/@ffmpeg/ffmpeg@{Version}/dist/umd";
47 | string baseURLCore = $"https://unpkg.com/@ffmpeg/core@{CoreVersion}/dist/umd";
48 | string baseURLCoreMT = $"https://unpkg.com/@ffmpeg/core-mt@{CoreVersion}/dist/umd";
49 |
50 | FFmpeg? ffmpeg = null;
51 |
52 | async Task Load()
53 | {
54 | busy = true;
55 | StateHasChanged();
56 | videoEl = new HTMLVideoElement(videoResult);
57 | if (JS.IsUndefined("FFmpegWASM"))
58 | {
59 | // a quick patch to allow us the ability to specify the full path to the primary ffmpeg worker (814.ffmpeg.js in umd version) via our FFMessageLoadConfig
60 | // the ffmpeg.js script tries to build the path location itself (with the code being replaced) but will fail (in our scenario) so we patch it to allow us to specify the path
61 | // essentially the same as Pull request #562 (https://github.com/ffmpegwasm/ffmpeg.wasm/pull/562) except this works on the minified UMD version
62 | // The ffmpeg.js included with SpawnDev.FFmpegWasm is pre-patched
63 | var FFmpegObjUrl = await ToBlobURL($"{baseURLFFmpeg}/ffmpeg.js", "application/javascript", (js) =>
64 | {
65 | var findStr = "new Worker(new URL(e.p+e.u(814),e.b),{type:void 0})";
66 | if (js.Contains(findStr))
67 | {
68 | js = js.Replace(findStr, "new Worker(r.workerLoadURL,{type:void 0})");
69 | JS.Log("ffmpeg.js patched.");
70 | }
71 | else
72 | {
73 | JS.Log("ffmpeg.js not patched.");
74 | }
75 | return js;
76 | });
77 | await JS.Import(FFmpegObjUrl);
78 | URL.RevokeObjectURL(FFmpegObjUrl);
79 | }
80 | ffmpeg = new FFmpeg();
81 | ffmpeg.OnLog += FFmpeg_OnLog;
82 | ffmpeg.OnProgress += FFmpeg_OnProgress;
83 | await ffmpeg.Load(new FFMessageLoadConfig
84 | {
85 | WorkerLoadURL = await ToBlobURL($"{baseURLFFmpeg}/814.ffmpeg.js", "application/javascript"),
86 | CoreURL = await ToBlobURL($"{baseURLCore}/ffmpeg-core.js", "application/javascript"),
87 | WasmURL = await ToBlobURL($"{baseURLCore}/ffmpeg-core.wasm", "application/wasm"),
88 | });
89 | JS.Set("_ffmpeg", ffmpeg);
90 | busy = false;
91 | loaded = true;
92 | StateHasChanged();
93 | }
94 |
95 | async Task Transcode()
96 | {
97 | busy = true;
98 | StateHasChanged();
99 | logMessage = "Downloading source video";
100 | StateHasChanged();
101 | await ffmpeg.WriteFile("input.webm", await FetchFile("https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm"));
102 | logMessage = "Transcoding source video";
103 | StateHasChanged();
104 | var ret = await ffmpeg.Exec(new string[] { "-i", "input.webm", "output.mp4" });
105 | logMessage = "Source video transcoded";
106 | StateHasChanged();
107 | using var data = await ffmpeg.ReadFileUint8Array("output.mp4");
108 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/mp4" });
109 | var objSrc = URL.CreateObjectURL(blob);
110 | videoEl.Src = objSrc;
111 | busy = false;
112 | StateHasChanged();
113 | }
114 |
115 | void FFmpeg_OnLog(FFmpegLogEvent ev)
116 | {
117 | logMessage = ev.Message;
118 | JS.Log("FFmpeg_OnLog", ev.Message);
119 | StateHasChanged();
120 | }
121 |
122 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
123 | {
124 | var progress = ev.Progress;
125 | var time = ev.Time;
126 | progressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
127 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
128 | StateHasChanged();
129 | }
130 |
131 | async Task FetchFile(string resource)
132 | {
133 | using var resp = await JS.Fetch(resource, new FetchOptions { });
134 | using var body = await resp.ArrayBuffer();
135 | return new Uint8Array(body);
136 | }
137 | async Task FetchText(string resource)
138 | {
139 | using var resp = await JS.Fetch(resource);
140 | return await resp.Text();
141 | }
142 | async Task ToBlobURL(string src, string mimeType)
143 | {
144 | using var data = await FetchFile(src);
145 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = mimeType });
146 | return URL.CreateObjectURL(blob);
147 | }
148 | async Task ToBlobURL(string src, string mimeType, Func patcher)
149 | {
150 | var text = await FetchText(src);
151 | if (patcher != null) text = patcher(text);
152 | using var blob = new Blob(new string[] { text }, new BlobOptions { Type = mimeType });
153 | return URL.CreateObjectURL(blob);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/BasicFactoryExample.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 | @using System.Text
3 | @using SpawnDev.BlazorJS
4 | @using SpawnDev.BlazorJS.FFmpegWasm
5 | @implements IDisposable
6 |
7 | Basic Demo of ffmpeg.wasm in Blazor
8 | A simple demo of ffmpeg.wasm in Blazor WASM using SpawnDev.BlazorJS.FFmpegWASM packages
9 | Page source code
10 |
11 |
12 |
13 | Load ffmpeg-core (~31 MB)
14 |
15 |
16 |
17 |
18 | Select file to test transcoding to mp4.
19 |
20 |
21 |
Use WorkerFS
22 |
23 |
Transcode to mp4
24 | Multithreading will be used: @FFmpegFactory.MultiThreadSupported
25 |
26 |
27 |
28 |
29 |
30 | @(Math.Round(percentComplete, 3)) %
31 |
32 |
33 |
34 | @logMessage
35 | @progressMessage
36 | Open Developer Tools (Ctrl+Shift+I) to View Logs
37 |
38 | @code {
39 | [Inject]
40 | BlazorJSRuntime JS { get; set; }
41 |
42 | [Inject]
43 | FFmpegFactory FFmpegFactory { get; set; }
44 |
45 | ElementReference videoResult;
46 | HTMLVideoElement? videoEl;
47 | bool loaded = false;
48 | bool busy = false;
49 | string logMessage = "";
50 | string progressMessage = "";
51 | FFmpeg? ffmpeg = null;
52 | ElementReference fileInputRef;
53 | HTMLInputElement? fileInput;
54 | bool beenInit = false;
55 | double percentComplete = 0;
56 | bool useWorkerFS = true;
57 | File? sourceFile = null;
58 |
59 | protected override void OnAfterRender(bool firstRender)
60 | {
61 | if (!beenInit)
62 | {
63 | beenInit = true;
64 | videoEl = new HTMLVideoElement(videoResult);
65 | fileInput = new HTMLInputElement(JS.ToJSRef(fileInputRef));
66 | fileInput.OnChange += FileInput_OnChange;
67 | }
68 | }
69 |
70 | public void Dispose()
71 | {
72 | if (beenInit)
73 | {
74 | beenInit = false;
75 | videoEl!.Dispose();
76 | fileInput!.OnChange -= FileInput_OnChange;
77 | fileInput.Dispose();
78 | }
79 | if (ffmpeg != null)
80 | {
81 | ffmpeg.Terminate();
82 | ffmpeg.Dispose();
83 | ffmpeg = null;
84 | }
85 | sourceFile?.Dispose();
86 | sourceFile = null;
87 | }
88 |
89 | void FileInput_OnChange(Event ev)
90 | {
91 | using var files = fileInput!.Files;
92 | sourceFile = files!.Length > 0 ? files[0] : null;
93 | StateHasChanged();
94 | }
95 |
96 | async Task Load()
97 | {
98 | busy = true;
99 | StateHasChanged();
100 | await FFmpegFactory.Init();
101 | ffmpeg = new FFmpeg();
102 | ffmpeg.OnLog += FFmpeg_OnLog;
103 | ffmpeg.OnProgress += FFmpeg_OnProgress;
104 | // Use FFmpegFactory extension methods supplied by the Nuget packages
105 | // SpawnDev.BlazorJS.FFmpegWasm.Core
106 | // SpawnDev.BlazorJS.FFmpegWasm.CoreMT
107 | //
108 | // From SpawnDev.BlazorJS.FFmpegWasm.Core
109 | // - Contains the ffmpeg.wasm core for single thread files
110 | // - Adds CreateLoadCoreConfig to FFmpegFactory
111 | // From SpawnDev.BlazorJS.FFmpegWasm.CoreMT
112 | // - Contains the ffmpeg.wasm core for multi thread files
113 | // - Adds CreateLoadCoreMTConfig to FFmpegFactory
114 | // Single thread and multi thread versions acn be used independently of each other to lower resource packaging.
115 | var loadConfig = FFmpegFactory.MultiThreadSupported ? FFmpegFactory.CreateLoadCoreMTConfig() : FFmpegFactory.CreateLoadCoreConfig();
116 | await ffmpeg.Load(loadConfig);
117 | busy = false;
118 | loaded = true;
119 | StateHasChanged();
120 | }
121 |
122 | async Task Transcode()
123 | {
124 | if (sourceFile == null) return;
125 | busy = true;
126 | StateHasChanged();
127 | var inputDir = "/input";
128 | var inputFile = $"/input/{sourceFile.Name}";
129 | await ffmpeg.CreateDir(inputDir);
130 | if (useWorkerFS)
131 | {
132 | // using WORKERFS. the file handle will be shared with the main worker
133 | await ffmpeg.MountWorkerFS(inputDir, new FSMountWorkerFSOptions { Files = new[] { sourceFile } });
134 | }
135 | else
136 | {
137 | // not using WORKERFS. the entire file will be read into memory and transferred to the main worker
138 | using var arrayBuffer = await sourceFile.ArrayBuffer();
139 | using var uint8Array = new Uint8Array(arrayBuffer);
140 | await ffmpeg.WriteFile(inputFile, uint8Array);
141 | }
142 | //var ls = await ffmpeg.ListDir(inputDir);
143 | logMessage = "Transcoding source video";
144 | StateHasChanged();
145 | var ret = await ffmpeg.Exec(new string[] { "-i", inputFile, "output.mp4" });
146 | logMessage = "Source video transcoded";
147 | StateHasChanged();
148 | // empty the input folder
149 | if (useWorkerFS)
150 | {
151 | await ffmpeg.Unmount(inputDir);
152 | }
153 | else
154 | {
155 | await ffmpeg.DeleteFile(inputFile);
156 | }
157 | // delete the input folder
158 | await ffmpeg.DeleteDir(inputDir);
159 | using var data = await ffmpeg.ReadFileUint8Array("output.mp4");
160 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/mp4" });
161 | var objSrc = URL.CreateObjectURL(blob);
162 | videoEl!.Src = objSrc;
163 | busy = false;
164 | StateHasChanged();
165 | }
166 |
167 | void FFmpeg_OnLog(FFmpegLogEvent ev)
168 | {
169 | logMessage = ev.Message;
170 | JS.Log("FFmpeg_OnLog", ev.Message);
171 | StateHasChanged();
172 | }
173 |
174 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
175 | {
176 | var progress = ev.Progress;
177 | var time = ev.Time;
178 | progressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
179 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
180 | percentComplete = ev.Progress * 100d;
181 | StateHasChanged();
182 | }
183 | }
184 |
185 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/Blank.razor:
--------------------------------------------------------------------------------
1 | @page "/blank"
2 |
3 | Blank
4 |
5 | @code {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/ExtractVideoFrames.razor:
--------------------------------------------------------------------------------
1 | @page "/ExtractVideoFrames"
2 |
3 | Extract Video Frames
4 | This demo uses ffmpeg.wasm to extract video frames.
5 |
6 | @outputFileName
7 |
8 |
9 |
10 |
11 | Select a source video.
12 |
13 |
14 |
15 |
Run
16 |
Cancel Run
17 |
18 | Multithreading will be used: @FFmpegFactory.MultiThreadSupported
19 |
20 |
21 |
22 |
23 |
24 |
25 | @(Math.Round(percentComplete, 3)) %
26 |
27 |
28 |
29 | @logMessage
30 | @progressMessage
31 | Open Developer Tools (Ctrl+Shift+I) to View Logs
32 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/ExtractVideoFrames.razor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components;
2 | using SpawnDev.BlazorJS.FFmpegWasm;
3 | using SpawnDev.BlazorJS.JSObjects;
4 | using SpawnDev.BlazorJS.Toolbox;
5 | using File = SpawnDev.BlazorJS.JSObjects.File;
6 |
7 | namespace SpawnDev.BlazorJS.FFmpegWasmDemo.Pages
8 | {
9 | public partial class ExtractVideoFrames
10 | {
11 | [Inject]
12 | BlazorJSRuntime JS { get; set; }
13 |
14 | [Inject]
15 | FFmpegFactory FFmpegFactory { get; set; }
16 |
17 | bool busy = false;
18 | string logMessage = "";
19 | string progressMessage = "";
20 | FFmpeg? ffmpeg = null;
21 | ElementReference fileInputRef;
22 | HTMLInputElement? fileInput;
23 | bool beenInit = false;
24 | double percentComplete = 0;
25 | string outputURL = "";
26 |
27 | protected override void OnAfterRender(bool firstRender)
28 | {
29 | if (!beenInit)
30 | {
31 | beenInit = true;
32 | fileInput = new HTMLInputElement(JS.ToJSRef(fileInputRef));
33 | fileInput.OnChange += FileInput_OnChange;
34 | }
35 | }
36 |
37 | public void Dispose()
38 | {
39 | CancelRun();
40 | _abortController?.Dispose();
41 | if (beenInit)
42 | {
43 | beenInit = false;
44 | fileInput.OnChange -= FileInput_OnChange;
45 | fileInput.Dispose();
46 | }
47 | if (ffmpeg != null)
48 | {
49 | ffmpeg.Terminate();
50 | ffmpeg.Dispose();
51 | ffmpeg = null;
52 | }
53 | }
54 |
55 | File? file = null;
56 |
57 | void FileInput_OnChange(Event ev)
58 | {
59 | using var files = fileInput!.Files;
60 | file = files?.FirstOrDefault();
61 | if (file == null) return;
62 |
63 | }
64 |
65 | async Task Run()
66 | {
67 | await TranscodeLocalFile();
68 | }
69 |
70 | AbortController? _abortController = null;
71 | void CancelRun()
72 | {
73 | _abortController?.Abort();
74 | Unload();
75 | }
76 | void Unload()
77 | {
78 | if (ffmpeg == null) return;
79 | ffmpeg.OnLog -= FFmpeg_OnLog;
80 | ffmpeg.OnProgress -= FFmpeg_OnProgress;
81 | ffmpeg.Terminate();
82 | ffmpeg = null;
83 | }
84 |
85 | async Task Load()
86 | {
87 | if (ffmpeg != null) return;
88 | await FFmpegFactory.Init();
89 | ffmpeg = new FFmpeg();
90 | ffmpeg.OnLog += FFmpeg_OnLog;
91 | ffmpeg.OnProgress += FFmpeg_OnProgress;
92 | // Use FFmpegFactory extension methods supplied by the Nuget packages
93 | // SpawnDev.BlazorJS.FFmpegWasm.Core
94 | // SpawnDev.BlazorJS.FFmpegWasm.CoreMT
95 | //
96 | // From SpawnDev.BlazorJS.FFmpegWasm.Core
97 | // - Contains the ffmpeg.wasm core for single thread files
98 | // - Adds CreateLoadCoreConfig to FFmpegFactory
99 | // From SpawnDev.BlazorJS.FFmpegWasm.CoreMT
100 | // - Contains the ffmpeg.wasm core for multi thread files
101 | // - Adds CreateLoadCoreMTConfig to FFmpegFactory
102 | // Single thread and multi thread versions acn be used independently of each other to lower resource packaging.
103 | var loadConfig = FFmpegFactory.MultiThreadSupported ? FFmpegFactory.CreateLoadCoreMTConfig() : FFmpegFactory.CreateLoadCoreConfig();
104 | await ffmpeg.Load(loadConfig);
105 | StateHasChanged();
106 | }
107 |
108 | // -ss 61.0 -t 2.5 -i StickAround.mp4 -filter_complex "[0:v] fps=12,scale=480:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" SmallerStickAround.gif
109 | string[] CreateExtractFramesCommand(string inputFile, string outputFile)
110 | {
111 | var ret = new string[] {
112 | "-i", inputFile,
113 | "-vsync", "0",
114 | outputFile,
115 | };
116 | return ret;
117 | }
118 |
119 | // https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg
120 | string outputFileName = "";
121 | async Task TranscodeLocalFile()
122 | {
123 | if (file == null) return;
124 | busy = true;
125 | StateHasChanged();
126 | await Load();
127 | if (ffmpeg == null) return;
128 | var outputDir = "/output";
129 | await ffmpeg.CreateDir(outputDir);
130 | var inputDir = "/input";
131 | var inputFile = $"{inputDir}/{file.Name}";
132 | var outputFile = $"{outputDir}/frame_%08d.png";
133 | await ffmpeg.CreateDir(inputDir);
134 | await ffmpeg.MountWorkerFS(inputDir, new FSMountWorkerFSOptions { Files = new[] { file } });
135 | var ls = await ffmpeg.ListDir(inputDir);
136 | logMessage = "Transcoding source video";
137 | StateHasChanged();
138 | _abortController?.Dispose();
139 | using var abortController = new AbortController();
140 | _abortController = abortController;
141 | using var signal = abortController.Signal;
142 | var cmd = CreateExtractFramesCommand(inputFile, outputFile);
143 | int result = -2;
144 | var cancelled = false;
145 | try
146 | {
147 | result = await ffmpeg.Exec(cmd, signal: signal);
148 | JS.Log("execTask", result);
149 | }
150 | catch (Exception ex)
151 | {
152 | // operation was cancelled
153 | var nmt = true;
154 | cancelled = true;
155 | logMessage = "Done";
156 | busy = false;
157 | StateHasChanged();
158 | return;
159 | }
160 | logMessage = "Done";
161 | StateHasChanged();
162 | await ffmpeg.Unmount(inputDir);
163 | await ffmpeg.DeleteDir(inputDir);
164 | var outputs = await ffmpeg.ListDir(outputDir);
165 | FileSystemDirectoryHandle? outputFolder = null;
166 | if (outputFolder != null)
167 | {
168 | foreach (var output in outputs)
169 | {
170 | if (new[] { ".", ".." }.Contains(output.Name)) continue;
171 | if (output.IsDir) continue;
172 | var fname = output.Name;
173 | using var fdata = await ffmpeg.ReadFileUint8Array($"{outputDir}/{fname}");
174 | await outputFolder.Write(fname, fdata);
175 | }
176 | }
177 | await ffmpeg.DeleteDir(outputDir);
178 | //using var data = await ffmpeg.ReadFileUint8Array(outputFile);
179 | //using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/gif" });
180 | //outputFileName = outputFile;
181 | //var objSrc = URL.CreateObjectURL(blob);
182 | //outputURL = objSrc;
183 | busy = false;
184 | _abortController = null;
185 | StateHasChanged();
186 | }
187 |
188 | void FFmpeg_OnLog(FFmpegLogEvent ev)
189 | {
190 | logMessage = ev.Message;
191 | JS.Log("FFmpeg_OnLog", ev.Message);
192 | StateHasChanged();
193 | }
194 |
195 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
196 | {
197 | var progress = ev.Progress;
198 | var time = ev.Time;
199 | progressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
200 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
201 | percentComplete = ev.Progress * 100d;
202 | StateHasChanged();
203 | }
204 |
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/Index.razor:
--------------------------------------------------------------------------------
1 | @page "/MultipleExamples"
2 |
3 | SpawnDev.BlazorJS.FFmpegWasm Demo
4 |
5 | SpawnDev.BlazorJS.FFmpegWasm
6 |
7 | SpawnDev.BlazorJS.FFmpegWasm is a Blazor WASM wrapper around
ffmpeg.wasm . This is a demo of that wrapper.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/RealTimeVideoProcessing.razor:
--------------------------------------------------------------------------------
1 | @page "/RealTimeVideoProcessing"
2 | @using SpawnDev.BlazorJS.JSObjects
3 | @implements IDisposable
4 |
5 |
13 |
14 | @code {
15 | // Based on:
16 | // Real-time video filters in browsers with FFmpeg and webcodecs
17 | // https://transloadit.com/devtips/real-time-video-filters-in-browsers-with-ffmpeg-and-webcodecs/
18 | [Inject]
19 | BlazorJSRuntime JS { get; set; } = default!;
20 |
21 | [Inject]
22 | FFmpegFactory FFmpegFactory { get; set; } = default!;
23 |
24 | MediaStream? stream = null;
25 | TransformStreamCallbacks? transformerCallbacks = null;
26 | TransformStream? transformStream = null;
27 | Task? transformerTask = null;
28 | ElementReference videoRef;
29 | HTMLVideoElement? video;
30 | FFmpeg? ffmpeg = null;
31 | Window? window = null;
32 |
33 | protected override async Task OnAfterRenderAsync(bool firstRender)
34 | {
35 | if (firstRender)
36 | {
37 | window = JS.Get("window");
38 | video = new HTMLVideoElement(videoRef);
39 |
40 | // load ffmpeg libs
41 | await FFmpegFactory.Init();
42 |
43 | // create ffmpeg instance
44 | ffmpeg = new FFmpeg();
45 |
46 | // load ffmpeg config
47 | var loadConfig = FFmpegFactory.CreateLoadCoreConfig();
48 | await ffmpeg.Load(loadConfig);
49 |
50 | transformerCallbacks = new TransformStreamCallbacks(Transformer_Start, Transformer_Transform, Transformer_Flush);
51 | // Start the video stream
52 | using var navigator = JS.Get("navigator");
53 | stream = await navigator.MediaDevices.GetUserMedia(new { video = true });
54 | if (stream != null)
55 | {
56 | using var inputTrack = stream.GetFirstVideoTrack();
57 | using var processor = new MediaStreamTrackProcessor(new MediaStreamTrackProcessorOptions { Track = inputTrack });
58 | using var generator = new MediaStreamTrackGenerator(new MediaStreamTrackGeneratorOptions { Kind = "video" });
59 |
60 | transformStream = new TransformStream(transformerCallbacks);
61 | // Pipe the processor through the transformer to the generator
62 | transformerTask = processor.Readable.PipeThrough(transformStream).PipeTo(generator.Writable);
63 |
64 | // Display the output stream in the video element
65 | video.SrcObject = new MediaStream([generator]);
66 | await video.Play();
67 | }
68 | }
69 | }
70 | async Task Transformer_Start(TransformStreamDefaultController controller)
71 | {
72 | Console.WriteLine("Transformer_Start");
73 | }
74 | async Task Transformer_Transform(VideoFrame chunk, TransformStreamDefaultController controller)
75 | {
76 | if (ffmpeg == null || window == null)
77 | {
78 | controller.Error("FFmpeg or Window not initialized.");
79 | return;
80 | }
81 | try
82 | {
83 | var w = chunk.DisplayWidth;
84 | var h = chunk.DisplayHeight;
85 | using var canvas = new OffscreenCanvas(w, h);
86 | using var ctx = canvas.Get2DContext();
87 | ctx.DrawImage(chunk, 0, 0, w, h);
88 |
89 | // Convert canvas to PNG Blob, then ArrayBuffer
90 | using var blob = await canvas.ConvertToBlob(new ConvertToBlobOptions { Type = "image/png" });
91 | using var arrayBuffer = await blob.ArrayBuffer();
92 |
93 | // Write input PNG to FFmpeg"s virtual filesystem
94 | var inputFilename = "in.png";
95 | var outputFilename = "out.png";
96 | await ffmpeg.WriteFile(inputFilename, new Uint8Array(arrayBuffer));
97 |
98 | // Execute FFmpeg command (grayscale filter)
99 | // Note: This is the performance bottleneck
100 | await ffmpeg.Exec(["-i", inputFilename, "-vf", "hue=s=0", outputFilename]);
101 |
102 | // Read the processed PNG file
103 | using var outputData = await ffmpeg.ReadFile(outputFilename);
104 |
105 | // Clean up files in virtual filesystem
106 | await ffmpeg.DeleteFile(inputFilename);
107 | await ffmpeg.DeleteFile(outputFilename);
108 |
109 | // Create an ImageBitmap from the output PNG data
110 | using var outputBlob = new Blob(new ArrayBuffer[] { outputData.Buffer }, new BlobOptions { Type = "image/png" });
111 |
112 | using var bitmap = await window.CreateImageBitmap(outputBlob);
113 |
114 | // Create a new VideoFrame with the processed bitmap
115 | using var newFrame = new VideoFrame(bitmap, new VideoFrameOptions
116 | {
117 | Timestamp = (int)chunk.Timestamp,
118 | Duration = (int)chunk.Duration,
119 | });
120 |
121 | // Enqueue the new frame into the output stream
122 | controller.Enqueue(newFrame);
123 | }
124 | catch (Exception ex)
125 | {
126 | Console.WriteLine($"Error processing video frame: {ex.Message}");
127 | }
128 | finally
129 | {
130 | chunk.Close(); // Dispose the VideoFrame to free resources
131 | }
132 | }
133 | async Task Transformer_Flush(TransformStreamDefaultController controller)
134 | {
135 | Console.WriteLine("Transformer_Flush");
136 | }
137 | public void Dispose()
138 | {
139 | // Clean up resources if necessary
140 | if (stream != null)
141 | {
142 | stream.StopAllTracks();
143 | stream.Dispose();
144 | }
145 | video?.Dispose();
146 | window?.Dispose();
147 | transformStream?.Dispose();
148 | transformerCallbacks?.Dispose();
149 | ffmpeg?.Dispose();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/VideoToGif.razor:
--------------------------------------------------------------------------------
1 | @page "/VideoToGif"
2 |
3 | Video To Gif
4 | This demo uses ffmpeg.wasm to create a gif from a video input.
5 |
6 | VideoToGif.razor
7 | VideoToGif.razor.cs
8 | At the moment this demo starts 5 seconds into the video and creates a 5 second gif.
9 |
10 |
11 | @outputFileName
12 |
13 |
14 |
15 |
16 | Load ffmpeg-core (~31 MB)
17 |
18 |
19 |
20 |
21 | Select a source video.
22 |
23 |
24 |
25 | Multithreading will be used: @FFmpegFactory.MultiThreadSupported
26 |
27 |
28 |
29 |
30 |
31 |
32 | @(Math.Round(percentComplete, 3)) %
33 |
34 |
35 |
36 | @logMessage
37 | @progressMessage
38 | Open Developer Tools (Ctrl+Shift+I) to View Logs
39 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Pages/VideoToGif.razor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components;
2 | using SpawnDev.BlazorJS.FFmpegWasm;
3 | using SpawnDev.BlazorJS.JSObjects;
4 | using File = SpawnDev.BlazorJS.JSObjects.File;
5 |
6 | namespace SpawnDev.BlazorJS.FFmpegWasmDemo.Pages
7 | {
8 | public partial class VideoToGif
9 | {
10 | [Inject]
11 | BlazorJSRuntime JS { get; set; }
12 |
13 | [Inject]
14 | FFmpegFactory FFmpegFactory { get; set; }
15 |
16 | bool loaded = false;
17 | bool busy = false;
18 | string logMessage = "";
19 | string progressMessage = "";
20 | FFmpeg? ffmpeg = null;
21 | ElementReference fileInputRef;
22 | HTMLInputElement? fileInput;
23 | bool beenInit = false;
24 | double percentComplete = 0;
25 | string outputURL = "";
26 |
27 | protected override void OnAfterRender(bool firstRender)
28 | {
29 | if (!beenInit)
30 | {
31 | beenInit = true;
32 | fileInput = new HTMLInputElement(JS.ToJSRef(fileInputRef));
33 | fileInput.OnChange += FileInput_OnChange;
34 | }
35 | }
36 |
37 | public void Dispose()
38 | {
39 | if (beenInit)
40 | {
41 | beenInit = false;
42 | fileInput.OnChange -= FileInput_OnChange;
43 | fileInput.Dispose();
44 | }
45 | if (ffmpeg != null)
46 | {
47 | ffmpeg.Terminate();
48 | ffmpeg.Dispose();
49 | ffmpeg = null;
50 | }
51 | }
52 |
53 | void FileInput_OnChange(Event ev)
54 | {
55 | using var files = fileInput!.Files;
56 | var file = files?.FirstOrDefault();
57 | if (file == null) return;
58 | _ = TranscodeLocalFile(file);
59 | }
60 |
61 | async Task Load()
62 | {
63 | busy = true;
64 | StateHasChanged();
65 | await FFmpegFactory.Init();
66 | ffmpeg = new FFmpeg();
67 | ffmpeg.OnLog += FFmpeg_OnLog;
68 | ffmpeg.OnProgress += FFmpeg_OnProgress;
69 | // Use FFmpegFactory extension methods supplied by the Nuget packages
70 | // SpawnDev.BlazorJS.FFmpegWasm.Core
71 | // SpawnDev.BlazorJS.FFmpegWasm.CoreMT
72 | //
73 | // From SpawnDev.BlazorJS.FFmpegWasm.Core
74 | // - Contains the ffmpeg.wasm core for single thread files
75 | // - Adds CreateLoadCoreConfig to FFmpegFactory
76 | // From SpawnDev.BlazorJS.FFmpegWasm.CoreMT
77 | // - Contains the ffmpeg.wasm core for multi thread files
78 | // - Adds CreateLoadCoreMTConfig to FFmpegFactory
79 | // Single thread and multi thread versions acn be used independently of each other to lower resource packaging.
80 | var loadConfig = FFmpegFactory.MultiThreadSupported ? FFmpegFactory.CreateLoadCoreMTConfig() : FFmpegFactory.CreateLoadCoreConfig();
81 | await ffmpeg.Load(loadConfig);
82 | busy = false;
83 | loaded = true;
84 | StateHasChanged();
85 | }
86 |
87 | // -ss 61.0 -t 2.5 -i StickAround.mp4 -filter_complex "[0:v] fps=12,scale=480:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" SmallerStickAround.gif
88 | string[] VideoToGifCommand(string inputFile, string outputFile, double start, double duration, bool usePaletteGen)
89 | {
90 | if (usePaletteGen)
91 | {
92 | var ret = new string[] {
93 | "-i", inputFile,
94 | "-ss", start.ToString(),
95 | "-t", duration.ToString(),
96 | //"-filter_complex", "[0:v] fps=12,scale=w=480:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1",
97 | "-filter_complex", "[0:v] fps=12,scale=480:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse",
98 | outputFile,
99 | };
100 | return ret;
101 | }
102 | else
103 | {
104 | var ret = new string[] {
105 | "-i", inputFile,
106 | "-ss", start.ToString(),
107 | "-t", duration.ToString(),
108 | "-f","gif",
109 | outputFile,
110 | };
111 | return ret;
112 | }
113 | }
114 |
115 | // https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg
116 | string outputFileName = "";
117 | async Task TranscodeLocalFile(File file)
118 | {
119 | busy = true;
120 | StateHasChanged();
121 | var inputDir = "/input";
122 | var inputFile = $"/input/{file.Name}";
123 | var outputFile = file.Name.Substring(0, file.Name.LastIndexOf(".")) + ".gif";
124 | await ffmpeg.CreateDir(inputDir);
125 | await ffmpeg.MountWorkerFS(inputDir, new FSMountWorkerFSOptions { Files = new[] { file } });
126 | var ls = await ffmpeg.ListDir(inputDir);
127 | logMessage = "Transcoding source video";
128 | StateHasChanged();
129 | var cmd = VideoToGifCommand(inputFile, outputFile, 1, 5, false);
130 | await ffmpeg.Exec(cmd);
131 | // -ss 61.0 -t 2.5 -i StickAround.mp4 -filter_complex "[0:v] fps=12,scale=w=480:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1" StickAroundPerFrame.gif
132 | logMessage = "Source video transcoded";
133 | StateHasChanged();
134 | await ffmpeg.Unmount(inputDir);
135 | await ffmpeg.DeleteDir(inputDir);
136 | using var data = await ffmpeg.ReadFileUint8Array(outputFile);
137 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/gif" });
138 | outputFileName = outputFile;
139 | var objSrc = URL.CreateObjectURL(blob);
140 | outputURL = objSrc;
141 | busy = false;
142 | StateHasChanged();
143 | }
144 |
145 | void FFmpeg_OnLog(FFmpegLogEvent ev)
146 | {
147 | logMessage = ev.Message;
148 | JS.Log("FFmpeg_OnLog", ev.Message);
149 | StateHasChanged();
150 | }
151 |
152 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
153 | {
154 | var progress = ev.Progress;
155 | var time = ev.Time;
156 | progressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
157 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
158 | percentComplete = ev.Progress * 100d;
159 | StateHasChanged();
160 | }
161 |
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Web;
2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
3 | using SpawnDev.BlazorJS;
4 | using SpawnDev.BlazorJS.FFmpegWasm;
5 | using SpawnDev.BlazorJS.FFmpegWasmDemo;
6 |
7 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
8 | builder.RootComponents.Add("#app");
9 | builder.RootComponents.Add("head::after");
10 | builder.Services.AddBlazorJSRuntime();
11 | builder.Services.AddSingleton();
12 | builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
13 | await builder.Build().BlazorJSRunAsync();
14 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "http": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "dotnetRunMessages": true,
10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
11 | "applicationUrl": "http://localhost:5000"
12 | },
13 | "https": {
14 | "commandName": "Project",
15 | "launchBrowser": true,
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | },
19 | "dotnetRunMessages": true,
20 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
21 | "applicationUrl": "https://localhost:7226;http://localhost:5192"
22 | },
23 | "IIS Express": {
24 | "commandName": "IISExpress",
25 | "launchBrowser": true,
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | },
29 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
30 | }
31 | },
32 | "$schema": "http://json.schemastore.org/launchsettings.json",
33 | "iisSettings": {
34 | "windowsAuthentication": false,
35 | "anonymousAuthentication": true,
36 | "iisExpress": {
37 | "applicationUrl": "http://localhost:42827",
38 | "sslPort": 44324
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Shared/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
4 |
7 |
8 |
9 |
12 |
13 |
14 | @Body
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Shared/MainLayout.razor.css:
--------------------------------------------------------------------------------
1 | .page {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | main {
8 | flex: 1;
9 | }
10 |
11 | .sidebar {
12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
13 | }
14 |
15 | .top-row {
16 | background-color: #f7f7f7;
17 | border-bottom: 1px solid #d6d5d5;
18 | justify-content: flex-end;
19 | height: 3.5rem;
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .top-row ::deep a, .top-row ::deep .btn-link {
25 | white-space: nowrap;
26 | margin-left: 1.5rem;
27 | text-decoration: none;
28 | }
29 |
30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .top-row ::deep a:first-child {
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | @media (max-width: 640.98px) {
40 | .top-row:not(.auth) {
41 | display: none;
42 | }
43 |
44 | .top-row.auth {
45 | justify-content: space-between;
46 | }
47 |
48 | .top-row ::deep a, .top-row ::deep .btn-link {
49 | margin-left: 0;
50 | }
51 | }
52 |
53 | @media (min-width: 641px) {
54 | .page {
55 | flex-direction: row;
56 | }
57 |
58 | .sidebar {
59 | width: 250px;
60 | height: 100vh;
61 | position: sticky;
62 | top: 0;
63 | }
64 |
65 | .top-row {
66 | position: sticky;
67 | top: 0;
68 | z-index: 1;
69 | }
70 |
71 | .top-row.auth ::deep a:first-child {
72 | flex: 1;
73 | text-align: right;
74 | width: 0;
75 | }
76 |
77 | .top-row, article {
78 | padding-left: 2rem !important;
79 | padding-right: 1.5rem !important;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Shared/NavMenu.razor:
--------------------------------------------------------------------------------
1 |
9 |
10 |
75 |
76 | @code {
77 | private bool collapseNavMenu = true;
78 |
79 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
80 |
81 | private void ToggleNavMenu()
82 | {
83 | collapseNavMenu = !collapseNavMenu;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Shared/NavMenu.razor.css:
--------------------------------------------------------------------------------
1 | .navbar-toggler {
2 | background-color: rgba(255, 255, 255, 0.1);
3 | }
4 |
5 | .top-row {
6 | height: 3.5rem;
7 | background-color: rgba(0,0,0,0.4);
8 | }
9 |
10 | .navbar-brand {
11 | font-size: 1.1rem;
12 | }
13 |
14 | .bi {
15 | width: 2rem;
16 | font-size: 1.1rem;
17 | vertical-align: text-top;
18 | top: -2px;
19 | }
20 |
21 | .nav-item {
22 | font-size: 0.9rem;
23 | padding-bottom: 0.5rem;
24 | }
25 |
26 | .nav-item:first-of-type {
27 | padding-top: 1rem;
28 | }
29 |
30 | .nav-item:last-of-type {
31 | padding-bottom: 1rem;
32 | }
33 |
34 | .nav-item ::deep a {
35 | color: #d7d7d7;
36 | border-radius: 4px;
37 | height: 3rem;
38 | display: flex;
39 | align-items: center;
40 | line-height: 3rem;
41 | }
42 |
43 | .nav-item ::deep a.active {
44 | background-color: rgba(255,255,255,0.25);
45 | color: white;
46 | }
47 |
48 | .nav-item ::deep a:hover {
49 | background-color: rgba(255,255,255,0.1);
50 | color: white;
51 | }
52 |
53 | @media (min-width: 641px) {
54 | .navbar-toggler {
55 | display: none;
56 | }
57 |
58 | .collapse {
59 | /* Never collapse the sidebar for wide screens */
60 | display: block;
61 | }
62 |
63 | .nav-scrollable {
64 | /* Allow sidebar to scroll for tall menus */
65 | height: calc(100vh - 3.5rem);
66 | overflow-y: auto;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/Shared/TranscodeWebmToMp4Video.razor:
--------------------------------------------------------------------------------
1 | @implements IDisposable
2 | @using System.Text
3 | @using System.Diagnostics
4 |
5 |
6 |
@title
7 |
8 |
9 |
10 |
Transcode webm to mp4
11 |
Open Developer Tools (Ctrl+Shift+I) to View Logs
12 |
13 |
14 | @loadButtonText
15 |
16 |
Multi-thread Supported: @FFmpegFactory.MultiThreadSupported
17 |
@LogMessage
18 |
@ProgressMessage
19 |
@(Math.Round(Stopwatch.Elapsed.TotalSeconds, 1)) seconds elapsed
20 |
21 |
22 | @code {
23 | ///
24 | /// Based on:
25 | /// https://ffmpegwasm.netlify.app/docs/getting-started/usage/
26 | ///
27 |
28 | public enum ThreadMode
29 | {
30 | AUTO,
31 | SINGLE_THREAD,
32 | MULTI_THREAD,
33 | }
34 |
35 | [Parameter]
36 | public ThreadMode FFmpegThreadMode { get; set; } = ThreadMode.AUTO;
37 |
38 | [Inject]
39 | BlazorJSRuntime JS { get; set; }
40 |
41 | [Inject]
42 | HttpClient HttpClient { get; set; }
43 |
44 | [Inject]
45 | FFmpegFactory FFmpegFactory { get; set; }
46 |
47 | bool loaded = false;
48 | bool loadDisabled = false;
49 | string title = "";
50 | string loadButtonText = "";
51 | ElementReference videoRef;
52 | HTMLVideoElement? videoEl = null;
53 | FFmpeg? FFmpeg = null;
54 | string LogMessage = "";
55 | string ProgressMessage = "";
56 | Stopwatch Stopwatch = new Stopwatch();
57 |
58 | protected override void OnInitialized()
59 | {
60 | switch (FFmpegThreadMode)
61 | {
62 | case ThreadMode.SINGLE_THREAD:
63 | title = "Transcode webm to mp4 video (single-thread)";
64 | loadButtonText = "Load ffmpeg-core single-thread(~31 MB)";
65 | break;
66 | case ThreadMode.MULTI_THREAD:
67 | title = "Transcode webm to mp4 video (multi-thread)";
68 | loadButtonText = "Load ffmpeg-core multi-thread (~31 MB)";
69 | if (!FFmpegFactory.MultiThreadSupported)
70 | {
71 | loadDisabled = true;
72 | LogMessage = "Multithread mode requires SharedArrayBuffer. See MDN SharedArrayBuffer documentation for more info.";
73 | }
74 | break;
75 | case ThreadMode.AUTO:
76 | title = "Transcode webm to mp4 video (auto detect)";
77 | loadButtonText = "Load ffmpeg-core auto detect (~31 MB)";
78 | break;
79 | }
80 | }
81 |
82 | async Task Load()
83 | {
84 | if (loaded) return;
85 | Stopwatch.Restart();
86 | await FFmpegFactory.Init();
87 | videoEl = new HTMLVideoElement(videoRef);
88 | FFmpeg = new FFmpeg();
89 | FFmpeg.OnLog += FFmpeg_OnLog;
90 | FFmpeg.OnProgress += FFmpeg_OnProgress;
91 | var useThreading = FFmpegThreadMode == ThreadMode.MULTI_THREAD || (FFmpegThreadMode == ThreadMode.AUTO && FFmpegFactory.MultiThreadSupported);
92 | var loadConfig = FFmpegFactory.MultiThreadSupported ? FFmpegFactory.CreateLoadCoreMTConfig() : FFmpegFactory.CreateLoadCoreConfig();
93 | try
94 | {
95 | await FFmpeg.Load(loadConfig);
96 | loaded = true;
97 | LogMessage = "FFmpeg loaded: " + (useThreading ? "multithread" : "single thread");
98 | }
99 | catch
100 | {
101 | LogMessage = "FFmpeg load failed: " + (useThreading ? "multithread" : "single thread");
102 | }
103 | Stopwatch.Stop();
104 | StateHasChanged();
105 | }
106 |
107 | async Task Transcode()
108 | {
109 | LogMessage = "Downloading source video";
110 | StateHasChanged();
111 | await FFmpeg.WriteFile("input.webm", await FFmpegFactory.FetchFile("https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm"));
112 | Stopwatch.Restart();
113 | LogMessage = "Transcoding source video";
114 | StateHasChanged();
115 | var ret = await FFmpeg.Exec(new string[] { "-i", "input.webm", "output.mp4" });
116 | Stopwatch.Stop();
117 | LogMessage = "Source video transcoded";
118 | StateHasChanged();
119 | using var data = await FFmpeg.ReadFileUint8Array("output.mp4");
120 | if (videoEl != null)
121 | {
122 | using var blob = new Blob(new Uint8Array[] { data }, new BlobOptions { Type = "video/mp4" });
123 | var objSrc = URL.CreateObjectURL(blob);
124 | videoEl.Src = objSrc;
125 | videoEl.Load();
126 | }
127 | }
128 |
129 | void FFmpeg_OnLog(FFmpegLogEvent ev)
130 | {
131 | LogMessage = ev.Message;
132 | JS.Log("FFmpeg_OnLog", ev.Message);
133 | StateHasChanged();
134 | }
135 |
136 | void FFmpeg_OnProgress(FFmpegProgressEvent ev)
137 | {
138 | var progress = ev.Progress;
139 | var time = ev.Time;
140 | ProgressMessage = $"{progress * 100} % (transcoded time: {time / 1000000} s)";
141 | JS.Log("FFmpeg_OnProgress", ev.Time, ev.Progress);
142 | StateHasChanged();
143 | }
144 |
145 | public void Dispose()
146 | {
147 | if (FFmpeg != null)
148 | {
149 | FFmpeg.OnLog -= FFmpeg_OnLog;
150 | FFmpeg.OnProgress -= FFmpeg_OnProgress;
151 | FFmpeg.Terminate();
152 | FFmpeg.Dispose();
153 | FFmpeg = null;
154 | }
155 | if (videoEl != null)
156 | {
157 | videoEl.Dispose();
158 | videoEl = null;
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/SpawnDev.BlazorJS.FFmpegWasmDemo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using Microsoft.AspNetCore.Components.Forms
4 | @using Microsoft.AspNetCore.Components.Routing
5 | @using Microsoft.AspNetCore.Components.Web
6 | @using Microsoft.AspNetCore.Components.Web.Virtualization
7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http
8 | @using Microsoft.JSInterop
9 | @using SpawnDev.BlazorJS.FFmpegWasmDemo
10 | @using SpawnDev.BlazorJS.FFmpegWasmDemo.Shared
11 | @using SpawnDev.BlazorJS.FFmpegWasm
12 | @using SpawnDev.BlazorJS
13 | @using SpawnDev.BlazorJS.JSObjects
14 | @using System.Text
15 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/appsettings.Develpment.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/appsettings.Develpment.json
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "FFmpegLoadConfig": {
3 |
4 | }
5 | }
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | }
4 |
5 | h1:focus {
6 | outline: none;
7 | }
8 |
9 | a, .btn-link {
10 | color: #0071c1;
11 | }
12 |
13 | .btn-primary {
14 | color: #fff;
15 | background-color: #1b6ec2;
16 | border-color: #1861ac;
17 | }
18 |
19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
21 | }
22 |
23 | .content {
24 | padding-top: 1.1rem;
25 | }
26 |
27 | .valid.modified:not([type=checkbox]) {
28 | outline: 1px solid #26b050;
29 | }
30 |
31 | .invalid {
32 | outline: 1px solid red;
33 | }
34 |
35 | .validation-message {
36 | color: red;
37 | }
38 |
39 | #blazor-error-ui {
40 | background: lightyellow;
41 | bottom: 0;
42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
43 | display: none;
44 | left: 0;
45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
46 | position: fixed;
47 | width: 100%;
48 | z-index: 1000;
49 | }
50 |
51 | #blazor-error-ui .dismiss {
52 | cursor: pointer;
53 | position: absolute;
54 | right: 0.75rem;
55 | top: 0.5rem;
56 | }
57 |
58 | .blazor-error-boundary {
59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
60 | padding: 1rem 1rem 1rem 3.7rem;
61 | color: white;
62 | }
63 |
64 | .blazor-error-boundary::after {
65 | content: "An error has occurred."
66 | }
67 |
68 | .loading-progress {
69 | position: relative;
70 | display: block;
71 | width: 8rem;
72 | height: 8rem;
73 | margin: 20vh auto 1rem auto;
74 | }
75 |
76 | .loading-progress circle {
77 | fill: none;
78 | stroke: #e0e0e0;
79 | stroke-width: 0.6rem;
80 | transform-origin: 50% 50%;
81 | transform: rotate(-90deg);
82 | }
83 |
84 | .loading-progress circle:last-child {
85 | stroke: #1b6ec2;
86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
87 | transition: stroke-dasharray 0.05s ease-in-out;
88 | }
89 |
90 | .loading-progress-text {
91 | position: absolute;
92 | text-align: center;
93 | font-weight: bold;
94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
95 | }
96 |
97 | .loading-progress-text:after {
98 | content: var(--blazor-load-percentage-text, "Loading");
99 | }
100 |
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/bootstrap-icons/bootstrap-icons.json:
--------------------------------------------------------------------------------
1 | {
2 | "123": 63103,
3 | "alarm-fill": 61697,
4 | "alarm": 61698,
5 | "align-bottom": 61699,
6 | "align-center": 61700,
7 | "align-end": 61701,
8 | "align-middle": 61702,
9 | "align-start": 61703,
10 | "align-top": 61704,
11 | "alt": 61705,
12 | "app-indicator": 61706,
13 | "app": 61707,
14 | "archive-fill": 61708,
15 | "archive": 61709,
16 | "arrow-90deg-down": 61710,
17 | "arrow-90deg-left": 61711,
18 | "arrow-90deg-right": 61712,
19 | "arrow-90deg-up": 61713,
20 | "arrow-bar-down": 61714,
21 | "arrow-bar-left": 61715,
22 | "arrow-bar-right": 61716,
23 | "arrow-bar-up": 61717,
24 | "arrow-clockwise": 61718,
25 | "arrow-counterclockwise": 61719,
26 | "arrow-down-circle-fill": 61720,
27 | "arrow-down-circle": 61721,
28 | "arrow-down-left-circle-fill": 61722,
29 | "arrow-down-left-circle": 61723,
30 | "arrow-down-left-square-fill": 61724,
31 | "arrow-down-left-square": 61725,
32 | "arrow-down-left": 61726,
33 | "arrow-down-right-circle-fill": 61727,
34 | "arrow-down-right-circle": 61728,
35 | "arrow-down-right-square-fill": 61729,
36 | "arrow-down-right-square": 61730,
37 | "arrow-down-right": 61731,
38 | "arrow-down-short": 61732,
39 | "arrow-down-square-fill": 61733,
40 | "arrow-down-square": 61734,
41 | "arrow-down-up": 61735,
42 | "arrow-down": 61736,
43 | "arrow-left-circle-fill": 61737,
44 | "arrow-left-circle": 61738,
45 | "arrow-left-right": 61739,
46 | "arrow-left-short": 61740,
47 | "arrow-left-square-fill": 61741,
48 | "arrow-left-square": 61742,
49 | "arrow-left": 61743,
50 | "arrow-repeat": 61744,
51 | "arrow-return-left": 61745,
52 | "arrow-return-right": 61746,
53 | "arrow-right-circle-fill": 61747,
54 | "arrow-right-circle": 61748,
55 | "arrow-right-short": 61749,
56 | "arrow-right-square-fill": 61750,
57 | "arrow-right-square": 61751,
58 | "arrow-right": 61752,
59 | "arrow-up-circle-fill": 61753,
60 | "arrow-up-circle": 61754,
61 | "arrow-up-left-circle-fill": 61755,
62 | "arrow-up-left-circle": 61756,
63 | "arrow-up-left-square-fill": 61757,
64 | "arrow-up-left-square": 61758,
65 | "arrow-up-left": 61759,
66 | "arrow-up-right-circle-fill": 61760,
67 | "arrow-up-right-circle": 61761,
68 | "arrow-up-right-square-fill": 61762,
69 | "arrow-up-right-square": 61763,
70 | "arrow-up-right": 61764,
71 | "arrow-up-short": 61765,
72 | "arrow-up-square-fill": 61766,
73 | "arrow-up-square": 61767,
74 | "arrow-up": 61768,
75 | "arrows-angle-contract": 61769,
76 | "arrows-angle-expand": 61770,
77 | "arrows-collapse": 61771,
78 | "arrows-expand": 61772,
79 | "arrows-fullscreen": 61773,
80 | "arrows-move": 61774,
81 | "aspect-ratio-fill": 61775,
82 | "aspect-ratio": 61776,
83 | "asterisk": 61777,
84 | "at": 61778,
85 | "award-fill": 61779,
86 | "award": 61780,
87 | "back": 61781,
88 | "backspace-fill": 61782,
89 | "backspace-reverse-fill": 61783,
90 | "backspace-reverse": 61784,
91 | "backspace": 61785,
92 | "badge-3d-fill": 61786,
93 | "badge-3d": 61787,
94 | "badge-4k-fill": 61788,
95 | "badge-4k": 61789,
96 | "badge-8k-fill": 61790,
97 | "badge-8k": 61791,
98 | "badge-ad-fill": 61792,
99 | "badge-ad": 61793,
100 | "badge-ar-fill": 61794,
101 | "badge-ar": 61795,
102 | "badge-cc-fill": 61796,
103 | "badge-cc": 61797,
104 | "badge-hd-fill": 61798,
105 | "badge-hd": 61799,
106 | "badge-tm-fill": 61800,
107 | "badge-tm": 61801,
108 | "badge-vo-fill": 61802,
109 | "badge-vo": 61803,
110 | "badge-vr-fill": 61804,
111 | "badge-vr": 61805,
112 | "badge-wc-fill": 61806,
113 | "badge-wc": 61807,
114 | "bag-check-fill": 61808,
115 | "bag-check": 61809,
116 | "bag-dash-fill": 61810,
117 | "bag-dash": 61811,
118 | "bag-fill": 61812,
119 | "bag-plus-fill": 61813,
120 | "bag-plus": 61814,
121 | "bag-x-fill": 61815,
122 | "bag-x": 61816,
123 | "bag": 61817,
124 | "bar-chart-fill": 61818,
125 | "bar-chart-line-fill": 61819,
126 | "bar-chart-line": 61820,
127 | "bar-chart-steps": 61821,
128 | "bar-chart": 61822,
129 | "basket-fill": 61823,
130 | "basket": 61824,
131 | "basket2-fill": 61825,
132 | "basket2": 61826,
133 | "basket3-fill": 61827,
134 | "basket3": 61828,
135 | "battery-charging": 61829,
136 | "battery-full": 61830,
137 | "battery-half": 61831,
138 | "battery": 61832,
139 | "bell-fill": 61833,
140 | "bell": 61834,
141 | "bezier": 61835,
142 | "bezier2": 61836,
143 | "bicycle": 61837,
144 | "binoculars-fill": 61838,
145 | "binoculars": 61839,
146 | "blockquote-left": 61840,
147 | "blockquote-right": 61841,
148 | "book-fill": 61842,
149 | "book-half": 61843,
150 | "book": 61844,
151 | "bookmark-check-fill": 61845,
152 | "bookmark-check": 61846,
153 | "bookmark-dash-fill": 61847,
154 | "bookmark-dash": 61848,
155 | "bookmark-fill": 61849,
156 | "bookmark-heart-fill": 61850,
157 | "bookmark-heart": 61851,
158 | "bookmark-plus-fill": 61852,
159 | "bookmark-plus": 61853,
160 | "bookmark-star-fill": 61854,
161 | "bookmark-star": 61855,
162 | "bookmark-x-fill": 61856,
163 | "bookmark-x": 61857,
164 | "bookmark": 61858,
165 | "bookmarks-fill": 61859,
166 | "bookmarks": 61860,
167 | "bookshelf": 61861,
168 | "bootstrap-fill": 61862,
169 | "bootstrap-reboot": 61863,
170 | "bootstrap": 61864,
171 | "border-all": 61865,
172 | "border-bottom": 61866,
173 | "border-center": 61867,
174 | "border-inner": 61868,
175 | "border-left": 61869,
176 | "border-middle": 61870,
177 | "border-outer": 61871,
178 | "border-right": 61872,
179 | "border-style": 61873,
180 | "border-top": 61874,
181 | "border-width": 61875,
182 | "border": 61876,
183 | "bounding-box-circles": 61877,
184 | "bounding-box": 61878,
185 | "box-arrow-down-left": 61879,
186 | "box-arrow-down-right": 61880,
187 | "box-arrow-down": 61881,
188 | "box-arrow-in-down-left": 61882,
189 | "box-arrow-in-down-right": 61883,
190 | "box-arrow-in-down": 61884,
191 | "box-arrow-in-left": 61885,
192 | "box-arrow-in-right": 61886,
193 | "box-arrow-in-up-left": 61887,
194 | "box-arrow-in-up-right": 61888,
195 | "box-arrow-in-up": 61889,
196 | "box-arrow-left": 61890,
197 | "box-arrow-right": 61891,
198 | "box-arrow-up-left": 61892,
199 | "box-arrow-up-right": 61893,
200 | "box-arrow-up": 61894,
201 | "box-seam": 61895,
202 | "box": 61896,
203 | "braces": 61897,
204 | "bricks": 61898,
205 | "briefcase-fill": 61899,
206 | "briefcase": 61900,
207 | "brightness-alt-high-fill": 61901,
208 | "brightness-alt-high": 61902,
209 | "brightness-alt-low-fill": 61903,
210 | "brightness-alt-low": 61904,
211 | "brightness-high-fill": 61905,
212 | "brightness-high": 61906,
213 | "brightness-low-fill": 61907,
214 | "brightness-low": 61908,
215 | "broadcast-pin": 61909,
216 | "broadcast": 61910,
217 | "brush-fill": 61911,
218 | "brush": 61912,
219 | "bucket-fill": 61913,
220 | "bucket": 61914,
221 | "bug-fill": 61915,
222 | "bug": 61916,
223 | "building": 61917,
224 | "bullseye": 61918,
225 | "calculator-fill": 61919,
226 | "calculator": 61920,
227 | "calendar-check-fill": 61921,
228 | "calendar-check": 61922,
229 | "calendar-date-fill": 61923,
230 | "calendar-date": 61924,
231 | "calendar-day-fill": 61925,
232 | "calendar-day": 61926,
233 | "calendar-event-fill": 61927,
234 | "calendar-event": 61928,
235 | "calendar-fill": 61929,
236 | "calendar-minus-fill": 61930,
237 | "calendar-minus": 61931,
238 | "calendar-month-fill": 61932,
239 | "calendar-month": 61933,
240 | "calendar-plus-fill": 61934,
241 | "calendar-plus": 61935,
242 | "calendar-range-fill": 61936,
243 | "calendar-range": 61937,
244 | "calendar-week-fill": 61938,
245 | "calendar-week": 61939,
246 | "calendar-x-fill": 61940,
247 | "calendar-x": 61941,
248 | "calendar": 61942,
249 | "calendar2-check-fill": 61943,
250 | "calendar2-check": 61944,
251 | "calendar2-date-fill": 61945,
252 | "calendar2-date": 61946,
253 | "calendar2-day-fill": 61947,
254 | "calendar2-day": 61948,
255 | "calendar2-event-fill": 61949,
256 | "calendar2-event": 61950,
257 | "calendar2-fill": 61951,
258 | "calendar2-minus-fill": 61952,
259 | "calendar2-minus": 61953,
260 | "calendar2-month-fill": 61954,
261 | "calendar2-month": 61955,
262 | "calendar2-plus-fill": 61956,
263 | "calendar2-plus": 61957,
264 | "calendar2-range-fill": 61958,
265 | "calendar2-range": 61959,
266 | "calendar2-week-fill": 61960,
267 | "calendar2-week": 61961,
268 | "calendar2-x-fill": 61962,
269 | "calendar2-x": 61963,
270 | "calendar2": 61964,
271 | "calendar3-event-fill": 61965,
272 | "calendar3-event": 61966,
273 | "calendar3-fill": 61967,
274 | "calendar3-range-fill": 61968,
275 | "calendar3-range": 61969,
276 | "calendar3-week-fill": 61970,
277 | "calendar3-week": 61971,
278 | "calendar3": 61972,
279 | "calendar4-event": 61973,
280 | "calendar4-range": 61974,
281 | "calendar4-week": 61975,
282 | "calendar4": 61976,
283 | "camera-fill": 61977,
284 | "camera-reels-fill": 61978,
285 | "camera-reels": 61979,
286 | "camera-video-fill": 61980,
287 | "camera-video-off-fill": 61981,
288 | "camera-video-off": 61982,
289 | "camera-video": 61983,
290 | "camera": 61984,
291 | "camera2": 61985,
292 | "capslock-fill": 61986,
293 | "capslock": 61987,
294 | "card-checklist": 61988,
295 | "card-heading": 61989,
296 | "card-image": 61990,
297 | "card-list": 61991,
298 | "card-text": 61992,
299 | "caret-down-fill": 61993,
300 | "caret-down-square-fill": 61994,
301 | "caret-down-square": 61995,
302 | "caret-down": 61996,
303 | "caret-left-fill": 61997,
304 | "caret-left-square-fill": 61998,
305 | "caret-left-square": 61999,
306 | "caret-left": 62000,
307 | "caret-right-fill": 62001,
308 | "caret-right-square-fill": 62002,
309 | "caret-right-square": 62003,
310 | "caret-right": 62004,
311 | "caret-up-fill": 62005,
312 | "caret-up-square-fill": 62006,
313 | "caret-up-square": 62007,
314 | "caret-up": 62008,
315 | "cart-check-fill": 62009,
316 | "cart-check": 62010,
317 | "cart-dash-fill": 62011,
318 | "cart-dash": 62012,
319 | "cart-fill": 62013,
320 | "cart-plus-fill": 62014,
321 | "cart-plus": 62015,
322 | "cart-x-fill": 62016,
323 | "cart-x": 62017,
324 | "cart": 62018,
325 | "cart2": 62019,
326 | "cart3": 62020,
327 | "cart4": 62021,
328 | "cash-stack": 62022,
329 | "cash": 62023,
330 | "cast": 62024,
331 | "chat-dots-fill": 62025,
332 | "chat-dots": 62026,
333 | "chat-fill": 62027,
334 | "chat-left-dots-fill": 62028,
335 | "chat-left-dots": 62029,
336 | "chat-left-fill": 62030,
337 | "chat-left-quote-fill": 62031,
338 | "chat-left-quote": 62032,
339 | "chat-left-text-fill": 62033,
340 | "chat-left-text": 62034,
341 | "chat-left": 62035,
342 | "chat-quote-fill": 62036,
343 | "chat-quote": 62037,
344 | "chat-right-dots-fill": 62038,
345 | "chat-right-dots": 62039,
346 | "chat-right-fill": 62040,
347 | "chat-right-quote-fill": 62041,
348 | "chat-right-quote": 62042,
349 | "chat-right-text-fill": 62043,
350 | "chat-right-text": 62044,
351 | "chat-right": 62045,
352 | "chat-square-dots-fill": 62046,
353 | "chat-square-dots": 62047,
354 | "chat-square-fill": 62048,
355 | "chat-square-quote-fill": 62049,
356 | "chat-square-quote": 62050,
357 | "chat-square-text-fill": 62051,
358 | "chat-square-text": 62052,
359 | "chat-square": 62053,
360 | "chat-text-fill": 62054,
361 | "chat-text": 62055,
362 | "chat": 62056,
363 | "check-all": 62057,
364 | "check-circle-fill": 62058,
365 | "check-circle": 62059,
366 | "check-square-fill": 62060,
367 | "check-square": 62061,
368 | "check": 62062,
369 | "check2-all": 62063,
370 | "check2-circle": 62064,
371 | "check2-square": 62065,
372 | "check2": 62066,
373 | "chevron-bar-contract": 62067,
374 | "chevron-bar-down": 62068,
375 | "chevron-bar-expand": 62069,
376 | "chevron-bar-left": 62070,
377 | "chevron-bar-right": 62071,
378 | "chevron-bar-up": 62072,
379 | "chevron-compact-down": 62073,
380 | "chevron-compact-left": 62074,
381 | "chevron-compact-right": 62075,
382 | "chevron-compact-up": 62076,
383 | "chevron-contract": 62077,
384 | "chevron-double-down": 62078,
385 | "chevron-double-left": 62079,
386 | "chevron-double-right": 62080,
387 | "chevron-double-up": 62081,
388 | "chevron-down": 62082,
389 | "chevron-expand": 62083,
390 | "chevron-left": 62084,
391 | "chevron-right": 62085,
392 | "chevron-up": 62086,
393 | "circle-fill": 62087,
394 | "circle-half": 62088,
395 | "circle-square": 62089,
396 | "circle": 62090,
397 | "clipboard-check": 62091,
398 | "clipboard-data": 62092,
399 | "clipboard-minus": 62093,
400 | "clipboard-plus": 62094,
401 | "clipboard-x": 62095,
402 | "clipboard": 62096,
403 | "clock-fill": 62097,
404 | "clock-history": 62098,
405 | "clock": 62099,
406 | "cloud-arrow-down-fill": 62100,
407 | "cloud-arrow-down": 62101,
408 | "cloud-arrow-up-fill": 62102,
409 | "cloud-arrow-up": 62103,
410 | "cloud-check-fill": 62104,
411 | "cloud-check": 62105,
412 | "cloud-download-fill": 62106,
413 | "cloud-download": 62107,
414 | "cloud-drizzle-fill": 62108,
415 | "cloud-drizzle": 62109,
416 | "cloud-fill": 62110,
417 | "cloud-fog-fill": 62111,
418 | "cloud-fog": 62112,
419 | "cloud-fog2-fill": 62113,
420 | "cloud-fog2": 62114,
421 | "cloud-hail-fill": 62115,
422 | "cloud-hail": 62116,
423 | "cloud-haze-fill": 62118,
424 | "cloud-haze": 62119,
425 | "cloud-haze2-fill": 62120,
426 | "cloud-lightning-fill": 62121,
427 | "cloud-lightning-rain-fill": 62122,
428 | "cloud-lightning-rain": 62123,
429 | "cloud-lightning": 62124,
430 | "cloud-minus-fill": 62125,
431 | "cloud-minus": 62126,
432 | "cloud-moon-fill": 62127,
433 | "cloud-moon": 62128,
434 | "cloud-plus-fill": 62129,
435 | "cloud-plus": 62130,
436 | "cloud-rain-fill": 62131,
437 | "cloud-rain-heavy-fill": 62132,
438 | "cloud-rain-heavy": 62133,
439 | "cloud-rain": 62134,
440 | "cloud-slash-fill": 62135,
441 | "cloud-slash": 62136,
442 | "cloud-sleet-fill": 62137,
443 | "cloud-sleet": 62138,
444 | "cloud-snow-fill": 62139,
445 | "cloud-snow": 62140,
446 | "cloud-sun-fill": 62141,
447 | "cloud-sun": 62142,
448 | "cloud-upload-fill": 62143,
449 | "cloud-upload": 62144,
450 | "cloud": 62145,
451 | "clouds-fill": 62146,
452 | "clouds": 62147,
453 | "cloudy-fill": 62148,
454 | "cloudy": 62149,
455 | "code-slash": 62150,
456 | "code-square": 62151,
457 | "code": 62152,
458 | "collection-fill": 62153,
459 | "collection-play-fill": 62154,
460 | "collection-play": 62155,
461 | "collection": 62156,
462 | "columns-gap": 62157,
463 | "columns": 62158,
464 | "command": 62159,
465 | "compass-fill": 62160,
466 | "compass": 62161,
467 | "cone-striped": 62162,
468 | "cone": 62163,
469 | "controller": 62164,
470 | "cpu-fill": 62165,
471 | "cpu": 62166,
472 | "credit-card-2-back-fill": 62167,
473 | "credit-card-2-back": 62168,
474 | "credit-card-2-front-fill": 62169,
475 | "credit-card-2-front": 62170,
476 | "credit-card-fill": 62171,
477 | "credit-card": 62172,
478 | "crop": 62173,
479 | "cup-fill": 62174,
480 | "cup-straw": 62175,
481 | "cup": 62176,
482 | "cursor-fill": 62177,
483 | "cursor-text": 62178,
484 | "cursor": 62179,
485 | "dash-circle-dotted": 62180,
486 | "dash-circle-fill": 62181,
487 | "dash-circle": 62182,
488 | "dash-square-dotted": 62183,
489 | "dash-square-fill": 62184,
490 | "dash-square": 62185,
491 | "dash": 62186,
492 | "diagram-2-fill": 62187,
493 | "diagram-2": 62188,
494 | "diagram-3-fill": 62189,
495 | "diagram-3": 62190,
496 | "diamond-fill": 62191,
497 | "diamond-half": 62192,
498 | "diamond": 62193,
499 | "dice-1-fill": 62194,
500 | "dice-1": 62195,
501 | "dice-2-fill": 62196,
502 | "dice-2": 62197,
503 | "dice-3-fill": 62198,
504 | "dice-3": 62199,
505 | "dice-4-fill": 62200,
506 | "dice-4": 62201,
507 | "dice-5-fill": 62202,
508 | "dice-5": 62203,
509 | "dice-6-fill": 62204,
510 | "dice-6": 62205,
511 | "disc-fill": 62206,
512 | "disc": 62207,
513 | "discord": 62208,
514 | "display-fill": 62209,
515 | "display": 62210,
516 | "distribute-horizontal": 62211,
517 | "distribute-vertical": 62212,
518 | "door-closed-fill": 62213,
519 | "door-closed": 62214,
520 | "door-open-fill": 62215,
521 | "door-open": 62216,
522 | "dot": 62217,
523 | "download": 62218,
524 | "droplet-fill": 62219,
525 | "droplet-half": 62220,
526 | "droplet": 62221,
527 | "earbuds": 62222,
528 | "easel-fill": 62223,
529 | "easel": 62224,
530 | "egg-fill": 62225,
531 | "egg-fried": 62226,
532 | "egg": 62227,
533 | "eject-fill": 62228,
534 | "eject": 62229,
535 | "emoji-angry-fill": 62230,
536 | "emoji-angry": 62231,
537 | "emoji-dizzy-fill": 62232,
538 | "emoji-dizzy": 62233,
539 | "emoji-expressionless-fill": 62234,
540 | "emoji-expressionless": 62235,
541 | "emoji-frown-fill": 62236,
542 | "emoji-frown": 62237,
543 | "emoji-heart-eyes-fill": 62238,
544 | "emoji-heart-eyes": 62239,
545 | "emoji-laughing-fill": 62240,
546 | "emoji-laughing": 62241,
547 | "emoji-neutral-fill": 62242,
548 | "emoji-neutral": 62243,
549 | "emoji-smile-fill": 62244,
550 | "emoji-smile-upside-down-fill": 62245,
551 | "emoji-smile-upside-down": 62246,
552 | "emoji-smile": 62247,
553 | "emoji-sunglasses-fill": 62248,
554 | "emoji-sunglasses": 62249,
555 | "emoji-wink-fill": 62250,
556 | "emoji-wink": 62251,
557 | "envelope-fill": 62252,
558 | "envelope-open-fill": 62253,
559 | "envelope-open": 62254,
560 | "envelope": 62255,
561 | "eraser-fill": 62256,
562 | "eraser": 62257,
563 | "exclamation-circle-fill": 62258,
564 | "exclamation-circle": 62259,
565 | "exclamation-diamond-fill": 62260,
566 | "exclamation-diamond": 62261,
567 | "exclamation-octagon-fill": 62262,
568 | "exclamation-octagon": 62263,
569 | "exclamation-square-fill": 62264,
570 | "exclamation-square": 62265,
571 | "exclamation-triangle-fill": 62266,
572 | "exclamation-triangle": 62267,
573 | "exclamation": 62268,
574 | "exclude": 62269,
575 | "eye-fill": 62270,
576 | "eye-slash-fill": 62271,
577 | "eye-slash": 62272,
578 | "eye": 62273,
579 | "eyedropper": 62274,
580 | "eyeglasses": 62275,
581 | "facebook": 62276,
582 | "file-arrow-down-fill": 62277,
583 | "file-arrow-down": 62278,
584 | "file-arrow-up-fill": 62279,
585 | "file-arrow-up": 62280,
586 | "file-bar-graph-fill": 62281,
587 | "file-bar-graph": 62282,
588 | "file-binary-fill": 62283,
589 | "file-binary": 62284,
590 | "file-break-fill": 62285,
591 | "file-break": 62286,
592 | "file-check-fill": 62287,
593 | "file-check": 62288,
594 | "file-code-fill": 62289,
595 | "file-code": 62290,
596 | "file-diff-fill": 62291,
597 | "file-diff": 62292,
598 | "file-earmark-arrow-down-fill": 62293,
599 | "file-earmark-arrow-down": 62294,
600 | "file-earmark-arrow-up-fill": 62295,
601 | "file-earmark-arrow-up": 62296,
602 | "file-earmark-bar-graph-fill": 62297,
603 | "file-earmark-bar-graph": 62298,
604 | "file-earmark-binary-fill": 62299,
605 | "file-earmark-binary": 62300,
606 | "file-earmark-break-fill": 62301,
607 | "file-earmark-break": 62302,
608 | "file-earmark-check-fill": 62303,
609 | "file-earmark-check": 62304,
610 | "file-earmark-code-fill": 62305,
611 | "file-earmark-code": 62306,
612 | "file-earmark-diff-fill": 62307,
613 | "file-earmark-diff": 62308,
614 | "file-earmark-easel-fill": 62309,
615 | "file-earmark-easel": 62310,
616 | "file-earmark-excel-fill": 62311,
617 | "file-earmark-excel": 62312,
618 | "file-earmark-fill": 62313,
619 | "file-earmark-font-fill": 62314,
620 | "file-earmark-font": 62315,
621 | "file-earmark-image-fill": 62316,
622 | "file-earmark-image": 62317,
623 | "file-earmark-lock-fill": 62318,
624 | "file-earmark-lock": 62319,
625 | "file-earmark-lock2-fill": 62320,
626 | "file-earmark-lock2": 62321,
627 | "file-earmark-medical-fill": 62322,
628 | "file-earmark-medical": 62323,
629 | "file-earmark-minus-fill": 62324,
630 | "file-earmark-minus": 62325,
631 | "file-earmark-music-fill": 62326,
632 | "file-earmark-music": 62327,
633 | "file-earmark-person-fill": 62328,
634 | "file-earmark-person": 62329,
635 | "file-earmark-play-fill": 62330,
636 | "file-earmark-play": 62331,
637 | "file-earmark-plus-fill": 62332,
638 | "file-earmark-plus": 62333,
639 | "file-earmark-post-fill": 62334,
640 | "file-earmark-post": 62335,
641 | "file-earmark-ppt-fill": 62336,
642 | "file-earmark-ppt": 62337,
643 | "file-earmark-richtext-fill": 62338,
644 | "file-earmark-richtext": 62339,
645 | "file-earmark-ruled-fill": 62340,
646 | "file-earmark-ruled": 62341,
647 | "file-earmark-slides-fill": 62342,
648 | "file-earmark-slides": 62343,
649 | "file-earmark-spreadsheet-fill": 62344,
650 | "file-earmark-spreadsheet": 62345,
651 | "file-earmark-text-fill": 62346,
652 | "file-earmark-text": 62347,
653 | "file-earmark-word-fill": 62348,
654 | "file-earmark-word": 62349,
655 | "file-earmark-x-fill": 62350,
656 | "file-earmark-x": 62351,
657 | "file-earmark-zip-fill": 62352,
658 | "file-earmark-zip": 62353,
659 | "file-earmark": 62354,
660 | "file-easel-fill": 62355,
661 | "file-easel": 62356,
662 | "file-excel-fill": 62357,
663 | "file-excel": 62358,
664 | "file-fill": 62359,
665 | "file-font-fill": 62360,
666 | "file-font": 62361,
667 | "file-image-fill": 62362,
668 | "file-image": 62363,
669 | "file-lock-fill": 62364,
670 | "file-lock": 62365,
671 | "file-lock2-fill": 62366,
672 | "file-lock2": 62367,
673 | "file-medical-fill": 62368,
674 | "file-medical": 62369,
675 | "file-minus-fill": 62370,
676 | "file-minus": 62371,
677 | "file-music-fill": 62372,
678 | "file-music": 62373,
679 | "file-person-fill": 62374,
680 | "file-person": 62375,
681 | "file-play-fill": 62376,
682 | "file-play": 62377,
683 | "file-plus-fill": 62378,
684 | "file-plus": 62379,
685 | "file-post-fill": 62380,
686 | "file-post": 62381,
687 | "file-ppt-fill": 62382,
688 | "file-ppt": 62383,
689 | "file-richtext-fill": 62384,
690 | "file-richtext": 62385,
691 | "file-ruled-fill": 62386,
692 | "file-ruled": 62387,
693 | "file-slides-fill": 62388,
694 | "file-slides": 62389,
695 | "file-spreadsheet-fill": 62390,
696 | "file-spreadsheet": 62391,
697 | "file-text-fill": 62392,
698 | "file-text": 62393,
699 | "file-word-fill": 62394,
700 | "file-word": 62395,
701 | "file-x-fill": 62396,
702 | "file-x": 62397,
703 | "file-zip-fill": 62398,
704 | "file-zip": 62399,
705 | "file": 62400,
706 | "files-alt": 62401,
707 | "files": 62402,
708 | "film": 62403,
709 | "filter-circle-fill": 62404,
710 | "filter-circle": 62405,
711 | "filter-left": 62406,
712 | "filter-right": 62407,
713 | "filter-square-fill": 62408,
714 | "filter-square": 62409,
715 | "filter": 62410,
716 | "flag-fill": 62411,
717 | "flag": 62412,
718 | "flower1": 62413,
719 | "flower2": 62414,
720 | "flower3": 62415,
721 | "folder-check": 62416,
722 | "folder-fill": 62417,
723 | "folder-minus": 62418,
724 | "folder-plus": 62419,
725 | "folder-symlink-fill": 62420,
726 | "folder-symlink": 62421,
727 | "folder-x": 62422,
728 | "folder": 62423,
729 | "folder2-open": 62424,
730 | "folder2": 62425,
731 | "fonts": 62426,
732 | "forward-fill": 62427,
733 | "forward": 62428,
734 | "front": 62429,
735 | "fullscreen-exit": 62430,
736 | "fullscreen": 62431,
737 | "funnel-fill": 62432,
738 | "funnel": 62433,
739 | "gear-fill": 62434,
740 | "gear-wide-connected": 62435,
741 | "gear-wide": 62436,
742 | "gear": 62437,
743 | "gem": 62438,
744 | "geo-alt-fill": 62439,
745 | "geo-alt": 62440,
746 | "geo-fill": 62441,
747 | "geo": 62442,
748 | "gift-fill": 62443,
749 | "gift": 62444,
750 | "github": 62445,
751 | "globe": 62446,
752 | "globe2": 62447,
753 | "google": 62448,
754 | "graph-down": 62449,
755 | "graph-up": 62450,
756 | "grid-1x2-fill": 62451,
757 | "grid-1x2": 62452,
758 | "grid-3x2-gap-fill": 62453,
759 | "grid-3x2-gap": 62454,
760 | "grid-3x2": 62455,
761 | "grid-3x3-gap-fill": 62456,
762 | "grid-3x3-gap": 62457,
763 | "grid-3x3": 62458,
764 | "grid-fill": 62459,
765 | "grid": 62460,
766 | "grip-horizontal": 62461,
767 | "grip-vertical": 62462,
768 | "hammer": 62463,
769 | "hand-index-fill": 62464,
770 | "hand-index-thumb-fill": 62465,
771 | "hand-index-thumb": 62466,
772 | "hand-index": 62467,
773 | "hand-thumbs-down-fill": 62468,
774 | "hand-thumbs-down": 62469,
775 | "hand-thumbs-up-fill": 62470,
776 | "hand-thumbs-up": 62471,
777 | "handbag-fill": 62472,
778 | "handbag": 62473,
779 | "hash": 62474,
780 | "hdd-fill": 62475,
781 | "hdd-network-fill": 62476,
782 | "hdd-network": 62477,
783 | "hdd-rack-fill": 62478,
784 | "hdd-rack": 62479,
785 | "hdd-stack-fill": 62480,
786 | "hdd-stack": 62481,
787 | "hdd": 62482,
788 | "headphones": 62483,
789 | "headset": 62484,
790 | "heart-fill": 62485,
791 | "heart-half": 62486,
792 | "heart": 62487,
793 | "heptagon-fill": 62488,
794 | "heptagon-half": 62489,
795 | "heptagon": 62490,
796 | "hexagon-fill": 62491,
797 | "hexagon-half": 62492,
798 | "hexagon": 62493,
799 | "hourglass-bottom": 62494,
800 | "hourglass-split": 62495,
801 | "hourglass-top": 62496,
802 | "hourglass": 62497,
803 | "house-door-fill": 62498,
804 | "house-door": 62499,
805 | "house-fill": 62500,
806 | "house": 62501,
807 | "hr": 62502,
808 | "hurricane": 62503,
809 | "image-alt": 62504,
810 | "image-fill": 62505,
811 | "image": 62506,
812 | "images": 62507,
813 | "inbox-fill": 62508,
814 | "inbox": 62509,
815 | "inboxes-fill": 62510,
816 | "inboxes": 62511,
817 | "info-circle-fill": 62512,
818 | "info-circle": 62513,
819 | "info-square-fill": 62514,
820 | "info-square": 62515,
821 | "info": 62516,
822 | "input-cursor-text": 62517,
823 | "input-cursor": 62518,
824 | "instagram": 62519,
825 | "intersect": 62520,
826 | "journal-album": 62521,
827 | "journal-arrow-down": 62522,
828 | "journal-arrow-up": 62523,
829 | "journal-bookmark-fill": 62524,
830 | "journal-bookmark": 62525,
831 | "journal-check": 62526,
832 | "journal-code": 62527,
833 | "journal-medical": 62528,
834 | "journal-minus": 62529,
835 | "journal-plus": 62530,
836 | "journal-richtext": 62531,
837 | "journal-text": 62532,
838 | "journal-x": 62533,
839 | "journal": 62534,
840 | "journals": 62535,
841 | "joystick": 62536,
842 | "justify-left": 62537,
843 | "justify-right": 62538,
844 | "justify": 62539,
845 | "kanban-fill": 62540,
846 | "kanban": 62541,
847 | "key-fill": 62542,
848 | "key": 62543,
849 | "keyboard-fill": 62544,
850 | "keyboard": 62545,
851 | "ladder": 62546,
852 | "lamp-fill": 62547,
853 | "lamp": 62548,
854 | "laptop-fill": 62549,
855 | "laptop": 62550,
856 | "layer-backward": 62551,
857 | "layer-forward": 62552,
858 | "layers-fill": 62553,
859 | "layers-half": 62554,
860 | "layers": 62555,
861 | "layout-sidebar-inset-reverse": 62556,
862 | "layout-sidebar-inset": 62557,
863 | "layout-sidebar-reverse": 62558,
864 | "layout-sidebar": 62559,
865 | "layout-split": 62560,
866 | "layout-text-sidebar-reverse": 62561,
867 | "layout-text-sidebar": 62562,
868 | "layout-text-window-reverse": 62563,
869 | "layout-text-window": 62564,
870 | "layout-three-columns": 62565,
871 | "layout-wtf": 62566,
872 | "life-preserver": 62567,
873 | "lightbulb-fill": 62568,
874 | "lightbulb-off-fill": 62569,
875 | "lightbulb-off": 62570,
876 | "lightbulb": 62571,
877 | "lightning-charge-fill": 62572,
878 | "lightning-charge": 62573,
879 | "lightning-fill": 62574,
880 | "lightning": 62575,
881 | "link-45deg": 62576,
882 | "link": 62577,
883 | "linkedin": 62578,
884 | "list-check": 62579,
885 | "list-nested": 62580,
886 | "list-ol": 62581,
887 | "list-stars": 62582,
888 | "list-task": 62583,
889 | "list-ul": 62584,
890 | "list": 62585,
891 | "lock-fill": 62586,
892 | "lock": 62587,
893 | "mailbox": 62588,
894 | "mailbox2": 62589,
895 | "map-fill": 62590,
896 | "map": 62591,
897 | "markdown-fill": 62592,
898 | "markdown": 62593,
899 | "mask": 62594,
900 | "megaphone-fill": 62595,
901 | "megaphone": 62596,
902 | "menu-app-fill": 62597,
903 | "menu-app": 62598,
904 | "menu-button-fill": 62599,
905 | "menu-button-wide-fill": 62600,
906 | "menu-button-wide": 62601,
907 | "menu-button": 62602,
908 | "menu-down": 62603,
909 | "menu-up": 62604,
910 | "mic-fill": 62605,
911 | "mic-mute-fill": 62606,
912 | "mic-mute": 62607,
913 | "mic": 62608,
914 | "minecart-loaded": 62609,
915 | "minecart": 62610,
916 | "moisture": 62611,
917 | "moon-fill": 62612,
918 | "moon-stars-fill": 62613,
919 | "moon-stars": 62614,
920 | "moon": 62615,
921 | "mouse-fill": 62616,
922 | "mouse": 62617,
923 | "mouse2-fill": 62618,
924 | "mouse2": 62619,
925 | "mouse3-fill": 62620,
926 | "mouse3": 62621,
927 | "music-note-beamed": 62622,
928 | "music-note-list": 62623,
929 | "music-note": 62624,
930 | "music-player-fill": 62625,
931 | "music-player": 62626,
932 | "newspaper": 62627,
933 | "node-minus-fill": 62628,
934 | "node-minus": 62629,
935 | "node-plus-fill": 62630,
936 | "node-plus": 62631,
937 | "nut-fill": 62632,
938 | "nut": 62633,
939 | "octagon-fill": 62634,
940 | "octagon-half": 62635,
941 | "octagon": 62636,
942 | "option": 62637,
943 | "outlet": 62638,
944 | "paint-bucket": 62639,
945 | "palette-fill": 62640,
946 | "palette": 62641,
947 | "palette2": 62642,
948 | "paperclip": 62643,
949 | "paragraph": 62644,
950 | "patch-check-fill": 62645,
951 | "patch-check": 62646,
952 | "patch-exclamation-fill": 62647,
953 | "patch-exclamation": 62648,
954 | "patch-minus-fill": 62649,
955 | "patch-minus": 62650,
956 | "patch-plus-fill": 62651,
957 | "patch-plus": 62652,
958 | "patch-question-fill": 62653,
959 | "patch-question": 62654,
960 | "pause-btn-fill": 62655,
961 | "pause-btn": 62656,
962 | "pause-circle-fill": 62657,
963 | "pause-circle": 62658,
964 | "pause-fill": 62659,
965 | "pause": 62660,
966 | "peace-fill": 62661,
967 | "peace": 62662,
968 | "pen-fill": 62663,
969 | "pen": 62664,
970 | "pencil-fill": 62665,
971 | "pencil-square": 62666,
972 | "pencil": 62667,
973 | "pentagon-fill": 62668,
974 | "pentagon-half": 62669,
975 | "pentagon": 62670,
976 | "people-fill": 62671,
977 | "people": 62672,
978 | "percent": 62673,
979 | "person-badge-fill": 62674,
980 | "person-badge": 62675,
981 | "person-bounding-box": 62676,
982 | "person-check-fill": 62677,
983 | "person-check": 62678,
984 | "person-circle": 62679,
985 | "person-dash-fill": 62680,
986 | "person-dash": 62681,
987 | "person-fill": 62682,
988 | "person-lines-fill": 62683,
989 | "person-plus-fill": 62684,
990 | "person-plus": 62685,
991 | "person-square": 62686,
992 | "person-x-fill": 62687,
993 | "person-x": 62688,
994 | "person": 62689,
995 | "phone-fill": 62690,
996 | "phone-landscape-fill": 62691,
997 | "phone-landscape": 62692,
998 | "phone-vibrate-fill": 62693,
999 | "phone-vibrate": 62694,
1000 | "phone": 62695,
1001 | "pie-chart-fill": 62696,
1002 | "pie-chart": 62697,
1003 | "pin-angle-fill": 62698,
1004 | "pin-angle": 62699,
1005 | "pin-fill": 62700,
1006 | "pin": 62701,
1007 | "pip-fill": 62702,
1008 | "pip": 62703,
1009 | "play-btn-fill": 62704,
1010 | "play-btn": 62705,
1011 | "play-circle-fill": 62706,
1012 | "play-circle": 62707,
1013 | "play-fill": 62708,
1014 | "play": 62709,
1015 | "plug-fill": 62710,
1016 | "plug": 62711,
1017 | "plus-circle-dotted": 62712,
1018 | "plus-circle-fill": 62713,
1019 | "plus-circle": 62714,
1020 | "plus-square-dotted": 62715,
1021 | "plus-square-fill": 62716,
1022 | "plus-square": 62717,
1023 | "plus": 62718,
1024 | "power": 62719,
1025 | "printer-fill": 62720,
1026 | "printer": 62721,
1027 | "puzzle-fill": 62722,
1028 | "puzzle": 62723,
1029 | "question-circle-fill": 62724,
1030 | "question-circle": 62725,
1031 | "question-diamond-fill": 62726,
1032 | "question-diamond": 62727,
1033 | "question-octagon-fill": 62728,
1034 | "question-octagon": 62729,
1035 | "question-square-fill": 62730,
1036 | "question-square": 62731,
1037 | "question": 62732,
1038 | "rainbow": 62733,
1039 | "receipt-cutoff": 62734,
1040 | "receipt": 62735,
1041 | "reception-0": 62736,
1042 | "reception-1": 62737,
1043 | "reception-2": 62738,
1044 | "reception-3": 62739,
1045 | "reception-4": 62740,
1046 | "record-btn-fill": 62741,
1047 | "record-btn": 62742,
1048 | "record-circle-fill": 62743,
1049 | "record-circle": 62744,
1050 | "record-fill": 62745,
1051 | "record": 62746,
1052 | "record2-fill": 62747,
1053 | "record2": 62748,
1054 | "reply-all-fill": 62749,
1055 | "reply-all": 62750,
1056 | "reply-fill": 62751,
1057 | "reply": 62752,
1058 | "rss-fill": 62753,
1059 | "rss": 62754,
1060 | "rulers": 62755,
1061 | "save-fill": 62756,
1062 | "save": 62757,
1063 | "save2-fill": 62758,
1064 | "save2": 62759,
1065 | "scissors": 62760,
1066 | "screwdriver": 62761,
1067 | "search": 62762,
1068 | "segmented-nav": 62763,
1069 | "server": 62764,
1070 | "share-fill": 62765,
1071 | "share": 62766,
1072 | "shield-check": 62767,
1073 | "shield-exclamation": 62768,
1074 | "shield-fill-check": 62769,
1075 | "shield-fill-exclamation": 62770,
1076 | "shield-fill-minus": 62771,
1077 | "shield-fill-plus": 62772,
1078 | "shield-fill-x": 62773,
1079 | "shield-fill": 62774,
1080 | "shield-lock-fill": 62775,
1081 | "shield-lock": 62776,
1082 | "shield-minus": 62777,
1083 | "shield-plus": 62778,
1084 | "shield-shaded": 62779,
1085 | "shield-slash-fill": 62780,
1086 | "shield-slash": 62781,
1087 | "shield-x": 62782,
1088 | "shield": 62783,
1089 | "shift-fill": 62784,
1090 | "shift": 62785,
1091 | "shop-window": 62786,
1092 | "shop": 62787,
1093 | "shuffle": 62788,
1094 | "signpost-2-fill": 62789,
1095 | "signpost-2": 62790,
1096 | "signpost-fill": 62791,
1097 | "signpost-split-fill": 62792,
1098 | "signpost-split": 62793,
1099 | "signpost": 62794,
1100 | "sim-fill": 62795,
1101 | "sim": 62796,
1102 | "skip-backward-btn-fill": 62797,
1103 | "skip-backward-btn": 62798,
1104 | "skip-backward-circle-fill": 62799,
1105 | "skip-backward-circle": 62800,
1106 | "skip-backward-fill": 62801,
1107 | "skip-backward": 62802,
1108 | "skip-end-btn-fill": 62803,
1109 | "skip-end-btn": 62804,
1110 | "skip-end-circle-fill": 62805,
1111 | "skip-end-circle": 62806,
1112 | "skip-end-fill": 62807,
1113 | "skip-end": 62808,
1114 | "skip-forward-btn-fill": 62809,
1115 | "skip-forward-btn": 62810,
1116 | "skip-forward-circle-fill": 62811,
1117 | "skip-forward-circle": 62812,
1118 | "skip-forward-fill": 62813,
1119 | "skip-forward": 62814,
1120 | "skip-start-btn-fill": 62815,
1121 | "skip-start-btn": 62816,
1122 | "skip-start-circle-fill": 62817,
1123 | "skip-start-circle": 62818,
1124 | "skip-start-fill": 62819,
1125 | "skip-start": 62820,
1126 | "slack": 62821,
1127 | "slash-circle-fill": 62822,
1128 | "slash-circle": 62823,
1129 | "slash-square-fill": 62824,
1130 | "slash-square": 62825,
1131 | "slash": 62826,
1132 | "sliders": 62827,
1133 | "smartwatch": 62828,
1134 | "snow": 62829,
1135 | "snow2": 62830,
1136 | "snow3": 62831,
1137 | "sort-alpha-down-alt": 62832,
1138 | "sort-alpha-down": 62833,
1139 | "sort-alpha-up-alt": 62834,
1140 | "sort-alpha-up": 62835,
1141 | "sort-down-alt": 62836,
1142 | "sort-down": 62837,
1143 | "sort-numeric-down-alt": 62838,
1144 | "sort-numeric-down": 62839,
1145 | "sort-numeric-up-alt": 62840,
1146 | "sort-numeric-up": 62841,
1147 | "sort-up-alt": 62842,
1148 | "sort-up": 62843,
1149 | "soundwave": 62844,
1150 | "speaker-fill": 62845,
1151 | "speaker": 62846,
1152 | "speedometer": 62847,
1153 | "speedometer2": 62848,
1154 | "spellcheck": 62849,
1155 | "square-fill": 62850,
1156 | "square-half": 62851,
1157 | "square": 62852,
1158 | "stack": 62853,
1159 | "star-fill": 62854,
1160 | "star-half": 62855,
1161 | "star": 62856,
1162 | "stars": 62857,
1163 | "stickies-fill": 62858,
1164 | "stickies": 62859,
1165 | "sticky-fill": 62860,
1166 | "sticky": 62861,
1167 | "stop-btn-fill": 62862,
1168 | "stop-btn": 62863,
1169 | "stop-circle-fill": 62864,
1170 | "stop-circle": 62865,
1171 | "stop-fill": 62866,
1172 | "stop": 62867,
1173 | "stoplights-fill": 62868,
1174 | "stoplights": 62869,
1175 | "stopwatch-fill": 62870,
1176 | "stopwatch": 62871,
1177 | "subtract": 62872,
1178 | "suit-club-fill": 62873,
1179 | "suit-club": 62874,
1180 | "suit-diamond-fill": 62875,
1181 | "suit-diamond": 62876,
1182 | "suit-heart-fill": 62877,
1183 | "suit-heart": 62878,
1184 | "suit-spade-fill": 62879,
1185 | "suit-spade": 62880,
1186 | "sun-fill": 62881,
1187 | "sun": 62882,
1188 | "sunglasses": 62883,
1189 | "sunrise-fill": 62884,
1190 | "sunrise": 62885,
1191 | "sunset-fill": 62886,
1192 | "sunset": 62887,
1193 | "symmetry-horizontal": 62888,
1194 | "symmetry-vertical": 62889,
1195 | "table": 62890,
1196 | "tablet-fill": 62891,
1197 | "tablet-landscape-fill": 62892,
1198 | "tablet-landscape": 62893,
1199 | "tablet": 62894,
1200 | "tag-fill": 62895,
1201 | "tag": 62896,
1202 | "tags-fill": 62897,
1203 | "tags": 62898,
1204 | "telegram": 62899,
1205 | "telephone-fill": 62900,
1206 | "telephone-forward-fill": 62901,
1207 | "telephone-forward": 62902,
1208 | "telephone-inbound-fill": 62903,
1209 | "telephone-inbound": 62904,
1210 | "telephone-minus-fill": 62905,
1211 | "telephone-minus": 62906,
1212 | "telephone-outbound-fill": 62907,
1213 | "telephone-outbound": 62908,
1214 | "telephone-plus-fill": 62909,
1215 | "telephone-plus": 62910,
1216 | "telephone-x-fill": 62911,
1217 | "telephone-x": 62912,
1218 | "telephone": 62913,
1219 | "terminal-fill": 62914,
1220 | "terminal": 62915,
1221 | "text-center": 62916,
1222 | "text-indent-left": 62917,
1223 | "text-indent-right": 62918,
1224 | "text-left": 62919,
1225 | "text-paragraph": 62920,
1226 | "text-right": 62921,
1227 | "textarea-resize": 62922,
1228 | "textarea-t": 62923,
1229 | "textarea": 62924,
1230 | "thermometer-half": 62925,
1231 | "thermometer-high": 62926,
1232 | "thermometer-low": 62927,
1233 | "thermometer-snow": 62928,
1234 | "thermometer-sun": 62929,
1235 | "thermometer": 62930,
1236 | "three-dots-vertical": 62931,
1237 | "three-dots": 62932,
1238 | "toggle-off": 62933,
1239 | "toggle-on": 62934,
1240 | "toggle2-off": 62935,
1241 | "toggle2-on": 62936,
1242 | "toggles": 62937,
1243 | "toggles2": 62938,
1244 | "tools": 62939,
1245 | "tornado": 62940,
1246 | "trash-fill": 62941,
1247 | "trash": 62942,
1248 | "trash2-fill": 62943,
1249 | "trash2": 62944,
1250 | "tree-fill": 62945,
1251 | "tree": 62946,
1252 | "triangle-fill": 62947,
1253 | "triangle-half": 62948,
1254 | "triangle": 62949,
1255 | "trophy-fill": 62950,
1256 | "trophy": 62951,
1257 | "tropical-storm": 62952,
1258 | "truck-flatbed": 62953,
1259 | "truck": 62954,
1260 | "tsunami": 62955,
1261 | "tv-fill": 62956,
1262 | "tv": 62957,
1263 | "twitch": 62958,
1264 | "twitter": 62959,
1265 | "type-bold": 62960,
1266 | "type-h1": 62961,
1267 | "type-h2": 62962,
1268 | "type-h3": 62963,
1269 | "type-italic": 62964,
1270 | "type-strikethrough": 62965,
1271 | "type-underline": 62966,
1272 | "type": 62967,
1273 | "ui-checks-grid": 62968,
1274 | "ui-checks": 62969,
1275 | "ui-radios-grid": 62970,
1276 | "ui-radios": 62971,
1277 | "umbrella-fill": 62972,
1278 | "umbrella": 62973,
1279 | "union": 62974,
1280 | "unlock-fill": 62975,
1281 | "unlock": 62976,
1282 | "upc-scan": 62977,
1283 | "upc": 62978,
1284 | "upload": 62979,
1285 | "vector-pen": 62980,
1286 | "view-list": 62981,
1287 | "view-stacked": 62982,
1288 | "vinyl-fill": 62983,
1289 | "vinyl": 62984,
1290 | "voicemail": 62985,
1291 | "volume-down-fill": 62986,
1292 | "volume-down": 62987,
1293 | "volume-mute-fill": 62988,
1294 | "volume-mute": 62989,
1295 | "volume-off-fill": 62990,
1296 | "volume-off": 62991,
1297 | "volume-up-fill": 62992,
1298 | "volume-up": 62993,
1299 | "vr": 62994,
1300 | "wallet-fill": 62995,
1301 | "wallet": 62996,
1302 | "wallet2": 62997,
1303 | "watch": 62998,
1304 | "water": 62999,
1305 | "whatsapp": 63000,
1306 | "wifi-1": 63001,
1307 | "wifi-2": 63002,
1308 | "wifi-off": 63003,
1309 | "wifi": 63004,
1310 | "wind": 63005,
1311 | "window-dock": 63006,
1312 | "window-sidebar": 63007,
1313 | "window": 63008,
1314 | "wrench": 63009,
1315 | "x-circle-fill": 63010,
1316 | "x-circle": 63011,
1317 | "x-diamond-fill": 63012,
1318 | "x-diamond": 63013,
1319 | "x-octagon-fill": 63014,
1320 | "x-octagon": 63015,
1321 | "x-square-fill": 63016,
1322 | "x-square": 63017,
1323 | "x": 63018,
1324 | "youtube": 63019,
1325 | "zoom-in": 63020,
1326 | "zoom-out": 63021,
1327 | "bank": 63022,
1328 | "bank2": 63023,
1329 | "bell-slash-fill": 63024,
1330 | "bell-slash": 63025,
1331 | "cash-coin": 63026,
1332 | "check-lg": 63027,
1333 | "coin": 63028,
1334 | "currency-bitcoin": 63029,
1335 | "currency-dollar": 63030,
1336 | "currency-euro": 63031,
1337 | "currency-exchange": 63032,
1338 | "currency-pound": 63033,
1339 | "currency-yen": 63034,
1340 | "dash-lg": 63035,
1341 | "exclamation-lg": 63036,
1342 | "file-earmark-pdf-fill": 63037,
1343 | "file-earmark-pdf": 63038,
1344 | "file-pdf-fill": 63039,
1345 | "file-pdf": 63040,
1346 | "gender-ambiguous": 63041,
1347 | "gender-female": 63042,
1348 | "gender-male": 63043,
1349 | "gender-trans": 63044,
1350 | "headset-vr": 63045,
1351 | "info-lg": 63046,
1352 | "mastodon": 63047,
1353 | "messenger": 63048,
1354 | "piggy-bank-fill": 63049,
1355 | "piggy-bank": 63050,
1356 | "pin-map-fill": 63051,
1357 | "pin-map": 63052,
1358 | "plus-lg": 63053,
1359 | "question-lg": 63054,
1360 | "recycle": 63055,
1361 | "reddit": 63056,
1362 | "safe-fill": 63057,
1363 | "safe2-fill": 63058,
1364 | "safe2": 63059,
1365 | "sd-card-fill": 63060,
1366 | "sd-card": 63061,
1367 | "skype": 63062,
1368 | "slash-lg": 63063,
1369 | "translate": 63064,
1370 | "x-lg": 63065,
1371 | "safe": 63066,
1372 | "apple": 63067,
1373 | "microsoft": 63069,
1374 | "windows": 63070,
1375 | "behance": 63068,
1376 | "dribbble": 63071,
1377 | "line": 63072,
1378 | "medium": 63073,
1379 | "paypal": 63074,
1380 | "pinterest": 63075,
1381 | "signal": 63076,
1382 | "snapchat": 63077,
1383 | "spotify": 63078,
1384 | "stack-overflow": 63079,
1385 | "strava": 63080,
1386 | "wordpress": 63081,
1387 | "vimeo": 63082,
1388 | "activity": 63083,
1389 | "easel2-fill": 63084,
1390 | "easel2": 63085,
1391 | "easel3-fill": 63086,
1392 | "easel3": 63087,
1393 | "fan": 63088,
1394 | "fingerprint": 63089,
1395 | "graph-down-arrow": 63090,
1396 | "graph-up-arrow": 63091,
1397 | "hypnotize": 63092,
1398 | "magic": 63093,
1399 | "person-rolodex": 63094,
1400 | "person-video": 63095,
1401 | "person-video2": 63096,
1402 | "person-video3": 63097,
1403 | "person-workspace": 63098,
1404 | "radioactive": 63099,
1405 | "webcam-fill": 63100,
1406 | "webcam": 63101,
1407 | "yin-yang": 63102,
1408 | "bandaid-fill": 63104,
1409 | "bandaid": 63105,
1410 | "bluetooth": 63106,
1411 | "body-text": 63107,
1412 | "boombox": 63108,
1413 | "boxes": 63109,
1414 | "dpad-fill": 63110,
1415 | "dpad": 63111,
1416 | "ear-fill": 63112,
1417 | "ear": 63113,
1418 | "envelope-check-fill": 63115,
1419 | "envelope-check": 63116,
1420 | "envelope-dash-fill": 63118,
1421 | "envelope-dash": 63119,
1422 | "envelope-exclamation-fill": 63121,
1423 | "envelope-exclamation": 63122,
1424 | "envelope-plus-fill": 63123,
1425 | "envelope-plus": 63124,
1426 | "envelope-slash-fill": 63126,
1427 | "envelope-slash": 63127,
1428 | "envelope-x-fill": 63129,
1429 | "envelope-x": 63130,
1430 | "explicit-fill": 63131,
1431 | "explicit": 63132,
1432 | "git": 63133,
1433 | "infinity": 63134,
1434 | "list-columns-reverse": 63135,
1435 | "list-columns": 63136,
1436 | "meta": 63137,
1437 | "nintendo-switch": 63140,
1438 | "pc-display-horizontal": 63141,
1439 | "pc-display": 63142,
1440 | "pc-horizontal": 63143,
1441 | "pc": 63144,
1442 | "playstation": 63145,
1443 | "plus-slash-minus": 63146,
1444 | "projector-fill": 63147,
1445 | "projector": 63148,
1446 | "qr-code-scan": 63149,
1447 | "qr-code": 63150,
1448 | "quora": 63151,
1449 | "quote": 63152,
1450 | "robot": 63153,
1451 | "send-check-fill": 63154,
1452 | "send-check": 63155,
1453 | "send-dash-fill": 63156,
1454 | "send-dash": 63157,
1455 | "send-exclamation-fill": 63159,
1456 | "send-exclamation": 63160,
1457 | "send-fill": 63161,
1458 | "send-plus-fill": 63162,
1459 | "send-plus": 63163,
1460 | "send-slash-fill": 63164,
1461 | "send-slash": 63165,
1462 | "send-x-fill": 63166,
1463 | "send-x": 63167,
1464 | "send": 63168,
1465 | "steam": 63169,
1466 | "terminal-dash": 63171,
1467 | "terminal-plus": 63172,
1468 | "terminal-split": 63173,
1469 | "ticket-detailed-fill": 63174,
1470 | "ticket-detailed": 63175,
1471 | "ticket-fill": 63176,
1472 | "ticket-perforated-fill": 63177,
1473 | "ticket-perforated": 63178,
1474 | "ticket": 63179,
1475 | "tiktok": 63180,
1476 | "window-dash": 63181,
1477 | "window-desktop": 63182,
1478 | "window-fullscreen": 63183,
1479 | "window-plus": 63184,
1480 | "window-split": 63185,
1481 | "window-stack": 63186,
1482 | "window-x": 63187,
1483 | "xbox": 63188,
1484 | "ethernet": 63189,
1485 | "hdmi-fill": 63190,
1486 | "hdmi": 63191,
1487 | "usb-c-fill": 63192,
1488 | "usb-c": 63193,
1489 | "usb-fill": 63194,
1490 | "usb-plug-fill": 63195,
1491 | "usb-plug": 63196,
1492 | "usb-symbol": 63197,
1493 | "usb": 63198,
1494 | "boombox-fill": 63199,
1495 | "displayport": 63201,
1496 | "gpu-card": 63202,
1497 | "memory": 63203,
1498 | "modem-fill": 63204,
1499 | "modem": 63205,
1500 | "motherboard-fill": 63206,
1501 | "motherboard": 63207,
1502 | "optical-audio-fill": 63208,
1503 | "optical-audio": 63209,
1504 | "pci-card": 63210,
1505 | "router-fill": 63211,
1506 | "router": 63212,
1507 | "thunderbolt-fill": 63215,
1508 | "thunderbolt": 63216,
1509 | "usb-drive-fill": 63217,
1510 | "usb-drive": 63218,
1511 | "usb-micro-fill": 63219,
1512 | "usb-micro": 63220,
1513 | "usb-mini-fill": 63221,
1514 | "usb-mini": 63222,
1515 | "cloud-haze2": 63223,
1516 | "device-hdd-fill": 63224,
1517 | "device-hdd": 63225,
1518 | "device-ssd-fill": 63226,
1519 | "device-ssd": 63227,
1520 | "displayport-fill": 63228,
1521 | "mortarboard-fill": 63229,
1522 | "mortarboard": 63230,
1523 | "terminal-x": 63231,
1524 | "arrow-through-heart-fill": 63232,
1525 | "arrow-through-heart": 63233,
1526 | "badge-sd-fill": 63234,
1527 | "badge-sd": 63235,
1528 | "bag-heart-fill": 63236,
1529 | "bag-heart": 63237,
1530 | "balloon-fill": 63238,
1531 | "balloon-heart-fill": 63239,
1532 | "balloon-heart": 63240,
1533 | "balloon": 63241,
1534 | "box2-fill": 63242,
1535 | "box2-heart-fill": 63243,
1536 | "box2-heart": 63244,
1537 | "box2": 63245,
1538 | "braces-asterisk": 63246,
1539 | "calendar-heart-fill": 63247,
1540 | "calendar-heart": 63248,
1541 | "calendar2-heart-fill": 63249,
1542 | "calendar2-heart": 63250,
1543 | "chat-heart-fill": 63251,
1544 | "chat-heart": 63252,
1545 | "chat-left-heart-fill": 63253,
1546 | "chat-left-heart": 63254,
1547 | "chat-right-heart-fill": 63255,
1548 | "chat-right-heart": 63256,
1549 | "chat-square-heart-fill": 63257,
1550 | "chat-square-heart": 63258,
1551 | "clipboard-check-fill": 63259,
1552 | "clipboard-data-fill": 63260,
1553 | "clipboard-fill": 63261,
1554 | "clipboard-heart-fill": 63262,
1555 | "clipboard-heart": 63263,
1556 | "clipboard-minus-fill": 63264,
1557 | "clipboard-plus-fill": 63265,
1558 | "clipboard-pulse": 63266,
1559 | "clipboard-x-fill": 63267,
1560 | "clipboard2-check-fill": 63268,
1561 | "clipboard2-check": 63269,
1562 | "clipboard2-data-fill": 63270,
1563 | "clipboard2-data": 63271,
1564 | "clipboard2-fill": 63272,
1565 | "clipboard2-heart-fill": 63273,
1566 | "clipboard2-heart": 63274,
1567 | "clipboard2-minus-fill": 63275,
1568 | "clipboard2-minus": 63276,
1569 | "clipboard2-plus-fill": 63277,
1570 | "clipboard2-plus": 63278,
1571 | "clipboard2-pulse-fill": 63279,
1572 | "clipboard2-pulse": 63280,
1573 | "clipboard2-x-fill": 63281,
1574 | "clipboard2-x": 63282,
1575 | "clipboard2": 63283,
1576 | "emoji-kiss-fill": 63284,
1577 | "emoji-kiss": 63285,
1578 | "envelope-heart-fill": 63286,
1579 | "envelope-heart": 63287,
1580 | "envelope-open-heart-fill": 63288,
1581 | "envelope-open-heart": 63289,
1582 | "envelope-paper-fill": 63290,
1583 | "envelope-paper-heart-fill": 63291,
1584 | "envelope-paper-heart": 63292,
1585 | "envelope-paper": 63293,
1586 | "filetype-aac": 63294,
1587 | "filetype-ai": 63295,
1588 | "filetype-bmp": 63296,
1589 | "filetype-cs": 63297,
1590 | "filetype-css": 63298,
1591 | "filetype-csv": 63299,
1592 | "filetype-doc": 63300,
1593 | "filetype-docx": 63301,
1594 | "filetype-exe": 63302,
1595 | "filetype-gif": 63303,
1596 | "filetype-heic": 63304,
1597 | "filetype-html": 63305,
1598 | "filetype-java": 63306,
1599 | "filetype-jpg": 63307,
1600 | "filetype-js": 63308,
1601 | "filetype-jsx": 63309,
1602 | "filetype-key": 63310,
1603 | "filetype-m4p": 63311,
1604 | "filetype-md": 63312,
1605 | "filetype-mdx": 63313,
1606 | "filetype-mov": 63314,
1607 | "filetype-mp3": 63315,
1608 | "filetype-mp4": 63316,
1609 | "filetype-otf": 63317,
1610 | "filetype-pdf": 63318,
1611 | "filetype-php": 63319,
1612 | "filetype-png": 63320,
1613 | "filetype-ppt": 63322,
1614 | "filetype-psd": 63323,
1615 | "filetype-py": 63324,
1616 | "filetype-raw": 63325,
1617 | "filetype-rb": 63326,
1618 | "filetype-sass": 63327,
1619 | "filetype-scss": 63328,
1620 | "filetype-sh": 63329,
1621 | "filetype-svg": 63330,
1622 | "filetype-tiff": 63331,
1623 | "filetype-tsx": 63332,
1624 | "filetype-ttf": 63333,
1625 | "filetype-txt": 63334,
1626 | "filetype-wav": 63335,
1627 | "filetype-woff": 63336,
1628 | "filetype-xls": 63338,
1629 | "filetype-xml": 63339,
1630 | "filetype-yml": 63340,
1631 | "heart-arrow": 63341,
1632 | "heart-pulse-fill": 63342,
1633 | "heart-pulse": 63343,
1634 | "heartbreak-fill": 63344,
1635 | "heartbreak": 63345,
1636 | "hearts": 63346,
1637 | "hospital-fill": 63347,
1638 | "hospital": 63348,
1639 | "house-heart-fill": 63349,
1640 | "house-heart": 63350,
1641 | "incognito": 63351,
1642 | "magnet-fill": 63352,
1643 | "magnet": 63353,
1644 | "person-heart": 63354,
1645 | "person-hearts": 63355,
1646 | "phone-flip": 63356,
1647 | "plugin": 63357,
1648 | "postage-fill": 63358,
1649 | "postage-heart-fill": 63359,
1650 | "postage-heart": 63360,
1651 | "postage": 63361,
1652 | "postcard-fill": 63362,
1653 | "postcard-heart-fill": 63363,
1654 | "postcard-heart": 63364,
1655 | "postcard": 63365,
1656 | "search-heart-fill": 63366,
1657 | "search-heart": 63367,
1658 | "sliders2-vertical": 63368,
1659 | "sliders2": 63369,
1660 | "trash3-fill": 63370,
1661 | "trash3": 63371,
1662 | "valentine": 63372,
1663 | "valentine2": 63373,
1664 | "wrench-adjustable-circle-fill": 63374,
1665 | "wrench-adjustable-circle": 63375,
1666 | "wrench-adjustable": 63376,
1667 | "filetype-json": 63377,
1668 | "filetype-pptx": 63378,
1669 | "filetype-xlsx": 63379,
1670 | "1-circle-fill": 63382,
1671 | "1-circle": 63383,
1672 | "1-square-fill": 63384,
1673 | "1-square": 63385,
1674 | "2-circle-fill": 63388,
1675 | "2-circle": 63389,
1676 | "2-square-fill": 63390,
1677 | "2-square": 63391,
1678 | "3-circle-fill": 63394,
1679 | "3-circle": 63395,
1680 | "3-square-fill": 63396,
1681 | "3-square": 63397,
1682 | "4-circle-fill": 63400,
1683 | "4-circle": 63401,
1684 | "4-square-fill": 63402,
1685 | "4-square": 63403,
1686 | "5-circle-fill": 63406,
1687 | "5-circle": 63407,
1688 | "5-square-fill": 63408,
1689 | "5-square": 63409,
1690 | "6-circle-fill": 63412,
1691 | "6-circle": 63413,
1692 | "6-square-fill": 63414,
1693 | "6-square": 63415,
1694 | "7-circle-fill": 63418,
1695 | "7-circle": 63419,
1696 | "7-square-fill": 63420,
1697 | "7-square": 63421,
1698 | "8-circle-fill": 63424,
1699 | "8-circle": 63425,
1700 | "8-square-fill": 63426,
1701 | "8-square": 63427,
1702 | "9-circle-fill": 63430,
1703 | "9-circle": 63431,
1704 | "9-square-fill": 63432,
1705 | "9-square": 63433,
1706 | "airplane-engines-fill": 63434,
1707 | "airplane-engines": 63435,
1708 | "airplane-fill": 63436,
1709 | "airplane": 63437,
1710 | "alexa": 63438,
1711 | "alipay": 63439,
1712 | "android": 63440,
1713 | "android2": 63441,
1714 | "box-fill": 63442,
1715 | "box-seam-fill": 63443,
1716 | "browser-chrome": 63444,
1717 | "browser-edge": 63445,
1718 | "browser-firefox": 63446,
1719 | "browser-safari": 63447,
1720 | "c-circle-fill": 63450,
1721 | "c-circle": 63451,
1722 | "c-square-fill": 63452,
1723 | "c-square": 63453,
1724 | "capsule-pill": 63454,
1725 | "capsule": 63455,
1726 | "car-front-fill": 63456,
1727 | "car-front": 63457,
1728 | "cassette-fill": 63458,
1729 | "cassette": 63459,
1730 | "cc-circle-fill": 63462,
1731 | "cc-circle": 63463,
1732 | "cc-square-fill": 63464,
1733 | "cc-square": 63465,
1734 | "cup-hot-fill": 63466,
1735 | "cup-hot": 63467,
1736 | "currency-rupee": 63468,
1737 | "dropbox": 63469,
1738 | "escape": 63470,
1739 | "fast-forward-btn-fill": 63471,
1740 | "fast-forward-btn": 63472,
1741 | "fast-forward-circle-fill": 63473,
1742 | "fast-forward-circle": 63474,
1743 | "fast-forward-fill": 63475,
1744 | "fast-forward": 63476,
1745 | "filetype-sql": 63477,
1746 | "fire": 63478,
1747 | "google-play": 63479,
1748 | "h-circle-fill": 63482,
1749 | "h-circle": 63483,
1750 | "h-square-fill": 63484,
1751 | "h-square": 63485,
1752 | "indent": 63486,
1753 | "lungs-fill": 63487,
1754 | "lungs": 63488,
1755 | "microsoft-teams": 63489,
1756 | "p-circle-fill": 63492,
1757 | "p-circle": 63493,
1758 | "p-square-fill": 63494,
1759 | "p-square": 63495,
1760 | "pass-fill": 63496,
1761 | "pass": 63497,
1762 | "prescription": 63498,
1763 | "prescription2": 63499,
1764 | "r-circle-fill": 63502,
1765 | "r-circle": 63503,
1766 | "r-square-fill": 63504,
1767 | "r-square": 63505,
1768 | "repeat-1": 63506,
1769 | "repeat": 63507,
1770 | "rewind-btn-fill": 63508,
1771 | "rewind-btn": 63509,
1772 | "rewind-circle-fill": 63510,
1773 | "rewind-circle": 63511,
1774 | "rewind-fill": 63512,
1775 | "rewind": 63513,
1776 | "train-freight-front-fill": 63514,
1777 | "train-freight-front": 63515,
1778 | "train-front-fill": 63516,
1779 | "train-front": 63517,
1780 | "train-lightrail-front-fill": 63518,
1781 | "train-lightrail-front": 63519,
1782 | "truck-front-fill": 63520,
1783 | "truck-front": 63521,
1784 | "ubuntu": 63522,
1785 | "unindent": 63523,
1786 | "unity": 63524,
1787 | "universal-access-circle": 63525,
1788 | "universal-access": 63526,
1789 | "virus": 63527,
1790 | "virus2": 63528,
1791 | "wechat": 63529,
1792 | "yelp": 63530,
1793 | "sign-stop-fill": 63531,
1794 | "sign-stop-lights-fill": 63532,
1795 | "sign-stop-lights": 63533,
1796 | "sign-stop": 63534,
1797 | "sign-turn-left-fill": 63535,
1798 | "sign-turn-left": 63536,
1799 | "sign-turn-right-fill": 63537,
1800 | "sign-turn-right": 63538,
1801 | "sign-turn-slight-left-fill": 63539,
1802 | "sign-turn-slight-left": 63540,
1803 | "sign-turn-slight-right-fill": 63541,
1804 | "sign-turn-slight-right": 63542,
1805 | "sign-yield-fill": 63543,
1806 | "sign-yield": 63544,
1807 | "ev-station-fill": 63545,
1808 | "ev-station": 63546,
1809 | "fuel-pump-diesel-fill": 63547,
1810 | "fuel-pump-diesel": 63548,
1811 | "fuel-pump-fill": 63549,
1812 | "fuel-pump": 63550,
1813 | "0-circle-fill": 63551,
1814 | "0-circle": 63552,
1815 | "0-square-fill": 63553,
1816 | "0-square": 63554,
1817 | "rocket-fill": 63555,
1818 | "rocket-takeoff-fill": 63556,
1819 | "rocket-takeoff": 63557,
1820 | "rocket": 63558,
1821 | "stripe": 63559,
1822 | "subscript": 63560,
1823 | "superscript": 63561,
1824 | "trello": 63562,
1825 | "envelope-at-fill": 63563,
1826 | "envelope-at": 63564,
1827 | "regex": 63565,
1828 | "text-wrap": 63566,
1829 | "sign-dead-end-fill": 63567,
1830 | "sign-dead-end": 63568,
1831 | "sign-do-not-enter-fill": 63569,
1832 | "sign-do-not-enter": 63570,
1833 | "sign-intersection-fill": 63571,
1834 | "sign-intersection-side-fill": 63572,
1835 | "sign-intersection-side": 63573,
1836 | "sign-intersection-t-fill": 63574,
1837 | "sign-intersection-t": 63575,
1838 | "sign-intersection-y-fill": 63576,
1839 | "sign-intersection-y": 63577,
1840 | "sign-intersection": 63578,
1841 | "sign-merge-left-fill": 63579,
1842 | "sign-merge-left": 63580,
1843 | "sign-merge-right-fill": 63581,
1844 | "sign-merge-right": 63582,
1845 | "sign-no-left-turn-fill": 63583,
1846 | "sign-no-left-turn": 63584,
1847 | "sign-no-parking-fill": 63585,
1848 | "sign-no-parking": 63586,
1849 | "sign-no-right-turn-fill": 63587,
1850 | "sign-no-right-turn": 63588,
1851 | "sign-railroad-fill": 63589,
1852 | "sign-railroad": 63590,
1853 | "building-add": 63591,
1854 | "building-check": 63592,
1855 | "building-dash": 63593,
1856 | "building-down": 63594,
1857 | "building-exclamation": 63595,
1858 | "building-fill-add": 63596,
1859 | "building-fill-check": 63597,
1860 | "building-fill-dash": 63598,
1861 | "building-fill-down": 63599,
1862 | "building-fill-exclamation": 63600,
1863 | "building-fill-gear": 63601,
1864 | "building-fill-lock": 63602,
1865 | "building-fill-slash": 63603,
1866 | "building-fill-up": 63604,
1867 | "building-fill-x": 63605,
1868 | "building-fill": 63606,
1869 | "building-gear": 63607,
1870 | "building-lock": 63608,
1871 | "building-slash": 63609,
1872 | "building-up": 63610,
1873 | "building-x": 63611,
1874 | "buildings-fill": 63612,
1875 | "buildings": 63613,
1876 | "bus-front-fill": 63614,
1877 | "bus-front": 63615,
1878 | "ev-front-fill": 63616,
1879 | "ev-front": 63617,
1880 | "globe-americas": 63618,
1881 | "globe-asia-australia": 63619,
1882 | "globe-central-south-asia": 63620,
1883 | "globe-europe-africa": 63621,
1884 | "house-add-fill": 63622,
1885 | "house-add": 63623,
1886 | "house-check-fill": 63624,
1887 | "house-check": 63625,
1888 | "house-dash-fill": 63626,
1889 | "house-dash": 63627,
1890 | "house-down-fill": 63628,
1891 | "house-down": 63629,
1892 | "house-exclamation-fill": 63630,
1893 | "house-exclamation": 63631,
1894 | "house-gear-fill": 63632,
1895 | "house-gear": 63633,
1896 | "house-lock-fill": 63634,
1897 | "house-lock": 63635,
1898 | "house-slash-fill": 63636,
1899 | "house-slash": 63637,
1900 | "house-up-fill": 63638,
1901 | "house-up": 63639,
1902 | "house-x-fill": 63640,
1903 | "house-x": 63641,
1904 | "person-add": 63642,
1905 | "person-down": 63643,
1906 | "person-exclamation": 63644,
1907 | "person-fill-add": 63645,
1908 | "person-fill-check": 63646,
1909 | "person-fill-dash": 63647,
1910 | "person-fill-down": 63648,
1911 | "person-fill-exclamation": 63649,
1912 | "person-fill-gear": 63650,
1913 | "person-fill-lock": 63651,
1914 | "person-fill-slash": 63652,
1915 | "person-fill-up": 63653,
1916 | "person-fill-x": 63654,
1917 | "person-gear": 63655,
1918 | "person-lock": 63656,
1919 | "person-slash": 63657,
1920 | "person-up": 63658,
1921 | "scooter": 63659,
1922 | "taxi-front-fill": 63660,
1923 | "taxi-front": 63661,
1924 | "amd": 63662,
1925 | "database-add": 63663,
1926 | "database-check": 63664,
1927 | "database-dash": 63665,
1928 | "database-down": 63666,
1929 | "database-exclamation": 63667,
1930 | "database-fill-add": 63668,
1931 | "database-fill-check": 63669,
1932 | "database-fill-dash": 63670,
1933 | "database-fill-down": 63671,
1934 | "database-fill-exclamation": 63672,
1935 | "database-fill-gear": 63673,
1936 | "database-fill-lock": 63674,
1937 | "database-fill-slash": 63675,
1938 | "database-fill-up": 63676,
1939 | "database-fill-x": 63677,
1940 | "database-fill": 63678,
1941 | "database-gear": 63679,
1942 | "database-lock": 63680,
1943 | "database-slash": 63681,
1944 | "database-up": 63682,
1945 | "database-x": 63683,
1946 | "database": 63684,
1947 | "houses-fill": 63685,
1948 | "houses": 63686,
1949 | "nvidia": 63687,
1950 | "person-vcard-fill": 63688,
1951 | "person-vcard": 63689,
1952 | "sina-weibo": 63690,
1953 | "tencent-qq": 63691,
1954 | "wikipedia": 63692
1955 | }
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/bootstrap-icons/fonts/bootstrap-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/bootstrap-icons/fonts/bootstrap-icons.woff
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/bootstrap-icons/fonts/bootstrap-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/css/bootstrap-icons/fonts/bootstrap-icons.woff2
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/favicon.png
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-bold-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-bold-italic.ttf
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-bold.ttf
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-italic.ttf
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/fonts/calibri-regular.ttf
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LostBeard/SpawnDev.BlazorJS.FFmpegWasm/852b1a03bab09724c2c619d00071447d88d9d343/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/icon-192.png
--------------------------------------------------------------------------------
/SpawnDev.BlazorJS.FFmpegWasmDemo/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpawnDev.BlazorJS.FFmpegWasmDemo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | An unhandled error has occurred.
27 |
Reload
28 |
🗙
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------