| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- import Foundation
- /// Resolves group template strings using track metadata.
- /// Supported placeholders: {Artist}, {Album}, {Genre}, {Date}, {Folder}, {Format}, {BPM}, {Key}
- struct GroupTemplateResolver {
- /// Preset templates for quick selection.
- static let presets: [(name: String, template: String)] = [
- ("No Grouping", ""),
- ("Album (Date)", "{Album} ({Date})"),
- ("Artist — Album", "{Artist} — {Album}"),
- ("Artist", "{Artist}"),
- ("Album", "{Album}"),
- ("Genre", "{Genre}"),
- ("Folder", "{Folder}"),
- ("Format", "{Format}"),
- ("BPM Range", "{BPM}"),
- ]
- /// Available placeholders with descriptions.
- static let placeholders: [(token: String, description: String)] = [
- ("{Artist}", "Track artist"),
- ("{Album}", "Album name"),
- ("{Genre}", "Genre"),
- ("{Date}", "Release date/year from metadata"),
- ("{Folder}", "Parent folder name"),
- ("{Format}", "File format (FLAC, MP3, etc.)"),
- ("{BPM}", "BPM (rounded to nearest 10)"),
- ("{Key}", "Musical key"),
- ]
- /// Resolve a template string for a track, returning the group header text.
- static func resolve(template: String, for track: Track) -> String {
- guard !template.isEmpty else { return "" }
- var result = template
- result = result.replacingOccurrences(of: "{Artist}", with: track.artist.isEmpty ? "Unknown Artist" : track.artist)
- result = result.replacingOccurrences(of: "{Album}", with: track.album.isEmpty ? "Unknown Album" : track.album)
- result = result.replacingOccurrences(of: "{Genre}", with: track.genre.isEmpty ? "Unknown Genre" : track.genre)
- let yearStr = track.year.map { String($0) } ?? ""
- result = result.replacingOccurrences(of: "{Year}", with: yearStr)
- result = result.replacingOccurrences(of: "{Date}", with: yearStr)
- result = result.replacingOccurrences(of: "{Folder}", with: folderName(for: track))
- result = result.replacingOccurrences(of: "{Format}", with: track.fileFormat.isEmpty ? "Unknown" : track.fileFormat)
- result = result.replacingOccurrences(of: "{BPM}", with: bpmRange(for: track))
- result = result.replacingOccurrences(of: "{Key}", with: track.musicalKey ?? "Unknown Key")
- // Clean up empty brackets etc.
- result = result.replacingOccurrences(of: "()", with: "")
- result = result.replacingOccurrences(of: "[]", with: "")
- result = result.trimmingCharacters(in: .whitespaces)
- return result.isEmpty ? "Ungrouped" : result
- }
- private static func folderName(for track: Track) -> String {
- let components = track.filePath.split(separator: "/")
- if components.count >= 2 {
- return String(components[components.count - 2])
- }
- return "Root"
- }
- private static func bpmRange(for track: Track) -> String {
- guard let bpm = track.bpm else { return "No BPM" }
- let rounded = Int(bpm / 10) * 10
- return "\(rounded)-\(rounded + 10) BPM"
- }
- }
|