diff --git a/.vscode/settings.json b/.vscode/settings.json index 0ecec27ec..ad081d03a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,15 +2,11 @@ "[javascript]": { "editor.formatOnSave": false }, - "editor.rulers": [ - 100 - ], + "editor.rulers": [100], "editor.fontLigatures": true, "prettier.tabWidth": 4, "eslint.alwaysShowStatus": true, - "prettier.disableLanguages": [ - "js" - ], + "prettier.disableLanguages": ["js"], "prettier.useTabs": true, "editor.formatOnSave": true, "editor.multiCursorModifier": "alt", @@ -27,8 +23,8 @@ "eslint.codeAction.disableRuleComment": {}, "eslint.codeAction.showDocumentation": {}, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.workingDirectories": [], "editor.tabSize": 2 -} \ No newline at end of file +} diff --git a/package.json b/package.json index 5d562b6aa..09a4b542b 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,8 @@ "@electron/remote": "^2.0.8", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", + "@ffmpeg/ffmpeg": "^0.12.7", + "@ffmpeg/util": "^0.12.1", "@headlessui/react": "^1.7.8", "@heroicons/react": "^2.0.14", "@material-ui/core": "^4.12.4", diff --git a/renderer/src/components/AudioRecorder/core/audioUtils.js b/renderer/src/components/AudioRecorder/core/audioUtils.js index 7e82974ef..a8b68af27 100644 --- a/renderer/src/components/AudioRecorder/core/audioUtils.js +++ b/renderer/src/components/AudioRecorder/core/audioUtils.js @@ -1,78 +1,59 @@ /* eslint-disable no-await-in-loop */ +import { fetchFile } from '@ffmpeg/util'; +import * as localforage from 'localforage'; import * as logger from '../../../logger'; - -const toWav = require('audiobuffer-to-wav'); +import packageInfo from '../../../../../package.json'; function sec_to_min_sec_milli_convertor(time) { - logger.debug('audioUtils.js', 'In time conversion function'); - let milliseconds = time.toString().split('.')[1]; - if (milliseconds === undefined) { - milliseconds = '0'; + try { + logger.debug('audioUtils.js', 'In time conversion function'); + let milliseconds = time.toString().split('.')[1]; + if (milliseconds === undefined) { + milliseconds = '0'; + } + const minutes = Math.floor(time / 60); + const seconds = (time - minutes * 60).toString().split('.')[0].padStart(2, 0); + const formatedStringTime = `${minutes.toString().padStart(2, 0)}:${seconds}:${milliseconds.padStart(2, 0)}`; + logger.debug('audioUtils.js', 'In time conversion function done'); + return [minutes, seconds, milliseconds, formatedStringTime]; + } catch (err) { + throw new Error(`audio generation failed : ${err}`); } - const minutes = Math.floor(time / 60); - const seconds = (time - minutes * 60).toString().split('.')[0].padStart(2, 0); - const formatedStringTime = `${minutes.toString().padStart(2, 0)}:${seconds}:${milliseconds.padStart(2, 0)}`; - return [minutes, seconds, milliseconds, formatedStringTime]; } async function generateTimeStampData(buffers, book, chapter) { - logger.debug('audioUtils.js', 'In TimeStamp Generation'); - return new Promise((resolve) => { - let fileString = 'verse_number\tstart_timestamp\tduration\n'; - const seperator = '\t'; - const fileType = 'tsv'; - const file = `${book}_${chapter.toString().padStart(3, 0)}.${fileType}`; - let start = 0; - buffers.forEach((buffer, index) => { - const currentVerse = `Verse_${(index + 1).toString().padStart(2, 0)}`; - const startTimeString = sec_to_min_sec_milli_convertor(start)[3]; - const durationString = sec_to_min_sec_milli_convertor(buffer.duration)[3]; - fileString += `${currentVerse + seperator + startTimeString + seperator + durationString}\n`; - start += buffer.duration; + try { + logger.debug('audioUtils.js', 'In TimeStamp Generation'); + return new Promise((resolve) => { + let fileString = 'verse_number\tstart_timestamp\tduration\n'; + const seperator = '\t'; + const fileType = 'tsv'; + const file = `${book}_${chapter.toString().padStart(3, 0)}.${fileType}`; + let start = 0; // because of 1 sec silence in merged verses + buffers.forEach((buffer, index) => { + const currentVerse = `Verse_${(index + 1).toString().padStart(2, 0)}`; + const startTimeString = sec_to_min_sec_milli_convertor(start)[3]; + let durationString; + const silenceDuration = 2; + let offset = 0; + if (index === 0 || (index === buffers.length - 1)) { + // adding this because of 2 sec silence in the start || 2 sec silence in the end add it to last verse + durationString = sec_to_min_sec_milli_convertor(buffer.duration + silenceDuration)[3]; + offset = silenceDuration; + } else { + offset = 0; + durationString = sec_to_min_sec_milli_convertor(buffer.duration)[3]; + } + fileString += `${currentVerse + seperator + startTimeString + seperator + durationString}\n`; + start += buffer.duration + offset; + }); + resolve([file, fileString]); + }).catch((err) => { + throw new Error(`audio generation failed : ${err}`); }); - resolve([file, fileString]); - }); -} - -async function fetchAndCombineAudio(audioArr, path) { - logger.debug('audioUtils.js', 'In Fetch and merge audio function'); - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const context = new window.AudioContext(); - - // store the decoded buff - const sources = []; - - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const url of audioArr) { - try { - const response = await fetch(path.join('file://', url)); - const buffer = await response.arrayBuffer(); - const decodedData = await context.decodeAudioData(buffer); - sources.push(decodedData); - } catch (err) { - logger.error('audioUtils.js', `Error reading audio - ${url} : ${err}`); - } - } - - logger.debug('audioUtils.js', 'In fetchAndCombineAudio : Fetch audio success '); - - const totalLength = sources.reduce((total, source) => total + source.length, 0); - const output = context.createBuffer(1, totalLength, context.sampleRate); - - let offset = 0; - - // eslint-disable-next-line no-restricted-syntax - for (const source of sources) { - output.copyToChannel(source.getChannelData(0), 0, offset); - offset += source.length; - } - const wavData = toWav(output); - logger.debug('audioUtils.js', 'In fetchAndCombineAudio : generate wav success'); - const blob = new Blob([new DataView(wavData)], { type: 'audio/wav' }); - logger.debug('audioUtils.js', 'In fetchAndCombineAudio : Generate Final merged Audio success '); - resolve({ blob, buffers: sources }); - }); + } catch (err) { + throw new Error('audio generation failed : err'); + } } function sortingLogic(a, b) { @@ -80,55 +61,108 @@ function sortingLogic(a, b) { return a.split('_')[1] - b.split('_')[1]; } -export async function mergeAudio(audioArr, dirPath, path, book, chapter) { +export async function mergeAudio(audioArr, dirPath, path, book, chapter, extension, ffmpeg, project) { + try { logger.debug('audioUtils.js', 'In Merge Audio fucntion'); - // const audio = new ConcatAudio(window); - return new Promise((resolve) => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { audioArr.sort(sortingLogic); for (let i = 0; i < audioArr.length; i++) { audioArr[i] = path.join(dirPath, audioArr[i]); } - logger.debug('audioUtils.js', 'start merging audios'); - - fetchAndCombineAudio(audioArr, path) - .then(async (mergedData) => { - logger.debug('audioUtils.js', `Audio merge success . Started Generate Timestamp : ${book} : ${chapter}`); - generateTimeStampData(mergedData.buffers, book, chapter) - .then((timeStampData) => { - logger.debug('audioUtils.js', `return Merged Audio for chapter : ${book} : ${chapter}`); - resolve([mergedData.blob, timeStampData]); - }); + logger.debug('audioUtils.js', 'mergeAudio : start merging audios'); + + const inputCmdArr = []; + const commandArr = []; + const audioBuffersArr = []; + let filterStr = ''; + let currentUser; + await localforage.getItem('userProfile').then((value) => { + currentUser = value?.username; }); + // only support specific data fields // title, artist, album, year, comment + const audioMetaDataArr = [ + '-metadata', `title=${book}_${chapter}`, + '-metadata', `artist=${currentUser || packageInfo.name}`, + '-metadata', `album=${project.name}`, + '-metadata', `comment=${packageInfo.name}_${packageInfo.version}`, + '-metadata', `date=${new Date().getFullYear().toString()}`, + ]; + + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + + for (let i = 0; i < audioArr.length; i++) { + const audioFile = await fetchFile(path.join('file://', audioArr[i])); + const audioFileCopy = new Uint8Array(audioFile); + await ffmpeg.writeFile(`input_${i}.wav`, audioFile); + inputCmdArr.push('-i', `input_${i}.wav`); + filterStr += `[${i}:0]`; + + const audioBuffer = await audioContext.decodeAudioData(audioFileCopy.buffer); + audioBuffersArr.push(audioBuffer); + } + filterStr += `concat=n=${audioArr.length + 1 }:v=0:a=1[out]`; + + commandArr.push( + ...inputCmdArr, + '-filter_complex', + `aevalsrc=0:d=2[silence1];[silence1]${filterStr}`, + '-map', + '[out]', + '-ar', + '48000', + `output.${extension}`, + ); + + console.log({ filterStr, commandArr }); + + logger.debug('audioUtils.js', 'mergeAudio : audio internal write done and buffers created'); + + // exeute merge process + await ffmpeg.exec(commandArr); + + // add end 2 sec to the create audio + await ffmpeg.exec([ + '-i', + `output.${extension}`, + '-filter_complex', + 'aevalsrc=0:d=2[silenceEnd];[0:0][silenceEnd]concat=n=2:v=0:a=1[outEnd]', + '-map', + '[outEnd]', + ...audioMetaDataArr, + '-ar', + '48000', + `outputMerged.${extension}`, + ]); + + // write the audio back and convert + + // generate unit8 buffer out + const fileData = await ffmpeg.readFile(`outputMerged.${extension}`); + + logger.debug('audioUtils.js', 'mergeAudio : audio merged buffer created'); + + // Create a Blob from the result + const blob = new Blob([fileData.buffer], { type: extension === 'mp3' ? 'audio/mpeg' : 'audio/wav' }); + logger.debug('audioUtils.js', 'mergeAudio : audio merged blob created'); + + if (blob) { + generateTimeStampData(audioBuffersArr, book, chapter) + .then((timeStampData) => { + logger.debug('audioUtils.js', `mergeAudio : return timestamp for Merged Audio for chapter : ${book} : ${chapter}`); + resolve([blob, timeStampData]); + }).catch((err) => { + throw new Error(`unable to generate audio : ${err}`); + }); + } else { + throw new Error('unable to generate audio'); + } + } catch (err) { + reject(new Error(`audio generation failed : ${err}`)); + } }); + } catch (err) { + throw new Error(`audio generation failed : ${err}`); + } } - -// old snippet for reference - -// export async function mergeAudio(audioArr, dirPath, path, book, chapter) { -// logger.debug('audioUtils.js', 'In Merge Audio fucntion'); -// const audio = new ConcatAudio(window); -// return new Promise((resolve) => { -// let merged; -// let output; -// audioArr.sort(); -// for (let i = 0; i < audioArr.length; i++) { -// audioArr[i] = path.join(dirPath, audioArr[i]); -// } -// logger.debug('audioUtils.js', 'start merging audios'); -// audio.fetchAudio(...audioArr) -// .then(async (buffers) => { -// // generate timestamp data string -// await generateTimeStampData(buffers, book, chapter) -// .then((timeStampData) => { -// // merging all buffers -// merged = audio.concatAudio(buffers); -// return [merged, timeStampData]; -// }) -// .then(async ([merged, timeStampData]) => { -// output = audio.export(merged, 'audio/mp3'); -// logger.debug('audioUtils.js', `return Merged Audio for chapter : ${book} : ${chapter}`); -// resolve([output.blob, timeStampData]); -// }); -// }); -// }); -// } diff --git a/renderer/src/components/EditorPage/AudioEditor/AudioEditor.js b/renderer/src/components/EditorPage/AudioEditor/AudioEditor.js index 274f4f5be..37a6cba42 100644 --- a/renderer/src/components/EditorPage/AudioEditor/AudioEditor.js +++ b/renderer/src/components/EditorPage/AudioEditor/AudioEditor.js @@ -158,9 +158,9 @@ const AudioEditor = ({ editor }) => { } else { // If found only one audio for the verse then making that audio as default one. // replace url with `${chapter}_${verseNum[1]}_1_default.mp3` - bookContent[key].contents[v].take1 = `${chapter}_${verseNum[1]}_1_default.mp3`; + bookContent[key].contents[v].take1 = `${chapter}_${verseNum[1]}_1_default.wav`; bookContent[key].contents[v].default = 'take1'; - fs.renameSync(path.join(filePath, chapterNum, verse), path.join(filePath, chapterNum, `${chapter}_${verseNum[1]}_1_default.mp3`)); + fs.renameSync(path.join(filePath, chapterNum, verse), path.join(filePath, chapterNum, `${chapter}_${verseNum[1]}_1_default.wav`)); } } }, diff --git a/renderer/src/components/EditorPage/AudioEditor/MainPlayer.js b/renderer/src/components/EditorPage/AudioEditor/MainPlayer.js index 7a88d2fe3..11aa9c471 100644 --- a/renderer/src/components/EditorPage/AudioEditor/MainPlayer.js +++ b/renderer/src/components/EditorPage/AudioEditor/MainPlayer.js @@ -105,14 +105,14 @@ const MainPlayer = () => { const path = require('path'); let i = 1; // Checking whether the take has any audio - if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}.mp3`))) { + if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}.wav`))) { while (i < 4) { // Looking for the existed default file so that we can easily rename both the files - if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.mp3`))) { + if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.wav`))) { // Checking whether the user is trying to default the same default file, else rename both. if (i !== value) { - fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.mp3`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.mp3`)); - fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}.mp3`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}_default.mp3`)); + fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.wav`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.wav`)); + fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}.wav`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${value}_default.wav`)); } } i += 1; @@ -121,6 +121,7 @@ const MainPlayer = () => { loadChapter(); } }; + const saveAudio = (blob) => { getDetails() .then(({ @@ -135,13 +136,13 @@ const MainPlayer = () => { let filePath; if (name.length > 0) { // While Re-recording replacing the file with same name - if (fs.existsSync(path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.mp3`))) { - filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.mp3`); + if (fs.existsSync(path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.wav`))) { + filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.wav`); } else { - filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}.mp3`); + filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}.wav`); } } else { - filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.mp3`); + filePath = path.join(projectsDir, 'audio', 'ingredients', bookId.toUpperCase(), chapter, `${chapter}_${verse}_${result}_default.wav`); } fs.mkdirSync(path.dirname(filePath), { recursive: true }); @@ -156,6 +157,9 @@ const MainPlayer = () => { }); }; fileReader.readAsArrayBuffer(blob); + + // // save as mp3 + // saveAudioAsMP3(blob, projectsDir); }); }; const playRecordingFeedback = useCallback( @@ -173,7 +177,7 @@ const MainPlayer = () => { } = useReactMediaRecorder({ audio: true, onStop: playRecordingFeedback, - blobPropertyBag: { type: 'audio/mp3' }, + blobPropertyBag: { type: 'audio/wav' }, }); const handleFunction = () => { // We have used trigger to identify whether the call is from DeleteAudio or Re-record @@ -191,15 +195,15 @@ const MainPlayer = () => { }, ); // Checking whether the user is trying to delete the default file. - if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.mp3`))) { + if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.wav`))) { // Since user is deleting the default file, we need to change the default to some other takes if available let i = 1; while (i < 4) { if (i !== +result) { - if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.mp3`))) { - fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.mp3`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.mp3`)); + if (fs.existsSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.wav`))) { + fs.renameSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}.wav`), path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${i}_default.wav`)); delete audioCurrentChapter.bookContent[chapter - 1].contents[versePosition][take]; - fs.unlinkSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.mp3`)); + fs.unlinkSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.wav`)); delete audioCurrentChapter.bookContent[chapter - 1].contents[versePosition][take]; i = 5; } @@ -208,13 +212,13 @@ const MainPlayer = () => { } // Deleting the default one if (i !== 6) { - fs.unlinkSync(path.join(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.mp3`))); + fs.unlinkSync(path.join(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}_default.wav`))); delete audioCurrentChapter.bookContent[chapter - 1].contents[versePosition][take]; delete audioCurrentChapter.bookContent[chapter - 1].contents[versePosition].default; } } else { delete audioCurrentChapter.bookContent[chapter - 1].contents[versePosition][take]; - fs.unlinkSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}.mp3`)); + fs.unlinkSync(path.join(audioCurrentChapter.filePath, audioCurrentChapter.chapterNum, `${chapter}_${verse}_${result}.wav`)); } // Since we are not loading the entire component and updating the JSON data (audioContent), we need to update it manually. setAudioContent(audioCurrentChapter.bookContent[chapter - 1].contents); diff --git a/renderer/src/core/burrito/importBurrito.js b/renderer/src/core/burrito/importBurrito.js index 50d0d565c..ed194c9b4 100644 --- a/renderer/src/core/burrito/importBurrito.js +++ b/renderer/src/core/burrito/importBurrito.js @@ -103,6 +103,7 @@ export const viewBurrito = async (filePath, currentUser, resource) => { result.version = metadata.meta.version; result.burritoType = `${metadata.type?.flavorType?.name} / ${metadata.type?.flavorType?.flavor?.name}`; result.ingredients = Object.keys(metadata.ingredients).map((key) => key); + result.ingredientsArray = metadata.ingredients; result.primaryKey = metadata.identification.primary; result.publicDomain = metadata.copyright?.publicDomain; result.language = metadata.languages.map((lang) => lang.name.en); @@ -331,6 +332,21 @@ const importBurrito = async (filePath, currentUser, updateBurritoVersion, concat } metadata.ingredients[key].checksum.md5 = checksum; metadata.ingredients[key].size = stats.size; + // specific to old .mp3 audio support + // change .mp3 to wav and mimetype to audio/wav (for old mp3 exports) + if (/\.mp3$/.test(key)) { + try { + fs.renameSync(path.join(audioDir, key), path.join(audioDir, key.replace('.mp3', '.wav'))); + metadata.ingredients[key].mimeType = 'audio/wav'; + const newKey = key.replace('.mp3', '.wav'); + metadata.ingredients[newKey] = metadata.ingredients[key]; + delete metadata.ingredients[key]; + } catch (err) { + logger.debug('importBurrito.js', `import failed - mp3 to wav section : ${err}`); + fs.unlinkSync(path.join(projectDir, `${projectName}_${id}`)); + throw new Error('import failed'); + } + } }); }) .catch((err) => logger.error('importBurrito.js', `${err}`)); diff --git a/renderer/src/layouts/projects/Export/ExportProjectPopUp.js b/renderer/src/layouts/projects/Export/ExportProjectPopUp.js index e1e072a7c..edd8b1fd7 100644 --- a/renderer/src/layouts/projects/Export/ExportProjectPopUp.js +++ b/renderer/src/layouts/projects/Export/ExportProjectPopUp.js @@ -12,6 +12,7 @@ import updateObsSB from '@/core/burrito/updateObsSB'; import { SnackBar } from '@/components/SnackBar'; // import useSystemNotification from '@/components/hooks/useSystemNotification'; import { LoadingSpinner } from '@/components/LoadingSpinner'; +import CustomMultiComboBox from '@/components/Resources/ResourceUtils/CustomMultiComboBox'; import CloseIcon from '@/illustrations/close-button-black.svg'; import { validate } from '../../../util/validate'; import * as logger from '../../../logger'; @@ -21,6 +22,8 @@ import { ProgressCircle } from '../../../components/ProgressCircle'; import { exportDefaultAudio, exportFullAudio } from './ExportUtils'; import packageInfo from '../../../../../package.json'; +const audioExportFormats = { wav: { ext: 'wav', mimeType: 'audio/wav' }, mp3: { ext: 'mp3', mimeType: 'audio/mpeg' } }; + export default function ExportProjectPopUp(props) { const { open, @@ -43,6 +46,8 @@ export default function ExportProjectPopUp(props) { const [totalExports, setTotalExports] = React.useState(0); const [exportStart, setExportstart] = React.useState(false); + const [selectedAudioExt, setSelectedAudioExt] = React.useState(audioExportFormats.wav); + // const { pushNotification } = useSystemNotification(); function resetExportProgress() { @@ -62,6 +67,7 @@ export default function ExportProjectPopUp(props) { setValid(false); setMetadata({}); setCheckText(false); + setSelectedAudioExt(audioExportFormats.wav); } } @@ -87,60 +93,60 @@ export default function ExportProjectPopUp(props) { const updateCommon = (fs, path, folder, project) => { const fse = window.require('fs-extra'); logger.debug('ExportProjectPopUp.js', 'Updated Scripture burrito'); - let data = fs.readFileSync(path.join(folder, 'metadata.json'), 'utf-8'); - const sb = JSON.parse(data); - if (!sb.copyright?.shortStatements && sb.copyright?.licenses) { - delete sb.copyright.publicDomain; - data = JSON.stringify(sb); - } - const success = validate('metadata', path.join(folder, 'metadata.json'), data, sb.meta.version); - if (success) { - logger.debug('ExportProjectPopUp.js', 'Burrito validated successfully'); - fse.copy(folder, path.join(folderPath, project.name)) - .then(() => { - deleteGitAfterCopy(fs, path.join(folderPath, project.name), path) - .then(() => { - resetExportProgress(); // reset export states - logger.debug('ExportProjectPopUp.js', 'Exported Successfully'); - setNotify('success'); - setSnackText(t('dynamic-msg-export-success')); - setOpenSnackBar(true); - closePopUp(false); - }); - }) - .catch((err) => { - resetExportProgress(); // reset export states - logger.error('ExportProjectPopUp.js', `Failed to export ${err}`); - setNotify('failure'); - setSnackText(t('dynamic-msg-export-fail')); - setOpenSnackBar(true); - closePopUp(false); - }); - } + let data = fs.readFileSync(path.join(folder, 'metadata.json'), 'utf-8'); + const sb = JSON.parse(data); + if (!sb.copyright?.shortStatements && sb.copyright?.licenses) { + delete sb.copyright.publicDomain; + data = JSON.stringify(sb); + } + const success = validate('metadata', path.join(folder, 'metadata.json'), data, sb.meta.version); + if (success) { + logger.debug('ExportProjectPopUp.js', 'Burrito validated successfully'); + fse.copy(folder, path.join(folderPath, project.name)) + .then(() => { + deleteGitAfterCopy(fs, path.join(folderPath, project.name), path) + .then(() => { + resetExportProgress(); // reset export states + logger.debug('ExportProjectPopUp.js', 'Exported Successfully'); + setNotify('success'); + setSnackText(t('dynamic-msg-export-success')); + setOpenSnackBar(true); + closePopUp(false); + }); + }) + .catch((err) => { + resetExportProgress(); // reset export states + logger.error('ExportProjectPopUp.js', `Failed to export ${err}`); + setNotify('failure'); + setSnackText(t('dynamic-msg-export-fail')); + setOpenSnackBar(true); + closePopUp(false); + }); + } }; const updateBurritoVersion = (username, fs, path, folder) => { setTotalExported(1); // 1 step of 2 finished if (project?.type === 'Text Translation') { - updateTranslationSB(username, project, openModal) + updateTranslationSB(username, project, openModal) .then(() => { updateCommon(fs, path, folder, project); }); - } else if (project?.type === 'OBS') { - updateObsSB(username, project, openModal) + } else if (project?.type === 'OBS') { + updateObsSB(username, project, openModal) .then(() => { updateCommon(fs, path, folder, project); }); - } + } setOpenModal(false); }; const ExportActions = { setNotify, setSnackText, setOpenSnackBar, setTotalExported, setTotalExports, setExportstart, resetExportProgress, setCheckText, - }; - const ExportStates = { + }; + const ExportStates = { checkText, audioExport, folderPath, project, exportStart, - }; + }; const exportBible = async () => { const fs = window.require('fs'); @@ -160,18 +166,18 @@ export default function ExportProjectPopUp(props) { setExportstart(true); // export start for all type of export if (project?.type === 'Audio') { if (audioExport === 'default' || audioExport === 'chapter') { - exportDefaultAudio(metadata, folder, path, fs, ExportActions, ExportStates, closePopUp, t); + exportDefaultAudio(metadata, folder, path, fs, ExportActions, ExportStates, closePopUp, t, selectedAudioExt); } else { setTotalExports(3);// 3 step process - exportFullAudio(metadata, folder, path, fs, ExportActions, ExportStates, closePopUp, t); + exportFullAudio(metadata, folder, path, fs, ExportActions, ExportStates, closePopUp, t, selectedAudioExt); } } else if (burrito?.meta?.version !== metadata?.meta?.version) { - setTotalExports(2); // total 2 steps process - setOpenModal(true); + setTotalExports(2); // total 2 steps process + setOpenModal(true); } else { - setTotalExports(2); // total 2 steps process - updateBurritoVersion(username, fs, path, folder); - } + setTotalExports(2); // total 2 steps process + updateBurritoVersion(username, fs, path, folder); + } }); } else { logger.warn('ExportProjectPopUp.js', 'Invalid Path'); @@ -181,6 +187,7 @@ export default function ExportProjectPopUp(props) { setOpenSnackBar(true); } }; + return ( <>