Add age-based sorting and clip report manifest

This commit is contained in:
Mikei386
2026-06-03 14:40:56 +02:00
parent 60fe54faa2
commit 2eec8d7769
3 changed files with 44 additions and 4 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ struct ContentView: View {
Text(sortMode.rawValue).tag(sortMode)
}
}
.help("Bestimmt die chronologische Reihenfolge der Quellvideos: nach Dateiname oder Änderungsdatum.")
.help("Bestimmt die chronologische Reihenfolge der Quellvideos. Wenn Dateinamen wie '118 Wochen alt' enthalten sind, nutze 'Alter in Wochen aufsteigend'.")
Picker("Mehrfach-Versionen", selection: $settings.duplicateClipSelection) {
ForEach(DuplicateClipSelection.allCases) { selection in
+1
View File
@@ -10,6 +10,7 @@ enum OutputFormat: String, CaseIterable, Identifiable, Codable {
enum SortMode: String, CaseIterable, Identifiable, Codable {
case filenameAscending = "Dateiname aufsteigend"
case ageWeeksAscending = "Alter in Wochen aufsteigend"
case modificationDateAscending = "Änderungsdatum aufsteigend"
var id: String { rawValue }
+42 -3
View File
@@ -113,7 +113,7 @@ final class VideoProcessor: ObservableObject {
settings: effectiveSettings,
outputFile: outputFile,
targetDimensions: targetDimensions,
inputClipCount: videos.count
selectedVideos: videos
)
appendLog("Render-Protokoll: \(reportURL.path)")
@@ -204,6 +204,15 @@ final class VideoProcessor: ObservableObject {
switch sortMode {
case .filenameAscending:
urls.sort { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }
case .ageWeeksAscending:
urls.sort { left, right in
let leftAge = ageInWeeks(from: left)
let rightAge = ageInWeeks(from: right)
if leftAge != rightAge {
return (leftAge ?? Int.max) < (rightAge ?? Int.max)
}
return left.lastPathComponent.localizedStandardCompare(right.lastPathComponent) == .orderedAscending
}
case .modificationDateAscending:
urls.sort {
let left = (try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? .distantPast
@@ -264,6 +273,18 @@ final class VideoProcessor: ObservableObject {
return selected
}
private func ageInWeeks(from url: URL) -> Int? {
let name = url.deletingPathExtension().lastPathComponent
let pattern = #"(\d{1,4})\s*Wochen\s+alt"#
guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]),
let match = regex.firstMatch(in: name, range: NSRange(name.startIndex..., in: name)),
match.numberOfRanges == 2,
let range = Range(match.range(at: 1), in: name) else {
return nil
}
return Int(name[range])
}
private func chooseDuplicateVariant(from variants: [VideoFile], selection: DuplicateClipSelection) -> VideoFile {
let sortedVariants = variants.sorted { left, right in
let leftVersion = duplicateVersionNumber(for: left.url) ?? Int.max
@@ -949,21 +970,36 @@ final class VideoProcessor: ObservableObject {
settings: RenderSettings,
outputFile: URL,
targetDimensions: VideoDimensions,
inputClipCount: Int
selectedVideos: [VideoFile]
) -> URL {
let reportURL = outputFile
.deletingPathExtension()
.appendingPathExtension("growthlapse-report.txt")
let selectedClipLines = selectedVideos.enumerated().map { index, video in
let age = ageInWeeks(from: video.url).map { ", \($0) Wochen" } ?? ""
return String(
format: "%03d. %@ - %@, %dx%d%@",
index + 1,
video.displayName,
formatSeconds(video.duration),
video.width,
video.height,
age
)
}
let summary = [
"GrowthLapse Render-Protokoll",
"Datum: \(Date())",
"Input-Ordner: \(settings.inputFolder?.path ?? "-")",
"Output: \(outputFile.path)",
"Input-Clips: \(inputClipCount)",
"Input-Clips: \(selectedVideos.count)",
"Zielauflösung: \(targetDimensions.width)x\(targetDimensions.height)",
"Format: \(settings.outputFormat.rawValue)",
"Encoder: \(settings.videoEncoder.rawValue)",
"FPS: \(settings.fps)",
"Sortierung: \(settings.sortMode.rawValue)",
"Mehrfach-Versionen: \(settings.duplicateClipSelection.rawValue)",
"Segmentlänge: \(settings.segmentLength)s",
"Ziellänge pro Clip: \(settings.targetClipLength)s",
"Übergangslänge: \(settings.transitionLength)s",
@@ -974,6 +1010,9 @@ final class VideoProcessor: ObservableObject {
"Stabilisierung Hintergrund-Anker: \(settings.stabilizationAnchorEnabled ? "an, X \(Int(settings.stabilizationAnchorX * 100))%, Y \(Int(settings.stabilizationAnchorY * 100))%, Bereich \(Int(settings.stabilizationAnchorSize * 100))%" : "aus")",
"Face Size Normalization: \(settings.faceNormalizationEnabled ? "an, Ziel \(Int(settings.targetFaceHeightRatio * 100))%" : "aus")",
"",
"Ausgewählte Clip-Reihenfolge:",
selectedClipLines.joined(separator: "\n"),
"",
"Log:",
logs.joined(separator: "\n")
].joined(separator: "\n")