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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user