Improve storage, paths
This commit is contained in:
@ -3,36 +3,23 @@ import AppKit
|
||||
import SDWebImageAVIFCoder
|
||||
import SDWebImageWebPCoder
|
||||
|
||||
private struct ImageJob {
|
||||
|
||||
let image: String
|
||||
|
||||
let version: String
|
||||
|
||||
let maximumWidth: CGFloat
|
||||
|
||||
let maximumHeight: CGFloat
|
||||
|
||||
let quality: CGFloat
|
||||
|
||||
let type: ImageFileType
|
||||
}
|
||||
|
||||
final class ImageGenerator {
|
||||
|
||||
private let storage: Storage
|
||||
|
||||
//private let inputImageFolder: URL
|
||||
private let settings: Settings
|
||||
|
||||
private let relativeImageOutputPath: String
|
||||
private var relativeImageOutputPath: String {
|
||||
settings.paths.imagesOutputFolderPath
|
||||
}
|
||||
|
||||
private var generatedImages: [String : [String]] = [:]
|
||||
|
||||
private var jobs: [ImageJob] = []
|
||||
private var jobs: [ImageGenerationJob] = []
|
||||
|
||||
init(storage: Storage, relativeImageOutputPath: String) {
|
||||
init(storage: Storage, settings: Settings) {
|
||||
self.storage = storage
|
||||
self.relativeImageOutputPath = relativeImageOutputPath
|
||||
self.settings = settings
|
||||
do {
|
||||
self.generatedImages = try storage.loadListOfGeneratedImages()
|
||||
} catch {
|
||||
@ -89,29 +76,28 @@ final class ImageGenerator {
|
||||
let width2x = maxWidth * 2
|
||||
let height2x = maxHeight * 2
|
||||
|
||||
_ = generateVersion(for: image, type: .avif, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
_ = generateVersion(for: image, type: .avif, maximumWidth: width2x, maximumHeight: height2x)
|
||||
generateVersion(for: image, type: .avif, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
generateVersion(for: image, type: .avif, maximumWidth: width2x, maximumHeight: height2x)
|
||||
|
||||
_ = generateVersion(for: image, type: .webp, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
_ = generateVersion(for: image, type: .webp, maximumWidth: width2x, maximumHeight: height2x)
|
||||
generateVersion(for: image, type: .webp, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
generateVersion(for: image, type: .webp, maximumWidth: width2x, maximumHeight: height2x)
|
||||
|
||||
_ = generateVersion(for: image, type: type, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
_ = generateVersion(for: image, type: type, maximumWidth: width2x, maximumHeight: height2x)
|
||||
generateVersion(for: image, type: type, maximumWidth: maxWidth, maximumHeight: maxHeight)
|
||||
generateVersion(for: image, type: type, maximumWidth: width2x, maximumHeight: height2x)
|
||||
}
|
||||
|
||||
func generateVersion(for image: String, type: ImageFileType, maximumWidth: CGFloat, maximumHeight: CGFloat) -> String {
|
||||
func generateVersion(for image: String, type: ImageFileType, maximumWidth: CGFloat, maximumHeight: CGFloat) {
|
||||
let version = versionFileName(image: image, type: type, width: maximumWidth, height: maximumHeight)
|
||||
let fullPath = "/" + relativeImageOutputPath + "/" + version
|
||||
if exists(version) {
|
||||
hasNowGenerated(version: version, for: image)
|
||||
return fullPath
|
||||
return
|
||||
}
|
||||
if hasPreviouslyGenerated(version: version, for: image), exists(version) {
|
||||
// Don't add job again
|
||||
return fullPath
|
||||
return
|
||||
}
|
||||
|
||||
let job = ImageJob(
|
||||
let job = ImageGenerationJob(
|
||||
image: image,
|
||||
version: version,
|
||||
maximumWidth: maximumWidth,
|
||||
@ -120,7 +106,6 @@ final class ImageGenerator {
|
||||
type: type)
|
||||
|
||||
jobs.append(job)
|
||||
return fullPath
|
||||
}
|
||||
|
||||
private func hasPreviouslyGenerated(version: String, for image: String) -> Bool {
|
||||
@ -149,7 +134,7 @@ final class ImageGenerator {
|
||||
|
||||
// MARK: Image operations
|
||||
|
||||
private func generate(job: ImageJob) -> Bool {
|
||||
private func generate(job: ImageGenerationJob) -> Bool {
|
||||
if hasPreviouslyGenerated(version: job.version, for: job.image), exists(job.version),
|
||||
exists(imageVersion: job.version) {
|
||||
return true
|
||||
@ -168,58 +153,70 @@ final class ImageGenerator {
|
||||
return false
|
||||
}
|
||||
|
||||
let representation = create(image: originalImage, width: job.maximumWidth, height: job.maximumHeight)
|
||||
|
||||
guard let data = create(image: representation, type: job.type, quality: job.quality) else {
|
||||
print("Failed to get data for type \(job.type)")
|
||||
return false
|
||||
}
|
||||
|
||||
if job.type == .avif {
|
||||
return inOutputImagesFolder { folder in
|
||||
let url = folder.appendingPathComponent(job.version)
|
||||
let out = url.path()
|
||||
let input = url.deletingPathExtension().appendingPathExtension(job.image.fileExtension!).path()
|
||||
print("avifenc -q 70 \(input) \(out)")
|
||||
hasNowGenerated(version: job.version, for: job.image)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
guard write(imageData: data, version: job.version) else {
|
||||
return false
|
||||
}
|
||||
hasNowGenerated(version: job.version, for: job.image)
|
||||
return true
|
||||
}
|
||||
|
||||
private func create(image originalImage: NSImage, width: CGFloat, height: CGFloat) -> NSBitmapImageRep {
|
||||
let sourceRep = originalImage.representations[0]
|
||||
let sourceSize = NSSize(width: sourceRep.pixelsWide, height: sourceRep.pixelsHigh)
|
||||
let maximumSize = NSSize(width: job.maximumWidth, height: job.maximumHeight)
|
||||
let maximumSize = NSSize(width: width, height: height)
|
||||
let destinationSize = sourceSize.scaledToFit(in: maximumSize)
|
||||
|
||||
// create NSBitmapRep manually, if using cgImage, the resulting size is wrong
|
||||
let rep = NSBitmapImageRep(bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(destinationSize.width),
|
||||
pixelsHigh: Int(destinationSize.height),
|
||||
bitsPerSample: 8,
|
||||
samplesPerPixel: 4,
|
||||
hasAlpha: true,
|
||||
isPlanar: false,
|
||||
colorSpaceName: NSColorSpaceName.deviceRGB,
|
||||
bytesPerRow: Int(destinationSize.width) * 4,
|
||||
bitsPerPixel: 32)!
|
||||
let representation = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(destinationSize.width),
|
||||
pixelsHigh: Int(destinationSize.height),
|
||||
bitsPerSample: 8,
|
||||
samplesPerPixel: 4,
|
||||
hasAlpha: true,
|
||||
isPlanar: false,
|
||||
colorSpaceName: NSColorSpaceName.deviceRGB,
|
||||
bytesPerRow: Int(destinationSize.width) * 4,
|
||||
bitsPerPixel: 32)!
|
||||
|
||||
let ctx = NSGraphicsContext(bitmapImageRep: rep)
|
||||
let ctx = NSGraphicsContext(bitmapImageRep: representation)
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.current = ctx
|
||||
originalImage.draw(in: NSMakeRect(0, 0, destinationSize.width, destinationSize.height))
|
||||
ctx?.flushGraphics()
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
return representation
|
||||
}
|
||||
|
||||
guard let data = create(image: rep, type: job.type, quality: job.quality) else {
|
||||
print("Failed to get data for type \(job.type)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
|
||||
let result = inOutputImagesFolder { folder in
|
||||
let url = folder.appendingPathComponent(job.version)
|
||||
if job.type == .avif {
|
||||
let out = url.path()
|
||||
let input = url.deletingPathExtension().appendingPathExtension(job.image.fileExtension!).path()
|
||||
print("avifenc -q 70 \(input) \(out)")
|
||||
return true
|
||||
}
|
||||
private func write(imageData data: Data, version: String) -> Bool {
|
||||
inOutputImagesFolder { folder in
|
||||
let url = folder.appendingPathComponent(version)
|
||||
do {
|
||||
try data.write(to: url)
|
||||
return true
|
||||
} catch {
|
||||
print("Failed to write image \(job.version): \(error)")
|
||||
print("Failed to write image \(version): \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
guard result else {
|
||||
return false
|
||||
}
|
||||
hasNowGenerated(version: job.version, for: job.image)
|
||||
return true
|
||||
}
|
||||
|
||||
private func exists(_ relativePath: String) -> Bool {
|
||||
@ -258,9 +255,9 @@ final class ImageGenerator {
|
||||
|
||||
private func createAvif(image: NSBitmapImageRep, quality: CGFloat) -> Data? {
|
||||
return Data()
|
||||
let newImage = NSImage(size: image.size)
|
||||
newImage.addRepresentation(image)
|
||||
return SDImageAVIFCoder.shared.encodedData(with: newImage, format: .AVIF, options: [.encodeCompressionQuality: quality])
|
||||
// let newImage = NSImage(size: image.size)
|
||||
// newImage.addRepresentation(image)
|
||||
// return SDImageAVIFCoder.shared.encodedData(with: newImage, format: .AVIF, options: [.encodeCompressionQuality: quality])
|
||||
}
|
||||
|
||||
private func createWebp(image: NSBitmapImageRep, quality: CGFloat) -> Data? {
|
||||
|
16
CHDataManagement/Generator/ImageJob.swift
Normal file
16
CHDataManagement/Generator/ImageJob.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
struct ImageGenerationJob {
|
||||
|
||||
let image: String
|
||||
|
||||
let version: String
|
||||
|
||||
let maximumWidth: CGFloat
|
||||
|
||||
let maximumHeight: CGFloat
|
||||
|
||||
let quality: CGFloat
|
||||
|
||||
let type: ImageFileType
|
||||
}
|
@ -16,7 +16,7 @@ final class LocalizedWebsiteGenerator {
|
||||
self.localizedPostSettings = content.settings.localized(in: language)
|
||||
self.imageGenerator = ImageGenerator(
|
||||
storage: content.storage,
|
||||
relativeImageOutputPath: content.settings.paths.imagesOutputFolderPath)
|
||||
settings: content.settings)
|
||||
}
|
||||
|
||||
private var outputDirectory: URL {
|
||||
@ -85,56 +85,6 @@ final class LocalizedWebsiteGenerator {
|
||||
return true
|
||||
}
|
||||
|
||||
private func generatePagesFolderIfNeeded() -> Bool {
|
||||
let relativePath = content.settings.paths.pagesOutputFolderPath
|
||||
|
||||
return content.storage.write(in: .outputPath) { folder in
|
||||
let outputFile = folder.appendingPathComponent(relativePath, isDirectory: true)
|
||||
do {
|
||||
try outputFile.ensureFolderExistence()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generate(page: Page) -> Bool {
|
||||
guard generatePagesFolderIfNeeded() else {
|
||||
print("Failed to generate output folder")
|
||||
return false
|
||||
}
|
||||
let pageGenerator = PageGenerator(
|
||||
content: content,
|
||||
imageGenerator: imageGenerator)
|
||||
|
||||
let content: String
|
||||
let results: PageGenerationResults
|
||||
do {
|
||||
(content, results) = try pageGenerator.generate(page: page, language: language)
|
||||
} catch {
|
||||
print("Failed to generate page \(page.id) in language \(language): \(error)")
|
||||
return false
|
||||
}
|
||||
guard !content.trimmed.isEmpty else {
|
||||
#warning("Generate page with placeholder content")
|
||||
return true
|
||||
}
|
||||
|
||||
let path = page.absoluteUrl(in: language) + ".html"
|
||||
guard save(content, to: path) else {
|
||||
print("Failed to save page")
|
||||
return false
|
||||
}
|
||||
guard imageGenerator.runJobs(callback: { _ in }) else {
|
||||
return false
|
||||
}
|
||||
guard copy(requiredFiles: results.files) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func copy(requiredFiles: Set<FileResource>) -> Bool {
|
||||
//print("Copying \(requiredVideoFiles.count) files...")
|
||||
for file in requiredFiles {
|
||||
|
Reference in New Issue
Block a user