Allow custom thumbnail paths in metadata

This commit is contained in:
Christoph Hagen 2022-09-29 21:23:41 +02:00
parent 9d2f1e4c90
commit c82080db82
7 changed files with 97 additions and 60 deletions

View File

@ -233,27 +233,3 @@ extension Element.LocalizedMetadata {
} }
} }
} }
// MARK: Thumbnails
extension Element {
static let defaultThumbnailName = "thumbnail.jpg"
static func localizedThumbnailName(for language: String) -> String {
"thumbnail-\(language).jpg"
}
static func findThumbnail(for language: String, in folder: URL) -> String? {
let localizedThumbnail = localizedThumbnailName(for: language)
let localizedThumbnailUrl = folder.appendingPathComponent(localizedThumbnail)
if localizedThumbnailUrl.exists {
return localizedThumbnail
}
let defaultThumbnailUrl = folder.appendingPathComponent(defaultThumbnailName)
if defaultThumbnailUrl.exists {
return defaultThumbnailName
}
return nil
}
}

View File

@ -85,6 +85,15 @@ struct Element {
*/ */
let images: [ManualImage] let images: [ManualImage]
/**
The path to the thumbnail file.
This property is optional, and defaults to ``GenericMetadata.defaultThumbnailName``.
Note: The generator first looks for localized versions of the thumbnail by appending `-[lang]` to the file name,
e.g. `customThumb-en.jpg`. If no file is found, then the specified file is tried.
*/
let thumbnailPath: String
/** /**
The style of thumbnail to use when generating overviews. The style of thumbnail to use when generating overviews.
@ -168,6 +177,7 @@ struct Element {
self.externalFiles = metadata.externalFiles ?? [] self.externalFiles = metadata.externalFiles ?? []
self.requiredFiles = metadata.requiredFiles ?? [] // Paths are already relative to root self.requiredFiles = metadata.requiredFiles ?? [] // Paths are already relative to root
self.images = metadata.images?.compactMap { ManualImage(input: $0, path: "") } ?? [] self.images = metadata.images?.compactMap { ManualImage(input: $0, path: "") } ?? []
self.thumbnailPath = metadata.thumbnailPath ?? Element.defaultThumbnailName
self.thumbnailStyle = log.unused(metadata.thumbnailStyle, "thumbnailStyle", source: source) ?? .large self.thumbnailStyle = log.unused(metadata.thumbnailStyle, "thumbnailStyle", source: source) ?? .large
self.useManualSorting = log.unused(metadata.useManualSorting, "useManualSorting", source: source) ?? true self.useManualSorting = log.unused(metadata.useManualSorting, "useManualSorting", source: source) ?? true
self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault
@ -237,6 +247,7 @@ struct Element {
self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path) self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path)
self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path) self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path)
self.images = metadata.images?.compactMap { ManualImage(input: $0, path: path) } ?? [] self.images = metadata.images?.compactMap { ManualImage(input: $0, path: path) } ?? []
self.thumbnailPath = metadata.thumbnailPath ?? Element.defaultThumbnailName
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source) self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
self.useManualSorting = metadata.useManualSorting ?? false self.useManualSorting = metadata.useManualSorting ?? false
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
@ -420,17 +431,6 @@ extension Element {
extension Element { extension Element {
/**
Get the full path of the thumbnail image for the language (relative to the root folder).
*/
func thumbnailFilePath(for language: String) -> String {
guard let thumbnailFile = Element.findThumbnail(for: language, in: inputFolder) else {
log.add(error: "Missing thumbnail", source: path)
return Element.defaultThumbnailName
}
return pathRelativeToRootForContainedInputFile(thumbnailFile)
}
/** /**
The full url (relative to root) for the localized page The full url (relative to root) for the localized page
- Parameter language: The language of the page where the url should point - Parameter language: The language of the page where the url should point
@ -497,7 +497,7 @@ extension Element {
} }
func linkPreviewImage(for language: String) -> String? { func linkPreviewImage(for language: String) -> String? {
localized(for: language).linkPreviewImage localized(for: language).linkPreviewImage ?? thumbnailFileName(for: language)
} }
} }
@ -663,3 +663,57 @@ extension Element {
return node return node
} }
} }
// MARK: Thumbnails
extension Element {
static let defaultThumbnailName = "thumbnail.jpg"
/**
Find the thumbnail for the element.
This function uses either the custom thumbnail path from the metadata or the default name
to find a thumbnail. It first checks if a localized version of the thumbnail exists, or returns the
generic version. If no thumbnail image could be found on disk, then an error is logged and the
generic path is returned.
- Parameter language: The language of the thumbnail
- Returns: The thumbnail (either the localized or the generic version)
*/
func thumbnailFilePath(for language: String) -> (source: String, destination: String) {
let localizedThumbnail = thumbnailPath.insert("-\(language)", beforeLast: ".")
let localizedThumbnailUrl = inputFolder.appendingPathComponent(localizedThumbnail)
if localizedThumbnailUrl.exists {
let source = pathRelativeToRootForContainedInputFile(localizedThumbnail)
let ext = thumbnailPath.lastComponentAfter(".")
let destination = pathRelativeToRootForContainedInputFile("thumbnail-\(language).\(ext)")
return (source, destination)
}
let thumbnailUrl = inputFolder.appendingPathComponent(thumbnailPath)
if !thumbnailUrl.exists {
log.add(error: "Missing thumbnail", source: path)
}
let source = pathRelativeToRootForContainedInputFile(thumbnailPath)
let ext = thumbnailPath.lastComponentAfter(".")
let destination = pathRelativeToRootForContainedInputFile("thumbnail.\(ext)")
return (source, destination)
}
private func thumbnailFileName(for language: String) -> String? {
let localizedThumbnailName = thumbnailPath.insert("-\(language)", beforeLast: ".")
let localizedThumbnail = pathRelativeToRootForContainedInputFile(localizedThumbnailName)
let localizedThumbnailUrl = inputFolder.appendingPathComponent(localizedThumbnail)
if localizedThumbnailUrl.exists {
return localizedThumbnailName
}
let thumbnailUrl = inputFolder.appendingPathComponent(thumbnailPath)
if !thumbnailUrl.exists {
return nil
}
return thumbnailPath
}
}

