Skip to content

Commit

Permalink
Added merged mp3 download
Browse files Browse the repository at this point in the history
  • Loading branch information
squaresmile committed Aug 12, 2024
1 parent 3bf6b4a commit c6bdd89
Show file tree
Hide file tree
Showing 5 changed files with 1,557 additions and 1,295 deletions.
50 changes: 39 additions & 11 deletions packages/db/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"typescript": "^5.5.4",
"vite": "5.0",
"vite-plugin-eslint2": "^4.4.0",
"vite-plugin-svgr": "^4.2.0"
"vite-plugin-svgr": "^4.2.0",
"wasm-media-encoders": "^0.7.0"
},
"overrides": {},
"scripts": {
Expand Down
54 changes: 50 additions & 4 deletions packages/db/src/Descriptor/VoiceLineMerger.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Thanks to crunker by jackedgson
// https://github.com/jackedgson/crunker/blob/master/src/crunker.js
import axios from "axios";
import { createMp3Encoder } from "wasm-media-encoders";

const padAudio = (audioContext: AudioContext, buffer: AudioBuffer, delay: number) => {
if (delay === 0) {
Expand Down Expand Up @@ -101,7 +102,7 @@ const writeWav = (buffer: AudioBuffer) => {
return view;
};

const mergeVoiceLine = async (audioAssetUrls: string[], delay: number[], fileName?: string) => {
const mergeVoiceLine = async (audioAssetUrls: string[], delay: number[], format: "mp3" | "wav", fileName?: string) => {
let AudioContext =
window.AudioContext || // Default
(window as any).webkitAudioContext || // Safari and old versions of Chrome
Expand Down Expand Up @@ -130,8 +131,53 @@ const mergeVoiceLine = async (audioAssetUrls: string[], delay: number[], fileNam
}

const combinedAudio = concatAudios(audioContext, paddedAudioBuffers);
const audioDataView = writeWav(combinedAudio);
const audioBlob = new Blob([audioDataView], { type: "audio/wav" });
const encodedData =
format === "wav"
? writeWav(combinedAudio)
: await createMp3Encoder().then((encoder) => {
encoder.configure({
sampleRate: combinedAudio.sampleRate,
channels: combinedAudio.numberOfChannels === 1 ? 1 : 2,
// Preferably VBR but there's a bug with duration in the VBR file
// https://github.com/arseneyr/wasm-media-encoders/issues/7
bitrate: 128,
});

let outBuffer = new Uint8Array(1024 * 1024);
let offset = 0;
let moreData = true;

while (true) {
const mp3Data = moreData
? encoder.encode(
combinedAudio.numberOfChannels === 1
? [combinedAudio.getChannelData(0)]
: [combinedAudio.getChannelData(0), combinedAudio.getChannelData(1)]
)
: encoder.finalize();

/* mp3Data is a Uint8Array that is still owned by the encoder and MUST be copied */

if (mp3Data.length + offset > outBuffer.length) {
const newBuffer = new Uint8Array(mp3Data.length + offset);
newBuffer.set(outBuffer);
outBuffer = newBuffer;
}

outBuffer.set(mp3Data, offset);
offset += mp3Data.length;

if (!moreData) {
break;
}

moreData = false;
}

return new Uint8Array(outBuffer.buffer, 0, offset);
});

const audioBlob = new Blob([encodedData], { type: format === "wav" ? "audio/wav" : "audio/mpeg" });

const audioIds = audioAssetUrls.map((url) => {
const splittedUrl = url.split("/");
Expand All @@ -141,7 +187,7 @@ const mergeVoiceLine = async (audioAssetUrls: string[], delay: number[], fileNam

const a = document.createElement("a");
a.href = URL.createObjectURL(audioBlob);
a.download = `${fileName ?? "merged"} - ${audioIds.join("&")}.wav`;
a.download = `${fileName ?? "merged"} - ${audioIds.join("&")}.${format}`;
a.click();
} catch (e) {
alert("Failed to download some voice line parts.");
Expand Down
13 changes: 11 additions & 2 deletions packages/db/src/Page/Servant/ServantVoiceLines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,19 @@ export const VoiceLinesTable = ({
title={`${t("Download")} ${voiceLineNames[index]} ${t("Merged File")}`}
onClick={() => {
const fileName = `${mergedDownloadNamePrefix} - ${voiceLineNames[index]}`;
mergeVoiceLine(line.audioAssets, line.delay, fileName);
mergeVoiceLine(line.audioAssets, line.delay, "wav", fileName);
}}
>
{t("Merged")}
{t("Merged")} (WAV)
</Dropdown.Item>
<Dropdown.Item
title={`${t("Download")} ${voiceLineNames[index]} ${t("Merged File")}`}
onClick={() => {
const fileName = `${mergedDownloadNamePrefix} - ${voiceLineNames[index]}`;
mergeVoiceLine(line.audioAssets, line.delay, "mp3", fileName);
}}
>
{t("Merged")} (MP3)
</Dropdown.Item>
{line.audioAssets.map((asset, i) => (
<Dropdown.Item
Expand Down
Loading

0 comments on commit c6bdd89

Please sign in to comment.