mirror of https://github.com/morpheus65535/bazarr
Merge branch 'shutdown-restart-refactor' of https://github.com/JaiZed/bazarr into shutdown-restart-refactor
This commit is contained in:
commit
bfa3a008aa
|
@ -44,12 +44,12 @@ class SystemLogs(Resource):
|
|||
if len(include) > 0:
|
||||
try:
|
||||
include_compiled = re.compile(include, flags)
|
||||
except:
|
||||
except Exception:
|
||||
include_compiled = None
|
||||
if len(exclude) > 0:
|
||||
try:
|
||||
exclude_compiled = re.compile(exclude, flags)
|
||||
except:
|
||||
except Exception:
|
||||
exclude_compiled = None
|
||||
elif ignore_case:
|
||||
include = include.casefold()
|
||||
|
|
|
@ -40,6 +40,7 @@ def validate_ip_address(ip_string):
|
|||
ONE_HUNDRED_YEARS_IN_MINUTES = 52560000
|
||||
ONE_HUNDRED_YEARS_IN_HOURS = 876000
|
||||
|
||||
|
||||
class Validator(OriginalValidator):
|
||||
# Give the ability to personalize messages sent by the original dynasync Validator class.
|
||||
default_messages = MappingProxyType(
|
||||
|
@ -105,7 +106,7 @@ validators = [
|
|||
Validator('general.subfolder', must_exist=True, default='current', is_type_of=str),
|
||||
Validator('general.subfolder_custom', must_exist=True, default='', is_type_of=str),
|
||||
Validator('general.upgrade_subs', must_exist=True, default=True, is_type_of=bool),
|
||||
Validator('general.upgrade_frequency', must_exist=True, default=12, is_type_of=int,
|
||||
Validator('general.upgrade_frequency', must_exist=True, default=12, is_type_of=int,
|
||||
is_in=[6, 12, 24, ONE_HUNDRED_YEARS_IN_HOURS]),
|
||||
Validator('general.days_to_upgrade_subs', must_exist=True, default=7, is_type_of=int, gte=0, lte=30),
|
||||
Validator('general.upgrade_manual', must_exist=True, default=True, is_type_of=bool),
|
||||
|
@ -487,25 +488,27 @@ def get_settings():
|
|||
settings_to_return[k].update({subk: subv})
|
||||
return settings_to_return
|
||||
|
||||
|
||||
def validate_log_regex():
|
||||
# handle bug in dynaconf that changes strings to numbers, so change them back to str
|
||||
if not isinstance(settings.log.include_filter, str):
|
||||
settings.log.include_filter = str(settings.log.include_filter)
|
||||
settings.log.include_filter = str(settings.log.include_filter)
|
||||
if not isinstance(settings.log.exclude_filter, str):
|
||||
settings.log.exclude_filter = str(settings.log.exclude_filter)
|
||||
settings.log.exclude_filter = str(settings.log.exclude_filter)
|
||||
|
||||
if (settings.log.use_regex):
|
||||
if settings.log.use_regex:
|
||||
# compile any regular expressions specified to see if they are valid
|
||||
# if invalid, tell the user which one
|
||||
try:
|
||||
re.compile(settings.log.include_filter)
|
||||
except:
|
||||
except Exception:
|
||||
raise ValidationError(f"Include filter: invalid regular expression: {settings.log.include_filter}")
|
||||
try:
|
||||
re.compile(settings.log.exclude_filter)
|
||||
except:
|
||||
except Exception:
|
||||
raise ValidationError(f"Exclude filter: invalid regular expression: {settings.log.exclude_filter}")
|
||||
|
||||
|
||||
def save_settings(settings_items):
|
||||
configure_debug = False
|
||||
configure_captcha = False
|
||||
|
@ -522,8 +525,7 @@ def save_settings(settings_items):
|
|||
undefined_subtitles_track_default_changed = False
|
||||
audio_tracks_parsing_changed = False
|
||||
reset_providers = False
|
||||
check_log_regex = False
|
||||
|
||||
|
||||
# Subzero Mods
|
||||
update_subzero = False
|
||||
subzero_mods = get_array_from(settings.general.subzero_mods)
|
||||
|
|
|
@ -323,7 +323,7 @@ def get_providers_auth():
|
|||
'response': settings.whisperai.response,
|
||||
'timeout': settings.whisperai.timeout,
|
||||
'ffmpeg_path': _FFMPEG_BINARY,
|
||||
'loglevel': settings.whisperai.loglevel,
|
||||
'loglevel': settings.whisperai.loglevel,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,10 +56,10 @@ class NoExceptionFormatter(logging.Formatter):
|
|||
def formatException(self, record):
|
||||
return ''
|
||||
|
||||
|
||||
|
||||
class UnwantedWaitressMessageFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
if settings.general.debug == True:
|
||||
if settings.general.debug is True:
|
||||
# no filtering in debug mode
|
||||
return True
|
||||
|
||||
|
@ -76,8 +76,8 @@ class UnwantedWaitressMessageFilter(logging.Filter):
|
|||
"Exception when servicing %r",
|
||||
[],
|
||||
]
|
||||
|
||||
wanted = True
|
||||
|
||||
wanted = True
|
||||
listLength = len(unwantedMessages)
|
||||
for i in range(0, listLength, 2):
|
||||
if record.msg == unwantedMessages[i]:
|
||||
|
@ -86,7 +86,7 @@ class UnwantedWaitressMessageFilter(logging.Filter):
|
|||
if len(unwantedMessages[i+1]) == 0 or str(exceptionTuple[1]) in unwantedMessages[i+1]:
|
||||
wanted = False
|
||||
break
|
||||
|
||||
|
||||
return wanted
|
||||
|
||||
|
||||
|
@ -163,7 +163,7 @@ def configure_logging(debug=False):
|
|||
logging.getLogger("ga4mp.ga4mp").setLevel(logging.ERROR)
|
||||
|
||||
logging.getLogger("waitress").setLevel(logging.ERROR)
|
||||
logging.getLogger("waitress").addFilter(UnwantedWaitressMessageFilter())
|
||||
logging.getLogger("waitress").addFilter(UnwantedWaitressMessageFilter())
|
||||
logging.getLogger("knowit").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("enzyme").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("guessit").setLevel(logging.WARNING)
|
||||
|
|
|
@ -8,7 +8,6 @@ from apscheduler.triggers.interval import IntervalTrigger
|
|||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.date import DateTrigger
|
||||
from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
|
||||
from apscheduler.jobstores.base import JobLookupError
|
||||
from datetime import datetime, timedelta
|
||||
from calendar import day_name
|
||||
from random import randrange
|
||||
|
@ -40,17 +39,20 @@ from dateutil.relativedelta import relativedelta
|
|||
|
||||
NO_INTERVAL = "None"
|
||||
NEVER_DATE = "Never"
|
||||
ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365
|
||||
ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365
|
||||
|
||||
|
||||
def a_long_time_from_now(job):
|
||||
# currently defined as more than a year from now
|
||||
delta = job.next_run_time - datetime.now(job.next_run_time.tzinfo)
|
||||
return delta.total_seconds() > ONE_YEAR_IN_SECONDS
|
||||
|
||||
|
||||
def in_a_century():
|
||||
century = datetime.now() + relativedelta(years=100)
|
||||
return century.year
|
||||
|
||||
|
||||
class Scheduler:
|
||||
|
||||
def __init__(self):
|
||||
|
@ -133,7 +135,6 @@ class Scheduler:
|
|||
return ", ".join(strings)
|
||||
|
||||
def get_time_from_cron(cron):
|
||||
year = str(cron[0])
|
||||
day = str(cron[4])
|
||||
hour = str(cron[5])
|
||||
|
||||
|
@ -183,8 +184,8 @@ class Scheduler:
|
|||
else:
|
||||
interval = get_time_from_cron(job.trigger.fields)
|
||||
task_list.append({'name': job.name, 'interval': interval,
|
||||
'next_run_in': next_run, 'next_run_time': next_run, 'job_id': job.id,
|
||||
'job_running': running})
|
||||
'next_run_in': next_run, 'next_run_time': next_run, 'job_id': job.id,
|
||||
'job_running': running})
|
||||
|
||||
return task_list
|
||||
|
||||
|
@ -218,9 +219,8 @@ class Scheduler:
|
|||
trigger = CronTrigger(day_of_week=settings.backup.day, hour=settings.backup.hour)
|
||||
elif backup == "Manually":
|
||||
trigger = CronTrigger(year=in_a_century())
|
||||
self.aps_scheduler.add_job(backup_to_zip, trigger,
|
||||
max_instances=1, coalesce=True, misfire_grace_time=15, id='backup',
|
||||
name='Backup Database and Configuration File', replace_existing=True)
|
||||
self.aps_scheduler.add_job(backup_to_zip, trigger, max_instances=1, coalesce=True, misfire_grace_time=15,
|
||||
id='backup', name='Backup Database and Configuration File', replace_existing=True)
|
||||
|
||||
def __sonarr_full_update_task(self):
|
||||
if settings.general.use_sonarr:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# coding=utf-8
|
||||
|
||||
import os
|
||||
import io
|
||||
|
||||
from threading import Thread
|
||||
|
||||
|
@ -20,6 +21,7 @@ from app.get_args import args # noqa E402
|
|||
from app.check_update import apply_update, check_releases, check_if_new_update # noqa E402
|
||||
from app.config import settings, configure_proxy_func, base_url # noqa E402
|
||||
from init import * # noqa E402
|
||||
import logging # noqa E402
|
||||
|
||||
# Install downloaded update
|
||||
if bazarr_version != '':
|
||||
|
|
|
@ -21,10 +21,13 @@ from .parser import movieParser
|
|||
bool_map = {"True": True, "False": False}
|
||||
|
||||
FEATURE_PREFIX = "SYNC_MOVIES "
|
||||
|
||||
|
||||
def trace(message):
|
||||
if settings.general.debug:
|
||||
logging.debug(FEATURE_PREFIX + message)
|
||||
|
||||
|
||||
def update_all_movies():
|
||||
movies_full_scan_subtitles()
|
||||
logging.info('BAZARR All existing movie subtitles indexed from disk.')
|
||||
|
@ -63,6 +66,7 @@ def get_movie_monitored_status(movie_id):
|
|||
else:
|
||||
return bool_map[existing_movie_monitored[0]]
|
||||
|
||||
|
||||
# Insert new movies in DB
|
||||
def add_movie(added_movie, send_event):
|
||||
try:
|
||||
|
@ -158,7 +162,7 @@ def update_movies(send_event=True):
|
|||
# Only movies that Radarr says have files downloaded will be kept up to date in the DB
|
||||
if movie['hasFile'] is True:
|
||||
if 'movieFile' in movie:
|
||||
if sync_monitored:
|
||||
if sync_monitored:
|
||||
if get_movie_monitored_status(movie['tmdbId']) != movie['monitored']:
|
||||
# monitored status is not the same as our DB
|
||||
trace(f"{i}: (Monitor Status Mismatch) {movie['title']}")
|
||||
|
@ -187,8 +191,8 @@ def update_movies(send_event=True):
|
|||
add_movie(parsed_movie, send_event)
|
||||
movies_added.append(parsed_movie['title'])
|
||||
else:
|
||||
trace(f"{i}: (Skipped File Missing) {movie['title']}")
|
||||
files_missing += 1
|
||||
trace(f"{i}: (Skipped File Missing) {movie['title']}")
|
||||
files_missing += 1
|
||||
|
||||
if send_event:
|
||||
hide_progress(id='movies_progress')
|
||||
|
@ -196,10 +200,12 @@ def update_movies(send_event=True):
|
|||
trace(f"Skipped {files_missing} file missing movies out of {i}")
|
||||
if sync_monitored:
|
||||
trace(f"Skipped {skipped_count} unmonitored movies out of {i}")
|
||||
trace(f"Processed {i - files_missing - skipped_count} movies out of {i} " +
|
||||
f"with {len(movies_added)} added, {len(movies_updated)} updated and {len(movies_deleted)} deleted")
|
||||
trace(f"Processed {i - files_missing - skipped_count} movies out of {i} "
|
||||
f"with {len(movies_added)} added, {len(movies_updated)} updated and "
|
||||
f"{len(movies_deleted)} deleted")
|
||||
else:
|
||||
trace(f"Processed {i - files_missing} movies out of {i} with {len(movies_added)} added and {len(movies_updated)} updated")
|
||||
trace(f"Processed {i - files_missing} movies out of {i} with {len(movies_added)} added and "
|
||||
f"{len(movies_updated)} updated")
|
||||
|
||||
logging.debug('BAZARR All movies synced from Radarr into database.')
|
||||
|
||||
|
|
|
@ -115,27 +115,27 @@ def movieParser(movie, action, tags_dict, movie_default_profile, audio_profiles)
|
|||
tags = [d['label'] for d in tags_dict if d['id'] in movie['tags']]
|
||||
|
||||
parsed_movie = {'radarrId': int(movie["id"]),
|
||||
'title': movie["title"],
|
||||
'path': os.path.join(movie["path"], movie['movieFile']['relativePath']),
|
||||
'tmdbId': str(movie["tmdbId"]),
|
||||
'poster': poster,
|
||||
'fanart': fanart,
|
||||
'audio_language': str(audio_language),
|
||||
'sceneName': sceneName,
|
||||
'monitored': str(bool(movie['monitored'])),
|
||||
'year': str(movie['year']),
|
||||
'sortTitle': movie['sortTitle'],
|
||||
'alternativeTitles': alternativeTitles,
|
||||
'format': format,
|
||||
'resolution': resolution,
|
||||
'video_codec': videoCodec,
|
||||
'audio_codec': audioCodec,
|
||||
'overview': overview,
|
||||
'imdbId': imdbId,
|
||||
'movie_file_id': int(movie['movieFile']['id']),
|
||||
'tags': str(tags),
|
||||
'file_size': movie['movieFile']['size']}
|
||||
|
||||
'title': movie["title"],
|
||||
'path': os.path.join(movie["path"], movie['movieFile']['relativePath']),
|
||||
'tmdbId': str(movie["tmdbId"]),
|
||||
'poster': poster,
|
||||
'fanart': fanart,
|
||||
'audio_language': str(audio_language),
|
||||
'sceneName': sceneName,
|
||||
'monitored': str(bool(movie['monitored'])),
|
||||
'year': str(movie['year']),
|
||||
'sortTitle': movie['sortTitle'],
|
||||
'alternativeTitles': alternativeTitles,
|
||||
'format': format,
|
||||
'resolution': resolution,
|
||||
'video_codec': videoCodec,
|
||||
'audio_codec': audioCodec,
|
||||
'overview': overview,
|
||||
'imdbId': imdbId,
|
||||
'movie_file_id': int(movie['movieFile']['id']),
|
||||
'tags': str(tags),
|
||||
'file_size': movie['movieFile']['size']}
|
||||
|
||||
if action == 'insert':
|
||||
parsed_movie['subtitles'] = '[]'
|
||||
parsed_movie['profileId'] = movie_default_profile
|
||||
|
|
|
@ -12,7 +12,7 @@ from utilities.path_mappings import path_mappings
|
|||
from subtitles.indexer.series import store_subtitles, series_full_scan_subtitles
|
||||
from subtitles.mass_download import episode_download_subtitles
|
||||
from app.event_handler import event_stream
|
||||
from sonarr.info import get_sonarr_info, url_sonarr
|
||||
from sonarr.info import get_sonarr_info
|
||||
|
||||
from .parser import episodeParser
|
||||
from .utils import get_episodes_from_sonarr_api, get_episodesFiles_from_sonarr_api
|
||||
|
@ -21,10 +21,13 @@ from .utils import get_episodes_from_sonarr_api, get_episodesFiles_from_sonarr_a
|
|||
bool_map = {"True": True, "False": False}
|
||||
|
||||
FEATURE_PREFIX = "SYNC_EPISODES "
|
||||
|
||||
|
||||
def trace(message):
|
||||
if settings.general.debug:
|
||||
logging.debug(FEATURE_PREFIX + message)
|
||||
|
||||
|
||||
def get_episodes_monitored_table(series_id):
|
||||
episodes_monitored = database.execute(
|
||||
select(TableEpisodes.episode_file_id, TableEpisodes.monitored)
|
||||
|
@ -32,7 +35,8 @@ def get_episodes_monitored_table(series_id):
|
|||
.all()
|
||||
episode_dict = dict((x, y) for x, y in episodes_monitored)
|
||||
return episode_dict
|
||||
|
||||
|
||||
|
||||
def update_all_episodes():
|
||||
series_full_scan_subtitles()
|
||||
logging.info('BAZARR All existing episode subtitles indexed from disk.')
|
||||
|
@ -74,7 +78,6 @@ def sync_episodes(series_id, send_event=True):
|
|||
if item:
|
||||
episode['episodeFile'] = item[0]
|
||||
|
||||
|
||||
sync_monitored = settings.sonarr.sync_only_monitored_series and settings.sonarr.sync_only_monitored_episodes
|
||||
if sync_monitored:
|
||||
episodes_monitored = get_episodes_monitored_table(series_id)
|
||||
|
@ -122,7 +125,7 @@ def sync_episodes(series_id, send_event=True):
|
|||
episodes_to_add.append(episodeParser(episode))
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
if sync_monitored:
|
||||
# try to avoid unnecessary database calls
|
||||
if settings.general.debug:
|
||||
|
@ -175,7 +178,6 @@ def sync_episodes(series_id, send_event=True):
|
|||
|
||||
def sync_one_episode(episode_id, defer_search=False):
|
||||
logging.debug(f'BAZARR syncing this specific episode from Sonarr: {episode_id}')
|
||||
url = url_sonarr()
|
||||
apikey_sonarr = settings.sonarr.apikey
|
||||
|
||||
# Check if there's a row in database for this episode ID
|
||||
|
|
|
@ -5,7 +5,6 @@ import logging
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from app.config import settings
|
||||
from sonarr.info import url_sonarr
|
||||
from subtitles.indexer.series import list_missing_subtitles
|
||||
from sonarr.rootfolder import check_sonarr_rootfolder
|
||||
from app.database import TableShows, TableLanguagesProfiles, database, insert, update, delete, select
|
||||
|
@ -20,10 +19,13 @@ from .utils import get_profile_list, get_tags, get_series_from_sonarr_api
|
|||
bool_map = {"True": True, "False": False}
|
||||
|
||||
FEATURE_PREFIX = "SYNC_SERIES "
|
||||
|
||||
|
||||
def trace(message):
|
||||
if settings.general.debug:
|
||||
logging.debug(FEATURE_PREFIX + message)
|
||||
|
||||
|
||||
def get_series_monitored_table():
|
||||
series_monitored = database.execute(
|
||||
select(TableShows.tvdbId, TableShows.monitored))\
|
||||
|
@ -31,6 +33,7 @@ def get_series_monitored_table():
|
|||
series_dict = dict((x, y) for x, y in series_monitored)
|
||||
return series_dict
|
||||
|
||||
|
||||
def update_series(send_event=True):
|
||||
check_sonarr_rootfolder()
|
||||
apikey_sonarr = settings.sonarr.apikey
|
||||
|
@ -74,7 +77,7 @@ def update_series(send_event=True):
|
|||
series_monitored = get_series_monitored_table()
|
||||
skipped_count = 0
|
||||
trace(f"Starting sync for {series_count} shows")
|
||||
|
||||
|
||||
for i, show in enumerate(series):
|
||||
if send_event:
|
||||
show_progress(id='series_progress',
|
||||
|
@ -152,7 +155,7 @@ def update_series(send_event=True):
|
|||
removed_series = list(set(current_shows_db) - set(current_shows_sonarr))
|
||||
|
||||
for series in removed_series:
|
||||
# try to avoid unnecessary database calls
|
||||
# try to avoid unnecessary database calls
|
||||
if settings.general.debug:
|
||||
series_title = database.execute(select(TableShows.title).where(TableShows.sonarrSeriesId == series)).first()[0]
|
||||
trace(f"Deleting {series_title}")
|
||||
|
|
|
@ -264,7 +264,10 @@ def list_missing_subtitles_movies(no=None, send_event=True):
|
|||
event_stream(type='badges')
|
||||
|
||||
|
||||
def movies_full_scan_subtitles(use_cache=settings.radarr.use_ffprobe_cache):
|
||||
def movies_full_scan_subtitles(use_cache=None):
|
||||
if use_cache is None:
|
||||
use_cache = settings.radarr.use_ffprobe_cache
|
||||
|
||||
movies = database.execute(
|
||||
select(TableMovies.path))\
|
||||
.all()
|
||||
|
|
|
@ -266,7 +266,10 @@ def list_missing_subtitles(no=None, epno=None, send_event=True):
|
|||
event_stream(type='badges')
|
||||
|
||||
|
||||
def series_full_scan_subtitles(use_cache=settings.sonarr.use_ffprobe_cache):
|
||||
def series_full_scan_subtitles(use_cache=None):
|
||||
if use_cache is None:
|
||||
use_cache = settings.sonarr.use_ffprobe_cache
|
||||
|
||||
episodes = database.execute(
|
||||
select(TableEpisodes.path))\
|
||||
.all()
|
||||
|
|
|
@ -26,8 +26,19 @@ def sync_subtitles(video_path, srt_path, srt_lang, forced, percent_score, sonarr
|
|||
|
||||
if not use_subsync_threshold or (use_subsync_threshold and percent_score < float(subsync_threshold)):
|
||||
subsync = SubSyncer()
|
||||
subsync.sync(video_path=video_path, srt_path=srt_path, srt_lang=srt_lang,
|
||||
sonarr_series_id=sonarr_series_id, sonarr_episode_id=sonarr_episode_id, radarr_id=radarr_id)
|
||||
sync_kwargs = {
|
||||
'video_path': video_path,
|
||||
'srt_path': srt_path,
|
||||
'srt_lang': srt_lang,
|
||||
'max_offset_seconds': str(settings.subsync.max_offset_seconds),
|
||||
'no_fix_framerate': settings.subsync.no_fix_framerate,
|
||||
'gss': settings.subsync.gss,
|
||||
'reference': None, # means choose automatically within video file
|
||||
'sonarr_series_id': sonarr_series_id,
|
||||
'sonarr_episode_id': sonarr_episode_id,
|
||||
'radarr_id': radarr_id,
|
||||
}
|
||||
subsync.sync(**sync_kwargs)
|
||||
del subsync
|
||||
gc.collect()
|
||||
return True
|
||||
|
|
|
@ -30,9 +30,9 @@ class SubSyncer:
|
|||
self.vad = 'subs_then_webrtc'
|
||||
self.log_dir_path = os.path.join(args.config_dir, 'log')
|
||||
|
||||
def sync(self, video_path, srt_path, srt_lang, sonarr_series_id=None, sonarr_episode_id=None, radarr_id=None,
|
||||
reference=None, max_offset_seconds=str(settings.subsync.max_offset_seconds),
|
||||
no_fix_framerate=settings.subsync.no_fix_framerate, gss=settings.subsync.gss):
|
||||
def sync(self, video_path, srt_path, srt_lang,
|
||||
max_offset_seconds, no_fix_framerate, gss, reference=None,
|
||||
sonarr_series_id=None, sonarr_episode_id=None, radarr_id=None):
|
||||
self.reference = video_path
|
||||
self.srtin = srt_path
|
||||
if self.srtin.casefold().endswith('.ass'):
|
||||
|
|
|
@ -16,7 +16,7 @@ def _escape(in_str):
|
|||
|
||||
|
||||
def pp_replace(pp_command, episode, subtitles, language, language_code2, language_code3, episode_language,
|
||||
episode_language_code2, episode_language_code3, score, subtitle_id, provider, uploader,
|
||||
episode_language_code2, episode_language_code3, score, subtitle_id, provider, uploader,
|
||||
release_info, series_id, episode_id):
|
||||
pp_command = re.sub(r'[\'"]?{{directory}}[\'"]?', _escape(os.path.dirname(episode)), pp_command)
|
||||
pp_command = re.sub(r'[\'"]?{{episode}}[\'"]?', _escape(episode), pp_command)
|
||||
|
|
|
@ -270,7 +270,7 @@ def parse_video_metadata(file, file_size, episode_file_id=None, movie_file_id=No
|
|||
if not os.path.exists(file):
|
||||
logging.error(f'Video file "{file}" cannot be found for analysis')
|
||||
return None
|
||||
|
||||
|
||||
# if we have ffprobe available
|
||||
if ffprobe_path:
|
||||
try:
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<base href="{{baseUrl}}" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/x-icon" href="./images/favicon.ico" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "Bazarr",
|
||||
"short_name": "Bazarr",
|
||||
"description": "Bazarr is a companion application to Sonarr and Radarr. It manages and downloads subtitles based on your requirements.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#be4bdb",
|
||||
"background_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/android-chrome-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/images/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/images/mstile-150x150.png",
|
||||
"sizes": "150x150",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Code, Table } from "@mantine/core";
|
||||
import { Code, Space, Table } from "@mantine/core";
|
||||
import { FunctionComponent } from "react";
|
||||
import {
|
||||
Check,
|
||||
|
@ -134,7 +134,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
settingKey="settings-general-subfolder"
|
||||
></Selector>
|
||||
<Message>
|
||||
Choose the folder you wish to store/read the subtitles
|
||||
Choose the folder you wish to store/read the subtitles.
|
||||
</Message>
|
||||
<CollapseBox
|
||||
settingKey="settings-general-subfolder"
|
||||
|
@ -171,7 +171,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
}}
|
||||
options={embeddedSubtitlesParserOption}
|
||||
></Selector>
|
||||
<Message>Embedded subtitles video parser</Message>
|
||||
<Message>Embedded Subtitles video parser.</Message>
|
||||
<Check
|
||||
label="Ignore Embedded PGS Subtitles"
|
||||
settingKey="settings-general-ignore_pgs_subs"
|
||||
|
@ -198,7 +198,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
settingKey="settings-general-embedded_subs_show_desired"
|
||||
></Check>
|
||||
<Message>
|
||||
Hide embedded subtitles for languages that are not currently
|
||||
Hide Embedded Subtitles for languages that are not currently
|
||||
desired.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
|
@ -218,7 +218,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
mb="lg"
|
||||
></Slider>
|
||||
<Message>
|
||||
Number of days to go back in history to upgrade subtitles
|
||||
Number of days to go back in history to upgrade subtitles.
|
||||
</Message>
|
||||
<Check
|
||||
label="Upgrade Manually Downloaded or Translated Subtitles"
|
||||
|
@ -232,22 +232,24 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
</Section>
|
||||
<Section header="Encoding">
|
||||
<Check
|
||||
label="Encode Subtitles To UTF8"
|
||||
label="Encode Subtitles To UTF-8"
|
||||
settingKey="settings-general-utf8_encode"
|
||||
></Check>
|
||||
<Message>
|
||||
Re-encode downloaded Subtitles to UTF8. Should be left enabled in most
|
||||
case.
|
||||
Re-encode downloaded subtitles to UTF-8. Should be left enabled in
|
||||
most cases.
|
||||
</Message>
|
||||
</Section>
|
||||
<Section header="Permissions">
|
||||
<Check
|
||||
label="Change file permission (chmod)"
|
||||
label="Change Subtitle File Permission (chmod)"
|
||||
settingKey="settings-general-chmod_enabled"
|
||||
></Check>
|
||||
<CollapseBox indent settingKey="settings-general-chmod_enabled">
|
||||
<Text placeholder="0777" settingKey="settings-general-chmod"></Text>
|
||||
<Message>Must be 4 digit octal</Message>
|
||||
<Message>
|
||||
Must be a 4 digit octal number. Only for non-Windows systems.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
</Section>
|
||||
<Section header="Performance / Optimization">
|
||||
|
@ -266,9 +268,9 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
options={adaptiveSearchingDelayOption}
|
||||
></Selector>
|
||||
<Message>
|
||||
The delay from the first search to adaptive searching applying.
|
||||
During this window Bazarr will continue to search for subtitles,
|
||||
even if they have been searched for recently.
|
||||
The delay from the first search to adaptive searching taking effect.
|
||||
During this time window Bazarr will continue to search for
|
||||
subtitles, even if they have been searched for recently.
|
||||
</Message>
|
||||
<Selector
|
||||
settingKey="settings-general-adaptive_searching_delta"
|
||||
|
@ -286,20 +288,20 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
settingKey="settings-general-multithreading"
|
||||
></Check>
|
||||
<Message>
|
||||
Search multiple providers at once (Don't choose this on low powered
|
||||
devices)
|
||||
Search multiple providers at once. (Don't choose this on low powered
|
||||
devices).
|
||||
</Message>
|
||||
<Check
|
||||
label="Skip video file hash calculation"
|
||||
settingKey="settings-general-skip_hashing"
|
||||
></Check>
|
||||
<Message>
|
||||
Skip video file hashing during search process to prevent sleeping hard
|
||||
disk drive from waking-up. On the other hand, this may decrease your
|
||||
search results scores.
|
||||
Skip video file hashing during search process to prevent a sleeping
|
||||
hard disk drive from waking up. However, this may decrease your search
|
||||
results scores.
|
||||
</Message>
|
||||
</Section>
|
||||
<Section header="Subzero Modifications">
|
||||
<Section header="Sub-Zero Modifications">
|
||||
<Check
|
||||
label="Hearing Impaired"
|
||||
settingOptions={{ onLoaded: SubzeroModification("remove_HI") }}
|
||||
|
@ -368,14 +370,14 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
playback devices.
|
||||
</Message>
|
||||
</Section>
|
||||
<Section header="Synchronizarion / Alignement">
|
||||
<Section header="Synchronization / Alignment">
|
||||
<Check
|
||||
label="Always use Audio Track as Reference for Syncing"
|
||||
settingKey="settings-subsync-force_audio"
|
||||
></Check>
|
||||
<Message>
|
||||
Use the audio track as reference for syncing, instead of using the
|
||||
embedded subtitle.
|
||||
Use the audio track as reference for syncing, instead of the embedded
|
||||
subtitle.
|
||||
</Message>
|
||||
<Check
|
||||
label="No Fix Framerate"
|
||||
|
@ -386,7 +388,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
between reference and subtitles.
|
||||
</Message>
|
||||
<Check
|
||||
label="Gold-Section Search"
|
||||
label="Golden-Section Search"
|
||||
settingKey="settings-subsync-gss"
|
||||
></Check>
|
||||
<Message>
|
||||
|
@ -394,7 +396,7 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
framerate ratio between video and subtitles.
|
||||
</Message>
|
||||
<Selector
|
||||
label="Max offset seconds"
|
||||
label="Max Offset Seconds"
|
||||
options={syncMaxOffsetSecondsOptions}
|
||||
settingKey="settings-subsync-max_offset_seconds"
|
||||
defaultValue={60}
|
||||
|
@ -407,8 +409,8 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
settingKey="settings-subsync-use_subsync"
|
||||
></Check>
|
||||
<Message>
|
||||
Enable the automatic subtitles synchronization after downloading a
|
||||
subtitles.
|
||||
Enable automatic subtitles synchronization after downloading a
|
||||
subtitle.
|
||||
</Message>
|
||||
<CollapseBox indent settingKey="settings-subsync-use_subsync">
|
||||
<MultiSelector
|
||||
|
@ -428,40 +430,73 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
label="Series Score Threshold"
|
||||
settingKey="settings-subsync-use_subsync_threshold"
|
||||
></Check>
|
||||
<CollapseBox settingKey="settings-subsync-use_subsync_threshold">
|
||||
<CollapseBox
|
||||
indent
|
||||
settingKey="settings-subsync-use_subsync_threshold"
|
||||
>
|
||||
<Slider settingKey="settings-subsync-subsync_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only series subtitles with scores <b>below</b> this value will be
|
||||
automatically synchronized.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Check
|
||||
label="Movies Score Threshold"
|
||||
settingKey="settings-subsync-use_subsync_movie_threshold"
|
||||
></Check>
|
||||
<CollapseBox settingKey="settings-subsync-use_subsync_movie_threshold">
|
||||
<CollapseBox
|
||||
indent
|
||||
settingKey="settings-subsync-use_subsync_movie_threshold"
|
||||
>
|
||||
<Slider settingKey="settings-subsync-subsync_movie_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only movie subtitles with scores <b>below</b> this value will be
|
||||
automatically synchronized.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
</CollapseBox>
|
||||
</Section>
|
||||
<Section header="Custom post-processing">
|
||||
<Section header="Custom Post-Processing">
|
||||
<Check
|
||||
settingKey="settings-general-use_postprocessing"
|
||||
label="Custom Post-Processing"
|
||||
></Check>
|
||||
<Message>
|
||||
Enable the post-processing execution after downloading a subtitles.
|
||||
Enable automatic execution of the post-processing command specified
|
||||
below after downloading a subtitle.
|
||||
</Message>
|
||||
<CollapseBox indent settingKey="settings-general-use_postprocessing">
|
||||
<Check
|
||||
settingKey="settings-general-use_postprocessing_threshold"
|
||||
label="Series Score Threshold"
|
||||
></Check>
|
||||
<CollapseBox settingKey="settings-general-use_postprocessing_threshold">
|
||||
<CollapseBox
|
||||
indent
|
||||
settingKey="settings-general-use_postprocessing_threshold"
|
||||
>
|
||||
<Slider settingKey="settings-general-postprocessing_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only series subtitles with scores <b>below</b> this value will be
|
||||
automatically post-processed.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Check
|
||||
settingKey="settings-general-use_postprocessing_threshold_movie"
|
||||
label="Movies Score Threshold"
|
||||
></Check>
|
||||
<CollapseBox settingKey="settings-general-use_postprocessing_threshold_movie">
|
||||
<CollapseBox
|
||||
indent
|
||||
settingKey="settings-general-use_postprocessing_threshold_movie"
|
||||
>
|
||||
<Slider settingKey="settings-general-postprocessing_threshold_movie"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only movie subtitles with scores <b>below</b> this value will be
|
||||
automatically post-processed.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Text
|
||||
label="Command"
|
||||
|
|
Loading…
Reference in New Issue