View File

@ -84,6 +84,15 @@ struct GenericMetadata {
*/ */
let images: Set<String>? let images: Set<String>?
/**
The path to the thumbnail file.
This property is optional, and defaults to ``Element.defaultThumbnailName``.
Note: The generator first looks for localized versions of the thumbnail by appending `-[lang]` to the file name,
e.g. `customThumb-en.jpg`. If no file is found, then the specified file is tried.
*/
let thumbnailPath: String?
/** /**
The style of thumbnail to use when generating overviews. The style of thumbnail to use when generating overviews.
@ -136,6 +145,7 @@ extension GenericMetadata: Codable {
.externalFiles, .externalFiles,
.requiredFiles, .requiredFiles,
.images, .images,
.thumbnailPath,
.thumbnailStyle, .thumbnailStyle,
.useManualSorting, .useManualSorting,
.overviewItemCount, .overviewItemCount,
@ -207,6 +217,7 @@ extension GenericMetadata {
externalFiles: [], externalFiles: [],
requiredFiles: [], requiredFiles: [],
images: [], images: [],
thumbnailPath: "",
thumbnailStyle: "", thumbnailStyle: "",
useManualSorting: false, useManualSorting: false,
overviewItemCount: 6, overviewItemCount: 6,

View File

@ -116,24 +116,20 @@ final class ValidationLog {
} }
func linkPreviewThumbnail(customFile: String?, for language: String, in folder: URL, source: String) -> String? { func linkPreviewThumbnail(customFile: String?, for language: String, in folder: URL, source: String) -> String? {
if let customFile = customFile { guard let customFile = customFile else {
let customFileUrl: URL
if customFile.starts(with: "/") {
customFileUrl = URL(fileURLWithPath: customFile)
} else {
customFileUrl = folder.appendingPathComponent(customFile)
}
guard customFileUrl.exists else {
missing(customFile, requiredBy: "property 'linkPreviewImage' in metadata of \(source)")
return nil
}
return customFile
}
guard let thumbnail = Element.findThumbnail(for: language, in: folder) else {
// Link preview images are not necessarily required
return nil return nil
} }
return thumbnail let customFileUrl: URL
if customFile.starts(with: "/") {
customFileUrl = URL(fileURLWithPath: customFile)
} else {
customFileUrl = folder.appendingPathComponent(customFile)
}
guard customFileUrl.exists else {
missing(customFile, requiredBy: "property 'linkPreviewImage' in metadata of \(source)")
return nil
}
return customFile
} }
func moreLinkText(_ elementText: String?, parent parentText: String?, source: String) -> String { func moreLinkText(_ elementText: String?, parent parentText: String?, source: String) -> String {

View File

@ -265,7 +265,7 @@ struct PageContentGenerator {
content[.title] = linkedPage.title(for: language) content[.title] = linkedPage.title(for: language)
let fullThumbnailPath = linkedPage.thumbnailFilePath(for: language) let fullThumbnailPath = linkedPage.thumbnailFilePath(for: language).destination
let relativeImageUrl = page.relativePathToOtherSiteElement(file: fullThumbnailPath) let relativeImageUrl = page.relativePathToOtherSiteElement(file: fullThumbnailPath)
let metadata = linkedPage.localized(for: language) let metadata = linkedPage.localized(for: language)

View File

@ -21,7 +21,7 @@ struct PageHeadGenerator {
// Note: Generate separate destination link for the image, // Note: Generate separate destination link for the image,
// since we don't want a single large image for thumbnails. // since we don't want a single large image for thumbnails.
// Warning: Link preview source path must be relative to root // Warning: Link preview source path must be relative to root
let linkPreviewImageName = image.insert("-link", beforeLast: ".") let linkPreviewImageName = "thumbnail-link.\(image.lastComponentAfter("."))"
let sourceImagePath = page.pathRelativeToRootForContainedInputFile(image) let sourceImagePath = page.pathRelativeToRootForContainedInputFile(image)
let destinationImagePath = page.pathRelativeToRootForContainedInputFile(linkPreviewImageName) let destinationImagePath = page.pathRelativeToRootForContainedInputFile(linkPreviewImageName)
files.requireImage( files.requireImage(

View File

@ -14,8 +14,8 @@ struct ThumbnailListGenerator {
} }
private func itemContent(_ item: Element, parent: Element, language: String, style: ThumbnailStyle) -> String { private func itemContent(_ item: Element, parent: Element, language: String, style: ThumbnailStyle) -> String {
let fullThumbnailPath = item.thumbnailFilePath(for: language) let (thumbnailSourcePath, thumbnailDestPath) = item.thumbnailFilePath(for: language)
let relativeImageUrl = parent.relativePathToFileWithPath(fullThumbnailPath) let relativeImageUrl = parent.relativePathToFileWithPath(thumbnailDestPath)
let metadata = item.localized(for: language) let metadata = item.localized(for: language)
var content = [ThumbnailKey : String]() var content = [ThumbnailKey : String]()
@ -37,16 +37,16 @@ struct ThumbnailListGenerator {
} }
files.requireImage( files.requireImage(
source: fullThumbnailPath, source: thumbnailSourcePath,
destination: fullThumbnailPath, destination: thumbnailDestPath,
requiredBy: item.path, requiredBy: item.path,
width: style.width, width: style.width,
desiredHeight: style.height) desiredHeight: style.height)
// Create image version for high-resolution screens // Create image version for high-resolution screens
files.requireImage( files.requireImage(
source: fullThumbnailPath, source: thumbnailSourcePath,
destination: fullThumbnailPath.insert("@2x", beforeLast: "."), destination: thumbnailDestPath.insert("@2x", beforeLast: "."),
requiredBy: item.path, requiredBy: item.path,
width: style.width * 2, width: style.width * 2,
desiredHeight: style.height * 2) desiredHeight: style.height * 2)