# Bazarr — automatic subtitle downloader # Connects to Radarr + Sonarr, downloads subtitles from OpenSubtitles/Subscene/etc. # After deploy, configure at bazarr:6767: # 1. Settings > Radarr: URL=http://radarr:7878, API key from Radarr 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 # 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: name: bazarr namespace: media labels: app: bazarr spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: bazarr template: metadata: labels: app: bazarr annotations: restartedAt: "2026-04-11T09:40:00Z" spec: containers: - name: bazarr image: lscr.io/linuxserver/bazarr:latest env: - name: PUID value: "1000" - name: PGID value: "1000" - name: TZ value: "America/Chicago" ports: - containerPort: 6767 volumeMounts: - name: config 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 cpu: 50m limits: memory: 512Mi cpu: 200m livenessProbe: httpGet: path: / port: 6767 initialDelaySeconds: 120 periodSeconds: 30 failureThreshold: 5 readinessProbe: httpGet: path: / port: 6767 initialDelaySeconds: 60 periodSeconds: 15 volumes: - name: config persistentVolumeClaim: claimName: bazarr-config - name: media persistentVolumeClaim: claimName: media-data - name: bazarr-patches configMap: name: bazarr-patches --- apiVersion: v1 kind: Service metadata: name: bazarr namespace: media spec: selector: app: bazarr ports: - name: http port: 6767 targetPort: 6767 type: ClusterIP