Skip to content

Commit 38384da

Browse files
committed
Fix service-derived bitrate analysis
1 parent 71e1366 commit 38384da

3 files changed

Lines changed: 80 additions & 12 deletions

File tree

lib/reencodarr/analyzer/broadway.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,18 @@ defmodule Reencodarr.Analyzer.Broadway do
253253

254254
# Helper function to check if MediaInfo is valid and complete
255255
defp has_valid_mediainfo?(video) do
256-
# Check for required fields that indicate complete MediaInfo
257-
is_number(video.duration) && video.duration > 0 &&
256+
# Service APIs provide partial mediaInfo that can look complete enough to
257+
# pass field checks while still missing the container-level bitrate. Only
258+
# skip the MediaInfo command when the stored payload came from MediaInfo.
259+
real_mediainfo_payload?(video.mediainfo) &&
260+
is_number(video.duration) && video.duration > 0 &&
258261
is_number(video.bitrate) && video.bitrate > 0
259262
end
260263

264+
defp real_mediainfo_payload?(%{"creatingLibrary" => _}), do: true
265+
defp real_mediainfo_payload?(%{"media" => %{"@ref" => ref}}) when is_binary(ref), do: true
266+
defp real_mediainfo_payload?(_), do: false
267+
261268
# Process videos that have MediaInfo but unchanged file size by transitioning to analyzed
262269
defp process_unchanged_mediainfo_videos([]), do: :ok
263270

lib/reencodarr/media/video/media_info_converter.ex

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,23 @@ defmodule Reencodarr.Media.Video.MediaInfoConverter do
171171
end
172172

173173
defp calculate_overall_bitrate(file, media_info) do
174-
case {file["overallBitrate"], media_info["videoBitrate"], media_info["audioBitrate"]} do
175-
{overall, _, _} when is_integer(overall) and overall > 0 -> overall
176-
{_, video, audio} when is_integer(video) and is_integer(audio) -> video + audio
177-
{_, video, _} when is_integer(video) -> video
178-
_ -> 0
174+
overall = file["overallBitrate"] || media_info["overallBitrate"]
175+
calculate_overall_bitrate(overall, media_info["videoBitrate"], media_info["audioBitrate"])
176+
end
177+
178+
defp calculate_overall_bitrate(overall, video_bitrate, audio_bitrate) do
179+
case {overall, video_bitrate, audio_bitrate} do
180+
{overall, _, _} when is_integer(overall) and overall > 0 ->
181+
overall
182+
183+
{_, video, audio} when is_integer(video) and video > 0 and is_integer(audio) ->
184+
video + audio
185+
186+
{_, video, _} when is_integer(video) and video > 0 ->
187+
video
188+
189+
_ ->
190+
0
179191
end
180192
end
181193

@@ -196,7 +208,8 @@ defmodule Reencodarr.Media.Video.MediaInfoConverter do
196208
end
197209

198210
defp build_general_track(file, overall_bitrate, subtitles, audio_languages) do
199-
duration = normalize_duration(file["runTime"])
211+
media_info = file["mediaInfo"] || %{}
212+
duration = normalize_duration(file["runTime"] || media_info["runTime"])
200213
final_bitrate = normalize_bitrate(overall_bitrate)
201214

202215
%{
@@ -217,10 +230,9 @@ defmodule Reencodarr.Media.Video.MediaInfoConverter do
217230
nil -> 3600.0
218231
# Default to 1 hour if zero
219232
0 -> 3600.0
220-
# Convert seconds to milliseconds
221-
time when is_integer(time) -> time * 1000.0
222-
# Convert seconds to milliseconds
223-
time when is_float(time) -> time * 1000.0
233+
time when is_integer(time) -> time * 1.0
234+
time when is_float(time) -> time
235+
time when is_binary(time) -> Parsers.parse_duration(time)
224236
_ -> 3600.0
225237
end
226238
end

test/reencodarr/media/video/media_info_converter_test.exs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,55 @@ defmodule Reencodarr.Media.Video.MediaInfoConverterTest do
176176
assert general["OverallBitRate"] == 5000
177177
end
178178

179+
test "does not use audio bitrate as overall bitrate when video bitrate is missing" do
180+
file =
181+
minimal_service_file(%{
182+
"runTime" => nil,
183+
"overallBitrate" => nil,
184+
"mediaInfo" => %{
185+
"width" => 1920,
186+
"height" => 1080,
187+
"videoCodec" => "x264",
188+
"audioCodec" => "DTS",
189+
"audioChannels" => 5.1,
190+
"audioLanguages" => "eng",
191+
"subtitles" => "",
192+
"videoBitrate" => 0,
193+
"audioBitrate" => 1_536_000,
194+
"runTime" => "42:39"
195+
}
196+
})
197+
198+
%{"media" => %{"track" => tracks}} = MediaInfoConverter.from_service_file(file, :sonarr)
199+
general = Enum.find(tracks, &(&1["@type"] == "General"))
200+
201+
assert general["OverallBitRate"] == 0
202+
assert general["Duration"] == 2559
203+
end
204+
205+
test "sums video and audio bitrate only when video bitrate is positive" do
206+
file =
207+
minimal_service_file(%{
208+
"overallBitrate" => nil,
209+
"mediaInfo" => %{
210+
"width" => 1920,
211+
"height" => 1080,
212+
"videoCodec" => "HEVC",
213+
"audioCodec" => "AAC",
214+
"audioChannels" => 2,
215+
"audioLanguages" => "eng",
216+
"subtitles" => "",
217+
"videoBitrate" => 4_000_000,
218+
"audioBitrate" => 192_000
219+
}
220+
})
221+
222+
%{"media" => %{"track" => tracks}} = MediaInfoConverter.from_service_file(file, :sonarr)
223+
general = Enum.find(tracks, &(&1["@type"] == "General"))
224+
225+
assert general["OverallBitRate"] == 4_192_000
226+
end
227+
179228
test "handles missing mediaInfo gracefully" do
180229
file = Map.delete(minimal_service_file(), "mediaInfo")
181230
result = MediaInfoConverter.from_service_file(file, :sonarr)

0 commit comments

Comments
 (0)