Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"asi": true,
"esnext": true,
"esversion": 6,
"eqeqeq": true,
"camelcase": true,
"funcscope": true,
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
"homepage": "https://github.com/bemusic/bemuse-tools",
"dependencies": {
"babel": "^5.4.3",
"bemuse-indexer": "^3.1.0",
"bemuse-indexer": "drummaniac/bemuse-indexer#dtx-support",
"bluebird": "^2.9.9",
"bytes": "^1.0.0",
"chalk": "^1.0.0",
"co": "^4.0.2",
"cors": "^2.7.1",
"endpoint": "^0.4.2",
"express": "^4.12.4",
"fluent-ffmpeg": "^2.1.2",
"format-json": "^1.0.3",
"glob": "^4.3.5",
"gulp-util": "^3.0.1",
Expand All @@ -40,7 +41,8 @@
"mkdirp": "^0.5.0",
"rx": "^2.5.3",
"temp": "^0.8.1",
"throat": "^1.0.0"
"throat": "^1.0.0",
"xa-dtx": "^0.0.1"
},
"devDependencies": {
"chai": "^1.10.0",
Expand Down
126 changes: 42 additions & 84 deletions src/audio.js
Original file line number Diff line number Diff line change
@@ -1,103 +1,61 @@

import Promise from 'bluebird'
import co from 'co'
import fs from 'fs'
import Throat from 'throat'
import { cpus } from 'os'
import endpoint from 'endpoint'
import { spawn, execFile as _execFile } from 'child_process'
import { extname, basename } from 'path'

import tmp from './temporary'

let readFile = Promise.promisify(fs.readFile, fs)
let writeFile = Promise.promisify(fs.writeFile, fs)
let execFile = Promise.promisify(_execFile)

let throat = new Throat(cpus().length || 1)
import xaConvert from 'xa-dtx'
import ffmpeg from 'fluent-ffmpeg'
import {ReadableBufferStream, WritableBufferStream} from './bufferstream'

export class AudioConvertor {
constructor(type, ...extra) {
this._target = type
this._extra = extra
}

convert(file) {
let ext = extname(file.name).toLowerCase()
if (ext === '.' + this._target && !this.force) {
let ext = extname(file.name)
let name = basename(file.name, ext) + '.' + this._target
ext = ext.toLowerCase().substr(1)

if (ext === this._target && !this.force) {
return Promise.resolve(file)
} else if (ext === 'xa') {
return xaConvert(file.path).then(wav => {
return this._doFfmpeg(wav.buffer, 'wav', this._target, name)
}).then(buffer => file.derive(name, buffer))
} else {
let name = basename(file.name, ext) + '.' + this._target
return this._doConvert(file.path, this._target)
return this
._doFfmpeg(file.path, ext, this._target, name)
.then(buffer => file.derive(name, buffer))
}
}
_doConvert(path, type) {
if (type === 'm4a') {
return co(function*() {
let wav = yield this._SoX(path, 'wav')
let prefix = tmp()
let wavPath = prefix + '.wav'
let m4aPath = prefix + '.m4a'
yield writeFile(wavPath, wav)
if (process.platform.match(/^win/)) {
yield execFile('qaac', ['-o', m4aPath, '-c', '128', wavPath])
} else {
yield execFile('afconvert', [wavPath, m4aPath, '-f', 'm4af',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to use ffmpeg’s AAC encoder before, however the result doesn’t sound good at 128kbps. Apple’s proprietary encoder (afconvert and qaac) produces a much more superior sound at 128kbps. That’s why I don’t recommend using ffmpeg.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have compared qaac and ffmpeg output but did not hear any significant difference. Maybe my ears not so perfect :)

'-b', '128000', '-q', '127', '-s', '2'])
}
return yield readFile(m4aPath)
}.bind(this))
} else {
return this._SoX(path, type)
}
}
_SoX(path, type) {
return co(function*() {
let typeArgs = [ ]
try {
let fd = yield Promise.promisify(fs.open, fs)(path, 'r')
let buffer = new Buffer(4)
let read = yield Promise.promisify(fs.read, fs)(fd, buffer, 0, 4, null)
yield Promise.promisify(fs.close, fs)(fd)
if (read === 0) {
console.error('[WARN] Empty keysound file.')
} else if (buffer[0] === 0x49 && buffer[1] === 0x44 && buffer[2] === 0x33) {
typeArgs = [ '-t', 'mp3' ]
} else if (buffer[0] === 0xFF && buffer[1] === 0xFB) {
typeArgs = [ '-t', 'mp3' ]
} else if (buffer[0] === 0x4F && buffer[1] === 0x67 && buffer[2] === 0x67 && buffer[3] === 0x53) {
typeArgs = [ '-t', 'ogg' ]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that the audio converter still works in the cases where mp3/ogg files have .wav extension and vice versa? I do this manual format detection because many BMS archives have this problem.

Sorry for lack of automated testing in audio.js...

Copy link
Copy Markdown
Author

@mugabe mugabe Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but every file I have tried was succesfully detected. Btw I pass files to ffmpeg's pipe, so ffmpeg have no information about file extension at all. I believe that ffmpeg itself guesses format by head bytes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that the audio converter still works in the cases where mp3/ogg files have .wav extension and vice versa? I do this manual format detection because many BMS archives have this problem.

Sorry for lack of automated testing in audio.js...

}
} catch (e) {
console.error('[WARN] Unable to detect file type!')

_doFfmpeg(input) {
return new Promise((resolve, reject) => {
let readStream

// init input stream
if (typeof input === 'string') {
readStream = fs.createReadStream(input)
} else {
readStream = new ReadableBufferStream(input)
}
return yield this._doSoX(path, type, typeArgs)
}.bind(this));
}
_doSoX(path, type, inputTypeArgs) {
return throat(() => new Promise((resolve, reject) => {
let sox = spawn('sox', [...inputTypeArgs, path, '-t', type, ...this._extra, '-'])
sox.stdin.end()
sox.stderr.on('data', x => process.stderr.write(x))
let data = new Promise((resolve, reject) => {
sox.stdout.pipe(endpoint((err, buffer) => {
if (err) {
console.error('Error reading audio!')
reject(err)
} else {
resolve(buffer)
}
}))
})
sox.on('close', (code) => {
if (code === 0) {
resolve(data)
} else {
console.error('Unable to convert audio file -- SoX exited ' + code)
reject(new Error('SoX process exited: ' + code))
}
})
}))

let writeStream = new WritableBufferStream()

// do ffmpeg
ffmpeg(readStream)
.output(writeStream)
.outputOptions(this._extra)
.on('end', () => {
// let outBuffer = writeStream._buffer.slice(0, writeStream._bufferPos)
resolve(writeStream.buffer)
})
.on('error', (err) => {
console.error('Unable to convert audio file -- ffmpeg exited ' + err)
reject(new Error('ffmpeg process exited: ' + err))
})
.run()
})
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/bufferstream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import stream from 'stream'

export class ReadableBufferStream extends stream.PassThrough {
constructor(buffer) {
super()

this.pause()
this.end(buffer)
}
}

export class WritableBufferStream extends stream.Writable {
constructor() {
super()

this._buffer = Buffer.alloc(1024 * 64)
this._bufferPos = 0
}

_write(chunk, encoding, callback) {
let size = chunk.length

let newBufferSize = this._buffer.length
while (size + this._bufferPos > newBufferSize) {
newBufferSize *= 2
}
if (newBufferSize > this._buffer.length) {
let newBuffer = Buffer.alloc(newBufferSize)
this._buffer.copy(newBuffer, 0, 0, this._buffer.length)
this._buffer = newBuffer
}

chunk.copy(this._buffer, this._bufferPos, 0)
this._bufferPos += size

callback()
}

get buffer() {
return this._buffer.slice(0, this._bufferPos)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

@mugabe mugabe Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. WritableStreamBuffer works and very similar to mine.
But ReadableStreamBuffer useless - very slow and does not implement some required methods which fluent-ffmpeg calls.

}
2 changes: 1 addition & 1 deletion src/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class Directory {
this._path = path
}
files(pattern) {
return glob(pattern, { cwd: this._path })
return glob(pattern, { cwd: this._path, nocase: true })
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix!

.map(name => readFile(path.join(this._path, name)).then(buffer =>
new FileEntry(this, name, buffer)))
}
Expand Down
2 changes: 1 addition & 1 deletion src/indexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function index(path, { recursive }) {

console.log('-> Scanning files...')
let dirs = new Map()
let pattern = (recursive ? '**/' : '') + '*/*.{bms,bme,bml,bmson}'
let pattern = (recursive ? '**/' : '') + '*/*.{bms,bme,bml,bmson,dtx}'
for (var name of yield glob(pattern, { cwd: path })) {
let bmsPath = join(path, name)
put(dirs, dirname(bmsPath), () => []).push(basename(bmsPath))
Expand Down
11 changes: 7 additions & 4 deletions src/packer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@ export function packIntoBemuse(path) {
let directory = new Directory(path)
let packer = new BemusePacker(directory)

console.log('-> Loading audios')
let audio = yield directory.files('*.{mp3,wav,ogg}')
console.log('-> Loading audios!')
let audio = yield directory.files('**/*.{mp3,wav,ogg,xa}')

console.log('-> Converting audio to ogg [better audio performance]')
let oggc = new AudioConvertor('ogg', '-C', '3')
let oggc = new AudioConvertor('ogg',
'-q:a', '6', '-c:a', 'libvorbis', '-f', 'ogg')
oggc.force = true
let oggs = yield dotMap(audio, file => oggc.convert(file))

console.log('-> Converting audio to m4a [for iOS and Safari]')
let m4ac = new AudioConvertor('m4a')
let m4ac = new AudioConvertor('m4a',
'-b:a', '192k', '-c:a', 'aac', '-movflags',
'frag_keyframe+empty_moov', '-f', 'ipod', '-vn')
let m4as = yield dotMap(audio, file => m4ac.convert(file))

packer.pack('m4a', m4as)
Expand Down