Hello, world!
Disclaimer & Invitation for Feedback
Although I've experimented with script add-ins and rename scripts, this is my first foray into script functions, so the way I've used the API is probably naive. Besides, my knowledge of JS is ankle-deep. I welcome all criticisms!
Instructions
The button is attached. A natural home for it would be the archive pull-down menu.
Select one or more files and click.
Purpose
Everyone has their favorite comic book format. I don't love cbr because fewer people have the tools to manipulate RARs. So I like to convert everything to cbz.
Some design choices are explained in the comments at the top of the code.
Caution
The image files are renamed. The script assumes that AddFilesFromFolder passes files sorted by name_stem to the Rename command. This seems to work. If not, the order will be wrong in the cbz!
Script/Button
CBR to CBZ.dcf (11.5 KB)
// === CBR to CBZ
// === This script converts cbr files to cbz
// ===
// === Design choices you should be aware of:
// === 1. File names in a cbr can be long and unwieldy.
// === For compatibility with more devices, files are serially renamed:
// === 0001.jpg 0002.jpg etc
// === 2. When the CRB contains a folder that contains files (as opposed to just files),
// === the structure is simplified:
// === the resulting CBZ only contains the files.
// === 3. There might be a way to go straight from cbr to cbz without a temp folder,
// === but what I tried told me "operation not supported by this VFS". Besides,
// === we're leaving the temp folders in case of failure so users can troubleshoot.
// Script entry point. DO passes the clickData object to the script.
function OnClick(clickData) {
// While developing the script, clear the output log to identify new messages
// The DOpus object is available to all scripts
// DOpus.ClearOutput()
// Note: apart from selected_files, the Tab object has selected_dirs and many more goodies
var selected_files = clickData.func.sourcetab.selected_files
// Instantiate dialog object -- displayed later with dlg.Show()
// Not using var so it will be available in the global scope
dlg = clickData.func.Dlg
dlg.buttons = "Got it."
// Create an enumerator object for the files selected in the tab
// JS enumerators: https://msdn.microsoft.com/en-us/library/6ch9zb09(v=vs.94).aspx
var enumFiles = new Enumerator(selected_files);
enumFiles.moveFirst();
// Enumerate the files selected in the tab
while ( ! enumFiles.atEnd() ) {
var currentFile = enumFiles.item()
if (currentFile.ext.toLowerCase() == '.cbr') {
cbr_to_cbz(currentFile)
}
else {
dlg.message = "Not a cbr: " + currentFile.name
dlg.Show()
}
enumFiles.moveNext()
}
}
function cbr_to_cbz(cbrFile) {
var cwd = cbrFile.path
// For extraction, create tmp directory with a unique name
// If we're creating a tmp folder based on the stem, beware of dots
var noDots = cbrFile.name_stem.replace(".", "_")
var timestamp = new Date().getTime()
var tmpDirName = noDots + timestamp.toString()
// Simple Opus commands can be run with RunCommand
// For more complex ones, add properties then use Run
var cmd = DOpus.Create.Command()
cmd.RunCommand("CreateFolder " + '"' + tmpDirName + '"' + " NOSEL")
// Extract to the new folder
var dest = cwd + "\\" + tmpDirName
cmd.SetDest(dest)
cmd.AddFile(cbrFile)
cmd.AddLine("Copy EXTRACT")
cmd.Run()
// The FSUtil object provides several utility methods for dealing with the file system.
var util = DOpus.FSUtil()
// Let's look at the extraction folder
var extracted = util.ReadDir(dest)
var dirFileCount = countDirsAndFiles(extracted)
var numDir = dirFileCount[0]
var numFiles = dirFileCount[1]
var dirName = dirFileCount[2]
// We can handle one directory in the cbr but not more, and not a mix
if(numDir > 1 || (numDir == 1 && numFiles > 0) || (numDir == 0 && numFiles == 0) ) {
dlg.message = "The file " + cbrFile +
" has a structure we don't know how to handle. It contains " +
numDir.toString() + " folder(s) and " +
numFiles.toString() + " file(s)." +
"\n\nPlease inspect the extracted folder:\n" + tmpDirName
dlg.icon = "info"
dlg.Show()
return
}
// Still here?
// If there's one folder, dig deeper
// Reset the command
cmd.Clear()
cmd.ClearFiles()
if(numDir == 1) {
// Do we have subfolders? Let's inspect the one folder.
extracted = util.ReadDir(dirName)
dirFileCount = countDirsAndFiles(extracted)
numDir = dirFileCount[0]
numFiles = dirFileCount[1]
if(numDir > 0 || numFiles == 0) {
dlg.message = "The file " + cbrFile +
" has a structure we don't know how to handle. It contains " +
"a folder within a folder, or a folder with no files." +
"\n\nPlease inspect the extracted folder:\n" + tmpDirName
dlg.icon = "info"
dlg.Show()
return
}
else {
// Add the files to be zipped.
var folderToZip = dirName
}
}
else {
// All files, no folders.
// Add the files to be zipped.
var folderToZip = dest
}
cmd.AddFilesFromFolder(folderToZip)
cmd.SetDest(cwd)
// Renaming the files by serializing the image. This averts errors with some readers.
// Compute the number of zeroes for the rename preset based on the number of files.
// !!! We're trusting AddFilesFromFolder to pass the files sorted by name_stem
// !!! This is what it seems to do, and what we need for the order to be preserved in the cbz
var numZeroes = numFiles.toString().length -1
// for some reason str.repeat() is not recognized in this JS
var zeroes = numZeroes == 0 ? '' : Array(numZeroes + 1).join("0")
cmd.AddLine('Rename PATTERN ^(.*)\\.([^.]+)$ TO "[#].\\2" REGEXP NUMBER ' +
zeroes + '1 BY 1 CASE=lower')
cmd.AddLine("Copy ARCHIVE CREATEFOLDER=" + '"' + cbrFile.name_stem + ".cbz" + '"')
cmd.Run()
// Delete the temporary folders
// The doc says we might have to close the enumerator before deleting
extracted.Close()
cmd.Clear()
cmd.ClearFiles()
cmd.AddFile(dest)
cmd.AddLine("Delete NORECYCLE QUIET")
cmd.Run()
}
function countDirsAndFiles(coll) {
// Counts folders in a collection
var numDirs = 0
var numFiles = 0
var dirName = ''
while ( ! coll.complete ) {
var currentFile = coll.Next()
if(currentFile.is_dir) {
numDirs++
// If we have only one folder, we'll want its name
dirName = currentFile
}
else { numFiles++ }
}
return [numDirs, numFiles, dirName]
}