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) {
|
return async function(dispatch: AppDispatch, getState: () => RootState) {
|
||||||
const { audioExportService, netmdService } = serviceRegistry;
|
const { audioExportService, netmdService } = serviceRegistry;
|
||||||
const wireformat = WireformatDict[format];
|
|
||||||
|
|
||||||
await netmdService?.stop();
|
await netmdService?.stop();
|
||||||
dispatch(batchActions([uploadDialogActions.setVisible(true), uploadDialogActions.setCancelUpload(false)]));
|
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 conversionIterator = async function*(files: File[]) {
|
||||||
let converted: Promise<{ file: File; data: ArrayBuffer }>[] = [];
|
let converted: Promise<{ file: File; data: ArrayBuffer; format: Wireformat }>[] = [];
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
function convertNext() {
|
function convertNext() {
|
||||||
|
@ -518,11 +517,12 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
|
||||||
converted.push(
|
converted.push(
|
||||||
new Promise(async (resolve, reject) => {
|
new Promise(async (resolve, reject) => {
|
||||||
let data: ArrayBuffer;
|
let data: ArrayBuffer;
|
||||||
|
let format: Wireformat;
|
||||||
try {
|
try {
|
||||||
await audioExportService!.prepare(f);
|
await audioExportService!.prepare(f);
|
||||||
data = await audioExportService!.export({ format });
|
({ data, format } = await audioExportService!.export({ requestedFormat }));
|
||||||
convertNext();
|
convertNext();
|
||||||
resolve({ file: f, data: data });
|
resolve({ file: f, data: data, format: format });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err;
|
error = err;
|
||||||
errorMessage = `${f.name}: Unsupported or unrecognized format`;
|
errorMessage = `${f.name}: Unsupported or unrecognized format`;
|
||||||
|
@ -553,7 +553,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { file, data } = item;
|
const { file, data, format } = item;
|
||||||
|
|
||||||
let title = file.name;
|
let title = file.name;
|
||||||
try {
|
try {
|
||||||
|
@ -577,7 +577,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
|
||||||
updateTrack();
|
updateTrack();
|
||||||
updateProgressCallback({ written: 0, encrypted: 0, total: 100 });
|
updateProgressCallback({ written: 0, encrypted: 0, total: 100 });
|
||||||
try {
|
try {
|
||||||
await netmdService?.upload(halfWidthTitle, fullWidthTitle, data, wireformat, updateProgressCallback);
|
await netmdService?.upload(halfWidthTitle, fullWidthTitle, data, format, updateProgressCallback);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err;
|
error = err;
|
||||||
errorMessage = `${file.name}: Error uploading to device. There might not be enough space left.`;
|
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 { createWorker, setLogging } from '@ffmpeg/ffmpeg';
|
||||||
import { AtracdencProcess } from './atracdenc-worker';
|
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
|
const AtracdencWorker = require('worker-loader!./atracdenc-worker'); // eslint-disable-line import/no-webpack-loader-syntax
|
||||||
|
|
||||||
interface LogPayload {
|
interface LogPayload {
|
||||||
|
@ -10,7 +12,7 @@ interface LogPayload {
|
||||||
|
|
||||||
export interface AudioExportService {
|
export interface AudioExportService {
|
||||||
init(): Promise<void>;
|
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 }>;
|
info(): Promise<{ format: string | null; input: string | null }>;
|
||||||
prepare(file: File): Promise<void>;
|
prepare(file: File): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -21,12 +23,14 @@ export class FFMpegAudioExportService implements AudioExportService {
|
||||||
public loglines: { action: string; message: string }[] = [];
|
public loglines: { action: string; message: string }[] = [];
|
||||||
public inFileName: string = ``;
|
public inFileName: string = ``;
|
||||||
public outFileNameNoExt: string = ``;
|
public outFileNameNoExt: string = ``;
|
||||||
|
public inFile?: File;
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
setLogging(true);
|
setLogging(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare(file: File) {
|
async prepare(file: File) {
|
||||||
|
this.inFile = file;
|
||||||
this.loglines = [];
|
this.loglines = [];
|
||||||
this.ffmpegProcess = createWorker({
|
this.ffmpegProcess = createWorker({
|
||||||
logger: (payload: LogPayload) => {
|
logger: (payload: LogPayload) => {
|
||||||
|
@ -79,33 +83,45 @@ export class FFMpegAudioExportService implements AudioExportService {
|
||||||
return { format, input };
|
return { format, input };
|
||||||
}
|
}
|
||||||
|
|
||||||
async export({ format }: { format: string }) {
|
async export({ requestedFormat }: { requestedFormat: 'SP' | 'LP2' | 'LP4' | 'LP105' }) {
|
||||||
let result: ArrayBuffer;
|
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`;
|
const outFileName = `${this.outFileNameNoExt}.raw`;
|
||||||
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-ac 2 -ar 44100 -f s16be');
|
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-ac 2 -ar 44100 -f s16be');
|
||||||
let { data } = await this.ffmpegProcess.read(outFileName);
|
let { data } = await this.ffmpegProcess.read(outFileName);
|
||||||
result = data.buffer;
|
result = data.buffer;
|
||||||
|
format = Wireformat.pcm;
|
||||||
} else {
|
} else {
|
||||||
const outFileName = `${this.outFileNameNoExt}.wav`;
|
const outFileName = `${this.outFileNameNoExt}.wav`;
|
||||||
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-f wav -ar 44100 -ac 2');
|
await this.ffmpegProcess.transcode(this.inFileName, outFileName, '-f wav -ar 44100 -ac 2');
|
||||||
let { data } = await this.ffmpegProcess.read(outFileName);
|
let { data } = await this.ffmpegProcess.read(outFileName);
|
||||||
let bitrate: string = `0`;
|
let bitrate: string = `0`;
|
||||||
switch (format) {
|
switch (requestedFormat) {
|
||||||
case `LP2`:
|
case `LP2`:
|
||||||
bitrate = `128`;
|
bitrate = `128`;
|
||||||
|
format = Wireformat.lp2;
|
||||||
break;
|
break;
|
||||||
case `LP105`:
|
case `LP105`:
|
||||||
bitrate = `102`;
|
bitrate = `102`;
|
||||||
|
format = Wireformat.l105kbps;
|
||||||
break;
|
break;
|
||||||
case `LP4`:
|
case `LP4`:
|
||||||
bitrate = `64`;
|
bitrate = `64`;
|
||||||
|
format = Wireformat.lp4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result = await this.atracdencProcess!.encode(data.buffer, bitrate);
|
result = await this.atracdencProcess!.encode(data.buffer, bitrate);
|
||||||
}
|
}
|
||||||
this.ffmpegProcess.worker.terminate();
|
this.ffmpegProcess.worker.terminate();
|
||||||
this.atracdencProcess!.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;
|
declare let process: any;
|
||||||
|
|
Loading…
Reference in New Issue