Files
TZGyn 2d031e58a4 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.
2026-10-20 00:36:40 +08:00

67 lines
3.0 KiB
Swift

import Foundation
/// Where a produced file goes, and what it is called.
enum OutputNaming {
/// ``name.pdf`` for ``photo.jpg`` — appends ``-2``, ``-3`` … when the
/// target already exists (unless the user asked to overwrite).
static func destination(for source: URL, format: Format, settings: Settings = .shared) -> URL {
let base = source.deletingPathExtension().lastPathComponent
let directory: URL
switch settings.outputLocation {
case .besideOriginal:
directory = source.deletingLastPathComponent()
case .chosenFolder:
let folder = settings.resolvedOutputFolder
directory = FileManager.default.fileExists(atPath: folder.path)
? folder
: source.deletingLastPathComponent()
}
let candidate = directory.appendingPathComponent(base).appendingPathExtension(format.ext)
return settings.conflictPolicy == .overwrite ? candidate : availableURL(for: candidate)
}
/// A sibling of any output a tool produces (e.g. ``photo-compressed.jpg``).
static func sibling(for source: URL, suffix: String, extension ext: String? = nil) -> URL {
let base = source.deletingPathExtension().lastPathComponent
let directory = source.deletingLastPathComponent()
let resolvedExtension = ext ?? source.pathExtension
return availableURL(
for: directory.appendingPathComponent(base + suffix).appendingPathExtension(resolvedExtension)
)
}
/// A working directory for multi-file outputs (split pages, extracted
/// images, unarchived trees).
static func artifactDirectory(for source: URL, suffix: String) -> URL {
let base = source.deletingPathExtension().lastPathComponent
let directory = source.deletingLastPathComponent()
let url = directory.appendingPathComponent(base + suffix, isDirectory: true)
var candidate = url
var index = 2
while FileManager.default.fileExists(atPath: candidate.path) {
candidate = directory.appendingPathComponent("\(base + suffix) \(index)", isDirectory: true)
index += 1
}
return candidate
}
/// ``foo 2.txt``, ``foo 3.txt`` … until the name is free.
static func availableURL(for url: URL) -> URL {
guard FileManager.default.fileExists(atPath: url.path) else { return url }
let directory = url.deletingLastPathComponent()
let ext = url.pathExtension
let base = url.deletingPathExtension().lastPathComponent
var index = 2
while true {
let name = ext.isEmpty ? "\(base) \(index)" : "\(base) \(index).\(ext)"
let candidate = directory.appendingPathComponent(name)
if !FileManager.default.fileExists(atPath: candidate.path) { return candidate }
index += 1
}
}
static func uniqueChild(of directory: URL, suggestedName: String) -> URL {
availableURL(for: directory.appendingPathComponent(suggestedName))
}
}