given a folder with numbered files
./R1_235_CC
./R1_235_CC/M99_R1.086208.tif
./R1_235_CC/M99_R1.086209.tif
./R1_235_CC/M99_R1.086210.tif
./R1_235_CC/M99_R1.086211.tif
produces ffmpeg input command
ffmpeg -f image2 -start_number 086208 -i './R1_235_CC/M99_R1.%06d.tif'
import java.io.File
import java.io.FileFilter
import java.text.ParseException
val MEDIASEQ_UNKNOWN = -1
fun getMediaSequence(f: File) = getMediaSequence(f.name)
fun getMediaSequence(s: String): Int {
if (s.startsWith(".")) return MEDIASEQ_UNKNOWN
val basename = getBaseName(s)
val split = basename.split("[^0-9]+".toRegex())
return split.lastOrNull()?.toIntOrNull() ?: MEDIASEQ_UNKNOWN
}
fun getDirMediaSequence(dir: File, filter: FileFilter? = null): Int {
return dir.listFiles(filter).orEmpty().map { getMediaSequence(it) }.max() ?: MEDIASEQ_UNKNOWN
}
val MEDIASEQ_CMP = Comparator { o1: File, o2: File -> getMediaSequence(o1) - getMediaSequence(o2) }
val MEDIASEQ_CMPString = Comparator { o1: String, o2: String -> getMediaSequence(o1) - getMediaSequence(o2) }
fun getBaseName(filename: String) = filename.substringAfterLast("/").substringBeforeLast(".")
fun dpxInfoFromFile(f: File): DpxFilename {
// simple - 0177670.dpx
// vdms - Scrubs_Ep101_102_CR_B019_1641738.dpx
// cbs - BX505_SR7378_01.00089437.dpx
val bn = getBaseName(f.name)
var number = ""
var reel: String? = ""
for (i in bn.length - 1 downTo 0) {
val c = bn[i]
if (Character.isDigit(c)) {
number = c + number
} else {
reel = bn.substring(0, i)
break
}
}
if (number.isNotBlank()) {
val pf = f.parentFile
var clip: String? = pf?.name
if ((clip ?: "").matches("[0-9]+x[0-9]+".toRegex())) {
//CBS weird corner case: /$CLIP/$videoRes/*.dpx
clip = f.parentFile.parentFile.name
}
reel = if (reel.isNullOrBlank()) null else reel
val filenamesPattern = f.name.replace(number, "%0${number.length}d")
return DpxFilename(clip, reel, number, filenamesPattern)
}
throw ParseException("Can't parse filename ${f.absolutePath}", 0)
}
fun dpxInfoFromFiles(url: String, dpxList: List<File>): DpxFilename {
val dpxls = dpxList.sortedWith(MEDIASEQ_CMP)
val firstDpx = dpxls.firstOrNull() ?: throw IllegalStateException("No dpx files found at URL $url")
val lastDpx = dpxls.lastOrNull() ?: throw IllegalStateException("No dpx files found at URL $url")
val dpxinfo1 = dpxInfoFromFile(firstDpx)
val dpxinfo2 = dpxInfoFromFile(lastDpx)
if (dpxinfo1.filenamesPattern == dpxinfo2.filenamesPattern) {
return dpxinfo1
}
if (dpxinfo1.number.startsWith("0") || dpxinfo2.number.startsWith("0")) {
throw IllegalStateException("cant create dpx number pattern ${firstDpx.name} vs ${lastDpx.name}")
}
val filenamesPattern = firstDpx.name.replace("${Integer.parseInt(dpxinfo1.number)}", "%d")
return DpxFilename(dpxinfo1.clipName, dpxinfo1.reel, dpxinfo1.number, filenamesPattern)
}
data class DpxFilename(val clipName: String? // Usually it is DPX folder
, val reel: String? // Usually is it part of the dpx filename
, val number: String, val filenamesPattern: String)
args.forEach {
val f = File(it)
when {
f.isDirectory -> {
val files = f.listFiles().orEmpty()
val jpgs = files.filter { ff -> ff.name.toLowerCase().endsWith("jpg") }
val pngs = files.filter { ff -> ff.name.toLowerCase().endsWith("png") }
val dpxs = files.filter { ff -> ff.name.toLowerCase().endsWith("dpx") }
val tifs = files.filter { ff -> ff.name.toLowerCase().endsWith("tif") }
val ls = arrayOf(jpgs, pngs, dpxs, tifs).maxBy { ff -> ff.size } ?: emptyList()
val info = dpxInfoFromFiles(it, ls)
println("ffmpeg -f image2 -start_number ${info.number} -i '$it/${info.filenamesPattern}'")
}
f.isFile -> {
println(dpxInfoFromFile(f))
}
}
}