Spaces:
Sleeping
Sleeping
Joshua Lochner commited on
Commit ·
ebe3b4a
1
Parent(s): 5ac137c
Run prettier
Browse files- src/components/AudioManager.tsx +44 -23
- src/components/AudioPlayer.tsx +4 -1
- src/components/AudioRecorder.tsx +35 -21
- src/components/Transcript.tsx +7 -4
- src/utils/BlobFix.ts +246 -245
src/components/AudioManager.tsx
CHANGED
|
@@ -132,7 +132,13 @@ export enum AudioSource {
|
|
| 132 |
export function AudioManager(props: { transcriber: Transcriber }) {
|
| 133 |
const [progress, setProgress] = useState<number | undefined>(undefined);
|
| 134 |
const [audioData, setAudioData] = useState<
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
>(undefined);
|
| 137 |
const [audioDownloadUrl, setAudioDownloadUrl] = useState<
|
| 138 |
string | undefined
|
|
@@ -145,7 +151,10 @@ export function AudioManager(props: { transcriber: Transcriber }) {
|
|
| 145 |
setAudioDownloadUrl(undefined);
|
| 146 |
};
|
| 147 |
|
| 148 |
-
const setAudioFromDownload = async (
|
|
|
|
|
|
|
|
|
|
| 149 |
const audioCTX = new AudioContext({
|
| 150 |
sampleRate: Constants.SAMPLING_RATE,
|
| 151 |
});
|
|
@@ -199,7 +208,10 @@ export function AudioManager(props: { transcriber: Transcriber }) {
|
|
| 199 |
onDownloadProgress(progressEvent) {
|
| 200 |
setProgress(progressEvent.progress || 0);
|
| 201 |
},
|
| 202 |
-
})) as {
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
let mimeType = headers["content-type"];
|
| 205 |
if (!mimeType || mimeType === "audio/wave") {
|
|
@@ -247,22 +259,23 @@ export function AudioManager(props: { transcriber: Transcriber }) {
|
|
| 247 |
buffer: decoded,
|
| 248 |
url: blobUrl,
|
| 249 |
source: AudioSource.FILE,
|
| 250 |
-
mimeType: mimeType
|
| 251 |
});
|
| 252 |
}}
|
| 253 |
/>
|
| 254 |
-
{navigator.mediaDevices && (
|
| 255 |
-
<
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
| 266 |
</div>
|
| 267 |
{
|
| 268 |
<AudioDataBar
|
|
@@ -272,7 +285,10 @@ export function AudioManager(props: { transcriber: Transcriber }) {
|
|
| 272 |
</div>
|
| 273 |
{audioData && (
|
| 274 |
<>
|
| 275 |
-
<AudioPlayer
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
<div className='relative w-full flex justify-center items-center'>
|
| 278 |
<TranscribeButton
|
|
@@ -379,13 +395,14 @@ function SettingsModal(props: {
|
|
| 379 |
models[key].length == 2,
|
| 380 |
)
|
| 381 |
.map((key) => (
|
| 382 |
-
<option key={key} value={key}>{`whisper-${key}${
|
| 383 |
-
|
|
|
|
| 384 |
// @ts-ignore
|
| 385 |
models[key][
|
| 386 |
-
|
| 387 |
]
|
| 388 |
-
|
| 389 |
))}
|
| 390 |
</select>
|
| 391 |
<div className='flex justify-between items-center mb-3 px-1'>
|
|
@@ -458,7 +475,7 @@ function SettingsModal(props: {
|
|
| 458 |
</>
|
| 459 |
}
|
| 460 |
onClose={props.onClose}
|
| 461 |
-
onSubmit={() => {
|
| 462 |
/>
|
| 463 |
);
|
| 464 |
}
|
|
@@ -545,7 +562,11 @@ function UrlModal(props: {
|
|
| 545 |
function FileTile(props: {
|
| 546 |
icon: JSX.Element;
|
| 547 |
text: string;
|
| 548 |
-
onFileUpdate: (
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
}) {
|
| 550 |
// const audioPlayer = useRef<HTMLAudioElement>(null);
|
| 551 |
|
|
|
|
| 132 |
export function AudioManager(props: { transcriber: Transcriber }) {
|
| 133 |
const [progress, setProgress] = useState<number | undefined>(undefined);
|
| 134 |
const [audioData, setAudioData] = useState<
|
| 135 |
+
| {
|
| 136 |
+
buffer: AudioBuffer;
|
| 137 |
+
url: string;
|
| 138 |
+
source: AudioSource;
|
| 139 |
+
mimeType: string;
|
| 140 |
+
}
|
| 141 |
+
| undefined
|
| 142 |
>(undefined);
|
| 143 |
const [audioDownloadUrl, setAudioDownloadUrl] = useState<
|
| 144 |
string | undefined
|
|
|
|
| 151 |
setAudioDownloadUrl(undefined);
|
| 152 |
};
|
| 153 |
|
| 154 |
+
const setAudioFromDownload = async (
|
| 155 |
+
data: ArrayBuffer,
|
| 156 |
+
mimeType: string,
|
| 157 |
+
) => {
|
| 158 |
const audioCTX = new AudioContext({
|
| 159 |
sampleRate: Constants.SAMPLING_RATE,
|
| 160 |
});
|
|
|
|
| 208 |
onDownloadProgress(progressEvent) {
|
| 209 |
setProgress(progressEvent.progress || 0);
|
| 210 |
},
|
| 211 |
+
})) as {
|
| 212 |
+
data: ArrayBuffer;
|
| 213 |
+
headers: { "content-type": string };
|
| 214 |
+
};
|
| 215 |
|
| 216 |
let mimeType = headers["content-type"];
|
| 217 |
if (!mimeType || mimeType === "audio/wave") {
|
|
|
|
| 259 |
buffer: decoded,
|
| 260 |
url: blobUrl,
|
| 261 |
source: AudioSource.FILE,
|
| 262 |
+
mimeType: mimeType,
|
| 263 |
});
|
| 264 |
}}
|
| 265 |
/>
|
| 266 |
+
{navigator.mediaDevices && (
|
| 267 |
+
<>
|
| 268 |
+
<VerticalBar />
|
| 269 |
+
<RecordTile
|
| 270 |
+
icon={<MicrophoneIcon />}
|
| 271 |
+
text={"Record"}
|
| 272 |
+
setAudioData={(e) => {
|
| 273 |
+
props.transcriber.onInputChange();
|
| 274 |
+
setAudioFromRecording(e);
|
| 275 |
+
}}
|
| 276 |
+
/>
|
| 277 |
+
</>
|
| 278 |
+
)}
|
| 279 |
</div>
|
| 280 |
{
|
| 281 |
<AudioDataBar
|
|
|
|
| 285 |
</div>
|
| 286 |
{audioData && (
|
| 287 |
<>
|
| 288 |
+
<AudioPlayer
|
| 289 |
+
audioUrl={audioData.url}
|
| 290 |
+
mimeType={audioData.mimeType}
|
| 291 |
+
/>
|
| 292 |
|
| 293 |
<div className='relative w-full flex justify-center items-center'>
|
| 294 |
<TranscribeButton
|
|
|
|
| 395 |
models[key].length == 2,
|
| 396 |
)
|
| 397 |
.map((key) => (
|
| 398 |
+
<option key={key} value={key}>{`whisper-${key}${
|
| 399 |
+
props.transcriber.multilingual ? "" : ".en"
|
| 400 |
+
} (${
|
| 401 |
// @ts-ignore
|
| 402 |
models[key][
|
| 403 |
+
props.transcriber.quantized ? 0 : 1
|
| 404 |
]
|
| 405 |
+
}MB)`}</option>
|
| 406 |
))}
|
| 407 |
</select>
|
| 408 |
<div className='flex justify-between items-center mb-3 px-1'>
|
|
|
|
| 475 |
</>
|
| 476 |
}
|
| 477 |
onClose={props.onClose}
|
| 478 |
+
onSubmit={() => {}}
|
| 479 |
/>
|
| 480 |
);
|
| 481 |
}
|
|
|
|
| 562 |
function FileTile(props: {
|
| 563 |
icon: JSX.Element;
|
| 564 |
text: string;
|
| 565 |
+
onFileUpdate: (
|
| 566 |
+
decoded: AudioBuffer,
|
| 567 |
+
blobUrl: string,
|
| 568 |
+
mimeType: string,
|
| 569 |
+
) => void;
|
| 570 |
}) {
|
| 571 |
// const audioPlayer = useRef<HTMLAudioElement>(null);
|
| 572 |
|
src/components/AudioPlayer.tsx
CHANGED
|
@@ -1,6 +1,9 @@
|
|
| 1 |
import { useEffect, useRef } from "react";
|
| 2 |
|
| 3 |
-
export default function AudioPlayer(props: {
|
|
|
|
|
|
|
|
|
|
| 4 |
const audioPlayer = useRef<HTMLAudioElement>(null);
|
| 5 |
const audioSource = useRef<HTMLSourceElement>(null);
|
| 6 |
|
|
|
|
| 1 |
import { useEffect, useRef } from "react";
|
| 2 |
|
| 3 |
+
export default function AudioPlayer(props: {
|
| 4 |
+
audioUrl: string;
|
| 5 |
+
mimeType: string;
|
| 6 |
+
}) {
|
| 7 |
const audioPlayer = useRef<HTMLAudioElement>(null);
|
| 8 |
const audioSource = useRef<HTMLSourceElement>(null);
|
| 9 |
|
src/components/AudioRecorder.tsx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
| 1 |
-
import { useState, useEffect, useRef } from
|
| 2 |
|
| 3 |
-
import { formatAudioTimestamp } from
|
| 4 |
-
import { webmFixDuration } from
|
| 5 |
|
| 6 |
function getMimeType() {
|
| 7 |
const types = [
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
];
|
| 14 |
for (let i = 0; i < types.length; i++) {
|
| 15 |
if (MediaRecorder.isTypeSupported(types[i])) {
|
|
@@ -40,15 +40,19 @@ export default function AudioRecorder(props: {
|
|
| 40 |
|
| 41 |
try {
|
| 42 |
if (!streamRef.current) {
|
| 43 |
-
streamRef.current = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
const mimeType = getMimeType();
|
| 47 |
-
const mediaRecorder = new MediaRecorder(streamRef.current, {
|
|
|
|
|
|
|
| 48 |
|
| 49 |
mediaRecorderRef.current = mediaRecorder;
|
| 50 |
|
| 51 |
-
mediaRecorder.addEventListener(
|
| 52 |
if (event.data.size > 0) {
|
| 53 |
chunksRef.current.push(event.data);
|
| 54 |
}
|
|
@@ -58,8 +62,8 @@ export default function AudioRecorder(props: {
|
|
| 58 |
// Received a stop event
|
| 59 |
let blob = new Blob(chunksRef.current, { type: mimeType });
|
| 60 |
|
| 61 |
-
if (mimeType ===
|
| 62 |
-
blob = await webmFixDuration(blob, duration, blob.type)
|
| 63 |
}
|
| 64 |
|
| 65 |
setRecordedBlob(blob);
|
|
@@ -70,14 +74,16 @@ export default function AudioRecorder(props: {
|
|
| 70 |
});
|
| 71 |
mediaRecorder.start();
|
| 72 |
setRecording(true);
|
| 73 |
-
|
| 74 |
} catch (error) {
|
| 75 |
-
console.error(
|
| 76 |
}
|
| 77 |
};
|
| 78 |
|
| 79 |
const stopRecording = () => {
|
| 80 |
-
if (
|
|
|
|
|
|
|
|
|
|
| 81 |
mediaRecorderRef.current.stop(); // set state to inactive
|
| 82 |
setDuration(0);
|
| 83 |
setRecording(false);
|
|
@@ -116,18 +122,26 @@ export default function AudioRecorder(props: {
|
|
| 116 |
<div className='flex flex-col justify-center items-center'>
|
| 117 |
<button
|
| 118 |
type='button'
|
| 119 |
-
className={`m-2 inline-flex justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 transition-all duration-200 ${
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
| 121 |
onClick={handleToggleRecording}
|
| 122 |
>
|
| 123 |
-
{recording
|
|
|
|
|
|
|
| 124 |
</button>
|
| 125 |
|
| 126 |
{recordedBlob && (
|
| 127 |
<audio className='w-full' ref={audioRef} controls>
|
| 128 |
-
<source
|
|
|
|
|
|
|
|
|
|
| 129 |
</audio>
|
| 130 |
)}
|
| 131 |
</div>
|
| 132 |
);
|
| 133 |
-
}
|
|
|
|
| 1 |
+
import { useState, useEffect, useRef } from "react";
|
| 2 |
|
| 3 |
+
import { formatAudioTimestamp } from "../utils/AudioUtils";
|
| 4 |
+
import { webmFixDuration } from "../utils/BlobFix";
|
| 5 |
|
| 6 |
function getMimeType() {
|
| 7 |
const types = [
|
| 8 |
+
"audio/webm",
|
| 9 |
+
"audio/mp4",
|
| 10 |
+
"audio/ogg",
|
| 11 |
+
"audio/wav",
|
| 12 |
+
"audio/aac",
|
| 13 |
];
|
| 14 |
for (let i = 0; i < types.length; i++) {
|
| 15 |
if (MediaRecorder.isTypeSupported(types[i])) {
|
|
|
|
| 40 |
|
| 41 |
try {
|
| 42 |
if (!streamRef.current) {
|
| 43 |
+
streamRef.current = await navigator.mediaDevices.getUserMedia({
|
| 44 |
+
audio: true,
|
| 45 |
+
});
|
| 46 |
}
|
| 47 |
|
| 48 |
const mimeType = getMimeType();
|
| 49 |
+
const mediaRecorder = new MediaRecorder(streamRef.current, {
|
| 50 |
+
mimeType,
|
| 51 |
+
});
|
| 52 |
|
| 53 |
mediaRecorderRef.current = mediaRecorder;
|
| 54 |
|
| 55 |
+
mediaRecorder.addEventListener("dataavailable", async (event) => {
|
| 56 |
if (event.data.size > 0) {
|
| 57 |
chunksRef.current.push(event.data);
|
| 58 |
}
|
|
|
|
| 62 |
// Received a stop event
|
| 63 |
let blob = new Blob(chunksRef.current, { type: mimeType });
|
| 64 |
|
| 65 |
+
if (mimeType === "audio/webm") {
|
| 66 |
+
blob = await webmFixDuration(blob, duration, blob.type);
|
| 67 |
}
|
| 68 |
|
| 69 |
setRecordedBlob(blob);
|
|
|
|
| 74 |
});
|
| 75 |
mediaRecorder.start();
|
| 76 |
setRecording(true);
|
|
|
|
| 77 |
} catch (error) {
|
| 78 |
+
console.error("Error accessing microphone:", error);
|
| 79 |
}
|
| 80 |
};
|
| 81 |
|
| 82 |
const stopRecording = () => {
|
| 83 |
+
if (
|
| 84 |
+
mediaRecorderRef.current &&
|
| 85 |
+
mediaRecorderRef.current.state === "recording"
|
| 86 |
+
) {
|
| 87 |
mediaRecorderRef.current.stop(); // set state to inactive
|
| 88 |
setDuration(0);
|
| 89 |
setRecording(false);
|
|
|
|
| 122 |
<div className='flex flex-col justify-center items-center'>
|
| 123 |
<button
|
| 124 |
type='button'
|
| 125 |
+
className={`m-2 inline-flex justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 transition-all duration-200 ${
|
| 126 |
+
recording
|
| 127 |
+
? "bg-red-500 hover:bg-red-600"
|
| 128 |
+
: "bg-green-500 hover:bg-green-600"
|
| 129 |
+
}`}
|
| 130 |
onClick={handleToggleRecording}
|
| 131 |
>
|
| 132 |
+
{recording
|
| 133 |
+
? `Stop Recording (${formatAudioTimestamp(duration)})`
|
| 134 |
+
: "Start Recording"}
|
| 135 |
</button>
|
| 136 |
|
| 137 |
{recordedBlob && (
|
| 138 |
<audio className='w-full' ref={audioRef} controls>
|
| 139 |
+
<source
|
| 140 |
+
src={URL.createObjectURL(recordedBlob)}
|
| 141 |
+
type={recordedBlob.type}
|
| 142 |
+
/>
|
| 143 |
</audio>
|
| 144 |
)}
|
| 145 |
</div>
|
| 146 |
);
|
| 147 |
+
}
|
src/components/Transcript.tsx
CHANGED
|
@@ -20,11 +20,14 @@ export default function Transcript({ transcribedData }: Props) {
|
|
| 20 |
};
|
| 21 |
const exportTXT = () => {
|
| 22 |
let chunks = transcribedData?.chunks ?? [];
|
| 23 |
-
let text = chunks
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
const blob = new Blob([text], { type: "text/plain" });
|
| 26 |
saveBlob(blob, "transcript.txt");
|
| 27 |
-
}
|
| 28 |
const exportJSON = () => {
|
| 29 |
let jsonData = JSON.stringify(transcribedData?.chunks ?? [], null, 2);
|
| 30 |
|
|
@@ -41,8 +44,8 @@ export default function Transcript({ transcribedData }: Props) {
|
|
| 41 |
if (divRef.current) {
|
| 42 |
const diff = Math.abs(
|
| 43 |
divRef.current.offsetHeight +
|
| 44 |
-
|
| 45 |
-
|
| 46 |
);
|
| 47 |
|
| 48 |
if (diff <= 64) {
|
|
|
|
| 20 |
};
|
| 21 |
const exportTXT = () => {
|
| 22 |
let chunks = transcribedData?.chunks ?? [];
|
| 23 |
+
let text = chunks
|
| 24 |
+
.map((chunk) => chunk.text)
|
| 25 |
+
.join("")
|
| 26 |
+
.trim();
|
| 27 |
|
| 28 |
const blob = new Blob([text], { type: "text/plain" });
|
| 29 |
saveBlob(blob, "transcript.txt");
|
| 30 |
+
};
|
| 31 |
const exportJSON = () => {
|
| 32 |
let jsonData = JSON.stringify(transcribedData?.chunks ?? [], null, 2);
|
| 33 |
|
|
|
|
| 44 |
if (divRef.current) {
|
| 45 |
const diff = Math.abs(
|
| 46 |
divRef.current.offsetHeight +
|
| 47 |
+
divRef.current.scrollTop -
|
| 48 |
+
divRef.current.scrollHeight,
|
| 49 |
);
|
| 50 |
|
| 51 |
if (diff <= 64) {
|
src/utils/BlobFix.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
| 10 |
* (forked from https://github.com/yusitnikov/fix-webm-duration)
|
| 11 |
*/
|
| 12 |
|
| 13 |
-
|
| 14 |
/*
|
| 15 |
* This is the list of possible WEBM file sections by their IDs.
|
| 16 |
* Possible types: Container, Binary, Uint, Int, String, Float, Date
|
|
@@ -21,246 +20,246 @@ interface Section {
|
|
| 21 |
}
|
| 22 |
|
| 23 |
const sections: Record<number, Section> = {
|
| 24 |
-
0xa45dfa3: { name:
|
| 25 |
-
0x286: { name:
|
| 26 |
-
0x2f7: { name:
|
| 27 |
-
0x2f2: { name:
|
| 28 |
-
0x2f3: { name:
|
| 29 |
-
0x282: { name:
|
| 30 |
-
0x287: { name:
|
| 31 |
-
0x285: { name:
|
| 32 |
-
0x6c: { name:
|
| 33 |
-
0x3f: { name:
|
| 34 |
-
0xb538667: { name:
|
| 35 |
-
0x3e8a: { name:
|
| 36 |
-
0x3e9a: { name:
|
| 37 |
-
0x3ea5: { name:
|
| 38 |
-
0x3eb5: { name:
|
| 39 |
-
0x3e5b: { name:
|
| 40 |
-
0x3e7b: { name:
|
| 41 |
-
0x2532: { name:
|
| 42 |
-
0x8538067: { name:
|
| 43 |
-
0x14d9b74: { name:
|
| 44 |
-
0xdbb: { name:
|
| 45 |
-
0x13ab: { name:
|
| 46 |
-
0x13ac: { name:
|
| 47 |
-
0x549a966: { name:
|
| 48 |
-
0x33a4: { name:
|
| 49 |
-
0x3384: { name:
|
| 50 |
-
0x1cb923: { name:
|
| 51 |
-
0x1c83ab: { name:
|
| 52 |
-
0x1eb923: { name:
|
| 53 |
-
0x1e83bb: { name:
|
| 54 |
-
0x444: { name:
|
| 55 |
-
0x2924: { name:
|
| 56 |
-
0x29fc: { name:
|
| 57 |
-
0x29bf: { name:
|
| 58 |
-
0x29a5: { name:
|
| 59 |
-
0xad7b1: { name:
|
| 60 |
-
0x489: { name:
|
| 61 |
-
0x461: { name:
|
| 62 |
-
0x3ba9: { name:
|
| 63 |
-
0xd80: { name:
|
| 64 |
-
0x1741: { name:
|
| 65 |
// 0xf43b675: { name: 'Cluster', type: 'Container' },
|
| 66 |
-
0x67: { name:
|
| 67 |
-
0x1854: { name:
|
| 68 |
-
0x18d7: { name:
|
| 69 |
-
0x27: { name:
|
| 70 |
-
0x2b: { name:
|
| 71 |
-
0x23: { name:
|
| 72 |
-
0x20: { name:
|
| 73 |
-
0x21: { name:
|
| 74 |
-
0x22: { name:
|
| 75 |
-
0x35a1: { name:
|
| 76 |
-
0x26: { name:
|
| 77 |
-
0x6e: { name:
|
| 78 |
-
0x25: { name:
|
| 79 |
-
0x1b: { name:
|
| 80 |
-
0x7a: { name:
|
| 81 |
-
0x7b: { name:
|
| 82 |
-
0x7d: { name:
|
| 83 |
-
0x24: { name:
|
| 84 |
-
0x35a2: { name:
|
| 85 |
-
0xe: { name:
|
| 86 |
-
0x68: { name:
|
| 87 |
-
0x4c: { name:
|
| 88 |
-
0x4d: { name:
|
| 89 |
-
0x4b: { name:
|
| 90 |
-
0x4e: { name:
|
| 91 |
-
0x4f: { name:
|
| 92 |
-
0x48: { name:
|
| 93 |
-
0x49: { name:
|
| 94 |
-
0x4a: { name:
|
| 95 |
-
0x2f: { name:
|
| 96 |
-
0x654ae6b: { name:
|
| 97 |
-
0x2e: { name:
|
| 98 |
-
0x57: { name:
|
| 99 |
-
0x33c5: { name:
|
| 100 |
-
0x3: { name:
|
| 101 |
-
0x39: { name:
|
| 102 |
-
0x8: { name:
|
| 103 |
-
0x15aa: { name:
|
| 104 |
-
0x1c: { name:
|
| 105 |
-
0x2de7: { name:
|
| 106 |
-
0x2df8: { name:
|
| 107 |
-
0x3e383: { name:
|
| 108 |
-
0x34e7a: { name:
|
| 109 |
-
0x3314f: { name:
|
| 110 |
-
0x137f: { name:
|
| 111 |
-
0x15ee: { name:
|
| 112 |
-
0x136e: { name:
|
| 113 |
-
0x2b59c: { name:
|
| 114 |
-
0x6: { name:
|
| 115 |
-
0x23a2: { name:
|
| 116 |
-
0x58688: { name:
|
| 117 |
-
0x3446: { name:
|
| 118 |
-
0x1a9697: { name:
|
| 119 |
-
0x1b4040: { name:
|
| 120 |
-
0x6b240: { name:
|
| 121 |
-
0x2a: { name:
|
| 122 |
-
0x2fab: { name:
|
| 123 |
-
0x16aa: { name:
|
| 124 |
-
0x16bb: { name:
|
| 125 |
-
0x2624: { name:
|
| 126 |
-
0x26fc: { name:
|
| 127 |
-
0x26bf: { name:
|
| 128 |
-
0x26a5: { name:
|
| 129 |
-
0x60: { name:
|
| 130 |
-
0x1a: { name:
|
| 131 |
-
0x13b8: { name:
|
| 132 |
-
0x13c0: { name:
|
| 133 |
-
0x13b9: { name:
|
| 134 |
-
0x30: { name:
|
| 135 |
-
0x3a: { name:
|
| 136 |
-
0x14aa: { name:
|
| 137 |
-
0x14bb: { name:
|
| 138 |
-
0x14cc: { name:
|
| 139 |
-
0x14dd: { name:
|
| 140 |
-
0x14b0: { name:
|
| 141 |
-
0x14ba: { name:
|
| 142 |
-
0x14b2: { name:
|
| 143 |
-
0x14b3: { name:
|
| 144 |
-
0xeb524: { name:
|
| 145 |
-
0xfb523: { name:
|
| 146 |
-
0x383e3: { name:
|
| 147 |
-
0x61: { name:
|
| 148 |
-
0x35: { name:
|
| 149 |
-
0x38b5: { name:
|
| 150 |
-
0x1f: { name:
|
| 151 |
-
0x3d7b: { name:
|
| 152 |
-
0x2264: { name:
|
| 153 |
-
0x62: { name:
|
| 154 |
-
0x63: { name:
|
| 155 |
-
0x64: { name:
|
| 156 |
-
0x65: { name:
|
| 157 |
-
0x66: { name:
|
| 158 |
-
0x69: { name:
|
| 159 |
-
0x6d: { name:
|
| 160 |
-
0x40: { name:
|
| 161 |
-
0x41: { name:
|
| 162 |
-
0x46: { name:
|
| 163 |
-
0x47: { name:
|
| 164 |
-
0x44: { name:
|
| 165 |
-
0x2d80: { name:
|
| 166 |
-
0x2240: { name:
|
| 167 |
-
0x1031: { name:
|
| 168 |
-
0x1032: { name:
|
| 169 |
-
0x1033: { name:
|
| 170 |
-
0x1034: { name:
|
| 171 |
-
0x254: { name:
|
| 172 |
-
0x255: { name:
|
| 173 |
-
0x1035: { name:
|
| 174 |
-
0x7e1: { name:
|
| 175 |
-
0x7e2: { name:
|
| 176 |
-
0x7e3: { name:
|
| 177 |
-
0x7e4: { name:
|
| 178 |
-
0x7e5: { name:
|
| 179 |
-
0x7e6: { name:
|
| 180 |
-
0xc53bb6b: { name:
|
| 181 |
-
0x3b: { name:
|
| 182 |
-
0x33: { name:
|
| 183 |
-
0x37: { name:
|
| 184 |
-
0x77: { name:
|
| 185 |
-
0x71: { name:
|
| 186 |
-
0x70: { name:
|
| 187 |
-
0x32: { name:
|
| 188 |
-
0x1378: { name:
|
| 189 |
-
0x6a: { name:
|
| 190 |
-
0x5b: { name:
|
| 191 |
-
0x16: { name:
|
| 192 |
-
0x17: { name:
|
| 193 |
-
0x135f: { name:
|
| 194 |
-
0x6b: { name:
|
| 195 |
-
0x941a469: { name:
|
| 196 |
-
0x21a7: { name:
|
| 197 |
-
0x67e: { name:
|
| 198 |
-
0x66e: { name:
|
| 199 |
-
0x660: { name:
|
| 200 |
-
0x65c: { name:
|
| 201 |
-
0x6ae: { name:
|
| 202 |
-
0x675: { name:
|
| 203 |
-
0x661: { name:
|
| 204 |
-
0x662: { name:
|
| 205 |
-
0x43a770: { name:
|
| 206 |
-
0x5b9: { name:
|
| 207 |
-
0x5bc: { name:
|
| 208 |
-
0x5bd: { name:
|
| 209 |
-
0x5db: { name:
|
| 210 |
-
0x5dd: { name:
|
| 211 |
-
0x36: { name:
|
| 212 |
-
0x33c4: { name:
|
| 213 |
-
0x1654: { name:
|
| 214 |
-
0x11: { name:
|
| 215 |
-
0x12: { name:
|
| 216 |
-
0x18: { name:
|
| 217 |
-
0x598: { name:
|
| 218 |
-
0x2e67: { name:
|
| 219 |
-
0x2ebc: { name:
|
| 220 |
-
0x23c3: { name:
|
| 221 |
-
0xf: { name:
|
| 222 |
-
0x9: { name:
|
| 223 |
-
0x0: { name:
|
| 224 |
-
0x5: { name:
|
| 225 |
-
0x37c: { name:
|
| 226 |
-
0x37e: { name:
|
| 227 |
-
0x2944: { name:
|
| 228 |
-
0x2955: { name:
|
| 229 |
-
0x50d: { name:
|
| 230 |
-
0x2911: { name:
|
| 231 |
-
0x2922: { name:
|
| 232 |
-
0x2933: { name:
|
| 233 |
-
0x254c367: { name:
|
| 234 |
-
0x3373: { name:
|
| 235 |
-
0x23c0: { name:
|
| 236 |
-
0x28ca: { name:
|
| 237 |
-
0x23ca: { name:
|
| 238 |
-
0x23c5: { name:
|
| 239 |
-
0x23c9: { name:
|
| 240 |
-
0x23c4: { name:
|
| 241 |
-
0x23c6: { name:
|
| 242 |
-
0x27c8: { name:
|
| 243 |
-
0x5a3: { name:
|
| 244 |
-
0x47a: { name:
|
| 245 |
-
0x484: { name:
|
| 246 |
-
0x487: { name:
|
| 247 |
-
0x485: { name:
|
| 248 |
};
|
| 249 |
|
| 250 |
class WebmBase<T> {
|
| 251 |
source?: Uint8Array;
|
| 252 |
data?: T;
|
| 253 |
|
| 254 |
-
constructor(private name =
|
| 255 |
|
| 256 |
-
updateBySource() {
|
| 257 |
|
| 258 |
setSource(source: Uint8Array) {
|
| 259 |
this.source = source;
|
| 260 |
this.updateBySource();
|
| 261 |
}
|
| 262 |
|
| 263 |
-
updateByData() {
|
| 264 |
|
| 265 |
setData(data: T) {
|
| 266 |
this.data = data;
|
|
@@ -270,12 +269,12 @@ class WebmBase<T> {
|
|
| 270 |
|
| 271 |
class WebmUint extends WebmBase<string> {
|
| 272 |
constructor(name: string, type: string) {
|
| 273 |
-
super(name, type ||
|
| 274 |
}
|
| 275 |
|
| 276 |
updateBySource() {
|
| 277 |
// use hex representation of a number instead of number value
|
| 278 |
-
this.data =
|
| 279 |
for (let i = 0; i < this.source!.length; i++) {
|
| 280 |
const hex = this.source![i].toString(16);
|
| 281 |
this.data += padHex(hex);
|
|
@@ -301,12 +300,12 @@ class WebmUint extends WebmBase<string> {
|
|
| 301 |
}
|
| 302 |
|
| 303 |
function padHex(hex: string) {
|
| 304 |
-
return hex.length % 2 === 1 ?
|
| 305 |
}
|
| 306 |
|
| 307 |
class WebmFloat extends WebmBase<number> {
|
| 308 |
constructor(name: string, type: string) {
|
| 309 |
-
super(name, type ||
|
| 310 |
}
|
| 311 |
|
| 312 |
getFloatArrayType() {
|
|
@@ -345,7 +344,7 @@ class WebmContainer extends WebmBase<ContainerData[]> {
|
|
| 345 |
data: ContainerData[] = [];
|
| 346 |
|
| 347 |
constructor(name: string, type: string) {
|
| 348 |
-
super(name, type ||
|
| 349 |
}
|
| 350 |
|
| 351 |
readByte() {
|
|
@@ -375,16 +374,16 @@ class WebmContainer extends WebmBase<ContainerData[]> {
|
|
| 375 |
end = Math.min(this.offset + len, this.source!.length);
|
| 376 |
const data = this.source!.slice(this.offset, end);
|
| 377 |
|
| 378 |
-
const info = sections[id] || { name:
|
| 379 |
let ctr: any = WebmBase;
|
| 380 |
switch (info.type) {
|
| 381 |
-
case
|
| 382 |
ctr = WebmContainer;
|
| 383 |
break;
|
| 384 |
-
case
|
| 385 |
ctr = WebmUint;
|
| 386 |
break;
|
| 387 |
-
case
|
| 388 |
ctr = WebmFloat;
|
| 389 |
break;
|
| 390 |
}
|
|
@@ -402,7 +401,7 @@ class WebmContainer extends WebmBase<ContainerData[]> {
|
|
| 402 |
var bytes = 1, flag = 0x80;
|
| 403 |
x >= flag && bytes < 8;
|
| 404 |
bytes++, flag *= 0x80
|
| 405 |
-
) {
|
| 406 |
|
| 407 |
if (!draft) {
|
| 408 |
let value = flag + x;
|
|
@@ -455,7 +454,7 @@ class WebmContainer extends WebmBase<ContainerData[]> {
|
|
| 455 |
|
| 456 |
class WebmFile extends WebmContainer {
|
| 457 |
constructor(source: Uint8Array) {
|
| 458 |
-
super(
|
| 459 |
this.setSource(source);
|
| 460 |
}
|
| 461 |
|
|
@@ -466,13 +465,15 @@ class WebmFile extends WebmContainer {
|
|
| 466 |
}
|
| 467 |
|
| 468 |
const infoSection = segmentSection.getSectionById(
|
| 469 |
-
0x549a966
|
| 470 |
) as WebmContainer;
|
| 471 |
if (!infoSection) {
|
| 472 |
return false;
|
| 473 |
}
|
| 474 |
|
| 475 |
-
const timeScaleSection = infoSection.getSectionById(
|
|
|
|
|
|
|
| 476 |
if (!timeScaleSection) {
|
| 477 |
return false;
|
| 478 |
}
|
|
@@ -486,7 +487,7 @@ class WebmFile extends WebmContainer {
|
|
| 486 |
}
|
| 487 |
} else {
|
| 488 |
// append Duration section
|
| 489 |
-
durationSection = new WebmFloat(
|
| 490 |
durationSection.setValue(duration);
|
| 491 |
infoSection.data.push({
|
| 492 |
id: 0x489,
|
|
@@ -503,7 +504,7 @@ class WebmFile extends WebmContainer {
|
|
| 503 |
return true;
|
| 504 |
}
|
| 505 |
|
| 506 |
-
toBlob(type =
|
| 507 |
return new Blob([this.source!.buffer], { type });
|
| 508 |
}
|
| 509 |
}
|
|
@@ -518,13 +519,13 @@ class WebmFile extends WebmContainer {
|
|
| 518 |
export const webmFixDuration = (
|
| 519 |
blob: Blob,
|
| 520 |
duration: number,
|
| 521 |
-
type =
|
| 522 |
): Promise<Blob> => {
|
| 523 |
return new Promise((resolve, reject) => {
|
| 524 |
try {
|
| 525 |
const reader = new FileReader();
|
| 526 |
|
| 527 |
-
reader.addEventListener(
|
| 528 |
try {
|
| 529 |
const result = reader.result as ArrayBuffer;
|
| 530 |
const file = new WebmFile(new Uint8Array(result));
|
|
@@ -538,7 +539,7 @@ export const webmFixDuration = (
|
|
| 538 |
}
|
| 539 |
});
|
| 540 |
|
| 541 |
-
reader.addEventListener(
|
| 542 |
|
| 543 |
reader.readAsArrayBuffer(blob);
|
| 544 |
} catch (ex) {
|
|
|
|
| 10 |
* (forked from https://github.com/yusitnikov/fix-webm-duration)
|
| 11 |
*/
|
| 12 |
|
|
|
|
| 13 |
/*
|
| 14 |
* This is the list of possible WEBM file sections by their IDs.
|
| 15 |
* Possible types: Container, Binary, Uint, Int, String, Float, Date
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
const sections: Record<number, Section> = {
|
| 23 |
+
0xa45dfa3: { name: "EBML", type: "Container" },
|
| 24 |
+
0x286: { name: "EBMLVersion", type: "Uint" },
|
| 25 |
+
0x2f7: { name: "EBMLReadVersion", type: "Uint" },
|
| 26 |
+
0x2f2: { name: "EBMLMaxIDLength", type: "Uint" },
|
| 27 |
+
0x2f3: { name: "EBMLMaxSizeLength", type: "Uint" },
|
| 28 |
+
0x282: { name: "DocType", type: "String" },
|
| 29 |
+
0x287: { name: "DocTypeVersion", type: "Uint" },
|
| 30 |
+
0x285: { name: "DocTypeReadVersion", type: "Uint" },
|
| 31 |
+
0x6c: { name: "Void", type: "Binary" },
|
| 32 |
+
0x3f: { name: "CRC-32", type: "Binary" },
|
| 33 |
+
0xb538667: { name: "SignatureSlot", type: "Container" },
|
| 34 |
+
0x3e8a: { name: "SignatureAlgo", type: "Uint" },
|
| 35 |
+
0x3e9a: { name: "SignatureHash", type: "Uint" },
|
| 36 |
+
0x3ea5: { name: "SignaturePublicKey", type: "Binary" },
|
| 37 |
+
0x3eb5: { name: "Signature", type: "Binary" },
|
| 38 |
+
0x3e5b: { name: "SignatureElements", type: "Container" },
|
| 39 |
+
0x3e7b: { name: "SignatureElementList", type: "Container" },
|
| 40 |
+
0x2532: { name: "SignedElement", type: "Binary" },
|
| 41 |
+
0x8538067: { name: "Segment", type: "Container" },
|
| 42 |
+
0x14d9b74: { name: "SeekHead", type: "Container" },
|
| 43 |
+
0xdbb: { name: "Seek", type: "Container" },
|
| 44 |
+
0x13ab: { name: "SeekID", type: "Binary" },
|
| 45 |
+
0x13ac: { name: "SeekPosition", type: "Uint" },
|
| 46 |
+
0x549a966: { name: "Info", type: "Container" },
|
| 47 |
+
0x33a4: { name: "SegmentUID", type: "Binary" },
|
| 48 |
+
0x3384: { name: "SegmentFilename", type: "String" },
|
| 49 |
+
0x1cb923: { name: "PrevUID", type: "Binary" },
|
| 50 |
+
0x1c83ab: { name: "PrevFilename", type: "String" },
|
| 51 |
+
0x1eb923: { name: "NextUID", type: "Binary" },
|
| 52 |
+
0x1e83bb: { name: "NextFilename", type: "String" },
|
| 53 |
+
0x444: { name: "SegmentFamily", type: "Binary" },
|
| 54 |
+
0x2924: { name: "ChapterTranslate", type: "Container" },
|
| 55 |
+
0x29fc: { name: "ChapterTranslateEditionUID", type: "Uint" },
|
| 56 |
+
0x29bf: { name: "ChapterTranslateCodec", type: "Uint" },
|
| 57 |
+
0x29a5: { name: "ChapterTranslateID", type: "Binary" },
|
| 58 |
+
0xad7b1: { name: "TimecodeScale", type: "Uint" },
|
| 59 |
+
0x489: { name: "Duration", type: "Float" },
|
| 60 |
+
0x461: { name: "DateUTC", type: "Date" },
|
| 61 |
+
0x3ba9: { name: "Title", type: "String" },
|
| 62 |
+
0xd80: { name: "MuxingApp", type: "String" },
|
| 63 |
+
0x1741: { name: "WritingApp", type: "String" },
|
| 64 |
// 0xf43b675: { name: 'Cluster', type: 'Container' },
|
| 65 |
+
0x67: { name: "Timecode", type: "Uint" },
|
| 66 |
+
0x1854: { name: "SilentTracks", type: "Container" },
|
| 67 |
+
0x18d7: { name: "SilentTrackNumber", type: "Uint" },
|
| 68 |
+
0x27: { name: "Position", type: "Uint" },
|
| 69 |
+
0x2b: { name: "PrevSize", type: "Uint" },
|
| 70 |
+
0x23: { name: "SimpleBlock", type: "Binary" },
|
| 71 |
+
0x20: { name: "BlockGroup", type: "Container" },
|
| 72 |
+
0x21: { name: "Block", type: "Binary" },
|
| 73 |
+
0x22: { name: "BlockVirtual", type: "Binary" },
|
| 74 |
+
0x35a1: { name: "BlockAdditions", type: "Container" },
|
| 75 |
+
0x26: { name: "BlockMore", type: "Container" },
|
| 76 |
+
0x6e: { name: "BlockAddID", type: "Uint" },
|
| 77 |
+
0x25: { name: "BlockAdditional", type: "Binary" },
|
| 78 |
+
0x1b: { name: "BlockDuration", type: "Uint" },
|
| 79 |
+
0x7a: { name: "ReferencePriority", type: "Uint" },
|
| 80 |
+
0x7b: { name: "ReferenceBlock", type: "Int" },
|
| 81 |
+
0x7d: { name: "ReferenceVirtual", type: "Int" },
|
| 82 |
+
0x24: { name: "CodecState", type: "Binary" },
|
| 83 |
+
0x35a2: { name: "DiscardPadding", type: "Int" },
|
| 84 |
+
0xe: { name: "Slices", type: "Container" },
|
| 85 |
+
0x68: { name: "TimeSlice", type: "Container" },
|
| 86 |
+
0x4c: { name: "LaceNumber", type: "Uint" },
|
| 87 |
+
0x4d: { name: "FrameNumber", type: "Uint" },
|
| 88 |
+
0x4b: { name: "BlockAdditionID", type: "Uint" },
|
| 89 |
+
0x4e: { name: "Delay", type: "Uint" },
|
| 90 |
+
0x4f: { name: "SliceDuration", type: "Uint" },
|
| 91 |
+
0x48: { name: "ReferenceFrame", type: "Container" },
|
| 92 |
+
0x49: { name: "ReferenceOffset", type: "Uint" },
|
| 93 |
+
0x4a: { name: "ReferenceTimeCode", type: "Uint" },
|
| 94 |
+
0x2f: { name: "EncryptedBlock", type: "Binary" },
|
| 95 |
+
0x654ae6b: { name: "Tracks", type: "Container" },
|
| 96 |
+
0x2e: { name: "TrackEntry", type: "Container" },
|
| 97 |
+
0x57: { name: "TrackNumber", type: "Uint" },
|
| 98 |
+
0x33c5: { name: "TrackUID", type: "Uint" },
|
| 99 |
+
0x3: { name: "TrackType", type: "Uint" },
|
| 100 |
+
0x39: { name: "FlagEnabled", type: "Uint" },
|
| 101 |
+
0x8: { name: "FlagDefault", type: "Uint" },
|
| 102 |
+
0x15aa: { name: "FlagForced", type: "Uint" },
|
| 103 |
+
0x1c: { name: "FlagLacing", type: "Uint" },
|
| 104 |
+
0x2de7: { name: "MinCache", type: "Uint" },
|
| 105 |
+
0x2df8: { name: "MaxCache", type: "Uint" },
|
| 106 |
+
0x3e383: { name: "DefaultDuration", type: "Uint" },
|
| 107 |
+
0x34e7a: { name: "DefaultDecodedFieldDuration", type: "Uint" },
|
| 108 |
+
0x3314f: { name: "TrackTimecodeScale", type: "Float" },
|
| 109 |
+
0x137f: { name: "TrackOffset", type: "Int" },
|
| 110 |
+
0x15ee: { name: "MaxBlockAdditionID", type: "Uint" },
|
| 111 |
+
0x136e: { name: "Name", type: "String" },
|
| 112 |
+
0x2b59c: { name: "Language", type: "String" },
|
| 113 |
+
0x6: { name: "CodecID", type: "String" },
|
| 114 |
+
0x23a2: { name: "CodecPrivate", type: "Binary" },
|
| 115 |
+
0x58688: { name: "CodecName", type: "String" },
|
| 116 |
+
0x3446: { name: "AttachmentLink", type: "Uint" },
|
| 117 |
+
0x1a9697: { name: "CodecSettings", type: "String" },
|
| 118 |
+
0x1b4040: { name: "CodecInfoURL", type: "String" },
|
| 119 |
+
0x6b240: { name: "CodecDownloadURL", type: "String" },
|
| 120 |
+
0x2a: { name: "CodecDecodeAll", type: "Uint" },
|
| 121 |
+
0x2fab: { name: "TrackOverlay", type: "Uint" },
|
| 122 |
+
0x16aa: { name: "CodecDelay", type: "Uint" },
|
| 123 |
+
0x16bb: { name: "SeekPreRoll", type: "Uint" },
|
| 124 |
+
0x2624: { name: "TrackTranslate", type: "Container" },
|
| 125 |
+
0x26fc: { name: "TrackTranslateEditionUID", type: "Uint" },
|
| 126 |
+
0x26bf: { name: "TrackTranslateCodec", type: "Uint" },
|
| 127 |
+
0x26a5: { name: "TrackTranslateTrackID", type: "Binary" },
|
| 128 |
+
0x60: { name: "Video", type: "Container" },
|
| 129 |
+
0x1a: { name: "FlagInterlaced", type: "Uint" },
|
| 130 |
+
0x13b8: { name: "StereoMode", type: "Uint" },
|
| 131 |
+
0x13c0: { name: "AlphaMode", type: "Uint" },
|
| 132 |
+
0x13b9: { name: "OldStereoMode", type: "Uint" },
|
| 133 |
+
0x30: { name: "PixelWidth", type: "Uint" },
|
| 134 |
+
0x3a: { name: "PixelHeight", type: "Uint" },
|
| 135 |
+
0x14aa: { name: "PixelCropBottom", type: "Uint" },
|
| 136 |
+
0x14bb: { name: "PixelCropTop", type: "Uint" },
|
| 137 |
+
0x14cc: { name: "PixelCropLeft", type: "Uint" },
|
| 138 |
+
0x14dd: { name: "PixelCropRight", type: "Uint" },
|
| 139 |
+
0x14b0: { name: "DisplayWidth", type: "Uint" },
|
| 140 |
+
0x14ba: { name: "DisplayHeight", type: "Uint" },
|
| 141 |
+
0x14b2: { name: "DisplayUnit", type: "Uint" },
|
| 142 |
+
0x14b3: { name: "AspectRatioType", type: "Uint" },
|
| 143 |
+
0xeb524: { name: "ColourSpace", type: "Binary" },
|
| 144 |
+
0xfb523: { name: "GammaValue", type: "Float" },
|
| 145 |
+
0x383e3: { name: "FrameRate", type: "Float" },
|
| 146 |
+
0x61: { name: "Audio", type: "Container" },
|
| 147 |
+
0x35: { name: "SamplingFrequency", type: "Float" },
|
| 148 |
+
0x38b5: { name: "OutputSamplingFrequency", type: "Float" },
|
| 149 |
+
0x1f: { name: "Channels", type: "Uint" },
|
| 150 |
+
0x3d7b: { name: "ChannelPositions", type: "Binary" },
|
| 151 |
+
0x2264: { name: "BitDepth", type: "Uint" },
|
| 152 |
+
0x62: { name: "TrackOperation", type: "Container" },
|
| 153 |
+
0x63: { name: "TrackCombinePlanes", type: "Container" },
|
| 154 |
+
0x64: { name: "TrackPlane", type: "Container" },
|
| 155 |
+
0x65: { name: "TrackPlaneUID", type: "Uint" },
|
| 156 |
+
0x66: { name: "TrackPlaneType", type: "Uint" },
|
| 157 |
+
0x69: { name: "TrackJoinBlocks", type: "Container" },
|
| 158 |
+
0x6d: { name: "TrackJoinUID", type: "Uint" },
|
| 159 |
+
0x40: { name: "TrickTrackUID", type: "Uint" },
|
| 160 |
+
0x41: { name: "TrickTrackSegmentUID", type: "Binary" },
|
| 161 |
+
0x46: { name: "TrickTrackFlag", type: "Uint" },
|
| 162 |
+
0x47: { name: "TrickMasterTrackUID", type: "Uint" },
|
| 163 |
+
0x44: { name: "TrickMasterTrackSegmentUID", type: "Binary" },
|
| 164 |
+
0x2d80: { name: "ContentEncodings", type: "Container" },
|
| 165 |
+
0x2240: { name: "ContentEncoding", type: "Container" },
|
| 166 |
+
0x1031: { name: "ContentEncodingOrder", type: "Uint" },
|
| 167 |
+
0x1032: { name: "ContentEncodingScope", type: "Uint" },
|
| 168 |
+
0x1033: { name: "ContentEncodingType", type: "Uint" },
|
| 169 |
+
0x1034: { name: "ContentCompression", type: "Container" },
|
| 170 |
+
0x254: { name: "ContentCompAlgo", type: "Uint" },
|
| 171 |
+
0x255: { name: "ContentCompSettings", type: "Binary" },
|
| 172 |
+
0x1035: { name: "ContentEncryption", type: "Container" },
|
| 173 |
+
0x7e1: { name: "ContentEncAlgo", type: "Uint" },
|
| 174 |
+
0x7e2: { name: "ContentEncKeyID", type: "Binary" },
|
| 175 |
+
0x7e3: { name: "ContentSignature", type: "Binary" },
|
| 176 |
+
0x7e4: { name: "ContentSigKeyID", type: "Binary" },
|
| 177 |
+
0x7e5: { name: "ContentSigAlgo", type: "Uint" },
|
| 178 |
+
0x7e6: { name: "ContentSigHashAlgo", type: "Uint" },
|
| 179 |
+
0xc53bb6b: { name: "Cues", type: "Container" },
|
| 180 |
+
0x3b: { name: "CuePoint", type: "Container" },
|
| 181 |
+
0x33: { name: "CueTime", type: "Uint" },
|
| 182 |
+
0x37: { name: "CueTrackPositions", type: "Container" },
|
| 183 |
+
0x77: { name: "CueTrack", type: "Uint" },
|
| 184 |
+
0x71: { name: "CueClusterPosition", type: "Uint" },
|
| 185 |
+
0x70: { name: "CueRelativePosition", type: "Uint" },
|
| 186 |
+
0x32: { name: "CueDuration", type: "Uint" },
|
| 187 |
+
0x1378: { name: "CueBlockNumber", type: "Uint" },
|
| 188 |
+
0x6a: { name: "CueCodecState", type: "Uint" },
|
| 189 |
+
0x5b: { name: "CueReference", type: "Container" },
|
| 190 |
+
0x16: { name: "CueRefTime", type: "Uint" },
|
| 191 |
+
0x17: { name: "CueRefCluster", type: "Uint" },
|
| 192 |
+
0x135f: { name: "CueRefNumber", type: "Uint" },
|
| 193 |
+
0x6b: { name: "CueRefCodecState", type: "Uint" },
|
| 194 |
+
0x941a469: { name: "Attachments", type: "Container" },
|
| 195 |
+
0x21a7: { name: "AttachedFile", type: "Container" },
|
| 196 |
+
0x67e: { name: "FileDescription", type: "String" },
|
| 197 |
+
0x66e: { name: "FileName", type: "String" },
|
| 198 |
+
0x660: { name: "FileMimeType", type: "String" },
|
| 199 |
+
0x65c: { name: "FileData", type: "Binary" },
|
| 200 |
+
0x6ae: { name: "FileUID", type: "Uint" },
|
| 201 |
+
0x675: { name: "FileReferral", type: "Binary" },
|
| 202 |
+
0x661: { name: "FileUsedStartTime", type: "Uint" },
|
| 203 |
+
0x662: { name: "FileUsedEndTime", type: "Uint" },
|
| 204 |
+
0x43a770: { name: "Chapters", type: "Container" },
|
| 205 |
+
0x5b9: { name: "EditionEntry", type: "Container" },
|
| 206 |
+
0x5bc: { name: "EditionUID", type: "Uint" },
|
| 207 |
+
0x5bd: { name: "EditionFlagHidden", type: "Uint" },
|
| 208 |
+
0x5db: { name: "EditionFlagDefault", type: "Uint" },
|
| 209 |
+
0x5dd: { name: "EditionFlagOrdered", type: "Uint" },
|
| 210 |
+
0x36: { name: "ChapterAtom", type: "Container" },
|
| 211 |
+
0x33c4: { name: "ChapterUID", type: "Uint" },
|
| 212 |
+
0x1654: { name: "ChapterStringUID", type: "String" },
|
| 213 |
+
0x11: { name: "ChapterTimeStart", type: "Uint" },
|
| 214 |
+
0x12: { name: "ChapterTimeEnd", type: "Uint" },
|
| 215 |
+
0x18: { name: "ChapterFlagHidden", type: "Uint" },
|
| 216 |
+
0x598: { name: "ChapterFlagEnabled", type: "Uint" },
|
| 217 |
+
0x2e67: { name: "ChapterSegmentUID", type: "Binary" },
|
| 218 |
+
0x2ebc: { name: "ChapterSegmentEditionUID", type: "Uint" },
|
| 219 |
+
0x23c3: { name: "ChapterPhysicalEquiv", type: "Uint" },
|
| 220 |
+
0xf: { name: "ChapterTrack", type: "Container" },
|
| 221 |
+
0x9: { name: "ChapterTrackNumber", type: "Uint" },
|
| 222 |
+
0x0: { name: "ChapterDisplay", type: "Container" },
|
| 223 |
+
0x5: { name: "ChapString", type: "String" },
|
| 224 |
+
0x37c: { name: "ChapLanguage", type: "String" },
|
| 225 |
+
0x37e: { name: "ChapCountry", type: "String" },
|
| 226 |
+
0x2944: { name: "ChapProcess", type: "Container" },
|
| 227 |
+
0x2955: { name: "ChapProcessCodecID", type: "Uint" },
|
| 228 |
+
0x50d: { name: "ChapProcessPrivate", type: "Binary" },
|
| 229 |
+
0x2911: { name: "ChapProcessCommand", type: "Container" },
|
| 230 |
+
0x2922: { name: "ChapProcessTime", type: "Uint" },
|
| 231 |
+
0x2933: { name: "ChapProcessData", type: "Binary" },
|
| 232 |
+
0x254c367: { name: "Tags", type: "Container" },
|
| 233 |
+
0x3373: { name: "Tag", type: "Container" },
|
| 234 |
+
0x23c0: { name: "Targets", type: "Container" },
|
| 235 |
+
0x28ca: { name: "TargetTypeValue", type: "Uint" },
|
| 236 |
+
0x23ca: { name: "TargetType", type: "String" },
|
| 237 |
+
0x23c5: { name: "TagTrackUID", type: "Uint" },
|
| 238 |
+
0x23c9: { name: "TagEditionUID", type: "Uint" },
|
| 239 |
+
0x23c4: { name: "TagChapterUID", type: "Uint" },
|
| 240 |
+
0x23c6: { name: "TagAttachmentUID", type: "Uint" },
|
| 241 |
+
0x27c8: { name: "SimpleTag", type: "Container" },
|
| 242 |
+
0x5a3: { name: "TagName", type: "String" },
|
| 243 |
+
0x47a: { name: "TagLanguage", type: "String" },
|
| 244 |
+
0x484: { name: "TagDefault", type: "Uint" },
|
| 245 |
+
0x487: { name: "TagString", type: "String" },
|
| 246 |
+
0x485: { name: "TagBinary", type: "Binary" },
|
| 247 |
};
|
| 248 |
|
| 249 |
class WebmBase<T> {
|
| 250 |
source?: Uint8Array;
|
| 251 |
data?: T;
|
| 252 |
|
| 253 |
+
constructor(private name = "Unknown", private type = "Unknown") {}
|
| 254 |
|
| 255 |
+
updateBySource() {}
|
| 256 |
|
| 257 |
setSource(source: Uint8Array) {
|
| 258 |
this.source = source;
|
| 259 |
this.updateBySource();
|
| 260 |
}
|
| 261 |
|
| 262 |
+
updateByData() {}
|
| 263 |
|
| 264 |
setData(data: T) {
|
| 265 |
this.data = data;
|
|
|
|
| 269 |
|
| 270 |
class WebmUint extends WebmBase<string> {
|
| 271 |
constructor(name: string, type: string) {
|
| 272 |
+
super(name, type || "Uint");
|
| 273 |
}
|
| 274 |
|
| 275 |
updateBySource() {
|
| 276 |
// use hex representation of a number instead of number value
|
| 277 |
+
this.data = "";
|
| 278 |
for (let i = 0; i < this.source!.length; i++) {
|
| 279 |
const hex = this.source![i].toString(16);
|
| 280 |
this.data += padHex(hex);
|
|
|
|
| 300 |
}
|
| 301 |
|
| 302 |
function padHex(hex: string) {
|
| 303 |
+
return hex.length % 2 === 1 ? "0" + hex : hex;
|
| 304 |
}
|
| 305 |
|
| 306 |
class WebmFloat extends WebmBase<number> {
|
| 307 |
constructor(name: string, type: string) {
|
| 308 |
+
super(name, type || "Float");
|
| 309 |
}
|
| 310 |
|
| 311 |
getFloatArrayType() {
|
|
|
|
| 344 |
data: ContainerData[] = [];
|
| 345 |
|
| 346 |
constructor(name: string, type: string) {
|
| 347 |
+
super(name, type || "Container");
|
| 348 |
}
|
| 349 |
|
| 350 |
readByte() {
|
|
|
|
| 374 |
end = Math.min(this.offset + len, this.source!.length);
|
| 375 |
const data = this.source!.slice(this.offset, end);
|
| 376 |
|
| 377 |
+
const info = sections[id] || { name: "Unknown", type: "Unknown" };
|
| 378 |
let ctr: any = WebmBase;
|
| 379 |
switch (info.type) {
|
| 380 |
+
case "Container":
|
| 381 |
ctr = WebmContainer;
|
| 382 |
break;
|
| 383 |
+
case "Uint":
|
| 384 |
ctr = WebmUint;
|
| 385 |
break;
|
| 386 |
+
case "Float":
|
| 387 |
ctr = WebmFloat;
|
| 388 |
break;
|
| 389 |
}
|
|
|
|
| 401 |
var bytes = 1, flag = 0x80;
|
| 402 |
x >= flag && bytes < 8;
|
| 403 |
bytes++, flag *= 0x80
|
| 404 |
+
) {}
|
| 405 |
|
| 406 |
if (!draft) {
|
| 407 |
let value = flag + x;
|
|
|
|
| 454 |
|
| 455 |
class WebmFile extends WebmContainer {
|
| 456 |
constructor(source: Uint8Array) {
|
| 457 |
+
super("File", "File");
|
| 458 |
this.setSource(source);
|
| 459 |
}
|
| 460 |
|
|
|
|
| 465 |
}
|
| 466 |
|
| 467 |
const infoSection = segmentSection.getSectionById(
|
| 468 |
+
0x549a966,
|
| 469 |
) as WebmContainer;
|
| 470 |
if (!infoSection) {
|
| 471 |
return false;
|
| 472 |
}
|
| 473 |
|
| 474 |
+
const timeScaleSection = infoSection.getSectionById(
|
| 475 |
+
0xad7b1,
|
| 476 |
+
) as WebmFloat;
|
| 477 |
if (!timeScaleSection) {
|
| 478 |
return false;
|
| 479 |
}
|
|
|
|
| 487 |
}
|
| 488 |
} else {
|
| 489 |
// append Duration section
|
| 490 |
+
durationSection = new WebmFloat("Duration", "Float");
|
| 491 |
durationSection.setValue(duration);
|
| 492 |
infoSection.data.push({
|
| 493 |
id: 0x489,
|
|
|
|
| 504 |
return true;
|
| 505 |
}
|
| 506 |
|
| 507 |
+
toBlob(type = "video/webm") {
|
| 508 |
return new Blob([this.source!.buffer], { type });
|
| 509 |
}
|
| 510 |
}
|
|
|
|
| 519 |
export const webmFixDuration = (
|
| 520 |
blob: Blob,
|
| 521 |
duration: number,
|
| 522 |
+
type = "video/webm",
|
| 523 |
): Promise<Blob> => {
|
| 524 |
return new Promise((resolve, reject) => {
|
| 525 |
try {
|
| 526 |
const reader = new FileReader();
|
| 527 |
|
| 528 |
+
reader.addEventListener("loadend", () => {
|
| 529 |
try {
|
| 530 |
const result = reader.result as ArrayBuffer;
|
| 531 |
const file = new WebmFile(new Uint8Array(result));
|
|
|
|
| 539 |
}
|
| 540 |
});
|
| 541 |
|
| 542 |
+
reader.addEventListener("error", () => reject());
|
| 543 |
|
| 544 |
reader.readAsArrayBuffer(blob);
|
| 545 |
} catch (ex) {
|