dropped-frames
Version:
Repair dropped frames from an image sequence
135 lines (99 loc) • 4.2 kB
JavaScript
require('shelljs/global')
var async = require('async')
var globby = require('globby')
var leftPad = require('left-pad')
var path = require('path')
module.exports = class DroppedFrames {
constructor (config) {
this.config = config
}
copy (inputDirectory, outputDirectory, callback) {
var config = this.config
var inputGlob = path.join(inputDirectory, config.inputDirectoryPattern)
var directories = globby.sync([inputGlob])
async.eachSeries(directories, (directory, next) => {
var newDirectory = path.join(outputDirectory, path.basename(directory))
if (!test('-d', newDirectory)) mkdir(newDirectory)
var frames = globby.sync(path.join(directory, '*.dng'))
if (config.safe) this.safeCopyFrames(frames, newDirectory, next)
else this.unsafeCopyFrames(frames, newDirectory, next)
}, callback)
}
safeCopyFrames (frames, newDirectory, callback) {
var config = this.config
async.eachSeries(frames, (frame, frameDone) => {
var newFile = path.join(newDirectory, path.basename(frame))
if (test('-f', newFile)) return setImmediate(frameDone)
config.log('copying', frame, 'to', newFile)
exec(`dd if=${frame} of=${newFile} conv=noerror,sync`, { silent: !config.debug }, frameDone)
}, callback)
}
unsafeCopyFrames (frames, newDirectory, callback) {
var badFrames = []
var config = this.config
async.eachLimit(frames, config.parallelLimit, (frame, frameDone) => {
var newFile = path.join(newDirectory, path.basename(frame))
if (test('-f', newFile)) return setImmediate(frameDone)
config.log('copying', frame, 'to', newFile)
exec(`cp ${frame} ${newFile}`, { silent: !config.debug }, exitCode => {
if (exitCode > 0) badFrames.push(newFile)
frameDone()
})
}, () => {
if (badFrames.length > 0) {
config.log('bad frames found', badFrames)
this.replaceBadFrames(badFrames, callback)
} else {
callback()
}
})
}
replaceBadFrames (badFrames, callback = () => {}) {
badFrames
.sort()
.reduce((groupsOfBadFrames, badFrame, index, badFrames) => {
var frameNum = Number(this.getFrameNum(badFrame))
var previousFrame = badFrames[index - 1]
var previousFrameNum = previousFrame ? Number(this.getFrameNum(previousFrame)) : NaN
var isSequential = frameNum - previousFrameNum === 1
var currentGroup
if (groupsOfBadFrames.length === 0 || !isSequential) {
currentGroup = []
groupsOfBadFrames.push(currentGroup)
} else {
currentGroup = groupsOfBadFrames[groupsOfBadFrames.length - 1]
}
currentGroup.push(badFrame)
return groupsOfBadFrames
}, [])
.forEach(groupOfBadFrames => groupOfBadFrames.forEach(this.cloneClosestFrame.bind(this)))
callback()
}
getFrameNum (frame) {
var reg = new RegExp(`${this.config.separator}(\\d+)\\${path.extname(frame)}$`)
return frame.match(reg)[1]
}
cloneClosestFrame (badFrame, _, badFrames) {
var config = this.config
var ext = path.extname(badFrame)
var totalNumFrames = globby.sync(path.join(path.dirname(badFrame), `*${ext}`)).length
var middleIndex = Math.round(badFrames.length / 2)
var getNextFrame = badFrames.indexOf(badFrame) >= middleIndex
var frameNum = this.getFrameNum(badFrame)
var frameDigitLength = frameNum.length
frameNum = Number(frameNum)
if (frameNum + 1 > totalNumFrames) getNextFrame = false
if (frameNum - 1 <= 0) getNextFrame = true
var closestFrame
var closestFrameNum = frameNum
while (!closestFrame && (closestFrameNum += getNextFrame ? 1 : -1)) { // eslint-disable-line no-unmodified-loop-condition
var maybeClosestFrameName = badFrame.replace(/\d{6}\.dng$/, `${leftPad(closestFrameNum, frameDigitLength, 0)}${ext}`)
var maybeClosestFrame = globby.sync(maybeClosestFrameName)
if (!maybeClosestFrame.length === 0) continue
else closestFrame = maybeClosestFrame[0]
}
config.log('replacing bad frame', badFrame, 'with', closestFrame)
cp(closestFrame, badFrame)
}
}