diff --git a/CHANGELOG.md b/CHANGELOG.md index 569d1eee..ac8020a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Show when a release was previously grabbed or failed in Radarr's interactive search - Show when a release was previously grabbed or failed in Sonarr v5's interactive search - Filter Sonarr v5 releases by multi-season pack +- Show every audio and subtitle track in movie file details (Radarr 7.0.1 and later) ### Fixed - Open the exact movie on Letterboxd instead of a search diff --git a/Ruddarr/Localizable.xcstrings b/Ruddarr/Localizable.xcstrings index 8e27a4e1..0e1c7f7f 100644 --- a/Ruddarr/Localizable.xcstrings +++ b/Ruddarr/Localizable.xcstrings @@ -2029,6 +2029,17 @@ } } }, + "Forced" : { + "comment" : "Subtitles shown only for foreign dialogue and on-screen text", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forced" + } + } + } + }, "Framerate" : { "comment" : "Video frame rate", "localizations" : { @@ -4680,6 +4691,17 @@ } } }, + "SDH" : { + "comment" : "Subtitles for the deaf and hard of hearing", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "SDH" + } + } + } + }, "Search" : { "comment" : "Source of the release", "localizations" : { diff --git a/Ruddarr/Models/MediaFile.swift b/Ruddarr/Models/MediaFile.swift index 35a0ac8f..be1e40ba 100644 --- a/Ruddarr/Models/MediaFile.swift +++ b/Ruddarr/Models/MediaFile.swift @@ -103,6 +103,9 @@ struct FileMediaInfo: Equatable, Codable { let subtitles: String? + let audioStreams: [MediaInfoAudioStream]? + let subtitleStreams: [MediaInfoSubtitleStream]? + var videoCodecLabel: String? { guard var label = videoCodec else { return nil @@ -164,3 +167,46 @@ struct FileMediaInfo: Equatable, Codable { return codes.sorted(by: Languages.codeSort) } } + +struct MediaInfoAudioStream: Equatable, Codable { + let language: String? + let codec: String? + let bitrate: Int? + let channels: Float? + + var label: String { + Languages.name(byCode: language ?? "") + } + + var detail: String { + [ + codec, + channels.map { "\($0)" }, + bitrate.flatMap { formatBitrate($0) }.map { "(\($0))" }, + ] + .compactMap { $0 } + .joined(separator: " ") + } +} + +struct MediaInfoSubtitleStream: Equatable, Codable { + let language: String? + let format: String? + let forced: Bool? + let hearingImpaired: Bool? + + var label: String { + Languages.name(byCode: language ?? "") + } + + var detail: String { + let flags = [ + forced == true ? String(localized: "Forced", comment: "Subtitles shown only for foreign dialogue and on-screen text") : nil, + hearingImpaired == true ? String(localized: "SDH", comment: "Subtitles for the deaf and hard of hearing") : nil, + ].compactMap { $0 } + + return [format, flags.isEmpty ? nil : "(\(flags.formattedList()))"] + .compactMap { $0 } + .joined(separator: " ") + } +} diff --git a/Ruddarr/Views/Shared/MediaFileSheet.swift b/Ruddarr/Views/Shared/MediaFileSheet.swift index a1a4ae7d..e5a5bff2 100644 --- a/Ruddarr/Views/Shared/MediaFileSheet.swift +++ b/Ruddarr/Views/Shared/MediaFileSheet.swift @@ -132,7 +132,14 @@ struct MediaFileSheet: View { @ViewBuilder var audioMetadata: some View { - if let media = file.mediaInfo { + if let streams = file.mediaInfo?.audioStreams, !streams.isEmpty { + Section { + tracks(streams.map { ($0.label, $0.detail) }) + } header: { + headline("Audio") + .padding(.bottom, 4) + } + } else if let media = file.mediaInfo { Section { VStack(spacing: 6) { row( @@ -184,7 +191,14 @@ struct MediaFileSheet: View { @ViewBuilder var textMetadata: some View { - if let media = file.mediaInfo, let codes = media.subtitleCodes { + if let media = file.mediaInfo, let streams = media.subtitleStreams, !streams.isEmpty { + Section { + tracks(streams.map { ($0.label, $0.detail) }) + } header: { + headline("Subtitles") + .padding(.bottom, 4) + } + } else if let media = file.mediaInfo, let codes = media.subtitleCodes { Section { VStack(spacing: 6) { row( @@ -206,6 +220,18 @@ struct MediaFileSheet: View { } } + func tracks(_ tracks: [(label: String, detail: String)]) -> some View { + VStack(spacing: 6) { + ForEach(Array(tracks.enumerated()), id: \.offset) { index, track in + if index > 0 { + Divider() + } + + row(track.label, track.detail) + } + } + } + func headline(_ title: LocalizedStringKey) -> some View { Text(title) .font(.title2.bold()) diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt index 060950fb..cb633fc1 100644 --- a/TestFlight/WhatToTest.en-US.txt +++ b/TestFlight/WhatToTest.en-US.txt @@ -4,6 +4,7 @@ Added - Show when a release was previously grabbed or failed in Radarr's interactive search - Show when a release was previously grabbed or failed in Sonarr v5's interactive search - Filter Sonarr v5 releases by multi-season pack +- Show every audio and subtitle track in movie file details (Radarr 7.0.1 and later) Fixed - Open the exact movie on Letterboxd instead of a search