This Swift script guides users through selecting an input image from common directories using numbered lists and then converting it to a desired format (PNG, JPEG, or TIFF) while also letting them choose an output directory and filename. It simplifies the image conversion process on macOS by providing an interactive, user-friendly interface.
Click to view script…
#!/usr/bin/env swift
import Foundation
import AppKit
// MARK: - Helper Functions
/// Prompts the user for input with a message.
func prompt(_ message: String) -> String {
print(message, terminator: " ")
return readLine() ?? ""
}
/// Presents a numbered list of directory options to the user and returns the selected directory URL.
func selectDirectory(options: [(name: String, url: URL)], promptMessage: String) -> URL {
for (index, option) in options.enumerated() {
print("\(index + 1): \(option.name) -> \(option.url.path)")
}
let choiceStr = prompt(promptMessage)
if let choiceNum = Int(choiceStr), choiceNum > 0, choiceNum <= options.count {
let selected = options[choiceNum - 1]
if selected.name.hasPrefix("Other") {
let customPath = prompt("Enter custom directory path:")
return URL(fileURLWithPath: customPath, isDirectory: true)
} else {
return selected.url
}
} else {
let manualPath = prompt("Invalid selection. Enter directory path manually:")
return URL(fileURLWithPath: manualPath, isDirectory: true)
}
}
/// Presents a numbered list of files for the user to choose from.
func selectFile(from files: [URL], in directory: URL) -> URL {
for (index, file) in files.enumerated() {
print("\(index + 1): \(file.lastPathComponent)")
}
let choiceStr = prompt("Enter the number of your choice:")
if let choiceNum = Int(choiceStr), choiceNum > 0, choiceNum <= files.count {
return files[choiceNum - 1]
} else {
let manualFile = prompt("Invalid selection. Enter the file name (in \(directory.path)):")
return directory.appendingPathComponent(manualFile)
}
}
/// Converts a file size (in bytes) to a human-readable string.
func humanReadableSize(_ size: UInt64) -> String {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
formatter.allowedUnits = [.useKB, .useMB, .useGB, .useTB]
return formatter.string(fromByteCount: Int64(size))
}
// MARK: - Candidate Directories Setup
let fileManager = FileManager.default
var candidateDirs: [(name: String, url: URL)] = []
if let desktop = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first {
candidateDirs.append(("Desktop", desktop))
}
if let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
candidateDirs.append(("Documents", documents))
}
if let downloads = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first {
candidateDirs.append(("Downloads", downloads))
}
candidateDirs.append(("Other (enter path manually)", URL(fileURLWithPath: fileManager.currentDirectoryPath)))
// MARK: - Input Directory and File Selection
print("Select the input directory:")
let inputDirectory = selectDirectory(options: candidateDirs, promptMessage: "Enter the number of your choice:")
// Verify the input directory exists.
var isDir: ObjCBool = false
guard fileManager.fileExists(atPath: inputDirectory.path, isDirectory: &isDir), isDir.boolValue else {
print("Directory \(inputDirectory.path) does not exist or is not a directory.")
exit(1)
}
// Define supported image extensions.
let validImageExtensions: Set<String> = ["png", "jpg", "jpeg", "tiff", "gif"]
// Get and filter files in the chosen directory.
guard let filesInDirectory = try? fileManager.contentsOfDirectory(at: inputDirectory,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles]) else {
print("Unable to read contents of directory \(inputDirectory.path)")
exit(1)
}
let imageFiles = filesInDirectory.filter { validImageExtensions.contains($0.pathExtension.lowercased()) }
if imageFiles.isEmpty {
print("No image files found in \(inputDirectory.path)")
exit(1)
}
print("\nSelect the image file to convert:")
let inputFileURL = selectFile(from: imageFiles, in: inputDirectory)
// Optionally, display file size.
if let attrs = try? fileManager.attributesOfItem(atPath: inputFileURL.path),
let fileSize = attrs[.size] as? UInt64 {
print("Selected file: \(inputFileURL.lastPathComponent) (\(humanReadableSize(fileSize)))")
}
// MARK: - Output Format Selection
print("\nSelect the desired output image format:")
print("1: PNG")
print("2: JPEG")
print("3: TIFF")
let formatChoiceStr = prompt("Enter the number of your choice:")
var outputFileType: NSBitmapImageRep.FileType
var outputExtension: String
switch formatChoiceStr {
case "1":
outputFileType = .png
outputExtension = "png"
case "2":
outputFileType = .jpeg
outputExtension = "jpg"
case "3":
outputFileType = .tiff
outputExtension = "tiff"
default:
print("Invalid selection.")
exit(1)
}
// MARK: - Output Directory and File Name Selection
print("\nSelect the output directory:")
let outputDirectory = selectDirectory(options: candidateDirs, promptMessage: "Enter the number of your choice:")
isDir = false
guard fileManager.fileExists(atPath: outputDirectory.path, isDirectory: &isDir), isDir.boolValue else {
print("Output directory \(outputDirectory.path) does not exist or is not a directory.")
exit(1)
}
let defaultOutputName = inputFileURL.deletingPathExtension().lastPathComponent + "_converted." + outputExtension
let outputFileName = prompt("Enter output file name (default: \(defaultOutputName)):")
let finalOutputFileName = outputFileName.isEmpty ? defaultOutputName : outputFileName
let outputFileURL = outputDirectory.appendingPathComponent(finalOutputFileName)
// MARK: - Image Conversion
print("\nConverting image...")
guard let inputImage = NSImage(contentsOf: inputFileURL) else {
print("Failed to load image from \(inputFileURL.path)")
exit(1)
}
guard let tiffData = inputImage.tiffRepresentation else {
print("Failed to get TIFF representation of the image.")
exit(1)
}
guard let bitmapRep = NSBitmapImageRep(data: tiffData) else {
print("Failed to create bitmap representation.")
exit(1)
}
guard let outputImageData = bitmapRep.representation(using: outputFileType, properties: [:]) else {
print("Failed to convert image to selected format.")
exit(1)
}
do {
try outputImageData.write(to: outputFileURL)
print("Image successfully converted and saved to \(outputFileURL.path)")
} catch {
print("Error saving converted image: \(error)")
exit(1)
}