fix(bazarr): patch ffprobe refiner to handle BoxList audio language

Audio tracks in some video files return language as a BoxList (list of
Language objects) from the video metadata parser. The existing code
tries to add this directly to a Python set, which fails because BoxList
is not hashable — crashing subtitle search for every movie.

Fix: check if lang has __iter__ and iterate over it, or call .alpha3
directly if it's a single Language object.

Delivered via ConfigMap volumeMount so it survives pod restarts.
This commit is contained in:
Will Anderson
2026-04-11 04:39:33 -05:00
parent 7c17b8a93e
commit 0f15063260
+110
View File
@@ -5,6 +5,107 @@
# 2. Settings > Sonarr: URL=http://sonarr:8989, API key from Sonarr Settings > General
# 3. Settings > Providers: add OpenSubtitles.com (free account) for best coverage
# 4. Settings > Languages: add English (and any other desired languages)
---
# Patches Bazarr's ffprobe refiner to handle BoxList (list of Language objects)
# when audio tracks declare multiple languages. Without this, all subtitle searches
# fail with "unhashable type: 'BoxList'" because sets can't contain BoxList objects.
apiVersion: v1
kind: ConfigMap
metadata:
name: bazarr-patches
namespace: media
data:
ffprobe.py: |
# coding=utf-8
# fmt: off
import logging
import json
from subliminal import Movie
from guessit.jsonutils import GuessitEncoder
from utilities.path_mappings import path_mappings
from app.database import TableEpisodes, TableMovies, database, select
from utilities.video_analyzer import parse_video_metadata
def refine_from_ffprobe(path, video):
if isinstance(video, Movie):
file_id = database.execute(
select(TableMovies.movie_file_id, TableMovies.file_size)
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))) \
.first()
else:
file_id = database.execute(
select(TableEpisodes.episode_file_id, TableEpisodes.file_size)
.where(TableEpisodes.path == path_mappings.path_replace_reverse(path)))\
.first()
if not file_id:
return video
if isinstance(video, Movie):
data = parse_video_metadata(file=path, file_size=file_id.file_size,
movie_file_id=file_id.movie_file_id)
else:
data = parse_video_metadata(file=path, file_size=file_id.file_size,
episode_file_id=file_id.episode_file_id)
if not data or ('ffprobe' not in data and 'mediainfo' not in data):
logging.debug(f"No cache available for this file: {path}")
return video
if data['ffprobe']:
logging.debug('FFprobe found: %s', json.dumps(data['ffprobe'], cls=GuessitEncoder, indent=4,
ensure_ascii=False))
parser_data = data['ffprobe']
elif data['mediainfo']:
logging.debug('Mediainfo found: %s', json.dumps(data['mediainfo'], cls=GuessitEncoder, indent=4,
ensure_ascii=False))
parser_data = data['mediainfo']
else:
parser_data = {}
if 'video' not in parser_data:
logging.debug('BAZARR parser was unable to find video tracks in the file!')
else:
if 'resolution' in parser_data['video'][0]:
if not video.resolution:
video.resolution = parser_data['video'][0]['resolution']
if 'codec' in parser_data['video'][0]:
if not video.video_codec:
video.video_codec = parser_data['video'][0]['codec']
if 'frame_rate' in parser_data['video'][0]:
if not video.fps:
if isinstance(parser_data['video'][0]['frame_rate'], float):
video.fps = parser_data['video'][0]['frame_rate']
else:
try:
video.fps = parser_data['video'][0]['frame_rate'].magnitude
except AttributeError:
video.fps = parser_data['video'][0]['frame_rate']
if 'audio' not in parser_data:
logging.debug('BAZARR parser was unable to find audio tracks in the file!')
else:
if 'codec' in parser_data['audio'][0]:
if not video.audio_codec:
video.audio_codec = parser_data['audio'][0]['codec']
for track in parser_data['audio']:
if 'language' in track:
lang = track['language']
# lang may be a single Language object or a BoxList of Language objects
# (BoxList is not hashable and cannot be added to a set directly)
if hasattr(lang, 'alpha3'):
video.audio_languages.add(lang.alpha3)
elif hasattr(lang, '__iter__'):
for l in lang:
if hasattr(l, 'alpha3'):
video.audio_languages.add(l.alpha3)
return video
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -23,6 +124,9 @@ spec:
metadata:
labels:
app: bazarr
metadata:
annotations:
restartedAt: "2026-04-11T09:40:00Z"
spec:
containers:
- name: bazarr
@@ -41,6 +145,9 @@ spec:
mountPath: /config
- name: media
mountPath: /media
- name: bazarr-patches
mountPath: /app/bazarr/bin/bazarr/subtitles/refiners/ffprobe.py
subPath: ffprobe.py
resources:
requests:
memory: 128Mi
@@ -68,6 +175,9 @@ spec:
- name: media
persistentVolumeClaim:
claimName: media-data
- name: bazarr-patches
configMap:
name: bazarr-patches
---
apiVersion: v1
kind: Service