refactor(downloads/ffmpeg): refactor ffmpeg usage and add additional logging for ffmpeg
This commit is contained in:
parent
c865b21bf5
commit
37a74ad755
6 changed files with 58 additions and 25 deletions
|
|
@ -5139,7 +5139,9 @@
|
|||
<span class="label">Lossless Container</span>
|
||||
<span class="description">Container format for lossless downloads</span>
|
||||
</div>
|
||||
<select id="lossless-container-setting"></select>
|
||||
<select id="lossless-container-setting">
|
||||
<option value="nochange">Don't change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="info">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { losslessContainerSettings } from './storage';
|
||||
import { rebuildFlacWithoutMetadata } from './metadata.flac';
|
||||
import { getExtensionFromBlob } from './utils';
|
||||
import { rebuildFlacWithoutMetadata } from './metadata.flac.js';
|
||||
import {
|
||||
type ProgressEvent,
|
||||
isCustomFormat,
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
getContainerFormat,
|
||||
transcodeWithContainerFormat,
|
||||
} from './ffmpegFormats';
|
||||
import { ffmpeg } from './ffmpeg';
|
||||
import { ffmpegNewContainer } from './ffmpeg';
|
||||
|
||||
/**
|
||||
* Triggers a browser file download for the given blob.
|
||||
|
|
@ -60,12 +60,20 @@ export async function applyAudioPostProcessing(
|
|||
if (quality.endsWith('LOSSLESS')) {
|
||||
try {
|
||||
const containerFmt = getContainerFormat(losslessContainerSettings.getContainer());
|
||||
if (containerFmt) {
|
||||
if (await containerFmt.needsTranscode(blob)) {
|
||||
blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
|
||||
} else if ((await getExtensionFromBlob(blob)) === 'flac') {
|
||||
blob = await rebuildFlacWithoutMetadata(blob);
|
||||
}
|
||||
const extension = await getExtensionFromBlob(blob);
|
||||
|
||||
if (await containerFmt?.needsTranscode(blob)) {
|
||||
blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
|
||||
} else if (extension == 'flac') {
|
||||
blob = await rebuildFlacWithoutMetadata(blob);
|
||||
} else {
|
||||
blob = await ffmpegNewContainer(
|
||||
blob,
|
||||
extension == 'm4a' ? 'mp4' : extension,
|
||||
blob.type,
|
||||
onProgress,
|
||||
signal
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as Error)?.name === 'AbortError') {
|
||||
|
|
|
|||
26
js/ffmpeg.js
26
js/ffmpeg.js
|
|
@ -27,7 +27,7 @@ export function loadFfmpeg() {
|
|||
|
||||
async function ffmpegWorker(
|
||||
audioBlob,
|
||||
args = {},
|
||||
args = [],
|
||||
outputName = 'output',
|
||||
outputMime = 'application/octet-stream',
|
||||
onProgress = null,
|
||||
|
|
@ -93,7 +93,7 @@ async function ffmpegWorker(
|
|||
{
|
||||
audioData,
|
||||
extraFiles,
|
||||
...args,
|
||||
args,
|
||||
output: {
|
||||
name: outputName,
|
||||
mime: outputMime,
|
||||
|
|
@ -108,7 +108,7 @@ async function ffmpegWorker(
|
|||
|
||||
export async function ffmpeg(
|
||||
audioBlob,
|
||||
args = {},
|
||||
args = [],
|
||||
outputName = 'output',
|
||||
outputMime = 'application/octet-stream',
|
||||
onProgress = null,
|
||||
|
|
@ -128,4 +128,24 @@ export async function ffmpeg(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new FFmpeg container with copied codec and stripped metadata.
|
||||
* @param {Blob} audioBlob - The audio blob to process
|
||||
* @param {string} outputExtension - The extension for the output file
|
||||
* @param {string} outputMime - The MIME type for the output blob
|
||||
* @param {Function} onProgress - Callback function to track conversion progress
|
||||
* @param {AbortSignal} signal - AbortSignal for cancelling the operation
|
||||
* @returns {Promise<Blob>} A promise that resolves to the processed data blob
|
||||
*/
|
||||
export async function ffmpegNewContainer(audioBlob, outputExtension, outputMime, onProgress, signal) {
|
||||
return await ffmpeg(
|
||||
audioBlob,
|
||||
['-map_metadata', '-1', '-c', 'copy', '-strict', '-2'],
|
||||
`output.${outputExtension}`,
|
||||
outputMime,
|
||||
onProgress,
|
||||
signal
|
||||
);
|
||||
}
|
||||
|
||||
export { FfmpegError };
|
||||
|
|
|
|||
|
|
@ -141,15 +141,21 @@ self.onmessage = async (e) => {
|
|||
} finally {
|
||||
try {
|
||||
if (audioData) await ffmpeg.deleteFile('input');
|
||||
} catch {}
|
||||
} catch {
|
||||
self.postMessage({ type: 'log', message: 'Failed to delete input file from FFmpeg FS.' });
|
||||
}
|
||||
for (const file of extraFiles) {
|
||||
try {
|
||||
await ffmpeg.deleteFile(file.name);
|
||||
} catch {}
|
||||
} catch {
|
||||
self.postMessage({ type: 'log', message: `Failed to delete ${file.name} from FFmpeg FS.` });
|
||||
}
|
||||
}
|
||||
try {
|
||||
await ffmpeg.deleteFile(output.name);
|
||||
} catch {}
|
||||
} catch {
|
||||
self.postMessage({ type: 'log', message: `Failed to delete ${output.name} from FFmpeg FS.` });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
self.postMessage({ type: 'error', message: error.message });
|
||||
|
|
|
|||
|
|
@ -154,14 +154,6 @@ export const containerFormats: Record<string, ContainerFormat> = {
|
|||
extension: 'm4a',
|
||||
needsTranscode: async () => true,
|
||||
},
|
||||
nochange: {
|
||||
displayName: "Don't change",
|
||||
ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'],
|
||||
outputFilename: 'output.mp4',
|
||||
outputMime: 'audio/mp4',
|
||||
extension: 'mp4',
|
||||
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a',
|
||||
},
|
||||
};
|
||||
|
||||
/** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */
|
||||
|
|
@ -192,7 +184,7 @@ export async function transcodeWithCustomFormat(
|
|||
): Promise<Blob> {
|
||||
return ffmpeg(
|
||||
audioBlob,
|
||||
{ args: format.ffmpegArgs },
|
||||
format.ffmpegArgs,
|
||||
format.outputFilename,
|
||||
format.outputMime,
|
||||
onProgress,
|
||||
|
|
@ -214,7 +206,7 @@ export async function transcodeWithContainerFormat(
|
|||
): Promise<Blob> {
|
||||
return ffmpeg(
|
||||
audioBlob,
|
||||
{ args: format.ffmpegArgs },
|
||||
format.ffmpegArgs,
|
||||
format.outputFilename,
|
||||
format.outputMime,
|
||||
onProgress,
|
||||
|
|
|
|||
|
|
@ -877,6 +877,9 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
|||
}
|
||||
|
||||
if (losslessContainerSetting) {
|
||||
const noChangeOption = losslessContainerSetting.querySelector('option:last-child');
|
||||
noChangeOption.remove();
|
||||
|
||||
for (const [internalName, { displayName }] of Object.entries(containerFormats)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = internalName;
|
||||
|
|
@ -884,6 +887,8 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
|||
losslessContainerSetting.appendChild(option);
|
||||
}
|
||||
|
||||
losslessContainerSetting.append(noChangeOption);
|
||||
|
||||
losslessContainerSetting.value = losslessContainerSettings.getContainer();
|
||||
|
||||
losslessContainerSetting.addEventListener('change', (e) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue