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
|
# 2. Settings > Sonarr: URL=http://sonarr:8989, API key from Sonarr Settings > General
|
||||||
# 3. Settings > Providers: add OpenSubtitles.com (free account) for best coverage
|
# 3. Settings > Providers: add OpenSubtitles.com (free account) for best coverage
|
||||||
# 4. Settings > Languages: add English (and any other desired languages)
|
# 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
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -23,6 +124,9 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: bazarr
|
app: bazarr
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
restartedAt: "2026-04-11T09:40:00Z"
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: bazarr
|
- name: bazarr
|
||||||
@@ -41,6 +145,9 @@ spec:
|
|||||||
mountPath: /config
|
mountPath: /config
|
||||||
- name: media
|
- name: media
|
||||||
mountPath: /media
|
mountPath: /media
|
||||||
|
- name: bazarr-patches
|
||||||
|
mountPath: /app/bazarr/bin/bazarr/subtitles/refiners/ffprobe.py
|
||||||
|
subPath: ffprobe.py
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: 128Mi
|
memory: 128Mi
|
||||||
@@ -68,6 +175,9 @@ spec:
|
|||||||
- name: media
|
- name: media
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: media-data
|
claimName: media-data
|
||||||
|
- name: bazarr-patches
|
||||||
|
configMap:
|
||||||
|
name: bazarr-patches
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
Reference in New Issue
Block a user