Skip compression of atrac3 wave files

This commit is contained in:
Stefano Brilli 2021-08-31 19:19:54 +02:00
parent 7a87aa881b
commit 254c06c74a
3 changed files with 104 additions and 13 deletions

View File

@ -436,10 +436,9 @@ async function getTrackNameFromMediaTags(file: File, titleFormat: TitleFormatTyp
}
}
export function convertAndUpload(files: File[], format: UploadFormat, titleFormat: TitleFormatType) {
export function convertAndUpload(files: File[], requestedFormat: UploadFormat, titleFormat: TitleFormatType) {
return async function(dispatch: AppDispatch, getState: () => RootState) {
const { audioExportService, netmdService } = serviceRegistry;
const wireformat = WireformatDict[format];
await netmdService?.stop();
dispatch(batchActions([uploadDialogActions.setVisible(true), uploadDialogActions.setCancelUpload(false)]));
@ -484,7 +483,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
};
let conversionIterator = async function*(files: File[]) {
let converted: Promise<{ file: File; data: ArrayBuffer }>[] = [];
let converted: Promise<{ file: File; data: ArrayBuffer; format: Wireformat }>[] = [];
let i = 0;
function convertNext() {
@ -504,11 +503,12 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
converted.push(
new Promise(async (resolve, reject) => {
let data: ArrayBuffer;
let format: Wireformat;
try {
await audioExportService!.prepare(f);
data = await audioExportService!.export({ format });
({ data, format } = await audioExportService!.export({ requestedFormat }));
convertNext();
resolve({ file: f, data: data });
resolve({ file: f, data: data, format: format });
} catch (err) {
error = err;
errorMessage = `${f.name}: Unsupported or unrecognized format`;
@ -539,7 +539,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
break;
}
const { file, data } = item;
const { file, data, format } = item;
let title = file.name;
try {
@ -563,7 +563,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
updateTrack();
updateProgressCallback({ written: 0, encrypted: 0, total: 100 });
try {
await netmdService?.upload(halfWidthTitle, fullWidthTitle, data, wireformat, updateProgressCallback);
await netmdService?.upload(halfWidthTitle, fullWidthTitle, data, format, updateProgressCallback);
} catch (err) {
error = err;
errorMessage = `${file.name}: Error uploading to device. There might not be enough space left.`;

View File

@ -1,6 +1,8 @@
import { createWorker, setLogging } from '@ffmpeg/ffmpeg';
import { AtracdencProcess } from './atracdenc-worker';
import { getPublicPathFor } from '../utils';
import { getAtrac3Info, getPublicPathFor } from '../utils';
import { Wireformat } from 'netmd-js';
import { WireformatDict } from '../redux/actions';
const AtracdencWorker = require('worker-loader!./atracdenc-worker'); // eslint-disable-line import/no-webpack-loader-syntax
interface LogPayload {
@ -10,7 +12,7 @@ interface LogPayload {
export interface AudioExportService {
init(): Promise<void>;
export(params: { format: string }): Promise<ArrayBuffer>;
export(params: { requestedFormat: 'SP' | 'LP2' | 'LP4' }): Promise<{ data: ArrayBuffer; format: Wireformat }>;
info(): Promise<{ format: string | null; input: string | null }>;
prepare(file: File): Promise<void>;
}
@ -21,12 +23,14 @@ export class FFMpegAudioExportService implements AudioExportService {
public loglines: { action: string; message: string }[] = [];
public inFileName: string = ``;
public outFileNameNoExt: string = ``;
public inFile?: File;
async init() {
setLogging(true);
}
async prepare(file: File) {
this.inFile = file;
this.loglines = [];
this.ffmpegProcess = createWorker({
logger: (payload: LogPayload) => {
@ -79,33 +83,45 @@ export class FFMpegAudioExportService implements AudioExportService {
return { format, input };
}
async export({ format }: { format: string }) {
async export({ requestedFormat }: { requestedFormat: 'SP' | 'LP2' | 'LP4' | 'LP105' }) {
let result: ArrayBuffer;
if (format === `SP`) {
let format: Wireformat;
const atrac3Info = await getAtrac3Info(this.inFile!);
if (atrac3Info) {
format = WireformatDict[atrac3Info.mode];
result = (await this.inFile!.arrayBuffer()).slice(atrac3Info.dataOffset);
} else if (requestedFormat === `SP`) {
const outFileName = `${this.outFileNameNoExt}.raw`;
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-ac 2 -ar 44100 -f s16be');
let { data } = await this.ffmpegProcess.read(outFileName);
result = data.buffer;
format = Wireformat.pcm;
} else {
const outFileName = `${this.outFileNameNoExt}.wav`;
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-f wav -ar 44100 -ac 2');
let { data } = await this.ffmpegProcess.read(outFileName);
let bitrate: string = `0`;
switch (format) {
switch (requestedFormat) {
case `LP2`:
bitrate = `128`;
format = Wireformat.lp2;
break;
case `LP105`:
bitrate = `102`;
format = Wireformat.l105kbps;
break;
case `LP4`:
bitrate = `64`;
format = Wireformat.lp4;
break;
}
result = await this.atracdencProcess!.encode(data.buffer, bitrate);
}
this.ffmpegProcess.worker.terminate();
this.atracdencProcess!.terminate();
return result;
return {
data: result,
format,
};
}
}

View File

@ -391,4 +391,79 @@ export function askNotificationPermission(): Promise<NotificationPermission> {
}
}
export async function getAtrac3Info(file: File) {
// see: http://soundfile.sapp.org/doc/WaveFormat/
// and: https://www.fatalerrors.org/a/detailed-explanation-of-wav-file-format.html
const fileData = await file.arrayBuffer();
if (fileData.byteLength < 44) {
return null;
}
const riffDescriptor = new Uint32Array(fileData.slice(0, 12));
if (riffDescriptor[0] !== 0x46464952 || riffDescriptor[2] !== 0x45564157) {
// 'RIFF' && 'WAVE'
return null;
}
// WAVE format
const waveDescriptor = new Uint32Array(fileData.slice(12, 20));
if (waveDescriptor[0] !== 0x20746d66) {
return false;
}
const audioFormatAndChanneld = new Uint16Array(fileData.slice(20, 24));
if (audioFormatAndChanneld[0] !== 0x270 || audioFormatAndChanneld[1] !== 2) {
// 'atrac3' && 2 channels
return null;
}
const sampleRateAndByteRate = new Uint32Array(fileData.slice(24, 32));
if (sampleRateAndByteRate[0] !== 44100) {
// Sample rate
return null;
}
const byteRate = sampleRateAndByteRate[1];
let mode: 'LP2' | 'LP105' | 'LP4' | null = null;
if (byteRate > 16000) {
mode = 'LP2';
} else if (byteRate > 13000) {
mode = 'LP105';
} else if (byteRate > 8000) {
mode = 'LP4';
} else {
mode = null;
}
if (mode === null) {
return null;
}
const waveBlockEndOffset = new Uint16Array(fileData.slice(36, 38));
let dataOffset = -1;
const nextBlockStartOffset = waveBlockEndOffset[0] + 38;
const nextBlockEndOffset = nextBlockStartOffset + 8;
const nextBlock = new Uint32Array(fileData.slice(nextBlockStartOffset, nextBlockEndOffset));
if (nextBlock[0] === 0x61746164) {
// data
dataOffset = nextBlockEndOffset;
} else if (nextBlock[0] === 0x74636166) {
// fact
const dataBlockLength = 8;
dataOffset = nextBlockEndOffset + nextBlock[1] + dataBlockLength;
}
if (dataOffset === -1) {
return null;
}
return {
mode,
dataOffset,
};
}
declare let process: any;