From 6e161bf6b5a706beac180f6a9194e72ba849889c Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 12 Feb 2025 18:19:42 +0100 Subject: [PATCH] Generate AVIF images, fix result display --- .../Generator/ImageGenerator.swift | 70 ++++++++++--------- .../Generator/Results/GenerationResults.swift | 24 ++++++- .../Model/Content+Generation.swift | 3 +- CHDataManagement/Model/Content.swift | 4 +- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/CHDataManagement/Generator/ImageGenerator.swift b/CHDataManagement/Generator/ImageGenerator.swift index 2eb0a6f..e3b335e 100644 --- a/CHDataManagement/Generator/ImageGenerator.swift +++ b/CHDataManagement/Generator/ImageGenerator.swift @@ -19,27 +19,9 @@ final class ImageGenerator { settings.paths.imagesOutputFolderPath } - private var avifCommands: Set = [] - - /** - Write a file to the output folder containing a script to generate all missing AVIF images. - - - Note: AVIF images could be generated internally, but the process is very slow. - */ - func writeAvifCommandScript() { - guard !avifCommands.isEmpty else { - if storage.hasFileInOutputFolder("generate-images.sh") { - storage.deleteInOutputFolder("generate-images.sh") - } - return - } - let content = avifCommands.sorted().joined(separator: "\n") - storage.write(content, to: "generate-images.sh") - } - private func needsToGenerate(_ version: ImageVersion) -> Bool { if version.wasPreviouslyGenerated { - return false + return !exists(version) } if exists(version) { // Mark as already generated @@ -71,20 +53,12 @@ final class ImageGenerator { // Skip GIFs, since they can't be converted by avifenc return true } - // AVIF conversion is very slow, so we save bash commands - // for the conversion instead - let baseVersion = ImageVersion( - image: version.image, - type: version.image.type, - maximumWidth: version.maximumWidth, - maximumHeight: version.maximumHeight) - let originalImagePath = storage.outputPath(to: baseVersion.outputPath)!.path() - let generatedImagePath = storage.outputPath(to: version.outputPath)!.path() - let quality = Int(version.quality * 100) - avifCommands.insert("avifenc -q \(quality) '\(originalImagePath)' '\(generatedImagePath)'") - version.wasNowGenerated() - return true + if createAvifUsingBash(version: version) { + version.wasNowGenerated() + return true + } + return false } guard let data = version.image.dataContent() else { @@ -168,6 +142,38 @@ final class ImageGenerator { return SDImageAVIFCoder.shared.encodedData(with: newImage, format: .AVIF, options: [.encodeCompressionQuality: quality]) } + private func createAvifUsingBash(version: ImageVersion) -> Bool { + + let baseVersion = ImageVersion( + image: version.image, + type: version.image.type, + maximumWidth: version.maximumWidth, + maximumHeight: version.maximumHeight) + let originalImagePath = storage.outputPath(to: baseVersion.outputPath)!.path() + let generatedImagePath = storage.outputPath(to: version.outputPath)!.path() + let quality = Int(version.quality * 100) + + let process = Process() + process.launchPath = "/opt/homebrew/bin/avifenc" // Adjust based on installation + process.arguments = ["-q", "\(quality)", originalImagePath, generatedImagePath] + + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = pipe + + process.launch() + process.waitUntilExit() + + if process.terminationStatus != 0 { + print("ImageGenerator: Failed to create AVIF image \(version.image.id)") + let outputData = pipe.fileHandleForReading.readDataToEndOfFile() + let outputString = String(data: outputData, encoding: .utf8) ?? "" + print(outputString) + return false + } + return true + } + private func createWebp(image: NSBitmapImageRep, quality: CGFloat) -> Data? { let newImage = NSImage(size: image.size) newImage.addRepresentation(image) diff --git a/CHDataManagement/Generator/Results/GenerationResults.swift b/CHDataManagement/Generator/Results/GenerationResults.swift index 278a651..0ead354 100644 --- a/CHDataManagement/Generator/Results/GenerationResults.swift +++ b/CHDataManagement/Generator/Results/GenerationResults.swift @@ -70,7 +70,7 @@ final class GenerationResults: ObservableObject { let general = PageGenerationResults(itemId: id, delegate: self) self.general = general cache[id] = general - self.resultCount = 1 + self.resultCount = 0 } func makeResults(_ itemId: LocalizedItemId) -> PageGenerationResults { @@ -98,6 +98,28 @@ final class GenerationResults: ObservableObject { return makeResults(itemId) } + func reset() { + update { + self.inaccessibleFiles = [] + self.unparsableFiles = [] + self.missingFiles = [] + self.missingTags = [] + self.missingPages = [] + self.externalLinks = [] + self.requiredFiles = [] + self.imagesToGenerate = [] + self.invalidCommands = [] + self.invalidBlocks = [] + self.warnings = [] + self.unsavedOutputFiles = [] + self.emptyPages = [] + self.redirects = [:] + } + for result in cache.values { + result.reset() + } + } + func recalculate() { let inaccessibleFiles = cache.values.map { $0.inaccessibleFiles }.union() update { self.inaccessibleFiles = inaccessibleFiles } diff --git a/CHDataManagement/Model/Content+Generation.swift b/CHDataManagement/Model/Content+Generation.swift index 3264743..68bb424 100644 --- a/CHDataManagement/Model/Content+Generation.swift +++ b/CHDataManagement/Model/Content+Generation.swift @@ -4,6 +4,7 @@ extension Content { func generateWebsiteInAllLanguages() { performGenerationIfIdle { + self.results.reset() self.generatePagesInternal() self.generatePostFeedPagesInternal() self.generateTagPagesInternal() @@ -80,8 +81,6 @@ extension Content { } results.failed(image: image) } - - imageGenerator.writeAvifCommandScript() } func generateAllPages() { diff --git a/CHDataManagement/Model/Content.swift b/CHDataManagement/Model/Content.swift index f682d99..fbd805a 100644 --- a/CHDataManagement/Model/Content.swift +++ b/CHDataManagement/Model/Content.swift @@ -181,7 +181,9 @@ final class Content: ObservableObject { private(set) var lastModification: Date = .now func update(saveState: SaveState) { - self.saveState = saveState + DispatchQueue.main.async { + self.saveState = saveState + } } func setModificationTimestamp() {