Add Konverter: a local-only menu bar file converter
Drag files in Finder, hold a modifier, and drop on a wheel of outputs. Conversions run entirely on-device through ImageIO, PDFKit, AVFoundation, the bundled CLI tools, and optional ffmpeg/LibreOffice. Includes a --self-test suite (41 checks) and a --convert CLI mode so the conversion matrix can be verified without the UI.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
/// Headless conversion, used for verification and scripting:
|
||||
///
|
||||
/// Konverter --convert a.png b.pdf --to jpg
|
||||
/// Konverter --convert clip.mov --tool trimVideo --param start=0:02 --param end=0:05
|
||||
enum CLI {
|
||||
static func run() -> Int32 {
|
||||
let arguments = Array(CommandLine.arguments.dropFirst())
|
||||
|
||||
guard let convertIndex = arguments.firstIndex(of: "--convert") else {
|
||||
FileHandle.standardError.write(Data("usage: Konverter --convert <files…> --to <ext>\n".utf8))
|
||||
return 2
|
||||
}
|
||||
|
||||
var files: [URL] = []
|
||||
var target: Format?
|
||||
var tool: ToolID?
|
||||
var params = ToolParams()
|
||||
|
||||
var index = convertIndex + 1
|
||||
while index < arguments.count {
|
||||
let argument = arguments[index]
|
||||
switch argument {
|
||||
case "--to":
|
||||
index += 1
|
||||
if index < arguments.count { target = Format(rawValue: arguments[index]) }
|
||||
case "--tool":
|
||||
index += 1
|
||||
if index < arguments.count { tool = ToolID(rawValue: arguments[index]) }
|
||||
case "--param":
|
||||
index += 1
|
||||
if index < arguments.count {
|
||||
let pair = arguments[index].split(separator: "=", maxSplits: 1).map(String.init)
|
||||
if pair.count == 2 { params[pair[0]] = pair[1] }
|
||||
}
|
||||
default:
|
||||
if !argument.hasPrefix("--") {
|
||||
files.append(URL(fileURLWithPath: argument))
|
||||
}
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
guard !files.isEmpty else {
|
||||
FileHandle.standardError.write(Data("no input files\n".utf8))
|
||||
return 2
|
||||
}
|
||||
|
||||
do {
|
||||
if let tool {
|
||||
let outcome = try ToolRunner.run(tool: tool, inputs: files, params: params)
|
||||
if let message = outcome.message {
|
||||
print(message)
|
||||
}
|
||||
for output in outcome.outputs { print(output.path) }
|
||||
return outcome.outputs.isEmpty && outcome.message == nil ? 1 : 0
|
||||
}
|
||||
|
||||
guard let target else {
|
||||
FileHandle.standardError.write(Data("--to is required\n".utf8))
|
||||
return 2
|
||||
}
|
||||
|
||||
var written: [URL] = []
|
||||
for file in files {
|
||||
let destination = OutputNaming.destination(for: file, format: target)
|
||||
let outputs = try Converter.convert(source: file, to: target, at: destination)
|
||||
written.append(contentsOf: outputs)
|
||||
}
|
||||
for output in written { print(output.path) }
|
||||
return written.isEmpty ? 1 : 0
|
||||
} catch {
|
||||
let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
|
||||
FileHandle.standardError.write(Data((message + "\n").utf8))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user