Skip compression of atrac3 wave files
This commit is contained in:
parent
251e0bedcc
commit
8049ed9dff
|
@ -450,10 +450,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)]));
|
||||
|
@ -498,7 +497,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() {
|
||||
|
@ -518,11 +517,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`;
|
||||
|
@ -553,7 +553,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
|
|||
break;
|
||||
}
|
||||
|
||||
const { file, data } = item;
|
||||
const { file, data, format } = item;
|
||||
|
||||
let title = file.name;
|
||||
try {
|
||||
|
@ -577,7 +577,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.`;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
75
src/utils.ts
75
src/utils.ts
|
@ -472,4 +472,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;
|
||||
|
|
Loading…
Reference in New Issue