From f52c3bc8b93f43d77521dae66e225564bd0340d9 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Mon, 5 Dec 2022 11:43:30 +0100 Subject: [PATCH] Check dependencies and outputs --- .../Processing/DependencyCheck.swift | 77 +++++++++++++++++++ .../Generator/Processing/ImageGenerator.swift | 41 +++++++--- Sources/Generator/run.swift | 7 +- 3 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 Sources/Generator/Processing/DependencyCheck.swift diff --git a/Sources/Generator/Processing/DependencyCheck.swift b/Sources/Generator/Processing/DependencyCheck.swift new file mode 100644 index 0000000..350d2de --- /dev/null +++ b/Sources/Generator/Processing/DependencyCheck.swift @@ -0,0 +1,77 @@ +import Foundation + +func checkDependencies() -> Bool { + print("--- DEPENDENCIES -----------------------------------") + print(" ") + let result = checkImageOptimAvailability() && checkMagickAvailability() && checkCwebpAvailability() && checkAvifAvailability() + print(" Complete: \(result ? "Yes" : "No")") + print(" ") + return result +} + +private func checkImageOptimAvailability() -> Bool { + do { + let output = try safeShell("imageoptim --version") + let version = output.components(separatedBy: ".").compactMap { Int($0.trimmed) } + if version.count > 1 { + print(" ImageOptim: \(version.map { "\($0)" }.joined(separator: "."))") + } else { + print(" ImageOptim: Not found") + return false + } + } catch { + print(" ImageOptim: Failed to get version (\(error))") + return false + } + return true +} + +private func checkMagickAvailability() -> Bool { + do { + let output = try safeShell("magick --version") + guard let version = output.components(separatedBy: "ImageMagick ").dropFirst().first? + .components(separatedBy: " ").first else { + print(" Magick: Not found") + return false + } + print(" Magick: \(version)") + } catch { + print(" Magick: Failed to get version (\(error))") + return false + } + return true +} + +private func checkCwebpAvailability() -> Bool { + do { + let output = try safeShell("cwebp -version") + let version = output.components(separatedBy: ".").compactMap { Int($0.trimmed) } + if version.count > 1 { + print(" cwebp: \(version.map { "\($0)" }.joined(separator: "."))") + } else { + print(" cwebp: Not found") + return false + } + } catch { + print(" cwebp: Failed to get version (\(error))") + return false + } + return true +} + +private func checkAvifAvailability() -> Bool { + do { + let output = try safeShell("npx avif --version") + let version = output.components(separatedBy: ".").compactMap { Int($0.trimmed) } + if version.count > 1 { + print(" avif: \(version.map { "\($0)" }.joined(separator: "."))") + } else { + print(" avif: Not found") + return false + } + } catch { + print(" avif: Failed to get version (\(error))") + return false + } + return true +} diff --git a/Sources/Generator/Processing/ImageGenerator.swift b/Sources/Generator/Processing/ImageGenerator.swift index 010e278..ec67b91 100644 --- a/Sources/Generator/Processing/ImageGenerator.swift +++ b/Sources/Generator/Processing/ImageGenerator.swift @@ -46,7 +46,7 @@ final class ImageGenerator { private let numberOfTotalImages: Int - private lazy var numberImagesToCreate = jobs.reduce(0) { $0 + $1.images.count } + multiJobs.count * 2 + private lazy var numberOfImagesToCreate = jobs.reduce(0) { $0 + $1.images.count } + multiJobs.count * 2 private var numberOfImagesToOptimize = 0 @@ -100,7 +100,7 @@ final class ImageGenerator { for (baseImage, source) in multiJobs { createMultiImages(from: baseImage, path: source) } - print(" Generated images: \(numberOfGeneratedImages)/\(numberImagesToCreate)") + print(" Generated images: \(numberOfGeneratedImages)/\(numberOfImagesToCreate)") optimizeImages() print(" Optimized images: \(numberOfOptimizedImages)/\(numberOfImagesToOptimize)") @@ -219,14 +219,18 @@ final class ImageGenerator { generatedImages.insert(webpPath) didGenerateImage() - compress(at: source) + compress(at: sourcePath) } private func createAVIF(at destination: String, from source: String, quality: Int = 55, effort: Int = 5) { let folder = destination.dropAfterLast("/") let command = "npx avif --input=\(source) --quality=\(quality) --effort=\(effort) --output=\(folder) --overwrite" do { - _ = try safeShell(command) + let output = try safeShell(command) + if output == "" { + return + } + markImageAsFailed(destination, error: "Failed to create AVIF image: \(output)") } catch { markImageAsFailed(destination, error: "Failed to create AVIF image") } @@ -235,18 +239,26 @@ final class ImageGenerator { private func createWEBP(at destination: String, from source: String, quality: Int = 75) { let command = "cwebp \(source) -q \(quality) -o \(destination)" do { - _ = try safeShell(command) + let output = try safeShell(command) + if !output.contains("Error") { + return + } + markImageAsFailed(destination, error: "Failed to create WEBP image: \(output)") } catch { - markImageAsFailed(destination, error: "Failed to create WEBP image") + markImageAsFailed(destination, error: "Failed to create WEBP image: \(error)") } } private func compress(at destination: String, quality: Int = 70) { let command = "magick convert \(destination) -quality \(quality)% \(destination)" do { - _ = try safeShell(command) + let output = try safeShell(command) + if output == "" { + return + } + markImageAsFailed(destination, error: "Failed to compress image: \(output)") } catch { - markImageAsFailed(destination, error: "Failed to compress image") + markImageAsFailed(destination, error: "Failed to compress image: \(error)") } } @@ -267,11 +279,18 @@ final class ImageGenerator { private func optimizeImageBatch(_ batch: ArraySlice) -> Bool { let command = "imageoptim " + batch.joined(separator: " ") do { - _ = try safeShell(command) + let output = try safeShell(command) + if output.contains("Finished") { + return true + } + + for image in batch { + markImageAsFailed(image, error: "Failed to optimize image: \(output)") + } return true } catch { for image in batch { - markImageAsFailed(image, error: "Failed to optimize image") + markImageAsFailed(image, error: "Failed to optimize image: \(error)") } return false } @@ -281,7 +300,7 @@ final class ImageGenerator { private func didGenerateImage(count: Int = 1) { numberOfGeneratedImages += count - print(" Generated images: \(numberOfGeneratedImages)/\(numberImagesToCreate) \r", terminator: "") + print(" Generated images: \(numberOfGeneratedImages)/\(numberOfImagesToCreate) \r", terminator: "") fflush(stdout) } diff --git a/Sources/Generator/run.swift b/Sources/Generator/run.swift index 93dbd36..48d524e 100644 --- a/Sources/Generator/run.swift +++ b/Sources/Generator/run.swift @@ -14,7 +14,6 @@ struct CHGenerator: ParsableCommand { } private func loadConfiguration(at configPath: String) -> Configuration? { - print(" ") print("--- CONFIGURATION ----------------------------------") print(" ") print(" Configuration file: \(configPath)") @@ -128,6 +127,12 @@ private func finish(start: Date) { private func generate(configPath: String) throws { let start = Date() + print(" ") + + guard checkDependencies() else { + return + } + // 1. Load configuration guard let configuration = loadConfiguration(at: configPath) else { return