From 946de298938402a70e5c775b9df288ff902b9ad8 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 27 Sep 2026 19:11:27 +0200 Subject: [PATCH] Nachsehen, was in den Tonspuren wirklich steht MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Sprachwahl vergleicht Kürzel, und Kürzel lügen — beim ZDF-Fall ist die als deutsch ausgezeichnete Spur die mit Audiodeskription. Ob der **Name** der Spur das rettet, sieht man nur, wenn man beides nebeneinander legt. Alles andere wäre geraten, und davon hatten wir heute genug. `--spuren ` sucht einen Titel und druckt zu jeder Tonspur: Kürzel, Name aus der Datei, wie Jellyfin es zusammensetzt, Codec, Kanäle und die Kennzeichen. Dafür holt der Decoder jetzt auch `Title`, `IsHearingImpaired` und `IsOriginal`. `Title` ist das zweite Signal neben dem Kürzel und manchmal das einzige richtige: wo „eng" steht, kann im Namen „Deutsch" stehen. Wir haben es bisher weggeworfen. Co-Authored-By: Claude Opus 5 --- .../FlixyKit/Sources/FlixyProbe/Leiter.swift | 77 +++++++++++++++++++ .../FlixyKit/Sources/FlixyProbe/main.swift | 7 ++ .../Sources/JellyfinKit/JellyfinItem.swift | 12 +++ 3 files changed, 96 insertions(+) diff --git a/Packages/FlixyKit/Sources/FlixyProbe/Leiter.swift b/Packages/FlixyKit/Sources/FlixyProbe/Leiter.swift index bee29b0..c9425c1 100644 --- a/Packages/FlixyKit/Sources/FlixyProbe/Leiter.swift +++ b/Packages/FlixyKit/Sources/FlixyProbe/Leiter.swift @@ -446,3 +446,80 @@ private func anfrage(_ url: URL, _ kopfzeilen: [String: String]) -> URLRequest { for (name, wert) in kopfzeilen { request.setValue(wert, forHTTPHeaderField: name) } return request } + +/// Zeigt, was der Server über die Tonspuren eines Titels wirklich sagt. +/// +/// **Der Grund:** Die Sprachwahl vergleicht Kürzel, und Kürzel lügen. Ob bei +/// einer Datei der Titel das rettet — „eng" im Kürzel, „Deutsch" im Namen —, +/// sieht man nur, wenn man beides nebeneinander legt. Alles andere wäre +/// geraten. +func zeigeSpuren(host: String, suche: String) async { + guard let basis = URL(string: host.contains("://") ? host : "http://\(host)") else { return } + + print("Benutzername: ", terminator: "") + guard let name = readLine(), !name.isEmpty else { return } + guard let passwort = String(validatingCString: getpass("Passwort: ")), !passwort.isEmpty else { return } + + let identity = JellyfinIdentity( + client: "Flixy-Probe", + device: "Mac", + deviceID: "flixy-probe-" + UUID().uuidString, + version: "1.0" + ) + let client = JellyfinClient(baseURL: basis, identity: identity) + + guard let konto = try? await JellyfinAuthenticator(client: client).signIn( + userName: name, + password: passwort + ) else { + print("Anmeldung gescheitert.") + return + } + print("Angemeldet als \(konto.userName).\n") + + // Suchen, damit man nicht mit Kennungen hantieren muss. + let treffer = try? await client.send( + JellyfinRequest(path: "/Users/\(konto.userID)/Items", query: [ + URLQueryItem(name: "searchTerm", value: suche), + URLQueryItem(name: "Recursive", value: "true"), + URLQueryItem(name: "IncludeItemTypes", value: "Movie,Episode"), + URLQueryItem(name: "Limit", value: "5"), + URLQueryItem(name: "Fields", value: "MediaSources"), + ]), + as: JellyfinItemPage.self + ) + + guard let gefunden = treffer?.items, !gefunden.isEmpty else { + print("Nichts gefunden zu „\(suche)“.") + return + } + + for eintrag in gefunden { + print("── \(eintrag.name ?? "—")") + let quellen = eintrag.mediaSources ?? [] + guard let streams = quellen.first?.mediaStreams else { + print(" keine Spurangaben\n") + continue + } + + for spur in streams where spur.type == "Audio" { + let kürzel = spur.language ?? "—" + let name = spur.title ?? "—" + var merkmale: [String] = [] + if spur.isDefault == true { merkmale.append("Standard") } + if spur.isForced == true { merkmale.append("erzwungen") } + if spur.isHearingImpaired == true { merkmale.append("hörgeschädigt") } + if spur.isOriginal == true { merkmale.append("Original") } + + print(" [\(spur.index ?? -1)] Kürzel \(kürzel)") + print(" Name in der Datei: \(name)") + print(" Wie Jellyfin es zeigt: \(spur.displayTitle ?? "—")") + print(" \(spur.codec ?? "—"), \(spur.channels ?? 0) Kanäle" + + (merkmale.isEmpty ? "" : ", " + merkmale.joined(separator: ", "))) + } + print("") + } + + print("Zu lesen ist das so: stimmt das Kürzel nicht mit dem Namen überein,") + print("lügt die Datei — und dann hilft nur der Name oder das Merken.") +} diff --git a/Packages/FlixyKit/Sources/FlixyProbe/main.swift b/Packages/FlixyKit/Sources/FlixyProbe/main.swift index 03b4eff..84e134f 100644 --- a/Packages/FlixyKit/Sources/FlixyProbe/main.swift +++ b/Packages/FlixyKit/Sources/FlixyProbe/main.swift @@ -27,6 +27,13 @@ if let index = CommandLine.arguments.firstIndex(of: "--playlist"), exit(0) } +if let index = CommandLine.arguments.firstIndex(of: "--spuren"), + let host = CommandLine.arguments.dropFirst(index + 1).first, + let suche = CommandLine.arguments.dropFirst(index + 2).first { + await zeigeSpuren(host: host, suche: suche) + exit(0) +} + if let index = CommandLine.arguments.firstIndex(of: "--jellyfin"), let host = CommandLine.arguments.dropFirst(index + 1).first { await jellyfinLeiter(host: host) diff --git a/Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift b/Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift index c93919e..9d19af3 100644 --- a/Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift +++ b/Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift @@ -168,6 +168,16 @@ public struct JellyfinMediaStream: Decodable, Sendable { public let codec: String? public let language: String? public let displayTitle: String? + /// Was **in der Datei** steht — im Gegensatz zu `displayTitle`, das + /// Jellyfin aus Sprache, Codec und Kennzeichen zusammensetzt. + /// + /// Das zweite Signal neben dem Sprachkürzel, und manchmal das einzige + /// richtige: wo „eng" steht, kann im Titel „Deutsch" stehen. + public let title: String? + /// Für Hörgeschädigte. **Nicht** Audiodeskription — dafür hat Jellyfin + /// kein Kennzeichen. + public let isHearingImpaired: Bool? + public let isOriginal: Bool? public let channels: Int? public let isDefault: Bool? public let isForced: Bool? @@ -177,6 +187,8 @@ public struct JellyfinMediaStream: Decodable, Sendable { private enum CodingKeys: String, CodingKey { case index = "Index", type = "Type", codec = "Codec", language = "Language" case displayTitle = "DisplayTitle", channels = "Channels" + case title = "Title" + case isHearingImpaired = "IsHearingImpaired", isOriginal = "IsOriginal" case isDefault = "IsDefault", isForced = "IsForced" case height = "Height", width = "Width" }