mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-21 05:43:16 -07:00
Merge pull request #1956 from clinton-hall/inconstant
A lot of refactoring.
This commit is contained in:
commit
c02205ed06
69 changed files with 1748 additions and 2275 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -16,3 +16,5 @@
|
||||||
*.egg-info
|
*.egg-info
|
||||||
/.vscode
|
/.vscode
|
||||||
/htmlcov/
|
/htmlcov/
|
||||||
|
/.tox/
|
||||||
|
/.mypy_cache/
|
||||||
|
|
|
@ -15,35 +15,6 @@ repos:
|
||||||
- id: fix-byte-order-marker
|
- id: fix-byte-order-marker
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- repo: https://github.com/pycqa/flake8
|
|
||||||
rev: '6.0.0'
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
name: Flake8 primary tests
|
|
||||||
additional_dependencies:
|
|
||||||
- flake8-bugbear
|
|
||||||
- flake8-commas
|
|
||||||
- flake8-comprehensions
|
|
||||||
- flake8-docstrings
|
|
||||||
- flake8-future-import
|
|
||||||
- id: flake8
|
|
||||||
name: Flake8 selective tests
|
|
||||||
args: [
|
|
||||||
# ** SELECTIVE TESTS **
|
|
||||||
# Run flake8 tests (with plugins) for specific optional codes defined below
|
|
||||||
# -- flake8 --
|
|
||||||
# E123 closing bracket does not match indentation of opening bracket’s line
|
|
||||||
# E226 missing whitespace around arithmetic operator
|
|
||||||
# E241 multiple spaces after ‘,’
|
|
||||||
# E242 tab after ‘,’
|
|
||||||
# E704 multiple statements on one line
|
|
||||||
# W504 line break after binary operator
|
|
||||||
# W505 doc line too long
|
|
||||||
# -- flake8-bugbear --
|
|
||||||
# B902 Invalid first argument used for instance method.
|
|
||||||
# B903 Data class should be immutable or use __slots__ to save memory.
|
|
||||||
'--select=B902,B903,E123,E226,E241,E242,E704,W504,W505'
|
|
||||||
]
|
|
||||||
- repo: https://github.com/asottile/add-trailing-comma
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
rev: v2.4.0
|
rev: v2.4.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -54,10 +25,21 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py37-plus]
|
||||||
#- repo: https://github.com/pre-commit/mirrors-autopep8
|
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||||
# rev: v2.0.0
|
rev: v2.0.1
|
||||||
# hooks:
|
hooks:
|
||||||
# - id: autopep8
|
- id: autopep8
|
||||||
|
- repo: https://github.com/pycqa/flake8
|
||||||
|
rev: '6.0.0'
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
name: flake8
|
||||||
|
additional_dependencies:
|
||||||
|
- flake8-bugbear
|
||||||
|
- flake8-commas
|
||||||
|
- flake8-comprehensions
|
||||||
|
- flake8-docstrings
|
||||||
|
- flake8-future-import
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
|
|
|
@ -74,7 +74,6 @@ disable=
|
||||||
C0415, # import-outside-toplevel
|
C0415, # import-outside-toplevel
|
||||||
|
|
||||||
R0204, # redifined-variable-type
|
R0204, # redifined-variable-type
|
||||||
R0401, # cyclic-import
|
|
||||||
R0801, # duplicate-code
|
R0801, # duplicate-code
|
||||||
R0903, # too-few-public-methods
|
R0903, # too-few-public-methods
|
||||||
R0902, # too-many-instance-attributes
|
R0902, # too-many-instance-attributes
|
||||||
|
|
|
@ -4,10 +4,11 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
from nzb2media import main_db
|
import nzb2media.databases
|
||||||
|
import nzb2media.torrent
|
||||||
from nzb2media.auto_process import comics, games, movies, music, tv, books
|
from nzb2media.auto_process import comics, games, movies, music, tv, books
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.plugins.plex import plex_update
|
from nzb2media.plex import plex_update
|
||||||
from nzb2media.user_scripts import external_script
|
from nzb2media.user_scripts import external_script
|
||||||
from nzb2media.utils.encoding import char_replace, convert_to_ascii
|
from nzb2media.utils.encoding import char_replace, convert_to_ascii
|
||||||
from nzb2media.utils.links import replace_links
|
from nzb2media.utils.links import replace_links
|
||||||
|
@ -24,7 +25,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
if client_agent != 'manual' and not nzb2media.DOWNLOAD_INFO:
|
if client_agent != 'manual' and not nzb2media.DOWNLOAD_INFO:
|
||||||
log.debug(f'Adding TORRENT download info for directory {input_directory} to database')
|
log.debug(f'Adding TORRENT download info for directory {input_directory} to database')
|
||||||
|
|
||||||
my_db = main_db.DBConnection()
|
my_db = nzb2media.databases.DBConnection()
|
||||||
|
|
||||||
input_directory1 = input_directory
|
input_directory1 = input_directory
|
||||||
input_name1 = input_name
|
input_name1 = input_name
|
||||||
|
@ -116,7 +117,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
|
|
||||||
log.info(f'Output directory set to: {output_destination}')
|
log.info(f'Output directory set to: {output_destination}')
|
||||||
|
|
||||||
if nzb2media.SAFE_MODE and output_destination == nzb2media.TORRENT_DEFAULT_DIRECTORY:
|
if nzb2media.SAFE_MODE and output_destination == nzb2media.torrent.DEFAULT_DIRECTORY:
|
||||||
log.error(f'The output directory:[{input_directory}] is the Download Directory. Edit outputDirectory in autoProcessMedia.cfg. Exiting')
|
log.error(f'The output directory:[{input_directory}] is the Download Directory. Edit outputDirectory in autoProcessMedia.cfg. Exiting')
|
||||||
return [-1, '']
|
return [-1, '']
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
|
|
||||||
if section_name in {'HeadPhones', 'Lidarr'}:
|
if section_name in {'HeadPhones', 'Lidarr'}:
|
||||||
# Make sure we preserve folder structure for HeadPhones.
|
# Make sure we preserve folder structure for HeadPhones.
|
||||||
nzb2media.NOFLATTEN.extend(input_category)
|
nzb2media.torrent.NO_FLATTEN.extend(input_category)
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
full_file_name = os.path.basename(input_file)
|
full_file_name = os.path.basename(input_file)
|
||||||
|
|
||||||
target_file = nzb2media.os.path.join(output_destination, full_file_name)
|
target_file = nzb2media.os.path.join(output_destination, full_file_name)
|
||||||
if input_category in nzb2media.NOFLATTEN:
|
if input_category in nzb2media.torrent.NO_FLATTEN:
|
||||||
if not os.path.basename(file_path) in output_destination:
|
if not os.path.basename(file_path) in output_destination:
|
||||||
target_file = nzb2media.os.path.join(
|
target_file = nzb2media.os.path.join(
|
||||||
nzb2media.os.path.join(output_destination, os.path.basename(file_path)), full_file_name,
|
nzb2media.os.path.join(output_destination, os.path.basename(file_path)), full_file_name,
|
||||||
|
@ -186,7 +187,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
log.debug(f'Checking for archives to extract in directory: {input_directory}')
|
log.debug(f'Checking for archives to extract in directory: {input_directory}')
|
||||||
nzb2media.extract_files(input_directory, output_destination, keep_archive)
|
nzb2media.extract_files(input_directory, output_destination, keep_archive)
|
||||||
|
|
||||||
if input_category not in nzb2media.NOFLATTEN:
|
if input_category not in nzb2media.torrent.NO_FLATTEN:
|
||||||
# don't flatten hp in case multi cd albums, and we need to copy this back later.
|
# don't flatten hp in case multi cd albums, and we need to copy this back later.
|
||||||
nzb2media.flatten(output_destination)
|
nzb2media.flatten(output_destination)
|
||||||
|
|
||||||
|
@ -211,8 +212,8 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
|
|
||||||
log.info(f'Calling {section_name}:{usercat} to post-process:{input_name}')
|
log.info(f'Calling {section_name}:{usercat} to post-process:{input_name}')
|
||||||
|
|
||||||
if nzb2media.TORRENT_CHMOD_DIRECTORY:
|
if nzb2media.torrent.CHMOD_DIRECTORY:
|
||||||
nzb2media.rchmod(output_destination, nzb2media.TORRENT_CHMOD_DIRECTORY)
|
nzb2media.rchmod(output_destination, nzb2media.torrent.CHMOD_DIRECTORY)
|
||||||
|
|
||||||
if section_name == 'UserScript':
|
if section_name == 'UserScript':
|
||||||
result = external_script(output_destination, input_name, input_category, section)
|
result = external_script(output_destination, input_name, input_category, section)
|
||||||
|
@ -247,7 +248,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
plex_update(input_category)
|
plex_update(input_category)
|
||||||
|
|
||||||
if result.status_code:
|
if result.status_code:
|
||||||
if not nzb2media.TORRENT_RESUME_ON_FAILURE:
|
if not nzb2media.torrent.RESUME_ON_FAILURE:
|
||||||
log.error(
|
log.error(
|
||||||
'A problem was reported in the autoProcess* script. '
|
'A problem was reported in the autoProcess* script. '
|
||||||
'Torrent won\'t resume seeding (settings)',
|
'Torrent won\'t resume seeding (settings)',
|
||||||
|
@ -265,7 +266,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp
|
||||||
nzb2media.update_download_info_status(input_name, 1)
|
nzb2media.update_download_info_status(input_name, 1)
|
||||||
|
|
||||||
# remove torrent
|
# remove torrent
|
||||||
if nzb2media.USE_LINK == 'move-sym' and not nzb2media.DELETE_ORIGINAL == 1:
|
if nzb2media.USE_LINK == 'move-sym' and nzb2media.DELETE_ORIGINAL != 1:
|
||||||
log.debug(f'Checking for sym-links to re-direct in: {input_directory}')
|
log.debug(f'Checking for sym-links to re-direct in: {input_directory}')
|
||||||
for dirpath, _, files in os.walk(input_directory):
|
for dirpath, _, files in os.walk(input_directory):
|
||||||
for file in files:
|
for file in files:
|
||||||
|
@ -286,7 +287,7 @@ def main(args):
|
||||||
nzb2media.initialize()
|
nzb2media.initialize()
|
||||||
|
|
||||||
# clientAgent for Torrents
|
# clientAgent for Torrents
|
||||||
client_agent = nzb2media.TORRENT_CLIENT_AGENT
|
client_agent = nzb2media.torrent.CLIENT_AGENT
|
||||||
|
|
||||||
log.info('#########################################################')
|
log.info('#########################################################')
|
||||||
log.info(f'## ..::[{os.path.basename(__file__)}]::.. ##')
|
log.info(f'## ..::[{os.path.basename(__file__)}]::.. ##')
|
||||||
|
@ -335,7 +336,7 @@ def main(args):
|
||||||
input_hash = ''
|
input_hash = ''
|
||||||
input_id = ''
|
input_id = ''
|
||||||
|
|
||||||
if client_agent.lower() not in nzb2media.TORRENT_CLIENTS:
|
if client_agent.lower() not in nzb2media.torrent.CLIENTS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
input_name = os.path.basename(dir_name)
|
input_name = os.path.basename(dir_name)
|
||||||
|
@ -352,7 +353,6 @@ def main(args):
|
||||||
log.info(f'The {args[0]} script completed successfully.')
|
log.info(f'The {args[0]} script completed successfully.')
|
||||||
else:
|
else:
|
||||||
log.error(f'A problem was reported in the {args[0]} script.')
|
log.error(f'A problem was reported in the {args[0]} script.')
|
||||||
del nzb2media.MYAPP
|
|
||||||
return result.status_code
|
return result.status_code
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ jobs:
|
||||||
vmImage: 'Ubuntu-latest'
|
vmImage: 'Ubuntu-latest'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Python37:
|
|
||||||
python.version: '3.7'
|
|
||||||
Python38:
|
Python38:
|
||||||
python.version: '3.8'
|
python.version: '3.8'
|
||||||
Python39:
|
Python39:
|
||||||
|
|
|
@ -1,35 +1,29 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import locale
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import typing
|
import typing
|
||||||
from subprocess import DEVNULL
|
from typing import Any
|
||||||
|
|
||||||
from nzb2media import databases
|
import setuptools_scm
|
||||||
from nzb2media import main_db
|
|
||||||
from nzb2media import tool
|
import nzb2media.fork.medusa
|
||||||
from nzb2media import version_check
|
import nzb2media.fork.sickbeard
|
||||||
|
import nzb2media.fork.sickchill
|
||||||
|
import nzb2media.fork.sickgear
|
||||||
from nzb2media.configuration import Config
|
from nzb2media.configuration import Config
|
||||||
from nzb2media.nzb.configuration import configure_nzbs
|
from nzb2media.transcoder import configure_transcoder
|
||||||
from nzb2media.plugins.plex import configure_plex
|
|
||||||
from nzb2media.torrent.configuration import configure_torrent_class
|
|
||||||
from nzb2media.torrent.configuration import configure_torrents
|
|
||||||
from nzb2media.utils.files import make_dir
|
|
||||||
from nzb2media.utils.network import wake_up
|
from nzb2media.utils.network import wake_up
|
||||||
from nzb2media.utils.processes import RunningProcess
|
|
||||||
from nzb2media.utils.processes import restart
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
__version__ = setuptools_scm.get_version()
|
||||||
|
|
||||||
|
|
||||||
def module_path(module=__file__):
|
def module_path(module=__file__):
|
||||||
try:
|
try:
|
||||||
|
@ -39,107 +33,35 @@ def module_path(module=__file__):
|
||||||
return path.parent.absolute()
|
return path.parent.absolute()
|
||||||
|
|
||||||
|
|
||||||
SOURCE_ROOT = module_path()
|
APP_ROOT = module_path().parent
|
||||||
APP_ROOT = SOURCE_ROOT.parent
|
|
||||||
# init preliminaries
|
# init preliminaries
|
||||||
SYS_ARGV = sys.argv[1:]
|
APP_NAME: str = pathlib.Path(sys.argv[0]).name
|
||||||
APP_FILENAME = pathlib.Path(sys.argv[0])
|
|
||||||
APP_NAME: str = APP_FILENAME.name
|
|
||||||
LOG_DIR: pathlib.Path = APP_ROOT / 'logs'
|
LOG_DIR: pathlib.Path = APP_ROOT / 'logs'
|
||||||
LOG_FILE: pathlib.Path = LOG_DIR / 'nzbtomedia.log'
|
LOG_FILE: pathlib.Path = LOG_DIR / 'nzbtomedia.log'
|
||||||
PID_FILE = LOG_DIR / 'nzbtomedia.pid'
|
PID_FILE = LOG_DIR / 'nzbtomedia.pid'
|
||||||
CONFIG_FILE = APP_ROOT / 'autoProcessMedia.cfg'
|
CONFIG_FILE = APP_ROOT / 'autoProcessMedia.cfg'
|
||||||
CONFIG_SPEC_FILE = APP_ROOT / 'autoProcessMedia.cfg.spec'
|
CONFIG_SPEC_FILE = APP_ROOT / 'autoProcessMedia.cfg.spec'
|
||||||
CONFIG_MOVIE_FILE = APP_ROOT / 'autoProcessMovie.cfg'
|
|
||||||
CONFIG_TV_FILE = APP_ROOT / 'autoProcessTv.cfg'
|
|
||||||
TEST_FILE = APP_ROOT / 'tests' / 'test.mp4'
|
TEST_FILE = APP_ROOT / 'tests' / 'test.mp4'
|
||||||
MYAPP = None
|
|
||||||
__version__ = '12.1.11'
|
FORKS: typing.Mapping[str, typing.Mapping[str, Any]] = {
|
||||||
# Client Agents
|
'default': {'dir': None},
|
||||||
NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual']
|
'failed': {'dirName': None, 'failed': None},
|
||||||
TORRENT_CLIENTS = ['transmission', 'deluge', 'utorrent', 'rtorrent', 'qbittorrent', 'other', 'manual']
|
'failed-torrent': {'dir': None, 'failed': None, 'process_method': None},
|
||||||
# sickbeard fork/branch constants
|
**nzb2media.fork.sickbeard.CONFIG,
|
||||||
FORK_DEFAULT = 'default'
|
**nzb2media.fork.sickchill.CONFIG,
|
||||||
FORK_FAILED = 'failed'
|
**nzb2media.fork.sickgear.CONFIG,
|
||||||
FORK_FAILED_TORRENT = 'failed-torrent'
|
**nzb2media.fork.medusa.CONFIG,
|
||||||
FORK_SICKCHILL = 'SickChill'
|
}
|
||||||
FORK_SICKCHILL_API = 'SickChill-api'
|
|
||||||
FORK_SICKBEARD_API = 'SickBeard-api'
|
|
||||||
FORK_MEDUSA = 'Medusa'
|
|
||||||
FORK_MEDUSA_API = 'Medusa-api'
|
|
||||||
FORK_MEDUSA_APIV2 = 'Medusa-apiv2'
|
|
||||||
FORK_SICKGEAR = 'SickGear'
|
|
||||||
FORK_SICKGEAR_API = 'SickGear-api'
|
|
||||||
FORK_STHENO = 'Stheno'
|
|
||||||
FORKS: typing.Mapping[str, typing.Mapping] = {FORK_DEFAULT: {'dir': None}, FORK_FAILED: {'dirName': None, 'failed': None}, FORK_FAILED_TORRENT: {'dir': None, 'failed': None, 'process_method': None}, FORK_SICKCHILL: {'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'force_next': None}, FORK_SICKCHILL_API: {'path': None, 'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'force_replace': None, 'return_data': None, 'type': None, 'delete': None, 'force_next': None, 'is_priority': None, 'cmd': 'postprocess'}, FORK_SICKBEARD_API: {'path': None, 'failed': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'delete': None, 'force_next': None, 'cmd': 'postprocess'}, FORK_MEDUSA: {'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'ignore_subs': None}, FORK_MEDUSA_API: {'path': None, 'failed': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'delete_files': None, 'is_priority': None, 'cmd': 'postprocess'}, FORK_MEDUSA_APIV2: {'proc_dir': None, 'resource': None, 'failed': None, 'process_method': None, 'force': None, 'type': None, 'delete_on': None, 'is_priority': None}, FORK_SICKGEAR: {'dir': None, 'failed': None, 'process_method': None, 'force': None}, FORK_SICKGEAR_API: {'path': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'is_priority': None, 'failed': None, 'cmd': 'sg.postprocess'}, FORK_STHENO: {'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'ignore_subs': None}}
|
|
||||||
ALL_FORKS = {k: None for k in set(itertools.chain.from_iterable([FORKS[x].keys() for x in FORKS.keys()]))}
|
ALL_FORKS = {k: None for k in set(itertools.chain.from_iterable([FORKS[x].keys() for x in FORKS.keys()]))}
|
||||||
# SiCKRAGE OAuth2
|
|
||||||
SICKRAGE_OAUTH_CLIENT_ID = 'nzbtomedia'
|
|
||||||
SICKRAGE_OAUTH_TOKEN_URL = 'https://auth.sickrage.ca/realms/sickrage/protocol/openid-connect/token'
|
|
||||||
# NZBGet Exit Codes
|
|
||||||
NZBGET_POSTPROCESS_PAR_CHECK = 92
|
|
||||||
NZBGET_POSTPROCESS_SUCCESS = 93
|
|
||||||
NZBGET_POSTPROCESS_ERROR = 94
|
|
||||||
NZBGET_POSTPROCESS_NONE = 95
|
|
||||||
CFG = None
|
CFG = None
|
||||||
LOG_DEBUG = None
|
|
||||||
LOG_DB = None
|
|
||||||
LOG_ENV = None
|
|
||||||
LOG_GIT = None
|
|
||||||
SYS_ENCODING = None
|
|
||||||
FAILED = False
|
FAILED = False
|
||||||
AUTO_UPDATE = None
|
|
||||||
NZBTOMEDIA_VERSION = __version__
|
|
||||||
NEWEST_VERSION = None
|
|
||||||
NEWEST_VERSION_STRING = None
|
|
||||||
VERSION_NOTIFY = None
|
|
||||||
GIT_PATH = None
|
|
||||||
GIT_USER = None
|
|
||||||
GIT_BRANCH = None
|
|
||||||
GIT_REPO = None
|
|
||||||
FORCE_CLEAN = None
|
FORCE_CLEAN = None
|
||||||
SAFE_MODE = None
|
SAFE_MODE = None
|
||||||
NOEXTRACTFAILED = None
|
NOEXTRACTFAILED = None
|
||||||
NZB_CLIENT_AGENT = None
|
|
||||||
SABNZBD_HOST = ''
|
|
||||||
SABNZBD_PORT = None
|
|
||||||
SABNZBD_APIKEY = None
|
|
||||||
NZB_DEFAULT_DIRECTORY = None
|
|
||||||
TORRENT_CLIENT_AGENT = None
|
|
||||||
TORRENT_CLASS = None
|
|
||||||
USE_LINK = None
|
USE_LINK = None
|
||||||
OUTPUT_DIRECTORY = None
|
OUTPUT_DIRECTORY = None
|
||||||
NOFLATTEN: list[str] = []
|
|
||||||
DELETE_ORIGINAL = None
|
DELETE_ORIGINAL = None
|
||||||
TORRENT_CHMOD_DIRECTORY = None
|
|
||||||
TORRENT_DEFAULT_DIRECTORY = None
|
|
||||||
TORRENT_RESUME = None
|
|
||||||
TORRENT_RESUME_ON_FAILURE = None
|
|
||||||
REMOTE_PATHS = []
|
REMOTE_PATHS = []
|
||||||
UTORRENT_WEB_UI = None
|
|
||||||
UTORRENT_USER = None
|
|
||||||
UTORRENT_PASSWORD = None
|
|
||||||
TRANSMISSION_HOST = None
|
|
||||||
TRANSMISSION_PORT = None
|
|
||||||
TRANSMISSION_USER = None
|
|
||||||
TRANSMISSION_PASSWORD = None
|
|
||||||
SYNO_HOST = None
|
|
||||||
SYNO_PORT = None
|
|
||||||
SYNO_USER = None
|
|
||||||
SYNO_PASSWORD = None
|
|
||||||
DELUGE_HOST = None
|
|
||||||
DELUGE_PORT = None
|
|
||||||
DELUGE_USER = None
|
|
||||||
DELUGE_PASSWORD = None
|
|
||||||
QBITTORRENT_HOST = None
|
|
||||||
QBITTORRENT_PORT = None
|
|
||||||
QBITTORRENT_USER = None
|
|
||||||
QBITTORRENT_PASSWORD = None
|
|
||||||
PLEX_SSL = None
|
|
||||||
PLEX_HOST = None
|
|
||||||
PLEX_PORT = None
|
|
||||||
PLEX_TOKEN = None
|
|
||||||
PLEX_SECTION: list[str] = []
|
|
||||||
EXT_CONTAINER: list[str] = []
|
EXT_CONTAINER: list[str] = []
|
||||||
COMPRESSED_CONTAINER = []
|
COMPRESSED_CONTAINER = []
|
||||||
MEDIA_CONTAINER = []
|
MEDIA_CONTAINER = []
|
||||||
|
@ -148,104 +70,15 @@ META_CONTAINER = []
|
||||||
SECTIONS: list[str] = []
|
SECTIONS: list[str] = []
|
||||||
CATEGORIES: list[str] = []
|
CATEGORIES: list[str] = []
|
||||||
FORK_SET: list[str] = []
|
FORK_SET: list[str] = []
|
||||||
MOUNTED = None
|
|
||||||
GETSUBS = False
|
|
||||||
TRANSCODE = None
|
|
||||||
CONCAT = None
|
|
||||||
FFMPEG_PATH: pathlib.Path | None = None
|
|
||||||
SYS_PATH = None
|
SYS_PATH = None
|
||||||
DUPLICATE = None
|
|
||||||
IGNOREEXTENSIONS = []
|
|
||||||
VEXTENSION = None
|
|
||||||
OUTPUTVIDEOPATH = None
|
|
||||||
PROCESSOUTPUT = False
|
|
||||||
GENERALOPTS = []
|
|
||||||
OTHEROPTS = []
|
|
||||||
ALANGUAGE = None
|
|
||||||
AINCLUDE = False
|
|
||||||
SLANGUAGES = []
|
|
||||||
SINCLUDE = False
|
|
||||||
SUBSDIR = None
|
|
||||||
ALLOWSUBS = False
|
|
||||||
SEXTRACT = False
|
|
||||||
SEMBED = False
|
|
||||||
BURN = False
|
|
||||||
DEFAULTS = None
|
|
||||||
VCODEC = None
|
|
||||||
VCODEC_ALLOW = []
|
|
||||||
VPRESET = None
|
|
||||||
VFRAMERATE = None
|
|
||||||
VBITRATE = None
|
|
||||||
VLEVEL = None
|
|
||||||
VCRF = None
|
|
||||||
VRESOLUTION = None
|
|
||||||
ACODEC = None
|
|
||||||
ACODEC_ALLOW = []
|
|
||||||
ACHANNELS = None
|
|
||||||
ABITRATE = None
|
|
||||||
ACODEC2 = None
|
|
||||||
ACODEC2_ALLOW = []
|
|
||||||
ACHANNELS2 = None
|
|
||||||
ABITRATE2 = None
|
|
||||||
ACODEC3 = None
|
|
||||||
ACODEC3_ALLOW = []
|
|
||||||
ACHANNELS3 = None
|
|
||||||
ABITRATE3 = None
|
|
||||||
SCODEC = None
|
|
||||||
OUTPUTFASTSTART = None
|
|
||||||
OUTPUTQUALITYPERCENT = None
|
|
||||||
FFMPEG: pathlib.Path | None = None
|
|
||||||
SEVENZIP: pathlib.Path | None = None
|
|
||||||
SHOWEXTRACT = 0
|
|
||||||
PAR2CMD: pathlib.Path | None = None
|
|
||||||
FFPROBE: pathlib.Path | None = None
|
|
||||||
CHECK_MEDIA = None
|
CHECK_MEDIA = None
|
||||||
REQUIRE_LAN = None
|
REQUIRE_LAN = None
|
||||||
NICENESS = []
|
|
||||||
HWACCEL = False
|
|
||||||
PASSWORDS_FILE = None
|
PASSWORDS_FILE = None
|
||||||
DOWNLOAD_INFO = None
|
DOWNLOAD_INFO = None
|
||||||
GROUPS = None
|
GROUPS = None
|
||||||
USER_SCRIPT_MEDIAEXTENSIONS = None
|
|
||||||
USER_SCRIPT = None
|
|
||||||
USER_SCRIPT_PARAM = None
|
|
||||||
USER_SCRIPT_SUCCESSCODES = None
|
|
||||||
USER_SCRIPT_CLEAN = None
|
|
||||||
USER_DELAY = None
|
|
||||||
USER_SCRIPT_RUNONCE = None
|
|
||||||
__INITIALIZED__ = False
|
__INITIALIZED__ = False
|
||||||
|
|
||||||
|
|
||||||
def configure_logging():
|
|
||||||
global LOG_FILE
|
|
||||||
global LOG_DIR
|
|
||||||
if 'NTM_LOGFILE' in os.environ:
|
|
||||||
LOG_FILE = os.environ['NTM_LOGFILE']
|
|
||||||
LOG_DIR = os.path.split(LOG_FILE)[0]
|
|
||||||
if not make_dir(LOG_DIR):
|
|
||||||
print('No log folder, logging to screen only')
|
|
||||||
|
|
||||||
|
|
||||||
def configure_process():
|
|
||||||
global MYAPP
|
|
||||||
MYAPP = RunningProcess()
|
|
||||||
while MYAPP.alreadyrunning():
|
|
||||||
print('Waiting for existing session to end')
|
|
||||||
time.sleep(30)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_locale():
|
|
||||||
global SYS_ENCODING
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
SYS_ENCODING = locale.getpreferredencoding()
|
|
||||||
except (locale.Error, OSError):
|
|
||||||
pass
|
|
||||||
# For OSes that are poorly configured I'll just randomly force UTF-8
|
|
||||||
if not SYS_ENCODING or SYS_ENCODING in {'ANSI_X3.4-1968', 'US-ASCII', 'ASCII'}:
|
|
||||||
SYS_ENCODING = 'UTF-8'
|
|
||||||
|
|
||||||
|
|
||||||
def configure_migration():
|
def configure_migration():
|
||||||
global CONFIG_FILE
|
global CONFIG_FILE
|
||||||
global CFG
|
global CFG
|
||||||
|
@ -264,42 +97,14 @@ def configure_migration():
|
||||||
CFG = Config(None)
|
CFG = Config(None)
|
||||||
|
|
||||||
|
|
||||||
def configure_logging_part_2():
|
|
||||||
global LOG_DB
|
|
||||||
global LOG_DEBUG
|
|
||||||
global LOG_ENV
|
|
||||||
global LOG_GIT
|
|
||||||
# Enable/Disable DEBUG Logging
|
|
||||||
LOG_DB = int(CFG['General']['log_db'])
|
|
||||||
LOG_DEBUG = int(CFG['General']['log_debug'])
|
|
||||||
LOG_ENV = int(CFG['General']['log_env'])
|
|
||||||
LOG_GIT = int(CFG['General']['log_git'])
|
|
||||||
if LOG_ENV:
|
|
||||||
for item in os.environ:
|
|
||||||
log.info(f'{item}: {os.environ[item]}')
|
|
||||||
|
|
||||||
|
|
||||||
def configure_general():
|
def configure_general():
|
||||||
global VERSION_NOTIFY
|
|
||||||
global GIT_REPO
|
|
||||||
global GIT_PATH
|
|
||||||
global GIT_USER
|
|
||||||
global GIT_BRANCH
|
|
||||||
global FORCE_CLEAN
|
global FORCE_CLEAN
|
||||||
global FFMPEG_PATH
|
|
||||||
global SYS_PATH
|
global SYS_PATH
|
||||||
global CHECK_MEDIA
|
global CHECK_MEDIA
|
||||||
global REQUIRE_LAN
|
global REQUIRE_LAN
|
||||||
global SAFE_MODE
|
global SAFE_MODE
|
||||||
global NOEXTRACTFAILED
|
global NOEXTRACTFAILED
|
||||||
# Set Version and GIT variables
|
|
||||||
VERSION_NOTIFY = int(CFG['General']['version_notify'])
|
|
||||||
GIT_REPO = 'nzbToMedia'
|
|
||||||
GIT_PATH = CFG['General']['git_path']
|
|
||||||
GIT_USER = CFG['General']['git_user'] or 'clinton-hall'
|
|
||||||
GIT_BRANCH = CFG['General']['git_branch'] or 'master'
|
|
||||||
FORCE_CLEAN = int(CFG['General']['force_clean'])
|
FORCE_CLEAN = int(CFG['General']['force_clean'])
|
||||||
FFMPEG_PATH = pathlib.Path(CFG['General']['ffmpeg_path'])
|
|
||||||
SYS_PATH = CFG['General']['sys_path']
|
SYS_PATH = CFG['General']['sys_path']
|
||||||
CHECK_MEDIA = int(CFG['General']['check_media'])
|
CHECK_MEDIA = int(CFG['General']['check_media'])
|
||||||
REQUIRE_LAN = None if not CFG['General']['require_lan'] else CFG['General']['require_lan'].split(',')
|
REQUIRE_LAN = None if not CFG['General']['require_lan'] else CFG['General']['require_lan'].split(',')
|
||||||
|
@ -307,27 +112,6 @@ def configure_general():
|
||||||
NOEXTRACTFAILED = int(CFG['General']['no_extract_failed'])
|
NOEXTRACTFAILED = int(CFG['General']['no_extract_failed'])
|
||||||
|
|
||||||
|
|
||||||
def configure_updates():
|
|
||||||
global AUTO_UPDATE
|
|
||||||
global MYAPP
|
|
||||||
AUTO_UPDATE = int(CFG['General']['auto_update'])
|
|
||||||
version_checker = version_check.CheckVersion()
|
|
||||||
# Check for updates via GitHUB
|
|
||||||
if version_checker.check_for_new_version() and AUTO_UPDATE:
|
|
||||||
log.info('Auto-Updating nzbToMedia, Please wait ...')
|
|
||||||
if version_checker.update():
|
|
||||||
# restart nzbToMedia
|
|
||||||
try:
|
|
||||||
del MYAPP
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
restart()
|
|
||||||
else:
|
|
||||||
log.error('Update failed, not restarting. Check your log for more information.')
|
|
||||||
# Set Current Version
|
|
||||||
log.info(f'nzbToMedia Version:{NZBTOMEDIA_VERSION} Branch:{GIT_BRANCH} ({platform.system()} {platform.release()})')
|
|
||||||
|
|
||||||
|
|
||||||
def configure_wake_on_lan():
|
def configure_wake_on_lan():
|
||||||
if int(CFG['WakeOnLan']['wake']):
|
if int(CFG['WakeOnLan']['wake']):
|
||||||
wake_up()
|
wake_up()
|
||||||
|
@ -355,38 +139,6 @@ def configure_remote_paths():
|
||||||
REMOTE_PATHS = [(local.strip(), remote.strip()) for local, remote in REMOTE_PATHS]
|
REMOTE_PATHS = [(local.strip(), remote.strip()) for local, remote in REMOTE_PATHS]
|
||||||
|
|
||||||
|
|
||||||
def configure_niceness():
|
|
||||||
global NICENESS
|
|
||||||
try:
|
|
||||||
with subprocess.Popen(['nice'], stdout=DEVNULL, stderr=DEVNULL) as proc:
|
|
||||||
proc.communicate()
|
|
||||||
niceness = CFG['Posix']['niceness']
|
|
||||||
if len(niceness.split(',')) > 1: # Allow passing of absolute command, not just value.
|
|
||||||
NICENESS.extend(niceness.split(','))
|
|
||||||
else:
|
|
||||||
NICENESS.extend(['nice', f'-n{int(niceness)}'])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
with subprocess.Popen(['ionice'], stdout=DEVNULL, stderr=DEVNULL) as proc:
|
|
||||||
proc.communicate()
|
|
||||||
try:
|
|
||||||
ionice = CFG['Posix']['ionice_class']
|
|
||||||
NICENESS.extend(['ionice', f'-c{int(ionice)}'])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if 'ionice' in NICENESS:
|
|
||||||
ionice = CFG['Posix']['ionice_classdata']
|
|
||||||
NICENESS.extend([f'-n{int(ionice)}'])
|
|
||||||
else:
|
|
||||||
NICENESS.extend(['ionice', f'-n{int(ionice)}'])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def configure_containers():
|
def configure_containers():
|
||||||
global COMPRESSED_CONTAINER
|
global COMPRESSED_CONTAINER
|
||||||
global MEDIA_CONTAINER
|
global MEDIA_CONTAINER
|
||||||
|
@ -407,234 +159,6 @@ def configure_containers():
|
||||||
META_CONTAINER = META_CONTAINER.split(',')
|
META_CONTAINER = META_CONTAINER.split(',')
|
||||||
|
|
||||||
|
|
||||||
def configure_transcoder():
|
|
||||||
global MOUNTED
|
|
||||||
global GETSUBS
|
|
||||||
global TRANSCODE
|
|
||||||
global DUPLICATE
|
|
||||||
global CONCAT
|
|
||||||
global IGNOREEXTENSIONS
|
|
||||||
global OUTPUTFASTSTART
|
|
||||||
global GENERALOPTS
|
|
||||||
global OTHEROPTS
|
|
||||||
global OUTPUTQUALITYPERCENT
|
|
||||||
global OUTPUTVIDEOPATH
|
|
||||||
global PROCESSOUTPUT
|
|
||||||
global ALANGUAGE
|
|
||||||
global AINCLUDE
|
|
||||||
global SLANGUAGES
|
|
||||||
global SINCLUDE
|
|
||||||
global SEXTRACT
|
|
||||||
global SEMBED
|
|
||||||
global SUBSDIR
|
|
||||||
global VEXTENSION
|
|
||||||
global VCODEC
|
|
||||||
global VPRESET
|
|
||||||
global VFRAMERATE
|
|
||||||
global VBITRATE
|
|
||||||
global VRESOLUTION
|
|
||||||
global VCRF
|
|
||||||
global VLEVEL
|
|
||||||
global VCODEC_ALLOW
|
|
||||||
global ACODEC
|
|
||||||
global ACODEC_ALLOW
|
|
||||||
global ACHANNELS
|
|
||||||
global ABITRATE
|
|
||||||
global ACODEC2
|
|
||||||
global ACODEC2_ALLOW
|
|
||||||
global ACHANNELS2
|
|
||||||
global ABITRATE2
|
|
||||||
global ACODEC3
|
|
||||||
global ACODEC3_ALLOW
|
|
||||||
global ACHANNELS3
|
|
||||||
global ABITRATE3
|
|
||||||
global SCODEC
|
|
||||||
global BURN
|
|
||||||
global HWACCEL
|
|
||||||
global ALLOWSUBS
|
|
||||||
global DEFAULTS
|
|
||||||
MOUNTED = None
|
|
||||||
GETSUBS = int(CFG['Transcoder']['getSubs'])
|
|
||||||
TRANSCODE = int(CFG['Transcoder']['transcode'])
|
|
||||||
DUPLICATE = int(CFG['Transcoder']['duplicate'])
|
|
||||||
CONCAT = int(CFG['Transcoder']['concat'])
|
|
||||||
IGNOREEXTENSIONS = CFG['Transcoder']['ignoreExtensions']
|
|
||||||
if isinstance(IGNOREEXTENSIONS, str):
|
|
||||||
IGNOREEXTENSIONS = IGNOREEXTENSIONS.split(',')
|
|
||||||
OUTPUTFASTSTART = int(CFG['Transcoder']['outputFastStart'])
|
|
||||||
GENERALOPTS = CFG['Transcoder']['generalOptions']
|
|
||||||
if isinstance(GENERALOPTS, str):
|
|
||||||
GENERALOPTS = GENERALOPTS.split(',')
|
|
||||||
if GENERALOPTS == ['']:
|
|
||||||
GENERALOPTS = []
|
|
||||||
if '-fflags' not in GENERALOPTS:
|
|
||||||
GENERALOPTS.append('-fflags')
|
|
||||||
if '+genpts' not in GENERALOPTS:
|
|
||||||
GENERALOPTS.append('+genpts')
|
|
||||||
OTHEROPTS = CFG['Transcoder']['otherOptions']
|
|
||||||
if isinstance(OTHEROPTS, str):
|
|
||||||
OTHEROPTS = OTHEROPTS.split(',')
|
|
||||||
if OTHEROPTS == ['']:
|
|
||||||
OTHEROPTS = []
|
|
||||||
try:
|
|
||||||
OUTPUTQUALITYPERCENT = int(CFG['Transcoder']['outputQualityPercent'])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
OUTPUTVIDEOPATH = CFG['Transcoder']['outputVideoPath']
|
|
||||||
PROCESSOUTPUT = int(CFG['Transcoder']['processOutput'])
|
|
||||||
ALANGUAGE = CFG['Transcoder']['audioLanguage']
|
|
||||||
AINCLUDE = int(CFG['Transcoder']['allAudioLanguages'])
|
|
||||||
SLANGUAGES = CFG['Transcoder']['subLanguages']
|
|
||||||
if isinstance(SLANGUAGES, str):
|
|
||||||
SLANGUAGES = SLANGUAGES.split(',')
|
|
||||||
if SLANGUAGES == ['']:
|
|
||||||
SLANGUAGES = []
|
|
||||||
SINCLUDE = int(CFG['Transcoder']['allSubLanguages'])
|
|
||||||
SEXTRACT = int(CFG['Transcoder']['extractSubs'])
|
|
||||||
SEMBED = int(CFG['Transcoder']['embedSubs'])
|
|
||||||
SUBSDIR = CFG['Transcoder']['externalSubDir']
|
|
||||||
VEXTENSION = CFG['Transcoder']['outputVideoExtension'].strip()
|
|
||||||
VCODEC = CFG['Transcoder']['outputVideoCodec'].strip()
|
|
||||||
VCODEC_ALLOW = CFG['Transcoder']['VideoCodecAllow'].strip()
|
|
||||||
if isinstance(VCODEC_ALLOW, str):
|
|
||||||
VCODEC_ALLOW = VCODEC_ALLOW.split(',')
|
|
||||||
if VCODEC_ALLOW == ['']:
|
|
||||||
VCODEC_ALLOW = []
|
|
||||||
VPRESET = CFG['Transcoder']['outputVideoPreset'].strip()
|
|
||||||
try:
|
|
||||||
VFRAMERATE = float(CFG['Transcoder']['outputVideoFramerate'].strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
VCRF = int(CFG['Transcoder']['outputVideoCRF'].strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
VLEVEL = CFG['Transcoder']['outputVideoLevel'].strip()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
VBITRATE = int((CFG['Transcoder']['outputVideoBitrate'].strip()).replace('k', '000'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
VRESOLUTION = CFG['Transcoder']['outputVideoResolution']
|
|
||||||
ACODEC = CFG['Transcoder']['outputAudioCodec'].strip()
|
|
||||||
ACODEC_ALLOW = CFG['Transcoder']['AudioCodecAllow'].strip()
|
|
||||||
if isinstance(ACODEC_ALLOW, str):
|
|
||||||
ACODEC_ALLOW = ACODEC_ALLOW.split(',')
|
|
||||||
if ACODEC_ALLOW == ['']:
|
|
||||||
ACODEC_ALLOW = []
|
|
||||||
try:
|
|
||||||
ACHANNELS = int(CFG['Transcoder']['outputAudioChannels'].strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
ABITRATE = int((CFG['Transcoder']['outputAudioBitrate'].strip()).replace('k', '000'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
ACODEC2 = CFG['Transcoder']['outputAudioTrack2Codec'].strip()
|
|
||||||
ACODEC2_ALLOW = CFG['Transcoder']['AudioCodec2Allow'].strip()
|
|
||||||
if isinstance(ACODEC2_ALLOW, str):
|
|
||||||
ACODEC2_ALLOW = ACODEC2_ALLOW.split(',')
|
|
||||||
if ACODEC2_ALLOW == ['']:
|
|
||||||
ACODEC2_ALLOW = []
|
|
||||||
try:
|
|
||||||
ACHANNELS2 = int(CFG['Transcoder']['outputAudioTrack2Channels'].strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
ABITRATE2 = int((CFG['Transcoder']['outputAudioTrack2Bitrate'].strip()).replace('k', '000'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
ACODEC3 = CFG['Transcoder']['outputAudioOtherCodec'].strip()
|
|
||||||
ACODEC3_ALLOW = CFG['Transcoder']['AudioOtherCodecAllow'].strip()
|
|
||||||
if isinstance(ACODEC3_ALLOW, str):
|
|
||||||
ACODEC3_ALLOW = ACODEC3_ALLOW.split(',')
|
|
||||||
if ACODEC3_ALLOW == ['']:
|
|
||||||
ACODEC3_ALLOW = []
|
|
||||||
try:
|
|
||||||
ACHANNELS3 = int(CFG['Transcoder']['outputAudioOtherChannels'].strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
ABITRATE3 = int((CFG['Transcoder']['outputAudioOtherBitrate'].strip()).replace('k', '000'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
SCODEC = CFG['Transcoder']['outputSubtitleCodec'].strip()
|
|
||||||
BURN = int(CFG['Transcoder']['burnInSubtitle'].strip())
|
|
||||||
DEFAULTS = CFG['Transcoder']['outputDefault'].strip()
|
|
||||||
HWACCEL = int(CFG['Transcoder']['hwAccel'])
|
|
||||||
allow_subs = ['.mkv', '.mp4', '.m4v', 'asf', 'wma', 'wmv']
|
|
||||||
codec_alias = {'libx264': ['libx264', 'h264', 'h.264', 'AVC', 'MPEG-4'], 'libmp3lame': ['libmp3lame', 'mp3'], 'libfaac': ['libfaac', 'aac', 'faac']}
|
|
||||||
transcode_defaults = {
|
|
||||||
'iPad': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'iPad-1080p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1920:1080', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'iPad-720p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'Apple-TV': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'iPod': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'iPhone': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '460:320', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'PS3': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'xbox': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'Roku-480p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'Roku-720p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'Roku-1080p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 160000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
'mkv': {'VEXTENSION': '.mkv', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
|
||||||
'mkv-bluray': {'VEXTENSION': '.mkv', 'VCODEC': 'libx265', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'hevc', 'h265', 'libx265', 'h.265', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
|
||||||
'mp4-scene-release': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': 19, 'VLEVEL': '3.1', 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
|
||||||
'MKV-SD': {'VEXTENSION': '.mkv', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': '1200k', 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '720: -1', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
|
||||||
}
|
|
||||||
if DEFAULTS and DEFAULTS in transcode_defaults:
|
|
||||||
VEXTENSION = transcode_defaults[DEFAULTS]['VEXTENSION']
|
|
||||||
VCODEC = transcode_defaults[DEFAULTS]['VCODEC']
|
|
||||||
VPRESET = transcode_defaults[DEFAULTS]['VPRESET']
|
|
||||||
VFRAMERATE = transcode_defaults[DEFAULTS]['VFRAMERATE']
|
|
||||||
VBITRATE = transcode_defaults[DEFAULTS]['VBITRATE']
|
|
||||||
VRESOLUTION = transcode_defaults[DEFAULTS]['VRESOLUTION']
|
|
||||||
VCRF = transcode_defaults[DEFAULTS]['VCRF']
|
|
||||||
VLEVEL = transcode_defaults[DEFAULTS]['VLEVEL']
|
|
||||||
VCODEC_ALLOW = transcode_defaults[DEFAULTS]['VCODEC_ALLOW']
|
|
||||||
ACODEC = transcode_defaults[DEFAULTS]['ACODEC']
|
|
||||||
ACODEC_ALLOW = transcode_defaults[DEFAULTS]['ACODEC_ALLOW']
|
|
||||||
ACHANNELS = transcode_defaults[DEFAULTS]['ACHANNELS']
|
|
||||||
ABITRATE = transcode_defaults[DEFAULTS]['ABITRATE']
|
|
||||||
ACODEC2 = transcode_defaults[DEFAULTS]['ACODEC2']
|
|
||||||
ACODEC2_ALLOW = transcode_defaults[DEFAULTS]['ACODEC2_ALLOW']
|
|
||||||
ACHANNELS2 = transcode_defaults[DEFAULTS]['ACHANNELS2']
|
|
||||||
ABITRATE2 = transcode_defaults[DEFAULTS]['ABITRATE2']
|
|
||||||
ACODEC3 = transcode_defaults[DEFAULTS]['ACODEC3']
|
|
||||||
ACODEC3_ALLOW = transcode_defaults[DEFAULTS]['ACODEC3_ALLOW']
|
|
||||||
ACHANNELS3 = transcode_defaults[DEFAULTS]['ACHANNELS3']
|
|
||||||
ABITRATE3 = transcode_defaults[DEFAULTS]['ABITRATE3']
|
|
||||||
SCODEC = transcode_defaults[DEFAULTS]['SCODEC']
|
|
||||||
del transcode_defaults
|
|
||||||
if VEXTENSION in allow_subs:
|
|
||||||
ALLOWSUBS = 1
|
|
||||||
if not VCODEC_ALLOW and VCODEC:
|
|
||||||
VCODEC_ALLOW.extend([VCODEC])
|
|
||||||
for codec in VCODEC_ALLOW:
|
|
||||||
if codec in codec_alias:
|
|
||||||
extra = [item for item in codec_alias[codec] if item not in VCODEC_ALLOW]
|
|
||||||
VCODEC_ALLOW.extend(extra)
|
|
||||||
if not ACODEC_ALLOW and ACODEC:
|
|
||||||
ACODEC_ALLOW.extend([ACODEC])
|
|
||||||
for codec in ACODEC_ALLOW:
|
|
||||||
if codec in codec_alias:
|
|
||||||
extra = [item for item in codec_alias[codec] if item not in ACODEC_ALLOW]
|
|
||||||
ACODEC_ALLOW.extend(extra)
|
|
||||||
if not ACODEC2_ALLOW and ACODEC2:
|
|
||||||
ACODEC2_ALLOW.extend([ACODEC2])
|
|
||||||
for codec in ACODEC2_ALLOW:
|
|
||||||
if codec in codec_alias:
|
|
||||||
extra = [item for item in codec_alias[codec] if item not in ACODEC2_ALLOW]
|
|
||||||
ACODEC2_ALLOW.extend(extra)
|
|
||||||
if not ACODEC3_ALLOW and ACODEC3:
|
|
||||||
ACODEC3_ALLOW.extend([ACODEC3])
|
|
||||||
for codec in ACODEC3_ALLOW:
|
|
||||||
if codec in codec_alias:
|
|
||||||
extra = [item for item in codec_alias[codec] if item not in ACODEC3_ALLOW]
|
|
||||||
ACODEC3_ALLOW.extend(extra)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_passwords_file():
|
def configure_passwords_file():
|
||||||
global PASSWORDS_FILE
|
global PASSWORDS_FILE
|
||||||
PASSWORDS_FILE = CFG['passwords']['PassWordFile']
|
PASSWORDS_FILE = CFG['passwords']['PassWordFile']
|
||||||
|
@ -650,49 +174,19 @@ def configure_sections(section):
|
||||||
CATEGORIES = list(set(CATEGORIES))
|
CATEGORIES = list(set(CATEGORIES))
|
||||||
|
|
||||||
|
|
||||||
def configure_utility_locations():
|
|
||||||
global SHOWEXTRACT
|
|
||||||
global SEVENZIP
|
|
||||||
global FFMPEG
|
|
||||||
global FFPROBE
|
|
||||||
global PAR2CMD
|
|
||||||
|
|
||||||
# Setup FFMPEG, FFPROBE and SEVENZIP locations
|
|
||||||
FFMPEG = tool.find_transcoder(FFMPEG_PATH)
|
|
||||||
FFPROBE = tool.find_video_corruption_detector(FFMPEG_PATH)
|
|
||||||
PAR2CMD = tool.find_archive_repairer()
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
path = APP_ROOT / f'nzb2media/extractor/bin/{platform.machine()}'
|
|
||||||
else:
|
|
||||||
path = None
|
|
||||||
SEVENZIP = tool.find_unzip(path)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(section=None):
|
def initialize(section=None):
|
||||||
global __INITIALIZED__
|
global __INITIALIZED__
|
||||||
if __INITIALIZED__:
|
if __INITIALIZED__:
|
||||||
return False
|
return False
|
||||||
configure_logging()
|
|
||||||
configure_process()
|
|
||||||
configure_locale()
|
|
||||||
configure_migration()
|
configure_migration()
|
||||||
configure_logging_part_2()
|
|
||||||
# initialize the main SB database
|
# initialize the main SB database
|
||||||
main_db.upgrade_database(main_db.DBConnection(), databases.InitialSchema)
|
|
||||||
configure_general()
|
configure_general()
|
||||||
configure_updates()
|
|
||||||
configure_wake_on_lan()
|
configure_wake_on_lan()
|
||||||
configure_nzbs(CFG)
|
|
||||||
configure_torrents(CFG)
|
|
||||||
configure_remote_paths()
|
configure_remote_paths()
|
||||||
configure_plex(CFG)
|
|
||||||
configure_niceness()
|
|
||||||
configure_containers()
|
configure_containers()
|
||||||
configure_transcoder()
|
configure_transcoder()
|
||||||
configure_passwords_file()
|
configure_passwords_file()
|
||||||
configure_utility_locations()
|
|
||||||
configure_sections(section)
|
configure_sections(section)
|
||||||
configure_torrent_class()
|
|
||||||
__INITIALIZED__ = True
|
__INITIALIZED__ = True
|
||||||
# finished initializing
|
# finished initializing
|
||||||
return __INITIALIZED__
|
return __INITIALIZED__
|
||||||
|
|
78
nzb2media/app.py
Normal file
78
nzb2media/app.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import nzb2media
|
||||||
|
import nzb2media.nzb
|
||||||
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
|
from nzb2media.processor import nzbget, sab, manual
|
||||||
|
from nzb2media.processor.nzb import process
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('args', nargs='*')
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, section=None):
|
||||||
|
# Initialize the config
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
style='{',
|
||||||
|
format='{asctime} | {levelname:<8} | {message}',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S',
|
||||||
|
)
|
||||||
|
parsed = parser.parse_args(argv)
|
||||||
|
nzb2media.initialize(section)
|
||||||
|
|
||||||
|
log.info('#########################################################')
|
||||||
|
log.info('## ..::[ nzbToMedia ]::.. ##')
|
||||||
|
log.info('#########################################################')
|
||||||
|
|
||||||
|
# debug command line options
|
||||||
|
log.debug(f'Options passed into nzbToMedia: {parsed}')
|
||||||
|
args = parsed.args
|
||||||
|
|
||||||
|
# Post-Processing Result
|
||||||
|
result = ProcessResult(
|
||||||
|
message='',
|
||||||
|
status_code=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# NZBGet
|
||||||
|
if 'NZBOP_SCRIPTDIR' in os.environ:
|
||||||
|
result = nzbget.process()
|
||||||
|
# SABnzbd
|
||||||
|
elif 'SAB_SCRIPT' in os.environ:
|
||||||
|
result = sab.process_script()
|
||||||
|
# SABnzbd Pre 0.7.17
|
||||||
|
elif len(args) >= sab.MINIMUM_ARGUMENTS:
|
||||||
|
result = sab.process(args)
|
||||||
|
# Generic program
|
||||||
|
elif len(args) > 5 and args[5] == 'generic':
|
||||||
|
log.info('Script triggered from generic program')
|
||||||
|
result = process(
|
||||||
|
input_directory=args[1],
|
||||||
|
input_name=args[2],
|
||||||
|
input_category=args[3],
|
||||||
|
download_id=args[4],
|
||||||
|
)
|
||||||
|
elif nzb2media.nzb.NO_MANUAL:
|
||||||
|
log.warning('Invalid number of arguments received from client, and no_manual set')
|
||||||
|
else:
|
||||||
|
manual.process()
|
||||||
|
|
||||||
|
if not result.status_code:
|
||||||
|
log.info(f'The {section or "nzbToMedia"} script completed successfully.')
|
||||||
|
if result.message:
|
||||||
|
print(result.message + '!')
|
||||||
|
if 'NZBOP_SCRIPTDIR' in os.environ: # return code for nzbget v11
|
||||||
|
return nzbget.ExitCode.SUCCESS
|
||||||
|
else:
|
||||||
|
log.error(f'A problem was reported in the {args[0]} script.')
|
||||||
|
if result.message:
|
||||||
|
print(result.message + '!')
|
||||||
|
if 'NZBOP_SCRIPTDIR' in os.environ: # return code for nzbget v11
|
||||||
|
return nzbget.ExitCode.FAILURE
|
||||||
|
return result.status_code
|
|
@ -8,21 +8,22 @@ import time
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.torrent
|
||||||
import nzb2media.utils.common
|
import nzb2media.utils.common
|
||||||
from nzb2media import transcoder
|
from nzb2media import transcoder
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.auto_process.common import command_complete
|
from nzb2media.auto_process.common import command_complete
|
||||||
from nzb2media.auto_process.common import completed_download_handling
|
from nzb2media.auto_process.common import completed_download_handling
|
||||||
from nzb2media.plugins.subtitles import import_subs
|
from nzb2media.nzb import report_nzb
|
||||||
from nzb2media.plugins.subtitles import rename_subs
|
|
||||||
from nzb2media.scene_exceptions import process_all_exceptions
|
from nzb2media.scene_exceptions import process_all_exceptions
|
||||||
|
from nzb2media.subtitles import import_subs
|
||||||
|
from nzb2media.subtitles import rename_subs
|
||||||
from nzb2media.utils.encoding import convert_to_ascii
|
from nzb2media.utils.encoding import convert_to_ascii
|
||||||
from nzb2media.utils.files import extract_files
|
from nzb2media.utils.files import extract_files
|
||||||
from nzb2media.utils.files import list_media_files
|
from nzb2media.utils.files import list_media_files
|
||||||
from nzb2media.utils.identification import find_imdbid
|
from nzb2media.utils.identification import find_imdbid
|
||||||
from nzb2media.utils.network import find_download
|
from nzb2media.utils.network import find_download
|
||||||
from nzb2media.utils.network import server_responding
|
from nzb2media.utils.network import server_responding
|
||||||
from nzb2media.utils.nzb import report_nzb
|
|
||||||
from nzb2media.utils.paths import rchmod
|
from nzb2media.utils.paths import rchmod
|
||||||
from nzb2media.utils.paths import remote_dir
|
from nzb2media.utils.paths import remote_dir
|
||||||
from nzb2media.utils.paths import remove_dir
|
from nzb2media.utils.paths import remove_dir
|
||||||
|
@ -152,7 +153,7 @@ def process(*, section: str, dir_name: str, input_name: str = '', status: int =
|
||||||
if 'NZBOP_VERSION' in os.environ and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
|
if 'NZBOP_VERSION' in os.environ and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
|
||||||
print('[NZB] MARK=BAD')
|
print('[NZB] MARK=BAD')
|
||||||
if not status:
|
if not status:
|
||||||
if nzb2media.TRANSCODE == 1:
|
if transcoder.TRANSCODE == 1:
|
||||||
result, new_dir_name = transcoder.transcode_directory(dir_name)
|
result, new_dir_name = transcoder.transcode_directory(dir_name)
|
||||||
if not result:
|
if not result:
|
||||||
log.debug(f'Transcoding succeeded for files in {dir_name}')
|
log.debug(f'Transcoding succeeded for files in {dir_name}')
|
||||||
|
@ -168,7 +169,7 @@ def process(*, section: str, dir_name: str, input_name: str = '', status: int =
|
||||||
if not release and '.cp(tt' not in video and imdbid:
|
if not release and '.cp(tt' not in video and imdbid:
|
||||||
video_name, video_ext = os.path.splitext(video)
|
video_name, video_ext = os.path.splitext(video)
|
||||||
video2 = f'{video_name}.cp({imdbid}){video_ext}'
|
video2 = f'{video_name}.cp({imdbid}){video_ext}'
|
||||||
if not (client_agent in [nzb2media.TORRENT_CLIENT_AGENT, 'manual'] and nzb2media.USE_LINK == 'move-sym'):
|
if not (client_agent in [nzb2media.torrent.CLIENT_AGENT, 'manual'] and nzb2media.USE_LINK == 'move-sym'):
|
||||||
log.debug(f'Renaming: {video} to: {video2}')
|
log.debug(f'Renaming: {video} to: {video2}')
|
||||||
os.rename(video, video2)
|
os.rename(video, video2)
|
||||||
if not apikey: # If only using Transcoder functions, exit here.
|
if not apikey: # If only using Transcoder functions, exit here.
|
||||||
|
|
|
@ -12,21 +12,23 @@ from oauthlib.oauth2 import LegacyApplicationClient
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.fork.sickrage
|
||||||
|
import nzb2media.torrent
|
||||||
import nzb2media.utils.common
|
import nzb2media.utils.common
|
||||||
from nzb2media import transcoder
|
from nzb2media import transcoder
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.auto_process.common import command_complete
|
from nzb2media.auto_process.common import command_complete
|
||||||
from nzb2media.auto_process.common import completed_download_handling
|
from nzb2media.auto_process.common import completed_download_handling
|
||||||
from nzb2media.managers.sickbeard import InitSickBeard
|
from nzb2media.managers.sickbeard import InitSickBeard
|
||||||
from nzb2media.plugins.subtitles import import_subs
|
from nzb2media.nzb import report_nzb
|
||||||
from nzb2media.plugins.subtitles import rename_subs
|
|
||||||
from nzb2media.scene_exceptions import process_all_exceptions
|
from nzb2media.scene_exceptions import process_all_exceptions
|
||||||
|
from nzb2media.subtitles import import_subs
|
||||||
|
from nzb2media.subtitles import rename_subs
|
||||||
from nzb2media.utils.common import flatten
|
from nzb2media.utils.common import flatten
|
||||||
from nzb2media.utils.encoding import convert_to_ascii
|
from nzb2media.utils.encoding import convert_to_ascii
|
||||||
from nzb2media.utils.files import extract_files
|
from nzb2media.utils.files import extract_files
|
||||||
from nzb2media.utils.files import list_media_files
|
from nzb2media.utils.files import list_media_files
|
||||||
from nzb2media.utils.network import server_responding
|
from nzb2media.utils.network import server_responding
|
||||||
from nzb2media.utils.nzb import report_nzb
|
|
||||||
from nzb2media.utils.paths import rchmod
|
from nzb2media.utils.paths import rchmod
|
||||||
from nzb2media.utils.paths import remote_dir
|
from nzb2media.utils.paths import remote_dir
|
||||||
from nzb2media.utils.paths import remove_dir
|
from nzb2media.utils.paths import remove_dir
|
||||||
|
@ -85,7 +87,7 @@ def process(*, section: str, dir_name: str, input_name: str = '', status: int =
|
||||||
else:
|
else:
|
||||||
log.error('Server did not respond. Exiting')
|
log.error('Server did not respond. Exiting')
|
||||||
return ProcessResult.failure(f'{section}: Failed to post-process - {section} did not respond.')
|
return ProcessResult.failure(f'{section}: Failed to post-process - {section} did not respond.')
|
||||||
if client_agent == nzb2media.TORRENT_CLIENT_AGENT and nzb2media.USE_LINK == 'move-sym':
|
if client_agent == nzb2media.torrent.CLIENT_AGENT and nzb2media.USE_LINK == 'move-sym':
|
||||||
process_method = 'symlink'
|
process_method = 'symlink'
|
||||||
if not os.path.isdir(dir_name) and os.path.isfile(dir_name): # If the input directory is a file, assume single file download and split dir/name.
|
if not os.path.isdir(dir_name) and os.path.isfile(dir_name): # If the input directory is a file, assume single file download and split dir/name.
|
||||||
dir_name = os.path.split(os.path.normpath(dir_name))[0]
|
dir_name = os.path.split(os.path.normpath(dir_name))[0]
|
||||||
|
@ -160,7 +162,7 @@ def process(*, section: str, dir_name: str, input_name: str = '', status: int =
|
||||||
status = 1
|
status = 1
|
||||||
if 'NZBOP_VERSION' in os.environ and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
|
if 'NZBOP_VERSION' in os.environ and os.environ['NZBOP_VERSION'][0:5] >= '14.0':
|
||||||
print('[NZB] MARK=BAD')
|
print('[NZB] MARK=BAD')
|
||||||
if not status and nzb2media.TRANSCODE == 1:
|
if not status and transcoder.TRANSCODE == 1:
|
||||||
# only transcode successful downloads
|
# only transcode successful downloads
|
||||||
result, new_dir_name = transcoder.transcode_directory(dir_name)
|
result, new_dir_name = transcoder.transcode_directory(dir_name)
|
||||||
if not result:
|
if not result:
|
||||||
|
@ -300,8 +302,8 @@ def process(*, section: str, dir_name: str, input_name: str = '', status: int =
|
||||||
elif section == 'SiCKRAGE':
|
elif section == 'SiCKRAGE':
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
if api_version >= 2 and sso_username and sso_password:
|
if api_version >= 2 and sso_username and sso_password:
|
||||||
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=nzb2media.SICKRAGE_OAUTH_CLIENT_ID))
|
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=nzb2media.fork.sickrage.SICKRAGE_OAUTH_CLIENT_ID))
|
||||||
oauth_token = oauth.fetch_token(client_id=nzb2media.SICKRAGE_OAUTH_CLIENT_ID, token_url=nzb2media.SICKRAGE_OAUTH_TOKEN_URL, username=sso_username, password=sso_password)
|
oauth_token = oauth.fetch_token(client_id=nzb2media.fork.sickrage.SICKRAGE_OAUTH_CLIENT_ID, token_url=nzb2media.fork.sickrage.SICKRAGE_OAUTH_TOKEN_URL, username=sso_username, password=sso_password)
|
||||||
session.headers.update({'Authorization': 'Bearer ' + oauth_token['access_token']})
|
session.headers.update({'Authorization': 'Bearer ' + oauth_token['access_token']})
|
||||||
params = {'path': fork_params['path'], 'failed': str(bool(fork_params['failed'])).lower(), 'processMethod': 'move', 'forceReplace': str(bool(fork_params['force_replace'])).lower(), 'returnData': str(bool(fork_params['return_data'])).lower(), 'delete': str(bool(fork_params['delete'])).lower(), 'forceNext': str(bool(fork_params['force_next'])).lower(), 'nzbName': fork_params['nzbName']}
|
params = {'path': fork_params['path'], 'failed': str(bool(fork_params['failed'])).lower(), 'processMethod': 'move', 'forceReplace': str(bool(fork_params['force_replace'])).lower(), 'returnData': str(bool(fork_params['return_data'])).lower(), 'delete': str(bool(fork_params['delete'])).lower(), 'forceNext': str(bool(fork_params['force_next'])).lower(), 'nzbName': fork_params['nzbName']}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
from nzb2media import main_db
|
|
||||||
from nzb2media.utils.files import backup_versioned_file
|
from nzb2media.utils.files import backup_versioned_file
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -14,7 +17,7 @@ MAX_DB_VERSION = 2
|
||||||
|
|
||||||
def backup_database(version):
|
def backup_database(version):
|
||||||
log.info('Backing up database before upgrade')
|
log.info('Backing up database before upgrade')
|
||||||
if not backup_versioned_file(main_db.db_filename(), version):
|
if not backup_versioned_file(db_filename(), version):
|
||||||
logging.critical('Database backup failed, abort upgrading database')
|
logging.critical('Database backup failed, abort upgrading database')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
@ -25,7 +28,33 @@ def backup_database(version):
|
||||||
# = Main DB Migrations =
|
# = Main DB Migrations =
|
||||||
# ======================
|
# ======================
|
||||||
# Add new migrations at the bottom of the list; subclass the previous migration.
|
# Add new migrations at the bottom of the list; subclass the previous migration.
|
||||||
class InitialSchema(main_db.SchemaUpgrade):
|
class SchemaUpgrade:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def has_table(self, table_name):
|
||||||
|
return len(self.connection.action('SELECT 1 FROM sqlite_master WHERE name = ?;', (table_name,)).fetchall()) > 0
|
||||||
|
|
||||||
|
def has_column(self, table_name, column):
|
||||||
|
return column in self.connection.table_info(table_name)
|
||||||
|
|
||||||
|
def add_column(self, table, column, data_type='NUMERIC', default=0):
|
||||||
|
self.connection.action(f'ALTER TABLE {table} ADD {column} {data_type}')
|
||||||
|
self.connection.action(f'UPDATE {table} SET {column} = ?', (default,))
|
||||||
|
|
||||||
|
def check_db_version(self):
|
||||||
|
result = self.connection.select('SELECT db_version FROM db_version')
|
||||||
|
if result:
|
||||||
|
return int(result[-1]['db_version'])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def inc_db_version(self):
|
||||||
|
new_version = self.check_db_version() + 1
|
||||||
|
self.connection.action('UPDATE db_version SET db_version = ?', [new_version])
|
||||||
|
return new_version
|
||||||
|
|
||||||
|
|
||||||
|
class InitialSchema(SchemaUpgrade):
|
||||||
def test(self):
|
def test(self):
|
||||||
no_update = False
|
no_update = False
|
||||||
if self.has_table('db_version'):
|
if self.has_table('db_version'):
|
||||||
|
@ -35,7 +64,23 @@ class InitialSchema(main_db.SchemaUpgrade):
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if not self.has_table('downloads') and not self.has_table('db_version'):
|
if not self.has_table('downloads') and not self.has_table('db_version'):
|
||||||
queries = ['CREATE TABLE db_version (db_version INTEGER);', 'CREATE TABLE downloads (input_directory TEXT, input_name TEXT, input_hash TEXT, input_id TEXT, client_agent TEXT, status INTEGER, last_update NUMERIC, CONSTRAINT pk_downloadID PRIMARY KEY (input_directory, input_name));', 'INSERT INTO db_version (db_version) VALUES (2);']
|
queries = [
|
||||||
|
'CREATE TABLE db_version (db_version INTEGER);',
|
||||||
|
"""
|
||||||
|
CREATE TABLE downloads (
|
||||||
|
input_directory TEXT,
|
||||||
|
input_name TEXT,
|
||||||
|
input_hash TEXT,
|
||||||
|
input_id TEXT,
|
||||||
|
client_agent TEXT,
|
||||||
|
status INTEGER,
|
||||||
|
last_update NUMERIC,
|
||||||
|
CONSTRAINT pk_downloadID
|
||||||
|
PRIMARY KEY (input_directory, input_name)
|
||||||
|
);
|
||||||
|
""",
|
||||||
|
'INSERT INTO db_version (db_version) VALUES (2);',
|
||||||
|
]
|
||||||
for query in queries:
|
for query in queries:
|
||||||
self.connection.action(query)
|
self.connection.action(query)
|
||||||
else:
|
else:
|
||||||
|
@ -47,6 +92,221 @@ class InitialSchema(main_db.SchemaUpgrade):
|
||||||
log.critical(f'Your database version ({cur_db_version}) has been incremented past what this version of nzbToMedia supports ({MAX_DB_VERSION}).\nIf you have used other forks of nzbToMedia, your database may be unusable due to their modifications.')
|
log.critical(f'Your database version ({cur_db_version}) has been incremented past what this version of nzbToMedia supports ({MAX_DB_VERSION}).\nIf you have used other forks of nzbToMedia, your database may be unusable due to their modifications.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if cur_db_version < MAX_DB_VERSION: # We need to upgrade.
|
if cur_db_version < MAX_DB_VERSION: # We need to upgrade.
|
||||||
queries = ['CREATE TABLE downloads2 (input_directory TEXT, input_name TEXT, input_hash TEXT, input_id TEXT, client_agent TEXT, status INTEGER, last_update NUMERIC, CONSTRAINT pk_downloadID PRIMARY KEY (input_directory, input_name));', 'INSERT INTO downloads2 SELECT * FROM downloads;', 'DROP TABLE IF EXISTS downloads;', 'ALTER TABLE downloads2 RENAME TO downloads;', 'INSERT INTO db_version (db_version) VALUES (2);']
|
queries = [
|
||||||
|
"""
|
||||||
|
CREATE TABLE downloads2 (
|
||||||
|
input_directory TEXT,
|
||||||
|
input_name TEXT,
|
||||||
|
input_hash TEXT,
|
||||||
|
input_id TEXT,
|
||||||
|
client_agent TEXT,
|
||||||
|
status INTEGER,
|
||||||
|
last_update NUMERIC,
|
||||||
|
CONSTRAINT pk_downloadID
|
||||||
|
PRIMARY KEY (input_directory, input_name)
|
||||||
|
);
|
||||||
|
""",
|
||||||
|
'INSERT INTO downloads2 SELECT * FROM downloads;',
|
||||||
|
'DROP TABLE IF EXISTS downloads;',
|
||||||
|
'ALTER TABLE downloads2 RENAME TO downloads;',
|
||||||
|
'INSERT INTO db_version (db_version) VALUES (2);',
|
||||||
|
]
|
||||||
for query in queries:
|
for query in queries:
|
||||||
self.connection.action(query)
|
self.connection.action(query)
|
||||||
|
|
||||||
|
|
||||||
|
def db_filename(filename: str = 'nzbtomedia.db', suffix: str | None = None, root: os.PathLike | None = None):
|
||||||
|
"""Return the correct location of the database file.
|
||||||
|
|
||||||
|
@param filename: The sqlite database filename to use. If not specified, will be made to be nzbtomedia.db
|
||||||
|
@param suffix: The suffix to append to the filename. A '.' will be added
|
||||||
|
automatically, i.e. suffix='v0' will make dbfile.db.v0
|
||||||
|
@param root: The root path for the database.
|
||||||
|
@return: the correct location of the database file.
|
||||||
|
"""
|
||||||
|
if suffix:
|
||||||
|
filename = f'{filename}.{suffix}'
|
||||||
|
return os.path.join(root or '', filename)
|
||||||
|
|
||||||
|
|
||||||
|
class DBConnection:
|
||||||
|
def __init__(self, filename='nzbtomedia.db'):
|
||||||
|
self.filename = filename
|
||||||
|
self.connection = sqlite3.connect(db_filename(filename), 20)
|
||||||
|
self.connection.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
def check_db_version(self):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = self.select('SELECT db_version FROM db_version')
|
||||||
|
except sqlite3.OperationalError as error:
|
||||||
|
if 'no such table: db_version' in error.args[0]:
|
||||||
|
return 0
|
||||||
|
if result:
|
||||||
|
return int(result[0]['db_version'])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def fetch(self, query, args=None):
|
||||||
|
if query is None:
|
||||||
|
return
|
||||||
|
sql_result = None
|
||||||
|
attempt = 0
|
||||||
|
while attempt < 5:
|
||||||
|
try:
|
||||||
|
if args is None:
|
||||||
|
log.debug(f'{self.filename}: {query}')
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
sql_result = cursor.fetchone()[0]
|
||||||
|
else:
|
||||||
|
log.debug(f'{self.filename}: {query} with args {args}')
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute(query, args)
|
||||||
|
sql_result = cursor.fetchone()[0]
|
||||||
|
# get out of the connection attempt loop since we were successful
|
||||||
|
break
|
||||||
|
except sqlite3.OperationalError as error:
|
||||||
|
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
||||||
|
log.warning(f'DB error: {error}')
|
||||||
|
attempt += 1
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
log.error(f'DB error: {error}')
|
||||||
|
raise
|
||||||
|
except sqlite3.DatabaseError as error:
|
||||||
|
log.error(f'Fatal error executing query: {error}')
|
||||||
|
raise
|
||||||
|
return sql_result
|
||||||
|
|
||||||
|
def mass_action(self, querylist, log_transaction=False):
|
||||||
|
if querylist is None:
|
||||||
|
return
|
||||||
|
sql_result = []
|
||||||
|
attempt = 0
|
||||||
|
while attempt < 5:
|
||||||
|
try:
|
||||||
|
for query in querylist:
|
||||||
|
if len(query) == 1:
|
||||||
|
if log_transaction:
|
||||||
|
log.debug(query[0])
|
||||||
|
sql_result.append(self.connection.execute(query[0]))
|
||||||
|
elif len(query) > 1:
|
||||||
|
if log_transaction:
|
||||||
|
log.debug(f'{query[0]} with args {query[1]}')
|
||||||
|
sql_result.append(self.connection.execute(query[0], query[1]))
|
||||||
|
self.connection.commit()
|
||||||
|
log.debug(f'Transaction with {len(querylist)} query\'s executed')
|
||||||
|
return sql_result
|
||||||
|
except sqlite3.OperationalError as error:
|
||||||
|
sql_result = []
|
||||||
|
if self.connection:
|
||||||
|
self.connection.rollback()
|
||||||
|
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
||||||
|
log.warning(f'DB error: {error}')
|
||||||
|
attempt += 1
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
log.error(f'DB error: {error}')
|
||||||
|
raise
|
||||||
|
except sqlite3.DatabaseError as error:
|
||||||
|
if self.connection:
|
||||||
|
self.connection.rollback()
|
||||||
|
log.error(f'Fatal error executing query: {error}')
|
||||||
|
raise
|
||||||
|
return sql_result
|
||||||
|
|
||||||
|
def action(self, query, args=None):
|
||||||
|
if query is None:
|
||||||
|
return
|
||||||
|
sql_result = None
|
||||||
|
attempt = 0
|
||||||
|
while attempt < 5:
|
||||||
|
try:
|
||||||
|
if args is None:
|
||||||
|
log.debug(f'{self.filename}: {query}')
|
||||||
|
sql_result = self.connection.execute(query)
|
||||||
|
else:
|
||||||
|
log.debug(f'{self.filename}: {query} with args {args}')
|
||||||
|
sql_result = self.connection.execute(query, args)
|
||||||
|
self.connection.commit()
|
||||||
|
# get out of the connection attempt loop since we were successful
|
||||||
|
break
|
||||||
|
except sqlite3.OperationalError as error:
|
||||||
|
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
||||||
|
log.warning(f'DB error: {error}')
|
||||||
|
attempt += 1
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
log.error(f'DB error: {error}')
|
||||||
|
raise
|
||||||
|
except sqlite3.DatabaseError as error:
|
||||||
|
log.error(f'Fatal error executing query: {error}')
|
||||||
|
raise
|
||||||
|
return sql_result
|
||||||
|
|
||||||
|
def select(self, query, args=None):
|
||||||
|
sql_results = self.action(query, args).fetchall()
|
||||||
|
if sql_results is None:
|
||||||
|
return []
|
||||||
|
return sql_results
|
||||||
|
|
||||||
|
def upsert(self, table_name, value_dict, key_dict):
|
||||||
|
def gen_params(my_dict):
|
||||||
|
return [f'{k} = ?' for k in my_dict.keys()]
|
||||||
|
|
||||||
|
changes_before = self.connection.total_changes
|
||||||
|
items = list(value_dict.values()) + list(key_dict.values())
|
||||||
|
_params = ', '.join(gen_params(value_dict))
|
||||||
|
_conditions = ' AND '.join(gen_params(key_dict))
|
||||||
|
self.action(f'UPDATE {table_name} SET {_params} WHERE {_conditions}', items)
|
||||||
|
if self.connection.total_changes == changes_before:
|
||||||
|
_cols = ', '.join(map(str, value_dict.keys()))
|
||||||
|
values = list(value_dict.values())
|
||||||
|
_vals = ', '.join(['?'] * len(values))
|
||||||
|
self.action(f'INSERT OR IGNORE INTO {table_name} ({_cols}) VALUES ({_vals})', values)
|
||||||
|
|
||||||
|
def table_info(self, table_name):
|
||||||
|
# FIXME ? binding is not supported here, but I cannot find a way to escape a string manually
|
||||||
|
cursor = self.connection.execute(f'PRAGMA table_info({table_name})')
|
||||||
|
return {column['name']: {'type': column['type']} for column in cursor}
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check_database(connection, sanity_check):
|
||||||
|
sanity_check(connection).check()
|
||||||
|
|
||||||
|
|
||||||
|
class DBSanityCheck:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_database(connection, schema):
|
||||||
|
log.info('Checking database structure...')
|
||||||
|
_process_upgrade(connection, schema)
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_name(class_name):
|
||||||
|
return ' '.join([x.group() for x in re.finditer('([A-Z])([a-z0-9]+)', class_name)])
|
||||||
|
|
||||||
|
|
||||||
|
def _process_upgrade(connection, upgrade_class):
|
||||||
|
instance = upgrade_class(connection)
|
||||||
|
log.debug(f'Checking {pretty_name(upgrade_class.__name__)} database upgrade')
|
||||||
|
if not instance.test():
|
||||||
|
log.info(f'Database upgrade required: {pretty_name(upgrade_class.__name__)}')
|
||||||
|
try:
|
||||||
|
instance.execute()
|
||||||
|
except sqlite3.DatabaseError as error:
|
||||||
|
print(f'Error in {upgrade_class.__name__}: {error}')
|
||||||
|
raise
|
||||||
|
log.debug(f'{upgrade_class.__name__} upgrade completed')
|
||||||
|
else:
|
||||||
|
log.debug(f'{upgrade_class.__name__} upgrade not required')
|
||||||
|
for upgrade_sub_class in upgrade_class.__subclasses__():
|
||||||
|
_process_upgrade(connection, upgrade_sub_class)
|
||||||
|
|
||||||
|
|
||||||
|
upgrade_database(DBConnection(), InitialSchema)
|
||||||
|
|
41
nzb2media/deluge.py
Normal file
41
nzb2media/deluge.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from deluge_client import DelugeRPCClient
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
HOST = None
|
||||||
|
PORT = None
|
||||||
|
USERNAME = None
|
||||||
|
PASSWORD = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_deluge(config):
|
||||||
|
global HOST
|
||||||
|
global PORT
|
||||||
|
global USERNAME
|
||||||
|
global PASSWORD
|
||||||
|
|
||||||
|
HOST = config['DelugeHost'] # localhost
|
||||||
|
PORT = int(config['DelugePort']) # 8084
|
||||||
|
USERNAME = config['DelugeUSR'] # mysecretusr
|
||||||
|
PASSWORD = config['DelugePWD'] # mysecretpwr
|
||||||
|
|
||||||
|
|
||||||
|
def configure_client():
|
||||||
|
agent = 'deluge'
|
||||||
|
host = HOST
|
||||||
|
port = PORT
|
||||||
|
user = USERNAME
|
||||||
|
password = PASSWORD
|
||||||
|
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
||||||
|
client = DelugeRPCClient(host, port, user, password)
|
||||||
|
try:
|
||||||
|
client.connect()
|
||||||
|
except Exception:
|
||||||
|
log.error('Failed to connect to Deluge')
|
||||||
|
else:
|
||||||
|
return client
|
|
@ -1,158 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
from subprocess import DEVNULL
|
|
||||||
from subprocess import Popen
|
|
||||||
from subprocess import call
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def extract(file_path, output_destination):
|
|
||||||
success = 0
|
|
||||||
# Using Windows
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
if not os.path.exists(nzb2media.SEVENZIP):
|
|
||||||
log.error('EXTRACTOR: Could not find 7-zip, Exiting')
|
|
||||||
return False
|
|
||||||
wscriptlocation = os.path.join(os.environ['WINDIR'], 'system32', 'wscript.exe')
|
|
||||||
invislocation = os.path.join(nzb2media.APP_ROOT, 'nzb2media', 'extractor', 'bin', 'invisible.vbs')
|
|
||||||
cmd_7zip = [wscriptlocation, invislocation, str(nzb2media.SHOWEXTRACT), nzb2media.SEVENZIP, 'x', '-y']
|
|
||||||
ext_7zip = ['.rar', '.zip', '.tar.gz', 'tgz', '.tar.bz2', '.tbz', '.tar.lzma', '.tlz', '.7z', '.xz', '.gz']
|
|
||||||
extract_commands = dict.fromkeys(ext_7zip, cmd_7zip)
|
|
||||||
# Using unix
|
|
||||||
else:
|
|
||||||
required_cmds = ['unrar', 'unzip', 'tar', 'unxz', 'unlzma', '7zr', 'bunzip2', 'gunzip']
|
|
||||||
# ## Possible future suport:
|
|
||||||
# gunzip: gz (cmd will delete original archive)
|
|
||||||
# ## the following do not extract to destination dir
|
|
||||||
# '.xz': ['xz', '-d --keep'],
|
|
||||||
# '.lzma': ['xz', '-d --format=lzma --keep'],
|
|
||||||
# '.bz2': ['bzip2', '-d --keep']
|
|
||||||
extract_commands = {'.rar': ['unrar', 'x', '-o+', '-y'], '.tar': ['tar', '-xf'], '.zip': ['unzip'], '.tar.gz': ['tar', '-xzf'], '.tgz': ['tar', '-xzf'], '.tar.bz2': ['tar', '-xjf'], '.tbz': ['tar', '-xjf'], '.tar.lzma': ['tar', '--lzma', '-xf'], '.tlz': ['tar', '--lzma', '-xf'], '.tar.xz': ['tar', '--xz', '-xf'], '.txz': ['tar', '--xz', '-xf'], '.7z': ['7zr', 'x'], '.gz': ['gunzip']}
|
|
||||||
# Test command exists and if not, remove
|
|
||||||
if not os.getenv('TR_TORRENT_DIR'):
|
|
||||||
for cmd in required_cmds:
|
|
||||||
if call(['which', cmd], stdout=DEVNULL, stderr=DEVNULL):
|
|
||||||
# note, returns 0 if exists, or 1 if doesn't exist.
|
|
||||||
for key, val in extract_commands.items():
|
|
||||||
if cmd in val[0]:
|
|
||||||
if not call(['which', '7zr'], stdout=DEVNULL, stderr=DEVNULL):
|
|
||||||
# we do have '7zr'
|
|
||||||
extract_commands[key] = ['7zr', 'x', '-y']
|
|
||||||
elif not call(['which', '7z'], stdout=DEVNULL, stderr=DEVNULL):
|
|
||||||
# we do have '7z'
|
|
||||||
extract_commands[key] = ['7z', 'x', '-y']
|
|
||||||
elif not call(['which', '7za'], stdout=DEVNULL, stderr=DEVNULL):
|
|
||||||
# we do have '7za'
|
|
||||||
extract_commands[key] = ['7za', 'x', '-y']
|
|
||||||
else:
|
|
||||||
log.error(f'EXTRACTOR: {cmd} not found, disabling support for {key}')
|
|
||||||
del extract_commands[key]
|
|
||||||
else:
|
|
||||||
log.warning('EXTRACTOR: Cannot determine which tool to use when called from Transmission')
|
|
||||||
if not extract_commands:
|
|
||||||
log.warning('EXTRACTOR: No archive extracting programs found, plugin will be disabled')
|
|
||||||
ext = os.path.splitext(file_path)
|
|
||||||
cmd = []
|
|
||||||
if ext[1] in {'.gz', '.bz2', '.lzma'}:
|
|
||||||
# Check if this is a tar
|
|
||||||
if os.path.splitext(ext[0])[1] == '.tar':
|
|
||||||
cmd = extract_commands[f'.tar{ext[1]}']
|
|
||||||
else: # Try gunzip
|
|
||||||
cmd = extract_commands[ext[1]]
|
|
||||||
elif ext[1] in {'.1', '.01', '.001'} and os.path.splitext(ext[0])[1] in {'.rar', '.zip', '.7z'}:
|
|
||||||
cmd = extract_commands[os.path.splitext(ext[0])[1]]
|
|
||||||
elif ext[1] in {'.cb7', '.cba', '.cbr', '.cbt', '.cbz'}:
|
|
||||||
# don't extract these comic book archives.
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if ext[1] in extract_commands:
|
|
||||||
cmd = extract_commands[ext[1]]
|
|
||||||
else:
|
|
||||||
log.debug(f'EXTRACTOR: Unknown file type: {ext[1]}')
|
|
||||||
return False
|
|
||||||
# Create outputDestination folder
|
|
||||||
nzb2media.make_dir(output_destination)
|
|
||||||
if nzb2media.PASSWORDS_FILE and os.path.isfile(os.path.normpath(nzb2media.PASSWORDS_FILE)):
|
|
||||||
with open(os.path.normpath(nzb2media.PASSWORDS_FILE), encoding='utf-8') as fin:
|
|
||||||
passwords = [line.strip() for line in fin]
|
|
||||||
else:
|
|
||||||
passwords = []
|
|
||||||
log.info(f'Extracting {file_path} to {output_destination}')
|
|
||||||
log.debug(f'Extracting {cmd} {file_path} {output_destination}')
|
|
||||||
orig_files = []
|
|
||||||
orig_dirs = []
|
|
||||||
for directory, subdirs, files in os.walk(output_destination):
|
|
||||||
for subdir in subdirs:
|
|
||||||
orig_dirs.append(os.path.join(directory, subdir))
|
|
||||||
for file in files:
|
|
||||||
orig_files.append(os.path.join(directory, file))
|
|
||||||
pwd = os.getcwd() # Get our Present Working Directory
|
|
||||||
# Not all unpack commands accept full paths, so just extract into this directory
|
|
||||||
os.chdir(output_destination)
|
|
||||||
try: # now works same for nt and *nix
|
|
||||||
info = None
|
|
||||||
cmd.append(file_path) # add filePath to final cmd arg.
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
info = subprocess.STARTUPINFO()
|
|
||||||
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
||||||
else:
|
|
||||||
cmd = nzb2media.NICENESS + cmd
|
|
||||||
cmd2 = cmd
|
|
||||||
if 'gunzip' not in cmd: # gunzip doesn't support password
|
|
||||||
cmd2.append('-p-') # don't prompt for password.
|
|
||||||
with Popen(cmd2, stdout=DEVNULL, stderr=DEVNULL, startupinfo=info) as proc:
|
|
||||||
res = proc.wait() # should extract files fine.
|
|
||||||
if not res: # Both Linux and Windows return 0 for successful.
|
|
||||||
log.info(f'EXTRACTOR: Extraction was successful for {file_path} to {output_destination}')
|
|
||||||
success = 1
|
|
||||||
elif len(passwords) > 0 and 'gunzip' not in cmd:
|
|
||||||
log.info('EXTRACTOR: Attempting to extract with passwords')
|
|
||||||
for password in passwords:
|
|
||||||
if not password:
|
|
||||||
continue # if edited in windows or otherwise if blank lines.
|
|
||||||
cmd2 = cmd
|
|
||||||
# append password here.
|
|
||||||
passcmd = f'-p{password}'
|
|
||||||
cmd2.append(passcmd)
|
|
||||||
with Popen(cmd2, stdout=DEVNULL, stderr=DEVNULL, startupinfo=info) as proc:
|
|
||||||
res = proc.wait() # should extract files fine.
|
|
||||||
if not res or (res >= 0 and platform == 'Windows'):
|
|
||||||
log.info(f'EXTRACTOR: Extraction was successful for {file_path} to {output_destination} using password: {password}')
|
|
||||||
success = 1
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
log.error(f'EXTRACTOR: Extraction failed for {file_path}. Could not call command {cmd}')
|
|
||||||
os.chdir(pwd)
|
|
||||||
return False
|
|
||||||
os.chdir(pwd) # Go back to our Original Working Directory
|
|
||||||
if success:
|
|
||||||
# sleep to let files finish writing to disk
|
|
||||||
sleep(3)
|
|
||||||
perms = stat.S_IMODE(os.lstat(os.path.split(file_path)[0]).st_mode)
|
|
||||||
for directory, subdirs, files in os.walk(output_destination):
|
|
||||||
for subdir in subdirs:
|
|
||||||
if not os.path.join(directory, subdir) in orig_files:
|
|
||||||
try:
|
|
||||||
os.chmod(os.path.join(directory, subdir), perms)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
for file in files:
|
|
||||||
if not os.path.join(directory, file) in orig_files:
|
|
||||||
try:
|
|
||||||
shutil.copymode(file_path, os.path.join(directory, file))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
log.error(f'EXTRACTOR: Extraction failed for {file_path}. Result was {res}')
|
|
||||||
return False
|
|
16
nzb2media/fork/medusa.py
Normal file
16
nzb2media/fork/medusa.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
MEDUSA_KEYS = ('proc_dir', 'failed', 'process_method', 'force', 'delete_on', 'ignore_subs')
|
||||||
|
MEDUSA_API_KEYS = ('path', 'failed', 'process_method', 'force_replace', 'return_data', 'type', 'delete_files', 'is_priority')
|
||||||
|
MEDUSA_API_V2_KEYS = ('proc_dir', 'resource', 'failed', 'process_method', 'force', 'type', 'delete_on', 'is_priority')
|
||||||
|
|
||||||
|
CONFIG: dict[str, dict[str, Any]] = {
|
||||||
|
'Medusa': {key: None for key in MEDUSA_KEYS},
|
||||||
|
'Medusa-api': {
|
||||||
|
**{key: None for key in MEDUSA_API_KEYS},
|
||||||
|
'cmd': 'postprocess',
|
||||||
|
},
|
||||||
|
'Medusa-apiv2': {key: None for key in MEDUSA_API_V2_KEYS},
|
||||||
|
}
|
12
nzb2media/fork/sickbeard.py
Normal file
12
nzb2media/fork/sickbeard.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
SICKBEARD_API_KEYS = ('path', 'failed', 'process_method', 'force_replace', 'return_data', 'type', 'delete', 'force_next')
|
||||||
|
|
||||||
|
CONFIG: dict[str, dict[str, Any]] = {
|
||||||
|
'SickBeard-api': {
|
||||||
|
**{key: None for key in SICKBEARD_API_KEYS},
|
||||||
|
'cmd': 'postprocess',
|
||||||
|
},
|
||||||
|
}
|
14
nzb2media/fork/sickchill.py
Normal file
14
nzb2media/fork/sickchill.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
SICKCHILL_KEYS = ('proc_dir', 'failed', 'process_method', 'force', 'delete_on', 'force_next')
|
||||||
|
SICKCHILL_API_KEYS = ('path', 'proc_dir', 'failed', 'process_method', 'force', 'force_replace', 'return_data', 'type', 'delete', 'force_next', 'is_priority')
|
||||||
|
|
||||||
|
CONFIG: dict[str, dict[str, Any]] = {
|
||||||
|
'SickChill': {key: None for key in SICKCHILL_KEYS},
|
||||||
|
'SickChill-api': {
|
||||||
|
**{key: None for key in SICKCHILL_API_KEYS},
|
||||||
|
'cmd': 'postprocess',
|
||||||
|
},
|
||||||
|
}
|
14
nzb2media/fork/sickgear.py
Normal file
14
nzb2media/fork/sickgear.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
SICKGEAR_KEYS = ('dir', 'failed', 'process_method', 'force')
|
||||||
|
SICKGEAR_API_KEYS = ('path', 'process_method', 'force_replace', 'return_data', 'type', 'is_priority', 'failed')
|
||||||
|
|
||||||
|
CONFIG: dict[str, dict[str, Any]] = {
|
||||||
|
'SickGear': {key: None for key in SICKGEAR_KEYS},
|
||||||
|
'SickGear-api': {
|
||||||
|
'cmd': 'sg.postprocess',
|
||||||
|
**{key: None for key in SICKGEAR_API_KEYS},
|
||||||
|
},
|
||||||
|
}
|
4
nzb2media/fork/sickrage.py
Normal file
4
nzb2media/fork/sickrage.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
SICKRAGE_OAUTH_CLIENT_ID = 'nzbtomedia'
|
||||||
|
SICKRAGE_OAUTH_TOKEN_URL = 'https://auth.sickrage.ca/realms/sickrage/protocol/openid-connect/token'
|
|
@ -1,42 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
class GitHub:
|
|
||||||
"""Simple api wrapper for the Github API v3."""
|
|
||||||
|
|
||||||
def __init__(self, github_repo_user, github_repo, branch='master'):
|
|
||||||
self.github_repo_user = github_repo_user
|
|
||||||
self.github_repo = github_repo
|
|
||||||
self.branch = branch
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _access_api(path, params=None):
|
|
||||||
"""Access API at given an API path and optional parameters."""
|
|
||||||
route = '/'.join(path)
|
|
||||||
url = f'https://api.github.com/{route}'
|
|
||||||
data = requests.get(url, params=params, verify=False)
|
|
||||||
return data.json() if data.ok else []
|
|
||||||
|
|
||||||
def commits(self):
|
|
||||||
"""Get the 100 most recent commits from the specified user/repo/branch, starting from HEAD.
|
|
||||||
|
|
||||||
user: The github username of the person whose repo you're querying
|
|
||||||
repo: The repo name to query
|
|
||||||
branch: Optional, the branch name to show commits from
|
|
||||||
Returns a deserialized json object containing the commit info. See http://developer.github.com/v3/repos/commits/
|
|
||||||
"""
|
|
||||||
return self._access_api(['repos', self.github_repo_user, self.github_repo, 'commits'], params={'per_page': 100, 'sha': self.branch})
|
|
||||||
|
|
||||||
def compare(self, base, head, per_page=1):
|
|
||||||
"""Get compares between base and head.
|
|
||||||
|
|
||||||
user: The github username of the person whose repo you're querying
|
|
||||||
repo: The repo name to query
|
|
||||||
base: Start compare from branch
|
|
||||||
head: Current commit sha or branch name to compare
|
|
||||||
per_page: number of items per page
|
|
||||||
Returns a deserialized json object containing the compare info. See http://developer.github.com/v3/repos/commits/
|
|
||||||
"""
|
|
||||||
return self._access_api(['repos', self.github_repo_user, self.github_repo, 'compare', f'{base}...{head}'], params={'per_page': per_page})
|
|
|
@ -1,234 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import sqlite3
|
|
||||||
import time
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def db_filename(filename: str = 'nzbtomedia.db', suffix: str | None = None):
|
|
||||||
"""Return the correct location of the database file.
|
|
||||||
|
|
||||||
@param filename: The sqlite database filename to use. If not specified, will be made to be nzbtomedia.db
|
|
||||||
@param suffix: The suffix to append to the filename. A '.' will be added
|
|
||||||
automatically, i.e. suffix='v0' will make dbfile.db.v0
|
|
||||||
@return: the correct location of the database file.
|
|
||||||
"""
|
|
||||||
if suffix:
|
|
||||||
filename = f'{filename}.{suffix}'
|
|
||||||
return nzb2media.os.path.join(nzb2media.APP_ROOT, filename)
|
|
||||||
|
|
||||||
|
|
||||||
class DBConnection:
|
|
||||||
def __init__(self, filename='nzbtomedia.db'):
|
|
||||||
self.filename = filename
|
|
||||||
self.connection = sqlite3.connect(db_filename(filename), 20)
|
|
||||||
self.connection.row_factory = sqlite3.Row
|
|
||||||
|
|
||||||
def check_db_version(self):
|
|
||||||
result = None
|
|
||||||
try:
|
|
||||||
result = self.select('SELECT db_version FROM db_version')
|
|
||||||
except sqlite3.OperationalError as error:
|
|
||||||
if 'no such table: db_version' in error.args[0]:
|
|
||||||
return 0
|
|
||||||
if result:
|
|
||||||
return int(result[0]['db_version'])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def fetch(self, query, args=None):
|
|
||||||
if query is None:
|
|
||||||
return
|
|
||||||
sql_result = None
|
|
||||||
attempt = 0
|
|
||||||
while attempt < 5:
|
|
||||||
try:
|
|
||||||
if args is None:
|
|
||||||
log.debug(f'{self.filename}: {query}')
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute(query)
|
|
||||||
sql_result = cursor.fetchone()[0]
|
|
||||||
else:
|
|
||||||
log.debug(f'{self.filename}: {query} with args {args}')
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute(query, args)
|
|
||||||
sql_result = cursor.fetchone()[0]
|
|
||||||
# get out of the connection attempt loop since we were successful
|
|
||||||
break
|
|
||||||
except sqlite3.OperationalError as error:
|
|
||||||
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
|
||||||
log.warning(f'DB error: {error}')
|
|
||||||
attempt += 1
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
log.error(f'DB error: {error}')
|
|
||||||
raise
|
|
||||||
except sqlite3.DatabaseError as error:
|
|
||||||
log.error(f'Fatal error executing query: {error}')
|
|
||||||
raise
|
|
||||||
return sql_result
|
|
||||||
|
|
||||||
def mass_action(self, querylist, log_transaction=False):
|
|
||||||
if querylist is None:
|
|
||||||
return
|
|
||||||
sql_result = []
|
|
||||||
attempt = 0
|
|
||||||
while attempt < 5:
|
|
||||||
try:
|
|
||||||
for query in querylist:
|
|
||||||
if len(query) == 1:
|
|
||||||
if log_transaction:
|
|
||||||
log.debug(query[0])
|
|
||||||
sql_result.append(self.connection.execute(query[0]))
|
|
||||||
elif len(query) > 1:
|
|
||||||
if log_transaction:
|
|
||||||
log.debug(f'{query[0]} with args {query[1]}')
|
|
||||||
sql_result.append(self.connection.execute(query[0], query[1]))
|
|
||||||
self.connection.commit()
|
|
||||||
log.debug(f'Transaction with {len(querylist)} query\'s executed')
|
|
||||||
return sql_result
|
|
||||||
except sqlite3.OperationalError as error:
|
|
||||||
sql_result = []
|
|
||||||
if self.connection:
|
|
||||||
self.connection.rollback()
|
|
||||||
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
|
||||||
log.warning(f'DB error: {error}')
|
|
||||||
attempt += 1
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
log.error(f'DB error: {error}')
|
|
||||||
raise
|
|
||||||
except sqlite3.DatabaseError as error:
|
|
||||||
if self.connection:
|
|
||||||
self.connection.rollback()
|
|
||||||
log.error(f'Fatal error executing query: {error}')
|
|
||||||
raise
|
|
||||||
return sql_result
|
|
||||||
|
|
||||||
def action(self, query, args=None):
|
|
||||||
if query is None:
|
|
||||||
return
|
|
||||||
sql_result = None
|
|
||||||
attempt = 0
|
|
||||||
while attempt < 5:
|
|
||||||
try:
|
|
||||||
if args is None:
|
|
||||||
log.debug(f'{self.filename}: {query}')
|
|
||||||
sql_result = self.connection.execute(query)
|
|
||||||
else:
|
|
||||||
log.debug(f'{self.filename}: {query} with args {args}')
|
|
||||||
sql_result = self.connection.execute(query, args)
|
|
||||||
self.connection.commit()
|
|
||||||
# get out of the connection attempt loop since we were successful
|
|
||||||
break
|
|
||||||
except sqlite3.OperationalError as error:
|
|
||||||
if 'unable to open database file' in error.args[0] or 'database is locked' in error.args[0]:
|
|
||||||
log.warning(f'DB error: {error}')
|
|
||||||
attempt += 1
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
log.error(f'DB error: {error}')
|
|
||||||
raise
|
|
||||||
except sqlite3.DatabaseError as error:
|
|
||||||
log.error(f'Fatal error executing query: {error}')
|
|
||||||
raise
|
|
||||||
return sql_result
|
|
||||||
|
|
||||||
def select(self, query, args=None):
|
|
||||||
sql_results = self.action(query, args).fetchall()
|
|
||||||
if sql_results is None:
|
|
||||||
return []
|
|
||||||
return sql_results
|
|
||||||
|
|
||||||
def upsert(self, table_name, value_dict, key_dict):
|
|
||||||
def gen_params(my_dict):
|
|
||||||
return [f'{k} = ?' for k in my_dict.keys()]
|
|
||||||
|
|
||||||
changes_before = self.connection.total_changes
|
|
||||||
items = list(value_dict.values()) + list(key_dict.values())
|
|
||||||
_params = ', '.join(gen_params(value_dict))
|
|
||||||
_conditions = ' AND '.join(gen_params(key_dict))
|
|
||||||
self.action(f'UPDATE {table_name} SET {_params} WHERE {_conditions}', items)
|
|
||||||
if self.connection.total_changes == changes_before:
|
|
||||||
_cols = ', '.join(map(str, value_dict.keys()))
|
|
||||||
values = list(value_dict.values())
|
|
||||||
_vals = ', '.join(['?'] * len(values))
|
|
||||||
self.action(f'INSERT OR IGNORE INTO {table_name} ({_cols}) VALUES ({_vals})', values)
|
|
||||||
|
|
||||||
def table_info(self, table_name):
|
|
||||||
# FIXME ? binding is not supported here, but I cannot find a way to escape a string manually
|
|
||||||
cursor = self.connection.execute(f'PRAGMA table_info({table_name})')
|
|
||||||
return {column['name']: {'type': column['type']} for column in cursor}
|
|
||||||
|
|
||||||
|
|
||||||
def sanity_check_database(connection, sanity_check):
|
|
||||||
sanity_check(connection).check()
|
|
||||||
|
|
||||||
|
|
||||||
class DBSanityCheck:
|
|
||||||
def __init__(self, connection):
|
|
||||||
self.connection = connection
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# ===============
|
|
||||||
# = Upgrade API =
|
|
||||||
# ===============
|
|
||||||
def upgrade_database(connection, schema):
|
|
||||||
log.info('Checking database structure...')
|
|
||||||
_process_upgrade(connection, schema)
|
|
||||||
|
|
||||||
|
|
||||||
def pretty_name(class_name):
|
|
||||||
return ' '.join([x.group() for x in re.finditer('([A-Z])([a-z0-9]+)', class_name)])
|
|
||||||
|
|
||||||
|
|
||||||
def _process_upgrade(connection, upgrade_class):
|
|
||||||
instance = upgrade_class(connection)
|
|
||||||
log.debug(f'Checking {pretty_name(upgrade_class.__name__)} database upgrade')
|
|
||||||
if not instance.test():
|
|
||||||
log.info(f'Database upgrade required: {pretty_name(upgrade_class.__name__)}')
|
|
||||||
try:
|
|
||||||
instance.execute()
|
|
||||||
except sqlite3.DatabaseError as error:
|
|
||||||
print(f'Error in {upgrade_class.__name__}: {error}')
|
|
||||||
raise
|
|
||||||
log.debug(f'{upgrade_class.__name__} upgrade completed')
|
|
||||||
else:
|
|
||||||
log.debug(f'{upgrade_class.__name__} upgrade not required')
|
|
||||||
for upgrade_sub_class in upgrade_class.__subclasses__():
|
|
||||||
_process_upgrade(connection, upgrade_sub_class)
|
|
||||||
|
|
||||||
|
|
||||||
# Base migration class. All future DB changes should be subclassed from this class
|
|
||||||
class SchemaUpgrade:
|
|
||||||
def __init__(self, connection):
|
|
||||||
self.connection = connection
|
|
||||||
|
|
||||||
def has_table(self, table_name):
|
|
||||||
return len(self.connection.action('SELECT 1 FROM sqlite_master WHERE name = ?;', (table_name,)).fetchall()) > 0
|
|
||||||
|
|
||||||
def has_column(self, table_name, column):
|
|
||||||
return column in self.connection.table_info(table_name)
|
|
||||||
|
|
||||||
def add_column(self, table, column, data_type='NUMERIC', default=0):
|
|
||||||
self.connection.action(f'ALTER TABLE {table} ADD {column} {data_type}')
|
|
||||||
self.connection.action(f'UPDATE {table} SET {column} = ?', (default,))
|
|
||||||
|
|
||||||
def check_db_version(self):
|
|
||||||
result = self.connection.select('SELECT db_version FROM db_version')
|
|
||||||
if result:
|
|
||||||
return int(result[-1]['db_version'])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def inc_db_version(self):
|
|
||||||
new_version = self.check_db_version() + 1
|
|
||||||
self.connection.action('UPDATE db_version SET db_version = ?', [new_version])
|
|
||||||
return new_version
|
|
|
@ -8,6 +8,8 @@ from oauthlib.oauth2 import LegacyApplicationClient
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.fork.sickrage
|
||||||
|
import nzb2media.torrent
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.utils.paths import remote_dir
|
from nzb2media.utils.paths import remote_dir
|
||||||
|
|
||||||
|
@ -87,8 +89,8 @@ class InitSickBeard:
|
||||||
api_params = {'cmd': 'postprocess', 'help': '1'}
|
api_params = {'cmd': 'postprocess', 'help': '1'}
|
||||||
try:
|
try:
|
||||||
if self.api_version >= 2 and self.sso_username and self.sso_password:
|
if self.api_version >= 2 and self.sso_username and self.sso_password:
|
||||||
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=nzb2media.SICKRAGE_OAUTH_CLIENT_ID))
|
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=nzb2media.fork.sickrage.SICKRAGE_OAUTH_CLIENT_ID))
|
||||||
oauth_token = oauth.fetch_token(client_id=nzb2media.SICKRAGE_OAUTH_CLIENT_ID, token_url=nzb2media.SICKRAGE_OAUTH_TOKEN_URL, username=self.sso_username, password=self.sso_password)
|
oauth_token = oauth.fetch_token(client_id=nzb2media.fork.sickrage.SICKRAGE_OAUTH_CLIENT_ID, token_url=nzb2media.fork.sickrage.SICKRAGE_OAUTH_TOKEN_URL, username=self.sso_username, password=self.sso_password)
|
||||||
token = oauth_token['access_token']
|
token = oauth_token['access_token']
|
||||||
response = requests.get(url, headers={'Authorization': f'Bearer {token}'}, stream=True, verify=False)
|
response = requests.get(url, headers={'Authorization': f'Bearer {token}'}, stream=True, verify=False)
|
||||||
else:
|
else:
|
||||||
|
@ -258,7 +260,7 @@ class SickBeard:
|
||||||
self.extract = 0
|
self.extract = 0
|
||||||
else:
|
else:
|
||||||
self.extract = int(self.sb_init.config.get('extract', 0))
|
self.extract = int(self.sb_init.config.get('extract', 0))
|
||||||
if client_agent == nzb2media.TORRENT_CLIENT_AGENT and nzb2media.USE_LINK == 'move-sym':
|
if client_agent == nzb2media.torrent.CLIENT_AGENT and nzb2media.USE_LINK == 'move-sym':
|
||||||
self.process_method = 'symlink'
|
self.process_method = 'symlink'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -5,22 +5,52 @@ import os
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
CLIENTS = ['sabnzbd', 'nzbget', 'manual']
|
||||||
|
CLIENT_AGENT = None
|
||||||
|
DEFAULT_DIRECTORY = None
|
||||||
|
NO_MANUAL = None
|
||||||
|
|
||||||
|
SABNZBD_HOST = ''
|
||||||
|
SABNZBD_PORT = None
|
||||||
|
SABNZBD_APIKEY = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_nzbs(config):
|
||||||
|
global CLIENT_AGENT
|
||||||
|
global DEFAULT_DIRECTORY
|
||||||
|
global NO_MANUAL
|
||||||
|
|
||||||
|
nzb_config = config['Nzb']
|
||||||
|
CLIENT_AGENT = nzb_config['clientAgent'] # sabnzbd
|
||||||
|
DEFAULT_DIRECTORY = nzb_config['default_downloadDirectory']
|
||||||
|
NO_MANUAL = int(nzb_config['no_manual'], 0)
|
||||||
|
configure_sabnzbd(nzb_config)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_sabnzbd(config):
|
||||||
|
global SABNZBD_HOST
|
||||||
|
global SABNZBD_PORT
|
||||||
|
global SABNZBD_APIKEY
|
||||||
|
|
||||||
|
SABNZBD_HOST = config['sabnzbd_host']
|
||||||
|
# defaults to accommodate NzbGet
|
||||||
|
SABNZBD_PORT = int(config['sabnzbd_port'] or 8080)
|
||||||
|
SABNZBD_APIKEY = config['sabnzbd_apikey']
|
||||||
|
|
||||||
|
|
||||||
def get_nzoid(input_name):
|
def get_nzoid(input_name):
|
||||||
nzoid = None
|
nzoid = None
|
||||||
slots = []
|
slots = []
|
||||||
log.debug('Searching for nzoid from SAbnzbd ...')
|
log.debug('Searching for nzoid from SAbnzbd ...')
|
||||||
if 'http' in nzb2media.SABNZBD_HOST:
|
if 'http' in SABNZBD_HOST:
|
||||||
base_url = f'{nzb2media.SABNZBD_HOST}:{nzb2media.SABNZBD_PORT}/api'
|
base_url = f'{SABNZBD_HOST}:{SABNZBD_PORT}/api'
|
||||||
else:
|
else:
|
||||||
base_url = f'http://{nzb2media.SABNZBD_HOST}:{nzb2media.SABNZBD_PORT}/api'
|
base_url = f'http://{SABNZBD_HOST}:{SABNZBD_PORT}/api'
|
||||||
url = base_url
|
url = base_url
|
||||||
params = {'apikey': nzb2media.SABNZBD_APIKEY, 'mode': 'queue', 'output': 'json'}
|
params = {'apikey': SABNZBD_APIKEY, 'mode': 'queue', 'output': 'json'}
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, params=params, verify=False, timeout=(30, 120))
|
response = requests.get(url, params=params, verify=False, timeout=(30, 120))
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
|
@ -1,18 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
|
|
||||||
def configure_nzbs(config):
|
|
||||||
nzb_config = config['Nzb']
|
|
||||||
nzb2media.NZB_CLIENT_AGENT = nzb_config['clientAgent'] # sabnzbd
|
|
||||||
nzb2media.NZB_DEFAULT_DIRECTORY = nzb_config['default_downloadDirectory']
|
|
||||||
nzb2media.NZB_NO_MANUAL = int(nzb_config['no_manual'], 0)
|
|
||||||
configure_sabnzbd(nzb_config)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_sabnzbd(config):
|
|
||||||
nzb2media.SABNZBD_HOST = config['sabnzbd_host']
|
|
||||||
# defaults to accommodate NzbGet
|
|
||||||
nzb2media.SABNZBD_PORT = int(config['sabnzbd_port'] or 8080)
|
|
||||||
nzb2media.SABNZBD_APIKEY = config['sabnzbd_apikey']
|
|
|
@ -9,34 +9,46 @@ import nzb2media
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
SSL = None
|
||||||
|
HOST = None
|
||||||
|
PORT = None
|
||||||
|
TOKEN = None
|
||||||
|
SECTION: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
|
||||||
def configure_plex(config):
|
def configure_plex(config):
|
||||||
nzb2media.PLEX_SSL = int(config['Plex']['plex_ssl'])
|
global SSL
|
||||||
nzb2media.PLEX_HOST = config['Plex']['plex_host']
|
global HOST
|
||||||
nzb2media.PLEX_PORT = config['Plex']['plex_port']
|
global PORT
|
||||||
nzb2media.PLEX_TOKEN = config['Plex']['plex_token']
|
global TOKEN
|
||||||
|
global SECTION
|
||||||
|
|
||||||
|
SSL = int(config['Plex']['plex_ssl'])
|
||||||
|
HOST = config['Plex']['plex_host']
|
||||||
|
PORT = config['Plex']['plex_port']
|
||||||
|
TOKEN = config['Plex']['plex_token']
|
||||||
plex_section = config['Plex']['plex_sections'] or []
|
plex_section = config['Plex']['plex_sections'] or []
|
||||||
if plex_section:
|
if plex_section:
|
||||||
if isinstance(plex_section, list):
|
if isinstance(plex_section, list):
|
||||||
plex_section = ','.join(plex_section) # fix in case this imported as list.
|
plex_section = ','.join(plex_section) # fix in case this imported as list.
|
||||||
plex_section = [tuple(item.split(',')) for item in plex_section.split('|')]
|
plex_section = [tuple(item.split(',')) for item in plex_section.split('|')]
|
||||||
nzb2media.PLEX_SECTION = plex_section
|
SECTION = plex_section
|
||||||
|
|
||||||
|
|
||||||
def plex_update(category):
|
def plex_update(category):
|
||||||
if nzb2media.FAILED:
|
if nzb2media.FAILED:
|
||||||
return
|
return
|
||||||
scheme = 'https' if nzb2media.PLEX_SSL else 'http'
|
scheme = 'https' if SSL else 'http'
|
||||||
url = f'{scheme}://{nzb2media.PLEX_HOST}:{nzb2media.PLEX_PORT}/library/sections/'
|
url = f'{scheme}://{HOST}:{PORT}/library/sections/'
|
||||||
section = None
|
section = None
|
||||||
if not nzb2media.PLEX_SECTION:
|
if not SECTION:
|
||||||
return
|
return
|
||||||
log.debug(f'Attempting to update Plex Library for category {category}.')
|
log.debug(f'Attempting to update Plex Library for category {category}.')
|
||||||
for item in nzb2media.PLEX_SECTION:
|
for item in SECTION:
|
||||||
if item[0] == category:
|
if item[0] == category:
|
||||||
section = item[1]
|
section = item[1]
|
||||||
if section:
|
if section:
|
||||||
url = f'{url}{section}/refresh?X-Plex-Token={nzb2media.PLEX_TOKEN}'
|
url = f'{url}{section}/refresh?X-Plex-Token={TOKEN}'
|
||||||
requests.get(url, timeout=(60, 120), verify=False)
|
requests.get(url, timeout=(60, 120), verify=False)
|
||||||
log.debug('Plex Library has been refreshed.')
|
log.debug('Plex Library has been refreshed.')
|
||||||
else:
|
else:
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.nzb
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.processor import nzb
|
from nzb2media.processor import nzb
|
||||||
from nzb2media.utils.common import get_dirs
|
from nzb2media.utils.common import get_dirs
|
||||||
|
@ -20,25 +21,35 @@ def process():
|
||||||
result = ProcessResult(message='', status_code=0)
|
result = ProcessResult(message='', status_code=0)
|
||||||
for section, subsections in nzb2media.SECTIONS.items():
|
for section, subsections in nzb2media.SECTIONS.items():
|
||||||
for subsection in subsections:
|
for subsection in subsections:
|
||||||
if not nzb2media.CFG[section][subsection].isenabled():
|
if nzb2media.CFG[section][subsection].isenabled():
|
||||||
continue
|
result = _process(section, subsection)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _process(section, subsection):
|
||||||
for dir_name in get_dirs(section, subsection, link='move'):
|
for dir_name in get_dirs(section, subsection, link='move'):
|
||||||
log.info(f'Starting manual run for {section}:{subsection} - Folder: {dir_name}')
|
log.info(f'Starting manual run for {section}:{subsection} - Folder: {dir_name}')
|
||||||
log.info(f'Checking database for download info for {os.path.basename(dir_name)} ...')
|
log.info(f'Checking database for download info for {os.path.basename(dir_name)} ...')
|
||||||
nzb2media.DOWNLOAD_INFO = get_download_info(os.path.basename(dir_name), 0)
|
download_info = get_download_info(os.path.basename(dir_name), 0)
|
||||||
if nzb2media.DOWNLOAD_INFO:
|
nzb2media.DOWNLOAD_INFO = download_info
|
||||||
|
if download_info:
|
||||||
log.info(f'Found download info for {os.path.basename(dir_name)}, setting variables now ...')
|
log.info(f'Found download info for {os.path.basename(dir_name)}, setting variables now ...')
|
||||||
client_agent = nzb2media.DOWNLOAD_INFO[0]['client_agent'] or 'manual'
|
|
||||||
download_id = nzb2media.DOWNLOAD_INFO[0]['input_id'] or ''
|
|
||||||
else:
|
else:
|
||||||
log.info(f'Unable to locate download info for {os.path.basename(dir_name)}, continuing to try and process this release ...')
|
log.info(f'Unable to locate download info for {os.path.basename(dir_name)}, continuing to try and process this release ...')
|
||||||
client_agent = 'manual'
|
client_agent, download_id = _process_download_info(nzb2media.DOWNLOAD_INFO)
|
||||||
download_id = ''
|
if client_agent != 'manual' and client_agent.lower() not in nzb2media.nzb.CLIENTS:
|
||||||
if client_agent and client_agent.lower() not in nzb2media.NZB_CLIENTS:
|
|
||||||
continue
|
continue
|
||||||
input_name = os.path.basename(dir_name)
|
input_name = os.path.basename(dir_name)
|
||||||
results = nzb.process(dir_name, input_name, 0, client_agent=client_agent, download_id=download_id or None, input_category=subsection)
|
result = nzb.process(input_directory=dir_name, input_name=input_name, client_agent=client_agent, download_id=download_id or None, input_category=subsection)
|
||||||
if results.status_code:
|
if result.status_code:
|
||||||
log.error(f'A problem was reported when trying to perform a manual run for {section}:{subsection}.')
|
log.error(f'A problem was reported when trying to perform a manual run for {section}:{subsection}.')
|
||||||
result = results
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _process_download_info(download_info):
|
||||||
|
agent = None
|
||||||
|
download_id = None
|
||||||
|
if not download_info:
|
||||||
|
agent = download_info[0]['client_agent']
|
||||||
|
download_id = download_info[0]['input_id']
|
||||||
|
return agent or 'manual', download_id or ''
|
||||||
|
|
|
@ -4,7 +4,8 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
from nzb2media import main_db
|
import nzb2media.databases
|
||||||
|
import nzb2media.nzb
|
||||||
from nzb2media.auto_process import books
|
from nzb2media.auto_process import books
|
||||||
from nzb2media.auto_process import comics
|
from nzb2media.auto_process import comics
|
||||||
from nzb2media.auto_process import games
|
from nzb2media.auto_process import games
|
||||||
|
@ -12,28 +13,28 @@ from nzb2media.auto_process import movies
|
||||||
from nzb2media.auto_process import music
|
from nzb2media.auto_process import music
|
||||||
from nzb2media.auto_process import tv
|
from nzb2media.auto_process import tv
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.plugins.plex import plex_update
|
from nzb2media.nzb import get_nzoid
|
||||||
|
from nzb2media.plex import plex_update
|
||||||
from nzb2media.user_scripts import external_script
|
from nzb2media.user_scripts import external_script
|
||||||
from nzb2media.utils.common import clean_dir
|
from nzb2media.utils.common import clean_dir
|
||||||
from nzb2media.utils.download_info import update_download_info_status
|
from nzb2media.utils.download_info import update_download_info_status
|
||||||
from nzb2media.utils.encoding import char_replace
|
from nzb2media.utils.encoding import char_replace
|
||||||
from nzb2media.utils.encoding import convert_to_ascii
|
from nzb2media.utils.encoding import convert_to_ascii
|
||||||
from nzb2media.utils.files import extract_files
|
from nzb2media.utils.files import extract_files
|
||||||
from nzb2media.utils.nzb import get_nzoid
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
|
||||||
def process(input_directory, input_name=None, status=0, client_agent='manual', download_id=None, input_category=None, failure_link=None):
|
def process(*, input_directory, input_name=None, status=0, client_agent='manual', download_id=None, input_category=None, failure_link=None):
|
||||||
if nzb2media.SAFE_MODE and input_directory == nzb2media.NZB_DEFAULT_DIRECTORY:
|
if nzb2media.SAFE_MODE and input_directory == nzb2media.nzb.DEFAULT_DIRECTORY:
|
||||||
log.error(f'The input directory:[{input_directory}] is the Default Download Directory. Please configure category directories to prevent processing of other media.')
|
log.error(f'The input directory:[{input_directory}] is the Default Download Directory. Please configure category directories to prevent processing of other media.')
|
||||||
return ProcessResult(message='', status_code=-1)
|
return ProcessResult(message='', status_code=-1)
|
||||||
if not download_id and client_agent == 'sabnzbd':
|
if not download_id and client_agent == 'sabnzbd':
|
||||||
download_id = get_nzoid(input_name)
|
download_id = get_nzoid(input_name)
|
||||||
if client_agent != 'manual' and not nzb2media.DOWNLOAD_INFO:
|
if client_agent != 'manual' and not nzb2media.DOWNLOAD_INFO:
|
||||||
log.debug(f'Adding NZB download info for directory {input_directory} to database')
|
log.debug(f'Adding NZB download info for directory {input_directory} to database')
|
||||||
my_db = main_db.DBConnection()
|
my_db = nzb2media.databases.DBConnection()
|
||||||
input_directory1 = input_directory
|
input_directory1 = input_directory
|
||||||
input_name1 = input_name
|
input_name1 = input_name
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
from nzb2media.processor import nzb
|
from nzb2media.processor import nzb
|
||||||
|
|
||||||
|
|
||||||
|
class ExitCode(enum.IntEnum):
|
||||||
|
"""Exit codes for post-processing with NZBget."""
|
||||||
|
|
||||||
|
REPAIR = 92
|
||||||
|
SUCCESS = 93
|
||||||
|
FAILURE = 94
|
||||||
|
SKIPPED = 95
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
@ -86,7 +96,7 @@ def check_version():
|
||||||
# Check if the script is called from nzbget 11.0 or later
|
# Check if the script is called from nzbget 11.0 or later
|
||||||
if version[0:5] < '11.0':
|
if version[0:5] < '11.0':
|
||||||
log.error(f'NZBGet Version {version} is not supported. Please update NZBGet.')
|
log.error(f'NZBGet Version {version} is not supported. Please update NZBGet.')
|
||||||
sys.exit(nzb2media.NZBGET_POSTPROCESS_ERROR)
|
sys.exit(ExitCode.FAILURE)
|
||||||
log.info(f'Script triggered from NZBGet Version {version}.')
|
log.info(f'Script triggered from NZBGet Version {version}.')
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,4 +105,12 @@ def process():
|
||||||
status = parse_status()
|
status = parse_status()
|
||||||
download_id = parse_download_id()
|
download_id = parse_download_id()
|
||||||
failure_link = parse_failure_link()
|
failure_link = parse_failure_link()
|
||||||
return nzb.process(input_directory=os.environ['NZBPP_DIRECTORY'], input_name=os.environ['NZBPP_NZBNAME'], status=status, client_agent='nzbget', download_id=download_id, input_category=os.environ['NZBPP_CATEGORY'], failure_link=failure_link)
|
return nzb.process(
|
||||||
|
input_directory=os.environ['NZBPP_DIRECTORY'],
|
||||||
|
input_name=os.environ['NZBPP_NZBNAME'],
|
||||||
|
status=status,
|
||||||
|
client_agent='nzbget',
|
||||||
|
download_id=download_id,
|
||||||
|
input_category=os.environ['NZBPP_CATEGORY'],
|
||||||
|
failure_link=failure_link,
|
||||||
|
)
|
||||||
|
|
|
@ -13,7 +13,15 @@ MINIMUM_ARGUMENTS = 8
|
||||||
def process_script():
|
def process_script():
|
||||||
version = os.environ['SAB_VERSION']
|
version = os.environ['SAB_VERSION']
|
||||||
log.info(f'Script triggered from SABnzbd {version}.')
|
log.info(f'Script triggered from SABnzbd {version}.')
|
||||||
return nzb.process(input_directory=os.environ['SAB_COMPLETE_DIR'], input_name=os.environ['SAB_FINAL_NAME'], status=int(os.environ['SAB_PP_STATUS']), client_agent='sabnzbd', download_id=os.environ['SAB_NZO_ID'], input_category=os.environ['SAB_CAT'], failure_link=os.environ['SAB_FAILURE_URL'])
|
return nzb.process(
|
||||||
|
input_directory=os.environ['SAB_COMPLETE_DIR'],
|
||||||
|
input_name=os.environ['SAB_FINAL_NAME'],
|
||||||
|
status=int(os.environ['SAB_PP_STATUS']),
|
||||||
|
client_agent='sabnzbd',
|
||||||
|
download_id=os.environ['SAB_NZO_ID'],
|
||||||
|
input_category=os.environ['SAB_CAT'],
|
||||||
|
failure_link=os.environ['SAB_FAILURE_URL'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def process(args):
|
def process(args):
|
||||||
|
@ -35,4 +43,12 @@ def process(args):
|
||||||
"""
|
"""
|
||||||
version = '0.7.17+' if len(args) > MINIMUM_ARGUMENTS else ''
|
version = '0.7.17+' if len(args) > MINIMUM_ARGUMENTS else ''
|
||||||
log.info(f'Script triggered from SABnzbd {version}')
|
log.info(f'Script triggered from SABnzbd {version}')
|
||||||
return nzb.process(input_directory=args[1], input_name=args[2], status=int(args[7]), input_category=args[5], client_agent='sabnzbd', download_id='', failure_link=''.join(args[8:]))
|
return nzb.process(
|
||||||
|
input_directory=args[1],
|
||||||
|
input_name=args[2],
|
||||||
|
status=int(args[7]),
|
||||||
|
input_category=args[5],
|
||||||
|
client_agent='sabnzbd',
|
||||||
|
download_id='',
|
||||||
|
failure_link=''.join(args[8:]),
|
||||||
|
)
|
||||||
|
|
|
@ -4,18 +4,33 @@ import logging
|
||||||
|
|
||||||
from qbittorrent import Client as qBittorrentClient
|
from qbittorrent import Client as qBittorrentClient
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
HOST = None
|
||||||
|
PORT = None
|
||||||
|
USERNAME = None
|
||||||
|
PASSWORD = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_qbittorrent(config):
|
||||||
|
global HOST
|
||||||
|
global PORT
|
||||||
|
global USERNAME
|
||||||
|
global PASSWORD
|
||||||
|
|
||||||
|
HOST = config['qBittorrentHost'] # localhost
|
||||||
|
PORT = int(config['qBittorrentPort']) # 8080
|
||||||
|
USERNAME = config['qBittorrentUSR'] # mysecretusr
|
||||||
|
PASSWORD = config['qBittorrentPWD'] # mysecretpwr
|
||||||
|
|
||||||
|
|
||||||
def configure_client():
|
def configure_client():
|
||||||
agent = 'qbittorrent'
|
agent = 'qbittorrent'
|
||||||
host = nzb2media.QBITTORRENT_HOST
|
host = HOST
|
||||||
port = nzb2media.QBITTORRENT_PORT
|
port = PORT
|
||||||
user = nzb2media.QBITTORRENT_USER
|
user = USERNAME
|
||||||
password = nzb2media.QBITTORRENT_PASSWORD
|
password = PASSWORD
|
||||||
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
||||||
client = qBittorrentClient(f'http://{host}:{port}/')
|
client = qBittorrentClient(f'http://{host}:{port}/')
|
||||||
try:
|
try:
|
|
@ -8,6 +8,7 @@ import subprocess
|
||||||
from subprocess import DEVNULL
|
from subprocess import DEVNULL
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.tool
|
||||||
from nzb2media.utils.files import list_media_files
|
from nzb2media.utils.files import list_media_files
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -149,11 +150,11 @@ def par2(dirname):
|
||||||
if size > sofar:
|
if size > sofar:
|
||||||
sofar = size
|
sofar = size
|
||||||
parfile = item
|
parfile = item
|
||||||
if nzb2media.PAR2CMD and parfile:
|
if nzb2media.tool.PAR2CMD and parfile:
|
||||||
pwd = os.getcwd() # Get our Present Working Directory
|
pwd = os.getcwd() # Get our Present Working Directory
|
||||||
os.chdir(dirname) # set directory to run par on.
|
os.chdir(dirname) # set directory to run par on.
|
||||||
log.info(f'Running par2 on file {parfile}.')
|
log.info(f'Running par2 on file {parfile}.')
|
||||||
command = [nzb2media.PAR2CMD, 'r', parfile, '*']
|
command = [nzb2media.tool.PAR2CMD, 'r', parfile, '*']
|
||||||
cmd = ''
|
cmd = ''
|
||||||
for item in command:
|
for item in command:
|
||||||
cmd = f'{cmd} {item}'
|
cmd = f'{cmd} {item}'
|
||||||
|
@ -168,7 +169,6 @@ def par2(dirname):
|
||||||
log.info('par2 file processing succeeded')
|
log.info('par2 file processing succeeded')
|
||||||
os.chdir(pwd)
|
os.chdir(pwd)
|
||||||
|
|
||||||
|
|
||||||
# dict for custom groups
|
# dict for custom groups
|
||||||
# we can add more to this list
|
# we can add more to this list
|
||||||
# _customgroups = {'Q o Q': process_qoq, '-ECI': process_eci}
|
# _customgroups = {'Q o Q': process_qoq, '-ECI': process_eci}
|
||||||
|
|
|
@ -7,8 +7,7 @@ import re
|
||||||
import subliminal
|
import subliminal
|
||||||
from babelfish import Language
|
from babelfish import Language
|
||||||
|
|
||||||
from nzb2media import GETSUBS
|
from nzb2media.transcoder import GETSUBS, SLANGUAGES
|
||||||
from nzb2media import SLANGUAGES
|
|
||||||
from nzb2media.utils.files import list_media_files
|
from nzb2media.utils.files import list_media_files
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
40
nzb2media/synology.py
Normal file
40
nzb2media/synology.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from syno.downloadstation import DownloadStation
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
HOST = None
|
||||||
|
PORT = None
|
||||||
|
USERNAME = None
|
||||||
|
PASSWORD = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_syno(config):
|
||||||
|
global HOST
|
||||||
|
global PORT
|
||||||
|
global USERNAME
|
||||||
|
global PASSWORD
|
||||||
|
|
||||||
|
HOST = config['synoHost'] # localhost
|
||||||
|
PORT = int(config['synoPort'])
|
||||||
|
USERNAME = config['synoUSR'] # mysecretusr
|
||||||
|
PASSWORD = config['synoPWD'] # mysecretpwr
|
||||||
|
|
||||||
|
|
||||||
|
def configure_client():
|
||||||
|
agent = 'synology'
|
||||||
|
host = HOST
|
||||||
|
port = PORT
|
||||||
|
user = USERNAME
|
||||||
|
password = PASSWORD
|
||||||
|
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
||||||
|
try:
|
||||||
|
client = DownloadStation(host, port, user, password)
|
||||||
|
except Exception:
|
||||||
|
log.error('Failed to connect to synology')
|
||||||
|
else:
|
||||||
|
return client
|
|
@ -4,12 +4,25 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
import typing
|
import typing
|
||||||
|
from subprocess import call, DEVNULL, Popen
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import nzb2media
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
FFMPEG: pathlib.Path | None = None
|
||||||
|
FFPROBE: pathlib.Path | None = None
|
||||||
|
PAR2CMD: pathlib.Path | None = None
|
||||||
|
SEVENZIP: pathlib.Path | None = None
|
||||||
|
SHOWEXTRACT = 0
|
||||||
|
|
||||||
|
|
||||||
def in_path(name: str) -> pathlib.Path | None:
|
def in_path(name: str) -> pathlib.Path | None:
|
||||||
"""Find tool if its on the system loc."""
|
"""Find tool if its on the system loc."""
|
||||||
|
@ -112,3 +125,195 @@ def find_unzip(root: pathlib.Path | None = None) -> pathlib.Path | None:
|
||||||
log.debug(f'Failed to locate any of the following: {names}')
|
log.debug(f'Failed to locate any of the following: {names}')
|
||||||
log.warning('Transcoding of disk images and extraction zip files will not be possible!')
|
log.warning('Transcoding of disk images and extraction zip files will not be possible!')
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def configure_utility_locations():
|
||||||
|
# Setup FFMPEG, FFPROBE and SEVENZIP locations
|
||||||
|
global FFMPEG
|
||||||
|
global FFPROBE
|
||||||
|
global PAR2CMD
|
||||||
|
global SEVENZIP
|
||||||
|
FFMPEG = find_transcoder(FFMPEG_PATH)
|
||||||
|
FFPROBE = find_video_corruption_detector(FFMPEG_PATH)
|
||||||
|
PAR2CMD = find_archive_repairer()
|
||||||
|
SEVENZIP = find_unzip()
|
||||||
|
|
||||||
|
|
||||||
|
def extract(file_path, output_destination):
|
||||||
|
success = 0
|
||||||
|
# Using Windows
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
if not os.path.exists(nzb2media.tool.SEVENZIP):
|
||||||
|
log.error('EXTRACTOR: Could not find 7-zip, Exiting')
|
||||||
|
return False
|
||||||
|
wscriptlocation = os.path.join(os.environ['WINDIR'], 'system32', 'wscript.exe')
|
||||||
|
invislocation = os.path.join(nzb2media.APP_ROOT, 'nzb2media', 'extractor', 'bin', 'invisible.vbs')
|
||||||
|
cmd_7zip = [wscriptlocation, invislocation, str(nzb2media.tool.SHOWEXTRACT), nzb2media.tool.SEVENZIP, 'x', '-y']
|
||||||
|
ext_7zip = ['.rar', '.zip', '.tar.gz', 'tgz', '.tar.bz2', '.tbz', '.tar.lzma', '.tlz', '.7z', '.xz', '.gz']
|
||||||
|
extract_commands = dict.fromkeys(ext_7zip, cmd_7zip)
|
||||||
|
# Using unix
|
||||||
|
else:
|
||||||
|
required_cmds = ['unrar', 'unzip', 'tar', 'unxz', 'unlzma', '7zr', 'bunzip2', 'gunzip']
|
||||||
|
# ## Possible future suport:
|
||||||
|
# gunzip: gz (cmd will delete original archive)
|
||||||
|
# ## the following do not extract to destination dir
|
||||||
|
# '.xz': ['xz', '-d --keep'],
|
||||||
|
# '.lzma': ['xz', '-d --format=lzma --keep'],
|
||||||
|
# '.bz2': ['bzip2', '-d --keep']
|
||||||
|
extract_commands = {'.rar': ['unrar', 'x', '-o+', '-y'], '.tar': ['tar', '-xf'], '.zip': ['unzip'], '.tar.gz': ['tar', '-xzf'], '.tgz': ['tar', '-xzf'], '.tar.bz2': ['tar', '-xjf'], '.tbz': ['tar', '-xjf'], '.tar.lzma': ['tar', '--lzma', '-xf'], '.tlz': ['tar', '--lzma', '-xf'], '.tar.xz': ['tar', '--xz', '-xf'], '.txz': ['tar', '--xz', '-xf'], '.7z': ['7zr', 'x'], '.gz': ['gunzip']}
|
||||||
|
# Test command exists and if not, remove
|
||||||
|
if not os.getenv('TR_TORRENT_DIR'):
|
||||||
|
for cmd in required_cmds:
|
||||||
|
if call(['which', cmd], stdout=DEVNULL, stderr=DEVNULL):
|
||||||
|
# note, returns 0 if exists, or 1 if doesn't exist.
|
||||||
|
for key, val in extract_commands.items():
|
||||||
|
if cmd in val[0]:
|
||||||
|
if not call(['which', '7zr'], stdout=DEVNULL, stderr=DEVNULL):
|
||||||
|
# we do have '7zr'
|
||||||
|
extract_commands[key] = ['7zr', 'x', '-y']
|
||||||
|
elif not call(['which', '7z'], stdout=DEVNULL, stderr=DEVNULL):
|
||||||
|
# we do have '7z'
|
||||||
|
extract_commands[key] = ['7z', 'x', '-y']
|
||||||
|
elif not call(['which', '7za'], stdout=DEVNULL, stderr=DEVNULL):
|
||||||
|
# we do have '7za'
|
||||||
|
extract_commands[key] = ['7za', 'x', '-y']
|
||||||
|
else:
|
||||||
|
log.error(f'EXTRACTOR: {cmd} not found, disabling support for {key}')
|
||||||
|
del extract_commands[key]
|
||||||
|
else:
|
||||||
|
log.warning('EXTRACTOR: Cannot determine which tool to use when called from Transmission')
|
||||||
|
if not extract_commands:
|
||||||
|
log.warning('EXTRACTOR: No archive extracting programs found, plugin will be disabled')
|
||||||
|
ext = os.path.splitext(file_path)
|
||||||
|
cmd = []
|
||||||
|
if ext[1] in {'.gz', '.bz2', '.lzma'}:
|
||||||
|
# Check if this is a tar
|
||||||
|
if os.path.splitext(ext[0])[1] == '.tar':
|
||||||
|
cmd = extract_commands[f'.tar{ext[1]}']
|
||||||
|
else: # Try gunzip
|
||||||
|
cmd = extract_commands[ext[1]]
|
||||||
|
elif ext[1] in {'.1', '.01', '.001'} and os.path.splitext(ext[0])[1] in {'.rar', '.zip', '.7z'}:
|
||||||
|
cmd = extract_commands[os.path.splitext(ext[0])[1]]
|
||||||
|
elif ext[1] in {'.cb7', '.cba', '.cbr', '.cbt', '.cbz'}:
|
||||||
|
# don't extract these comic book archives.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if ext[1] in extract_commands:
|
||||||
|
cmd = extract_commands[ext[1]]
|
||||||
|
else:
|
||||||
|
log.debug(f'EXTRACTOR: Unknown file type: {ext[1]}')
|
||||||
|
return False
|
||||||
|
# Create outputDestination folder
|
||||||
|
nzb2media.make_dir(output_destination)
|
||||||
|
if nzb2media.PASSWORDS_FILE and os.path.isfile(os.path.normpath(nzb2media.PASSWORDS_FILE)):
|
||||||
|
with open(os.path.normpath(nzb2media.PASSWORDS_FILE), encoding='utf-8') as fin:
|
||||||
|
passwords = [line.strip() for line in fin]
|
||||||
|
else:
|
||||||
|
passwords = []
|
||||||
|
log.info(f'Extracting {file_path} to {output_destination}')
|
||||||
|
log.debug(f'Extracting {cmd} {file_path} {output_destination}')
|
||||||
|
orig_files = []
|
||||||
|
orig_dirs = []
|
||||||
|
for directory, subdirs, files in os.walk(output_destination):
|
||||||
|
for subdir in subdirs:
|
||||||
|
orig_dirs.append(os.path.join(directory, subdir))
|
||||||
|
for file in files:
|
||||||
|
orig_files.append(os.path.join(directory, file))
|
||||||
|
pwd = os.getcwd() # Get our Present Working Directory
|
||||||
|
# Not all unpack commands accept full paths, so just extract into this directory
|
||||||
|
os.chdir(output_destination)
|
||||||
|
try: # now works same for nt and *nix
|
||||||
|
info = None
|
||||||
|
cmd.append(file_path) # add filePath to final cmd arg.
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
info = subprocess.STARTUPINFO()
|
||||||
|
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
|
else:
|
||||||
|
cmd = NICENESS + cmd
|
||||||
|
cmd2 = cmd
|
||||||
|
if 'gunzip' not in cmd: # gunzip doesn't support password
|
||||||
|
cmd2.append('-p-') # don't prompt for password.
|
||||||
|
with Popen(cmd2, stdout=DEVNULL, stderr=DEVNULL, startupinfo=info) as proc:
|
||||||
|
res = proc.wait() # should extract files fine.
|
||||||
|
if not res: # Both Linux and Windows return 0 for successful.
|
||||||
|
log.info(f'EXTRACTOR: Extraction was successful for {file_path} to {output_destination}')
|
||||||
|
success = 1
|
||||||
|
elif len(passwords) > 0 and 'gunzip' not in cmd:
|
||||||
|
log.info('EXTRACTOR: Attempting to extract with passwords')
|
||||||
|
for password in passwords:
|
||||||
|
if not password:
|
||||||
|
continue # if edited in windows or otherwise if blank lines.
|
||||||
|
cmd2 = cmd
|
||||||
|
# append password here.
|
||||||
|
passcmd = f'-p{password}'
|
||||||
|
cmd2.append(passcmd)
|
||||||
|
with Popen(cmd2, stdout=DEVNULL, stderr=DEVNULL, startupinfo=info) as proc:
|
||||||
|
res = proc.wait() # should extract files fine.
|
||||||
|
if not res or (res >= 0 and platform == 'Windows'):
|
||||||
|
log.info(f'EXTRACTOR: Extraction was successful for {file_path} to {output_destination} using password: {password}')
|
||||||
|
success = 1
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
log.error(f'EXTRACTOR: Extraction failed for {file_path}. Could not call command {cmd}')
|
||||||
|
os.chdir(pwd)
|
||||||
|
return False
|
||||||
|
os.chdir(pwd) # Go back to our Original Working Directory
|
||||||
|
if success:
|
||||||
|
# sleep to let files finish writing to disk
|
||||||
|
sleep(3)
|
||||||
|
perms = stat.S_IMODE(os.lstat(os.path.split(file_path)[0]).st_mode)
|
||||||
|
for directory, subdirs, files in os.walk(output_destination):
|
||||||
|
for subdir in subdirs:
|
||||||
|
if not os.path.join(directory, subdir) in orig_files:
|
||||||
|
try:
|
||||||
|
os.chmod(os.path.join(directory, subdir), perms)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for file in files:
|
||||||
|
if not os.path.join(directory, file) in orig_files:
|
||||||
|
try:
|
||||||
|
shutil.copymode(file_path, os.path.join(directory, file))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
log.error(f'EXTRACTOR: Extraction failed for {file_path}. Result was {res}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def configure_niceness():
|
||||||
|
global NICENESS
|
||||||
|
try:
|
||||||
|
with subprocess.Popen(['nice'], stdout=DEVNULL, stderr=DEVNULL) as proc:
|
||||||
|
proc.communicate()
|
||||||
|
niceness = nzb2media.CFG['Posix']['niceness']
|
||||||
|
if len(niceness.split(',')) > 1: # Allow passing of absolute command, not just value.
|
||||||
|
NICENESS.extend(niceness.split(','))
|
||||||
|
else:
|
||||||
|
NICENESS.extend(['nice', f'-n{int(niceness)}'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
with subprocess.Popen(['ionice'], stdout=DEVNULL, stderr=DEVNULL) as proc:
|
||||||
|
proc.communicate()
|
||||||
|
try:
|
||||||
|
ionice = nzb2media.CFG['Posix']['ionice_class']
|
||||||
|
NICENESS.extend(['ionice', f'-c{int(ionice)}'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
if 'ionice' in NICENESS:
|
||||||
|
ionice = nzb2media.CFG['Posix']['ionice_classdata']
|
||||||
|
NICENESS.extend([f'-n{int(ionice)}'])
|
||||||
|
else:
|
||||||
|
NICENESS.extend(['ionice', f'-n{int(ionice)}'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
NICENESS: list[str] = []
|
||||||
|
FFMPEG_PATH: pathlib.Path | None = None
|
||||||
|
|
||||||
|
configure_niceness()
|
||||||
|
configure_utility_locations()
|
||||||
|
|
171
nzb2media/torrent.py
Normal file
171
nzb2media/torrent.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
import nzb2media
|
||||||
|
import nzb2media.deluge
|
||||||
|
import nzb2media.qbittorrent
|
||||||
|
import nzb2media.synology
|
||||||
|
import nzb2media.transmission
|
||||||
|
import nzb2media.utorrent
|
||||||
|
from nzb2media.deluge import configure_deluge
|
||||||
|
from nzb2media.qbittorrent import configure_qbittorrent
|
||||||
|
from nzb2media.synology import configure_syno
|
||||||
|
from nzb2media.transmission import configure_transmission
|
||||||
|
from nzb2media.utorrent import configure_utorrent
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
CLIENTS = ['transmission', 'deluge', 'utorrent', 'rtorrent', 'qbittorrent', 'other', 'manual']
|
||||||
|
CLIENT_AGENT = None
|
||||||
|
CLASS = None
|
||||||
|
CHMOD_DIRECTORY = None
|
||||||
|
DEFAULT_DIRECTORY = None
|
||||||
|
RESUME = None
|
||||||
|
RESUME_ON_FAILURE = None
|
||||||
|
|
||||||
|
torrent_clients = {
|
||||||
|
'deluge': nzb2media.deluge,
|
||||||
|
'qbittorrent': nzb2media.qbittorrent,
|
||||||
|
'transmission': nzb2media.transmission,
|
||||||
|
'utorrent': nzb2media.utorrent,
|
||||||
|
'synods': nzb2media.synology,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrents(config):
|
||||||
|
global CLIENT_AGENT
|
||||||
|
global DEFAULT_DIRECTORY
|
||||||
|
|
||||||
|
torrent_config = config['Torrent']
|
||||||
|
CLIENT_AGENT = torrent_config['clientAgent'] # utorrent | deluge | transmission | rtorrent | vuze | qbittorrent | synods | other
|
||||||
|
nzb2media.OUTPUT_DIRECTORY = torrent_config['outputDirectory'] # /abs/path/to/complete/
|
||||||
|
DEFAULT_DIRECTORY = torrent_config['default_downloadDirectory']
|
||||||
|
nzb2media.TORRENT_NO_MANUAL = int(torrent_config['no_manual'], 0)
|
||||||
|
configure_torrent_linking(torrent_config)
|
||||||
|
configure_flattening(torrent_config)
|
||||||
|
configure_torrent_deletion(torrent_config)
|
||||||
|
configure_torrent_categories(torrent_config)
|
||||||
|
configure_torrent_permissions(torrent_config)
|
||||||
|
configure_torrent_resuming(torrent_config)
|
||||||
|
configure_utorrent(torrent_config)
|
||||||
|
configure_transmission(torrent_config)
|
||||||
|
configure_deluge(torrent_config)
|
||||||
|
configure_qbittorrent(torrent_config)
|
||||||
|
configure_syno(torrent_config)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_linking(config):
|
||||||
|
nzb2media.USE_LINK = config['useLink'] # no | hard | sym
|
||||||
|
|
||||||
|
|
||||||
|
def configure_flattening(config):
|
||||||
|
global NO_FLATTEN
|
||||||
|
NO_FLATTEN = config['noFlatten']
|
||||||
|
if isinstance(NO_FLATTEN, str):
|
||||||
|
NO_FLATTEN = NO_FLATTEN.split(',')
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_categories(config):
|
||||||
|
nzb2media.CATEGORIES = config['categories'] # music,music_videos,pictures,software
|
||||||
|
if isinstance(nzb2media.CATEGORIES, str):
|
||||||
|
nzb2media.CATEGORIES = nzb2media.CATEGORIES.split(',')
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_resuming(config):
|
||||||
|
global RESUME_ON_FAILURE
|
||||||
|
global RESUME
|
||||||
|
RESUME_ON_FAILURE = int(config['resumeOnFailure'])
|
||||||
|
RESUME = int(config['resume'])
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_permissions(config):
|
||||||
|
global CHMOD_DIRECTORY
|
||||||
|
CHMOD_DIRECTORY = int(str(config['chmodDirectory']), 8)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_deletion(config):
|
||||||
|
nzb2media.DELETE_ORIGINAL = int(config['deleteOriginal'])
|
||||||
|
|
||||||
|
|
||||||
|
def configure_torrent_class():
|
||||||
|
# create torrent class
|
||||||
|
global CLASS
|
||||||
|
CLASS = create_torrent_class(CLIENT_AGENT)
|
||||||
|
|
||||||
|
|
||||||
|
def create_torrent_class(client_agent) -> object | None:
|
||||||
|
if nzb2media.APP_NAME != 'TorrentToMedia.py':
|
||||||
|
return None # Skip loading Torrent for NZBs.
|
||||||
|
try:
|
||||||
|
agent = torrent_clients[client_agent]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
nzb2media.deluge.configure_client()
|
||||||
|
return agent.configure_client()
|
||||||
|
|
||||||
|
|
||||||
|
def pause_torrent(client_agent, input_hash, input_id, input_name):
|
||||||
|
log.debug(f'Stopping torrent {input_name} in {client_agent} while processing')
|
||||||
|
try:
|
||||||
|
if client_agent == 'utorrent' and CLASS:
|
||||||
|
CLASS.stop(input_hash)
|
||||||
|
if client_agent == 'transmission' and CLASS:
|
||||||
|
CLASS.stop_torrent(input_id)
|
||||||
|
if client_agent == 'synods' and CLASS:
|
||||||
|
CLASS.pause_task(input_id)
|
||||||
|
if client_agent == 'deluge' and CLASS:
|
||||||
|
CLASS.core.pause_torrent([input_id])
|
||||||
|
if client_agent == 'qbittorrent' and CLASS:
|
||||||
|
CLASS.pause(input_hash)
|
||||||
|
time.sleep(5)
|
||||||
|
except Exception:
|
||||||
|
log.warning(f'Failed to stop torrent {input_name} in {client_agent}')
|
||||||
|
|
||||||
|
|
||||||
|
def resume_torrent(client_agent, input_hash, input_id, input_name):
|
||||||
|
if RESUME != 1:
|
||||||
|
return
|
||||||
|
log.debug(f'Starting torrent {input_name} in {client_agent}')
|
||||||
|
try:
|
||||||
|
if client_agent == 'utorrent' and CLASS:
|
||||||
|
CLASS.start(input_hash)
|
||||||
|
if client_agent == 'transmission' and CLASS:
|
||||||
|
CLASS.start_torrent(input_id)
|
||||||
|
if client_agent == 'synods' and CLASS:
|
||||||
|
CLASS.resume_task(input_id)
|
||||||
|
if client_agent == 'deluge' and CLASS:
|
||||||
|
CLASS.core.resume_torrent([input_id])
|
||||||
|
if client_agent == 'qbittorrent' and CLASS:
|
||||||
|
CLASS.resume(input_hash)
|
||||||
|
time.sleep(5)
|
||||||
|
except Exception:
|
||||||
|
log.warning(f'Failed to start torrent {input_name} in {client_agent}')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_torrent(client_agent, input_hash, input_id, input_name):
|
||||||
|
if nzb2media.DELETE_ORIGINAL == 1 or nzb2media.USE_LINK == 'move':
|
||||||
|
log.debug(f'Deleting torrent {input_name} from {client_agent}')
|
||||||
|
try:
|
||||||
|
if client_agent == 'utorrent' and CLASS:
|
||||||
|
CLASS.removedata(input_hash)
|
||||||
|
CLASS.remove(input_hash)
|
||||||
|
if client_agent == 'transmission' and CLASS:
|
||||||
|
CLASS.remove_torrent(input_id, True)
|
||||||
|
if client_agent == 'synods' and CLASS:
|
||||||
|
CLASS.delete_task(input_id)
|
||||||
|
if client_agent == 'deluge' and CLASS:
|
||||||
|
CLASS.core.remove_torrent(input_id, True)
|
||||||
|
if client_agent == 'qbittorrent' and CLASS:
|
||||||
|
CLASS.delete_permanently(input_hash)
|
||||||
|
time.sleep(5)
|
||||||
|
except Exception:
|
||||||
|
log.warning(f'Failed to delete torrent {input_name} in {client_agent}')
|
||||||
|
else:
|
||||||
|
resume_torrent(client_agent, input_hash, input_id, input_name)
|
||||||
|
|
||||||
|
|
||||||
|
NO_FLATTEN: list[str] = []
|
|
@ -1,91 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
from nzb2media.utils.torrent import create_torrent_class
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrents(config):
|
|
||||||
torrent_config = config['Torrent']
|
|
||||||
nzb2media.TORRENT_CLIENT_AGENT = torrent_config['clientAgent'] # utorrent | deluge | transmission | rtorrent | vuze | qbittorrent | synods | other
|
|
||||||
nzb2media.OUTPUT_DIRECTORY = torrent_config['outputDirectory'] # /abs/path/to/complete/
|
|
||||||
nzb2media.TORRENT_DEFAULT_DIRECTORY = torrent_config['default_downloadDirectory']
|
|
||||||
nzb2media.TORRENT_NO_MANUAL = int(torrent_config['no_manual'], 0)
|
|
||||||
configure_torrent_linking(torrent_config)
|
|
||||||
configure_flattening(torrent_config)
|
|
||||||
configure_torrent_deletion(torrent_config)
|
|
||||||
configure_torrent_categories(torrent_config)
|
|
||||||
configure_torrent_permissions(torrent_config)
|
|
||||||
configure_torrent_resuming(torrent_config)
|
|
||||||
configure_utorrent(torrent_config)
|
|
||||||
configure_transmission(torrent_config)
|
|
||||||
configure_deluge(torrent_config)
|
|
||||||
configure_qbittorrent(torrent_config)
|
|
||||||
configure_syno(torrent_config)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_linking(config):
|
|
||||||
nzb2media.USE_LINK = config['useLink'] # no | hard | sym
|
|
||||||
|
|
||||||
|
|
||||||
def configure_flattening(config):
|
|
||||||
nzb2media.NOFLATTEN = config['noFlatten']
|
|
||||||
if isinstance(nzb2media.NOFLATTEN, str):
|
|
||||||
nzb2media.NOFLATTEN = nzb2media.NOFLATTEN.split(',')
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_categories(config):
|
|
||||||
nzb2media.CATEGORIES = config['categories'] # music,music_videos,pictures,software
|
|
||||||
if isinstance(nzb2media.CATEGORIES, str):
|
|
||||||
nzb2media.CATEGORIES = nzb2media.CATEGORIES.split(',')
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_resuming(config):
|
|
||||||
nzb2media.TORRENT_RESUME_ON_FAILURE = int(config['resumeOnFailure'])
|
|
||||||
nzb2media.TORRENT_RESUME = int(config['resume'])
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_permissions(config):
|
|
||||||
nzb2media.TORRENT_CHMOD_DIRECTORY = int(str(config['chmodDirectory']), 8)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_deletion(config):
|
|
||||||
nzb2media.DELETE_ORIGINAL = int(config['deleteOriginal'])
|
|
||||||
|
|
||||||
|
|
||||||
def configure_utorrent(config):
|
|
||||||
nzb2media.UTORRENT_WEB_UI = config['uTorrentWEBui'] # http://localhost:8090/gui/
|
|
||||||
nzb2media.UTORRENT_USER = config['uTorrentUSR'] # mysecretusr
|
|
||||||
nzb2media.UTORRENT_PASSWORD = config['uTorrentPWD'] # mysecretpwr
|
|
||||||
|
|
||||||
|
|
||||||
def configure_transmission(config):
|
|
||||||
nzb2media.TRANSMISSION_HOST = config['TransmissionHost'] # localhost
|
|
||||||
nzb2media.TRANSMISSION_PORT = int(config['TransmissionPort'])
|
|
||||||
nzb2media.TRANSMISSION_USER = config['TransmissionUSR'] # mysecretusr
|
|
||||||
nzb2media.TRANSMISSION_PASSWORD = config['TransmissionPWD'] # mysecretpwr
|
|
||||||
|
|
||||||
|
|
||||||
def configure_syno(config):
|
|
||||||
nzb2media.SYNO_HOST = config['synoHost'] # localhost
|
|
||||||
nzb2media.SYNO_PORT = int(config['synoPort'])
|
|
||||||
nzb2media.SYNO_USER = config['synoUSR'] # mysecretusr
|
|
||||||
nzb2media.SYNO_PASSWORD = config['synoPWD'] # mysecretpwr
|
|
||||||
|
|
||||||
|
|
||||||
def configure_deluge(config):
|
|
||||||
nzb2media.DELUGE_HOST = config['DelugeHost'] # localhost
|
|
||||||
nzb2media.DELUGE_PORT = int(config['DelugePort']) # 8084
|
|
||||||
nzb2media.DELUGE_USER = config['DelugeUSR'] # mysecretusr
|
|
||||||
nzb2media.DELUGE_PASSWORD = config['DelugePWD'] # mysecretpwr
|
|
||||||
|
|
||||||
|
|
||||||
def configure_qbittorrent(config):
|
|
||||||
nzb2media.QBITTORRENT_HOST = config['qBittorrentHost'] # localhost
|
|
||||||
nzb2media.QBITTORRENT_PORT = int(config['qBittorrentPort']) # 8080
|
|
||||||
nzb2media.QBITTORRENT_USER = config['qBittorrentUSR'] # mysecretusr
|
|
||||||
nzb2media.QBITTORRENT_PASSWORD = config['qBittorrentPWD'] # mysecretpwr
|
|
||||||
|
|
||||||
|
|
||||||
def configure_torrent_class():
|
|
||||||
# create torrent class
|
|
||||||
nzb2media.TORRENT_CLASS = create_torrent_class(nzb2media.TORRENT_CLIENT_AGENT)
|
|
|
@ -1,26 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from deluge_client import DelugeRPCClient
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger()
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def configure_client():
|
|
||||||
agent = 'deluge'
|
|
||||||
host = nzb2media.DELUGE_HOST
|
|
||||||
port = nzb2media.DELUGE_PORT
|
|
||||||
user = nzb2media.DELUGE_USER
|
|
||||||
password = nzb2media.DELUGE_PASSWORD
|
|
||||||
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
|
||||||
client = DelugeRPCClient(host, port, user, password)
|
|
||||||
try:
|
|
||||||
client.connect()
|
|
||||||
except Exception:
|
|
||||||
log.error('Failed to connect to Deluge')
|
|
||||||
else:
|
|
||||||
return client
|
|
|
@ -1,24 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from syno.downloadstation import DownloadStation
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_client():
|
|
||||||
agent = 'synology'
|
|
||||||
host = nzb2media.SYNO_HOST
|
|
||||||
port = nzb2media.SYNO_PORT
|
|
||||||
user = nzb2media.SYNO_USER
|
|
||||||
password = nzb2media.SYNO_PASSWORD
|
|
||||||
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
|
||||||
try:
|
|
||||||
client = DownloadStation(host, port, user, password)
|
|
||||||
except Exception:
|
|
||||||
log.error('Failed to connect to synology')
|
|
||||||
else:
|
|
||||||
return client
|
|
|
@ -1,25 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from transmissionrpc.client import Client as TransmissionClient
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def configure_client():
|
|
||||||
agent = 'transmission'
|
|
||||||
host = nzb2media.TRANSMISSION_HOST
|
|
||||||
port = nzb2media.TRANSMISSION_PORT
|
|
||||||
user = nzb2media.TRANSMISSION_USER
|
|
||||||
password = nzb2media.TRANSMISSION_PASSWORD
|
|
||||||
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
|
||||||
try:
|
|
||||||
client = TransmissionClient(host, port, user, password)
|
|
||||||
except Exception:
|
|
||||||
log.error('Failed to connect to Transmission')
|
|
||||||
else:
|
|
||||||
return client
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
@ -17,17 +18,63 @@ from subprocess import PIPE
|
||||||
from babelfish import Language
|
from babelfish import Language
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
from nzb2media.utils.files import list_media_files
|
||||||
from nzb2media.utils.paths import make_dir
|
from nzb2media.utils.paths import make_dir
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
__author__ = 'Justin'
|
|
||||||
|
MOUNTED = None
|
||||||
|
GETSUBS = False
|
||||||
|
TRANSCODE = None
|
||||||
|
CONCAT = None
|
||||||
|
DUPLICATE = None
|
||||||
|
IGNOREEXTENSIONS = []
|
||||||
|
VEXTENSION = None
|
||||||
|
OUTPUTVIDEOPATH = None
|
||||||
|
PROCESSOUTPUT = False
|
||||||
|
GENERALOPTS: list[str] = []
|
||||||
|
OTHEROPTS: list[str] = []
|
||||||
|
ALANGUAGE = None
|
||||||
|
AINCLUDE = False
|
||||||
|
SLANGUAGES: list[str] = []
|
||||||
|
SINCLUDE = False
|
||||||
|
SUBSDIR = None
|
||||||
|
ALLOWSUBS = False
|
||||||
|
SEXTRACT = False
|
||||||
|
SEMBED = False
|
||||||
|
BURN = False
|
||||||
|
DEFAULTS = None
|
||||||
|
VCODEC = None
|
||||||
|
VCODEC_ALLOW = []
|
||||||
|
VPRESET = None
|
||||||
|
VFRAMERATE = None
|
||||||
|
VBITRATE = None
|
||||||
|
VLEVEL = None
|
||||||
|
VCRF = None
|
||||||
|
VRESOLUTION = None
|
||||||
|
ACODEC = None
|
||||||
|
ACODEC_ALLOW = []
|
||||||
|
ACHANNELS = None
|
||||||
|
ABITRATE = None
|
||||||
|
ACODEC2 = None
|
||||||
|
ACODEC2_ALLOW: list[str] = []
|
||||||
|
ACHANNELS2 = None
|
||||||
|
ABITRATE2 = None
|
||||||
|
ACODEC3 = None
|
||||||
|
ACODEC3_ALLOW = []
|
||||||
|
ACHANNELS3 = None
|
||||||
|
ABITRATE3 = None
|
||||||
|
SCODEC = None
|
||||||
|
OUTPUTFASTSTART = None
|
||||||
|
OUTPUTQUALITYPERCENT = None
|
||||||
|
HWACCEL = False
|
||||||
|
|
||||||
|
|
||||||
def is_video_good(video: pathlib.Path, status, require_lan=None):
|
def is_video_good(video: pathlib.Path, status, require_lan=None):
|
||||||
file_ext = video.suffix
|
file_ext = video.suffix
|
||||||
disable = False
|
disable = False
|
||||||
if file_ext not in nzb2media.MEDIA_CONTAINER or not nzb2media.FFPROBE or not nzb2media.CHECK_MEDIA or file_ext in {'.iso'} or (status > 0 and nzb2media.NOEXTRACTFAILED):
|
if file_ext not in nzb2media.MEDIA_CONTAINER or not nzb2media.tool.FFPROBE or not nzb2media.CHECK_MEDIA or file_ext in {'.iso'} or (status > 0 and nzb2media.NOEXTRACTFAILED):
|
||||||
disable = True
|
disable = True
|
||||||
else:
|
else:
|
||||||
test_details, res = get_video_details(nzb2media.TEST_FILE)
|
test_details, res = get_video_details(nzb2media.TEST_FILE)
|
||||||
|
@ -73,7 +120,7 @@ def zip_out(file, img):
|
||||||
if os.path.isfile(file):
|
if os.path.isfile(file):
|
||||||
cmd = ['cat', file]
|
cmd = ['cat', file]
|
||||||
else:
|
else:
|
||||||
cmd = [os.fspath(nzb2media.SEVENZIP), '-so', 'e', img, file]
|
cmd = [os.fspath(nzb2media.tool.SEVENZIP), '-so', 'e', img, file]
|
||||||
try:
|
try:
|
||||||
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
||||||
return proc
|
return proc
|
||||||
|
@ -86,13 +133,13 @@ def get_video_details(videofile, img=None):
|
||||||
video_details = {}
|
video_details = {}
|
||||||
result = 1
|
result = 1
|
||||||
file = videofile
|
file = videofile
|
||||||
if not nzb2media.FFPROBE:
|
if not nzb2media.tool.FFPROBE:
|
||||||
return video_details, result
|
return video_details, result
|
||||||
print_format = '-of' if 'avprobe' in nzb2media.FFPROBE.name else '-print_format'
|
print_format = '-of' if 'avprobe' in nzb2media.tool.FFPROBE.name else '-print_format'
|
||||||
try:
|
try:
|
||||||
if img:
|
if img:
|
||||||
videofile = '-'
|
videofile = '-'
|
||||||
command = [os.fspath(nzb2media.FFPROBE), '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', '-show_error', videofile]
|
command = [os.fspath(nzb2media.tool.FFPROBE), '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', '-show_error', videofile]
|
||||||
print_cmd(command)
|
print_cmd(command)
|
||||||
if img:
|
if img:
|
||||||
procin = zip_out(file, img)
|
procin = zip_out(file, img)
|
||||||
|
@ -107,7 +154,7 @@ def get_video_details(videofile, img=None):
|
||||||
video_details = json.loads(proc_out.decode())
|
video_details = json.loads(proc_out.decode())
|
||||||
except Exception:
|
except Exception:
|
||||||
try: # try this again without -show error in case of ffmpeg limitation
|
try: # try this again without -show error in case of ffmpeg limitation
|
||||||
command = [os.fspath(nzb2media.FFPROBE), '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', videofile]
|
command = [os.fspath(nzb2media.tool.FFPROBE), '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', videofile]
|
||||||
print_cmd(command)
|
print_cmd(command)
|
||||||
if img:
|
if img:
|
||||||
procin = zip_out(file, img)
|
procin = zip_out(file, img)
|
||||||
|
@ -140,6 +187,7 @@ def check_vid_file(video_details, result):
|
||||||
|
|
||||||
|
|
||||||
def build_commands(file, new_dir, movie_name):
|
def build_commands(file, new_dir, movie_name):
|
||||||
|
global VEXTENSION
|
||||||
if isinstance(file, str):
|
if isinstance(file, str):
|
||||||
input_file = file
|
input_file = file
|
||||||
if 'concat:' in file:
|
if 'concat:' in file:
|
||||||
|
@ -148,14 +196,14 @@ def build_commands(file, new_dir, movie_name):
|
||||||
directory, name = os.path.split(file)
|
directory, name = os.path.split(file)
|
||||||
name, ext = os.path.splitext(name)
|
name, ext = os.path.splitext(name)
|
||||||
check = re.match('VTS_([0-9][0-9])_[0-9]+', name)
|
check = re.match('VTS_([0-9][0-9])_[0-9]+', name)
|
||||||
if check and nzb2media.CONCAT:
|
if check and CONCAT:
|
||||||
name = movie_name
|
name = movie_name
|
||||||
elif check:
|
elif check:
|
||||||
name = f'{movie_name}.cd{check.groups()[0]}'
|
name = f'{movie_name}.cd{check.groups()[0]}'
|
||||||
elif nzb2media.CONCAT and re.match('(.+)[cC][dD][0-9]', name):
|
elif CONCAT and re.match('(.+)[cC][dD][0-9]', name):
|
||||||
name = re.sub('([ ._=:-]+[cC][dD][0-9])', '', name)
|
name = re.sub('([ ._=:-]+[cC][dD][0-9])', '', name)
|
||||||
if ext == nzb2media.VEXTENSION and new_dir == directory: # we need to change the name to prevent overwriting itself.
|
if ext == VEXTENSION and new_dir == directory: # we need to change the name to prevent overwriting itself.
|
||||||
nzb2media.VEXTENSION = f'-transcoded{nzb2media.VEXTENSION}' # adds '-transcoded.ext'
|
VEXTENSION = f'-transcoded{VEXTENSION}' # adds '-transcoded.ext'
|
||||||
new_file = file
|
new_file = file
|
||||||
else:
|
else:
|
||||||
img, data = next(file.items())
|
img, data = next(file.items())
|
||||||
|
@ -172,7 +220,7 @@ def build_commands(file, new_dir, movie_name):
|
||||||
video_details, result = get_video_details(data['files'][0], img)
|
video_details, result = get_video_details(data['files'][0], img)
|
||||||
input_file = '-'
|
input_file = '-'
|
||||||
file = '-'
|
file = '-'
|
||||||
newfile_path = os.path.normpath(os.path.join(new_dir, name) + nzb2media.VEXTENSION)
|
newfile_path = os.path.normpath(os.path.join(new_dir, name) + VEXTENSION)
|
||||||
map_cmd = []
|
map_cmd = []
|
||||||
video_cmd = []
|
video_cmd = []
|
||||||
audio_cmd = []
|
audio_cmd = []
|
||||||
|
@ -186,63 +234,63 @@ def build_commands(file, new_dir, movie_name):
|
||||||
audio_streams = []
|
audio_streams = []
|
||||||
sub_streams = []
|
sub_streams = []
|
||||||
map_cmd.extend(['-map', '0'])
|
map_cmd.extend(['-map', '0'])
|
||||||
if nzb2media.VCODEC:
|
if VCODEC:
|
||||||
video_cmd.extend(['-c:v', nzb2media.VCODEC])
|
video_cmd.extend(['-c:v', VCODEC])
|
||||||
if nzb2media.VCODEC == 'libx264' and nzb2media.VPRESET:
|
if VCODEC == 'libx264' and VPRESET:
|
||||||
video_cmd.extend(['-pre', nzb2media.VPRESET])
|
video_cmd.extend(['-pre', VPRESET])
|
||||||
else:
|
else:
|
||||||
video_cmd.extend(['-c:v', 'copy'])
|
video_cmd.extend(['-c:v', 'copy'])
|
||||||
if nzb2media.VFRAMERATE:
|
if VFRAMERATE:
|
||||||
video_cmd.extend(['-r', str(nzb2media.VFRAMERATE)])
|
video_cmd.extend(['-r', str(VFRAMERATE)])
|
||||||
if nzb2media.VBITRATE:
|
if VBITRATE:
|
||||||
video_cmd.extend(['-b:v', str(nzb2media.VBITRATE)])
|
video_cmd.extend(['-b:v', str(VBITRATE)])
|
||||||
if nzb2media.VRESOLUTION:
|
if VRESOLUTION:
|
||||||
video_cmd.extend(['-vf', f'scale={nzb2media.VRESOLUTION}'])
|
video_cmd.extend(['-vf', f'scale={VRESOLUTION}'])
|
||||||
if nzb2media.VPRESET:
|
if VPRESET:
|
||||||
video_cmd.extend(['-preset', nzb2media.VPRESET])
|
video_cmd.extend(['-preset', VPRESET])
|
||||||
if nzb2media.VCRF:
|
if VCRF:
|
||||||
video_cmd.extend(['-crf', str(nzb2media.VCRF)])
|
video_cmd.extend(['-crf', str(VCRF)])
|
||||||
if nzb2media.VLEVEL:
|
if VLEVEL:
|
||||||
video_cmd.extend(['-level', str(nzb2media.VLEVEL)])
|
video_cmd.extend(['-level', str(VLEVEL)])
|
||||||
if nzb2media.ACODEC:
|
if ACODEC:
|
||||||
audio_cmd.extend(['-c:a', nzb2media.ACODEC])
|
audio_cmd.extend(['-c:a', ACODEC])
|
||||||
if nzb2media.ACODEC in {'aac', 'dts'}:
|
if ACODEC in {'aac', 'dts'}:
|
||||||
# Allow users to use the experimental AAC codec that's built into recent versions of ffmpeg
|
# Allow users to use the experimental AAC codec that's built into recent versions of ffmpeg
|
||||||
audio_cmd.extend(['-strict', '-2'])
|
audio_cmd.extend(['-strict', '-2'])
|
||||||
else:
|
else:
|
||||||
audio_cmd.extend(['-c:a', 'copy'])
|
audio_cmd.extend(['-c:a', 'copy'])
|
||||||
if nzb2media.ACHANNELS:
|
if ACHANNELS:
|
||||||
audio_cmd.extend(['-ac', str(nzb2media.ACHANNELS)])
|
audio_cmd.extend(['-ac', str(ACHANNELS)])
|
||||||
if nzb2media.ABITRATE:
|
if ABITRATE:
|
||||||
audio_cmd.extend(['-b:a', str(nzb2media.ABITRATE)])
|
audio_cmd.extend(['-b:a', str(ABITRATE)])
|
||||||
if nzb2media.OUTPUTQUALITYPERCENT:
|
if OUTPUTQUALITYPERCENT:
|
||||||
audio_cmd.extend(['-q:a', str(nzb2media.OUTPUTQUALITYPERCENT)])
|
audio_cmd.extend(['-q:a', str(OUTPUTQUALITYPERCENT)])
|
||||||
if nzb2media.SCODEC and nzb2media.ALLOWSUBS:
|
if SCODEC and ALLOWSUBS:
|
||||||
sub_cmd.extend(['-c:s', nzb2media.SCODEC])
|
sub_cmd.extend(['-c:s', SCODEC])
|
||||||
elif nzb2media.ALLOWSUBS: # Not every subtitle codec can be used for every video container format!
|
elif ALLOWSUBS: # Not every subtitle codec can be used for every video container format!
|
||||||
sub_cmd.extend(['-c:s', 'copy'])
|
sub_cmd.extend(['-c:s', 'copy'])
|
||||||
else: # http://en.wikibooks.org/wiki/FFMPEG_An_Intermediate_Guide/subtitle_options
|
else: # http://en.wikibooks.org/wiki/FFMPEG_An_Intermediate_Guide/subtitle_options
|
||||||
sub_cmd.extend(['-sn']) # Don't copy the subtitles over
|
sub_cmd.extend(['-sn']) # Don't copy the subtitles over
|
||||||
if nzb2media.OUTPUTFASTSTART:
|
if OUTPUTFASTSTART:
|
||||||
other_cmd.extend(['-movflags', '+faststart'])
|
other_cmd.extend(['-movflags', '+faststart'])
|
||||||
else:
|
else:
|
||||||
video_streams = [item for item in video_details['streams'] if item['codec_type'] == 'video']
|
video_streams = [item for item in video_details['streams'] if item['codec_type'] == 'video']
|
||||||
audio_streams = [item for item in video_details['streams'] if item['codec_type'] == 'audio']
|
audio_streams = [item for item in video_details['streams'] if item['codec_type'] == 'audio']
|
||||||
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle']
|
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle']
|
||||||
if nzb2media.VEXTENSION not in ['.mkv', '.mpegts']:
|
if VEXTENSION not in ['.mkv', '.mpegts']:
|
||||||
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
||||||
for video in video_streams:
|
for video in video_streams:
|
||||||
codec = video['codec_name']
|
codec = video['codec_name']
|
||||||
frame_rate = video.get('avg_frame_rate', 0)
|
frame_rate = video.get('avg_frame_rate', 0)
|
||||||
width = video.get('width', 0)
|
width = video.get('width', 0)
|
||||||
height = video.get('height', 0)
|
height = video.get('height', 0)
|
||||||
scale = nzb2media.VRESOLUTION
|
scale = VRESOLUTION
|
||||||
if codec in nzb2media.VCODEC_ALLOW or not nzb2media.VCODEC:
|
if codec in VCODEC_ALLOW or not VCODEC:
|
||||||
video_cmd.extend(['-c:v', 'copy'])
|
video_cmd.extend(['-c:v', 'copy'])
|
||||||
else:
|
else:
|
||||||
video_cmd.extend(['-c:v', nzb2media.VCODEC])
|
video_cmd.extend(['-c:v', VCODEC])
|
||||||
if nzb2media.VFRAMERATE and not nzb2media.VFRAMERATE * 0.999 <= frame_rate <= nzb2media.VFRAMERATE * 1.001:
|
if VFRAMERATE and not VFRAMERATE * 0.999 <= frame_rate <= VFRAMERATE * 1.001:
|
||||||
video_cmd.extend(['-r', str(nzb2media.VFRAMERATE)])
|
video_cmd.extend(['-r', str(VFRAMERATE)])
|
||||||
if scale:
|
if scale:
|
||||||
w_scale = width / float(scale.split(':')[0])
|
w_scale = width / float(scale.split(':')[0])
|
||||||
h_scale = height / float(scale.split(':')[1])
|
h_scale = height / float(scale.split(':')[1])
|
||||||
|
@ -258,18 +306,18 @@ def build_commands(file, new_dir, movie_name):
|
||||||
scale = f'{_width}:{_height}'
|
scale = f'{_width}:{_height}'
|
||||||
if h_scale > 1:
|
if h_scale > 1:
|
||||||
video_cmd.extend(['-vf', f'scale={scale}'])
|
video_cmd.extend(['-vf', f'scale={scale}'])
|
||||||
if nzb2media.VBITRATE:
|
if VBITRATE:
|
||||||
video_cmd.extend(['-b:v', str(nzb2media.VBITRATE)])
|
video_cmd.extend(['-b:v', str(VBITRATE)])
|
||||||
if nzb2media.VPRESET:
|
if VPRESET:
|
||||||
video_cmd.extend(['-preset', nzb2media.VPRESET])
|
video_cmd.extend(['-preset', VPRESET])
|
||||||
if nzb2media.VCRF:
|
if VCRF:
|
||||||
video_cmd.extend(['-crf', str(nzb2media.VCRF)])
|
video_cmd.extend(['-crf', str(VCRF)])
|
||||||
if nzb2media.VLEVEL:
|
if VLEVEL:
|
||||||
video_cmd.extend(['-level', str(nzb2media.VLEVEL)])
|
video_cmd.extend(['-level', str(VLEVEL)])
|
||||||
no_copy = ['-vf', '-r', '-crf', '-level', '-preset', '-b:v']
|
no_copy = ['-vf', '-r', '-crf', '-level', '-preset', '-b:v']
|
||||||
if video_cmd[1] == 'copy' and any(i in video_cmd for i in no_copy):
|
if video_cmd[1] == 'copy' and any(i in video_cmd for i in no_copy):
|
||||||
video_cmd[1] = nzb2media.VCODEC
|
video_cmd[1] = VCODEC
|
||||||
if nzb2media.VCODEC == 'copy': # force copy. therefore ignore all other video transcoding.
|
if VCODEC == 'copy': # force copy. therefore ignore all other video transcoding.
|
||||||
video_cmd = ['-c:v', 'copy']
|
video_cmd = ['-c:v', 'copy']
|
||||||
_index = video['index']
|
_index = video['index']
|
||||||
map_cmd.extend(['-map', f'0:{_index}'])
|
map_cmd.extend(['-map', f'0:{_index}'])
|
||||||
|
@ -287,19 +335,19 @@ def build_commands(file, new_dir, movie_name):
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
audio1 = [item for item in audio_streams if item['tags']['language'] == nzb2media.ALANGUAGE]
|
audio1 = [item for item in audio_streams if item['tags']['language'] == ALANGUAGE]
|
||||||
except Exception: # no language tags. Assume only 1 language.
|
except Exception: # no language tags. Assume only 1 language.
|
||||||
audio1 = audio_streams
|
audio1 = audio_streams
|
||||||
try:
|
try:
|
||||||
audio2 = [item for item in audio1 if item['codec_name'] in nzb2media.ACODEC_ALLOW]
|
audio2 = [item for item in audio1 if item['codec_name'] in ACODEC_ALLOW]
|
||||||
except Exception:
|
except Exception:
|
||||||
audio2 = []
|
audio2 = []
|
||||||
try:
|
try:
|
||||||
audio3 = [item for item in audio_streams if item['tags']['language'] != nzb2media.ALANGUAGE]
|
audio3 = [item for item in audio_streams if item['tags']['language'] != ALANGUAGE]
|
||||||
except Exception:
|
except Exception:
|
||||||
audio3 = []
|
audio3 = []
|
||||||
try:
|
try:
|
||||||
audio4 = [item for item in audio3 if item['codec_name'] in nzb2media.ACODEC_ALLOW]
|
audio4 = [item for item in audio3 if item['codec_name'] in ACODEC_ALLOW]
|
||||||
except Exception:
|
except Exception:
|
||||||
audio4 = []
|
audio4 = []
|
||||||
if audio2: # right (or only) language and codec...
|
if audio2: # right (or only) language and codec...
|
||||||
|
@ -315,7 +363,7 @@ def build_commands(file, new_dir, movie_name):
|
||||||
a_mapped.extend([audio1[0]['index']])
|
a_mapped.extend([audio1[0]['index']])
|
||||||
bitrate = int(float(audio1[0].get('bit_rate', 0))) / 1000
|
bitrate = int(float(audio1[0].get('bit_rate', 0))) / 1000
|
||||||
channels = int(float(audio1[0].get('channels', 0)))
|
channels = int(float(audio1[0].get('channels', 0)))
|
||||||
audio_cmd.extend([f'-c:a:{used_audio}', nzb2media.ACODEC if nzb2media.ACODEC else 'copy'])
|
audio_cmd.extend([f'-c:a:{used_audio}', ACODEC if ACODEC else 'copy'])
|
||||||
elif audio4:
|
elif audio4:
|
||||||
# wrong language, right codec.
|
# wrong language, right codec.
|
||||||
_index = audio4[0]['index']
|
_index = audio4[0]['index']
|
||||||
|
@ -331,29 +379,29 @@ def build_commands(file, new_dir, movie_name):
|
||||||
a_mapped.extend([audio3[0]['index']])
|
a_mapped.extend([audio3[0]['index']])
|
||||||
bitrate = int(float(audio3[0].get('bit_rate', 0))) / 1000
|
bitrate = int(float(audio3[0].get('bit_rate', 0))) / 1000
|
||||||
channels = int(float(audio3[0].get('channels', 0)))
|
channels = int(float(audio3[0].get('channels', 0)))
|
||||||
audio_cmd.extend([f'-c:a:{used_audio}', nzb2media.ACODEC if nzb2media.ACODEC else 'copy'])
|
audio_cmd.extend([f'-c:a:{used_audio}', ACODEC if ACODEC else 'copy'])
|
||||||
if nzb2media.ACHANNELS and channels and channels > nzb2media.ACHANNELS:
|
if ACHANNELS and channels and channels > ACHANNELS:
|
||||||
audio_cmd.extend([f'-ac:a:{used_audio}', str(nzb2media.ACHANNELS)])
|
audio_cmd.extend([f'-ac:a:{used_audio}', str(ACHANNELS)])
|
||||||
if audio_cmd[1] == 'copy':
|
if audio_cmd[1] == 'copy':
|
||||||
audio_cmd[1] = nzb2media.ACODEC
|
audio_cmd[1] = ACODEC
|
||||||
if nzb2media.ABITRATE and not nzb2media.ABITRATE * 0.9 < bitrate < nzb2media.ABITRATE * 1.1:
|
if ABITRATE and not ABITRATE * 0.9 < bitrate < ABITRATE * 1.1:
|
||||||
audio_cmd.extend([f'-b:a:{used_audio}', str(nzb2media.ABITRATE)])
|
audio_cmd.extend([f'-b:a:{used_audio}', str(ABITRATE)])
|
||||||
if audio_cmd[1] == 'copy':
|
if audio_cmd[1] == 'copy':
|
||||||
audio_cmd[1] = nzb2media.ACODEC
|
audio_cmd[1] = ACODEC
|
||||||
if nzb2media.OUTPUTQUALITYPERCENT:
|
if OUTPUTQUALITYPERCENT:
|
||||||
audio_cmd.extend([f'-q:a:{used_audio}', str(nzb2media.OUTPUTQUALITYPERCENT)])
|
audio_cmd.extend([f'-q:a:{used_audio}', str(OUTPUTQUALITYPERCENT)])
|
||||||
if audio_cmd[1] == 'copy':
|
if audio_cmd[1] == 'copy':
|
||||||
audio_cmd[1] = nzb2media.ACODEC
|
audio_cmd[1] = ACODEC
|
||||||
if audio_cmd[1] in {'aac', 'dts'}:
|
if audio_cmd[1] in {'aac', 'dts'}:
|
||||||
audio_cmd[2:2] = ['-strict', '-2']
|
audio_cmd[2:2] = ['-strict', '-2']
|
||||||
if nzb2media.ACODEC2_ALLOW:
|
if ACODEC2_ALLOW:
|
||||||
used_audio += 1
|
used_audio += 1
|
||||||
try:
|
try:
|
||||||
audio5 = [item for item in audio1 if item['codec_name'] in nzb2media.ACODEC2_ALLOW]
|
audio5 = [item for item in audio1 if item['codec_name'] in ACODEC2_ALLOW]
|
||||||
except Exception:
|
except Exception:
|
||||||
audio5 = []
|
audio5 = []
|
||||||
try:
|
try:
|
||||||
audio6 = [item for item in audio3 if item['codec_name'] in nzb2media.ACODEC2_ALLOW]
|
audio6 = [item for item in audio3 if item['codec_name'] in ACODEC2_ALLOW]
|
||||||
except Exception:
|
except Exception:
|
||||||
audio6 = []
|
audio6 = []
|
||||||
if audio5: # right language and codec.
|
if audio5: # right language and codec.
|
||||||
|
@ -369,8 +417,8 @@ def build_commands(file, new_dir, movie_name):
|
||||||
a_mapped.extend([audio1[0]['index']])
|
a_mapped.extend([audio1[0]['index']])
|
||||||
bitrate = int(float(audio1[0].get('bit_rate', 0))) / 1000
|
bitrate = int(float(audio1[0].get('bit_rate', 0))) / 1000
|
||||||
channels = int(float(audio1[0].get('channels', 0)))
|
channels = int(float(audio1[0].get('channels', 0)))
|
||||||
if nzb2media.ACODEC2:
|
if ACODEC2:
|
||||||
audio_cmd2.extend([f'-c:a:{used_audio}', nzb2media.ACODEC2])
|
audio_cmd2.extend([f'-c:a:{used_audio}', ACODEC2])
|
||||||
else:
|
else:
|
||||||
audio_cmd2.extend([f'-c:a:{used_audio}', 'copy'])
|
audio_cmd2.extend([f'-c:a:{used_audio}', 'copy'])
|
||||||
elif audio6: # wrong language, right codec
|
elif audio6: # wrong language, right codec
|
||||||
|
@ -387,22 +435,22 @@ def build_commands(file, new_dir, movie_name):
|
||||||
a_mapped.extend([audio3[0]['index']])
|
a_mapped.extend([audio3[0]['index']])
|
||||||
bitrate = int(float(audio3[0].get('bit_rate', 0))) / 1000
|
bitrate = int(float(audio3[0].get('bit_rate', 0))) / 1000
|
||||||
channels = int(float(audio3[0].get('channels', 0)))
|
channels = int(float(audio3[0].get('channels', 0)))
|
||||||
if nzb2media.ACODEC2:
|
if ACODEC2:
|
||||||
audio_cmd2.extend([f'-c:a:{used_audio}', nzb2media.ACODEC2])
|
audio_cmd2.extend([f'-c:a:{used_audio}', ACODEC2])
|
||||||
else:
|
else:
|
||||||
audio_cmd2.extend([f'-c:a:{used_audio}', 'copy'])
|
audio_cmd2.extend([f'-c:a:{used_audio}', 'copy'])
|
||||||
if nzb2media.ACHANNELS2 and channels and channels > nzb2media.ACHANNELS2:
|
if ACHANNELS2 and channels and channels > ACHANNELS2:
|
||||||
audio_cmd2.extend([f'-ac:a:{used_audio}', str(nzb2media.ACHANNELS2)])
|
audio_cmd2.extend([f'-ac:a:{used_audio}', str(ACHANNELS2)])
|
||||||
if audio_cmd2[1] == 'copy':
|
if audio_cmd2[1] == 'copy':
|
||||||
audio_cmd2[1] = nzb2media.ACODEC2
|
audio_cmd2[1] = ACODEC2
|
||||||
if nzb2media.ABITRATE2 and not nzb2media.ABITRATE2 * 0.9 < bitrate < nzb2media.ABITRATE2 * 1.1:
|
if ABITRATE2 and not ABITRATE2 * 0.9 < bitrate < ABITRATE2 * 1.1:
|
||||||
audio_cmd2.extend([f'-b:a:{used_audio}', str(nzb2media.ABITRATE2)])
|
audio_cmd2.extend([f'-b:a:{used_audio}', str(ABITRATE2)])
|
||||||
if audio_cmd2[1] == 'copy':
|
if audio_cmd2[1] == 'copy':
|
||||||
audio_cmd2[1] = nzb2media.ACODEC2
|
audio_cmd2[1] = ACODEC2
|
||||||
if nzb2media.OUTPUTQUALITYPERCENT:
|
if OUTPUTQUALITYPERCENT:
|
||||||
audio_cmd2.extend([f'-q:a:{used_audio}', str(nzb2media.OUTPUTQUALITYPERCENT)])
|
audio_cmd2.extend([f'-q:a:{used_audio}', str(OUTPUTQUALITYPERCENT)])
|
||||||
if audio_cmd2[1] == 'copy':
|
if audio_cmd2[1] == 'copy':
|
||||||
audio_cmd2[1] = nzb2media.ACODEC2
|
audio_cmd2[1] = ACODEC2
|
||||||
if audio_cmd2[1] in {'aac', 'dts'}:
|
if audio_cmd2[1] in {'aac', 'dts'}:
|
||||||
audio_cmd2[2:2] = ['-strict', '-2']
|
audio_cmd2[2:2] = ['-strict', '-2']
|
||||||
if a_mapped[1] == a_mapped[0] and audio_cmd2[1:] == audio_cmd[1:]:
|
if a_mapped[1] == a_mapped[0] and audio_cmd2[1:] == audio_cmd[1:]:
|
||||||
|
@ -410,7 +458,7 @@ def build_commands(file, new_dir, movie_name):
|
||||||
del map_cmd[-2:]
|
del map_cmd[-2:]
|
||||||
else:
|
else:
|
||||||
audio_cmd.extend(audio_cmd2)
|
audio_cmd.extend(audio_cmd2)
|
||||||
if nzb2media.AINCLUDE and nzb2media.ACODEC3:
|
if AINCLUDE and ACODEC3:
|
||||||
# add commentary tracks back here.
|
# add commentary tracks back here.
|
||||||
audio_streams.extend(commentary)
|
audio_streams.extend(commentary)
|
||||||
for audio in audio_streams:
|
for audio in audio_streams:
|
||||||
|
@ -422,42 +470,42 @@ def build_commands(file, new_dir, movie_name):
|
||||||
audio_cmd3 = []
|
audio_cmd3 = []
|
||||||
bitrate = int(float(audio.get('bit_rate', 0))) / 1000
|
bitrate = int(float(audio.get('bit_rate', 0))) / 1000
|
||||||
channels = int(float(audio.get('channels', 0)))
|
channels = int(float(audio.get('channels', 0)))
|
||||||
if audio['codec_name'] in nzb2media.ACODEC3_ALLOW:
|
if audio['codec_name'] in ACODEC3_ALLOW:
|
||||||
audio_cmd3.extend([f'-c:a:{used_audio}', 'copy'])
|
audio_cmd3.extend([f'-c:a:{used_audio}', 'copy'])
|
||||||
elif nzb2media.ACODEC3:
|
elif ACODEC3:
|
||||||
audio_cmd3.extend([f'-c:a:{used_audio}', nzb2media.ACODEC3])
|
audio_cmd3.extend([f'-c:a:{used_audio}', ACODEC3])
|
||||||
else:
|
else:
|
||||||
audio_cmd3.extend([f'-c:a:{used_audio}', 'copy'])
|
audio_cmd3.extend([f'-c:a:{used_audio}', 'copy'])
|
||||||
if nzb2media.ACHANNELS3 and channels and channels > nzb2media.ACHANNELS3:
|
if ACHANNELS3 and channels and channels > ACHANNELS3:
|
||||||
audio_cmd3.extend([f'-ac:a:{used_audio}', str(nzb2media.ACHANNELS3)])
|
audio_cmd3.extend([f'-ac:a:{used_audio}', str(ACHANNELS3)])
|
||||||
if audio_cmd3[1] == 'copy':
|
if audio_cmd3[1] == 'copy':
|
||||||
audio_cmd3[1] = nzb2media.ACODEC3
|
audio_cmd3[1] = ACODEC3
|
||||||
if nzb2media.ABITRATE3 and not nzb2media.ABITRATE3 * 0.9 < bitrate < nzb2media.ABITRATE3 * 1.1:
|
if ABITRATE3 and not ABITRATE3 * 0.9 < bitrate < ABITRATE3 * 1.1:
|
||||||
audio_cmd3.extend([f'-b:a:{used_audio}', str(nzb2media.ABITRATE3)])
|
audio_cmd3.extend([f'-b:a:{used_audio}', str(ABITRATE3)])
|
||||||
if audio_cmd3[1] == 'copy':
|
if audio_cmd3[1] == 'copy':
|
||||||
audio_cmd3[1] = nzb2media.ACODEC3
|
audio_cmd3[1] = ACODEC3
|
||||||
if nzb2media.OUTPUTQUALITYPERCENT > 0:
|
if OUTPUTQUALITYPERCENT > 0:
|
||||||
audio_cmd3.extend([f'-q:a:{used_audio}', str(nzb2media.OUTPUTQUALITYPERCENT)])
|
audio_cmd3.extend([f'-q:a:{used_audio}', str(OUTPUTQUALITYPERCENT)])
|
||||||
if audio_cmd3[1] == 'copy':
|
if audio_cmd3[1] == 'copy':
|
||||||
audio_cmd3[1] = nzb2media.ACODEC3
|
audio_cmd3[1] = ACODEC3
|
||||||
if audio_cmd3[1] in {'aac', 'dts'}:
|
if audio_cmd3[1] in {'aac', 'dts'}:
|
||||||
audio_cmd3[2:2] = ['-strict', '-2']
|
audio_cmd3[2:2] = ['-strict', '-2']
|
||||||
audio_cmd.extend(audio_cmd3)
|
audio_cmd.extend(audio_cmd3)
|
||||||
s_mapped = []
|
s_mapped = []
|
||||||
burnt = 0
|
burnt = 0
|
||||||
num = 0
|
num = 0
|
||||||
for lan in nzb2media.SLANGUAGES:
|
for lan in SLANGUAGES:
|
||||||
try:
|
try:
|
||||||
subs1 = [item for item in sub_streams if item['tags']['language'] == lan]
|
subs1 = [item for item in sub_streams if item['tags']['language'] == lan]
|
||||||
except Exception:
|
except Exception:
|
||||||
subs1 = []
|
subs1 = []
|
||||||
if nzb2media.BURN and not subs1 and not burnt and os.path.isfile(file):
|
if BURN and not subs1 and not burnt and os.path.isfile(file):
|
||||||
for subfile in get_subs(file):
|
for subfile in get_subs(file):
|
||||||
if lan in os.path.split(subfile)[1]:
|
if lan in os.path.split(subfile)[1]:
|
||||||
video_cmd.extend(['-vf', f'subtitles={subfile}'])
|
video_cmd.extend(['-vf', f'subtitles={subfile}'])
|
||||||
burnt = 1
|
burnt = 1
|
||||||
for sub in subs1:
|
for sub in subs1:
|
||||||
if nzb2media.BURN and not burnt and os.path.isfile(input_file):
|
if BURN and not burnt and os.path.isfile(input_file):
|
||||||
subloc = 0
|
subloc = 0
|
||||||
for index, sub_stream in enumerate(sub_streams):
|
for index, sub_stream in enumerate(sub_streams):
|
||||||
if sub_stream['index'] == sub['index']:
|
if sub_stream['index'] == sub['index']:
|
||||||
|
@ -465,40 +513,40 @@ def build_commands(file, new_dir, movie_name):
|
||||||
break
|
break
|
||||||
video_cmd.extend(['-vf', f'subtitles={input_file}:si={subloc}'])
|
video_cmd.extend(['-vf', f'subtitles={input_file}:si={subloc}'])
|
||||||
burnt = 1
|
burnt = 1
|
||||||
if not nzb2media.ALLOWSUBS:
|
if not ALLOWSUBS:
|
||||||
break
|
break
|
||||||
if sub['codec_name'] in {'dvd_subtitle', 'VobSub'} and nzb2media.SCODEC == 'mov_text':
|
if sub['codec_name'] in {'dvd_subtitle', 'VobSub'} and SCODEC == 'mov_text':
|
||||||
continue # We can't convert these.
|
continue # We can't convert these.
|
||||||
_index = sub['index']
|
_index = sub['index']
|
||||||
map_cmd.extend(['-map', f'0:{_index}'])
|
map_cmd.extend(['-map', f'0:{_index}'])
|
||||||
s_mapped.extend([sub['index']])
|
s_mapped.extend([sub['index']])
|
||||||
if nzb2media.SINCLUDE:
|
if SINCLUDE:
|
||||||
for sub in sub_streams:
|
for sub in sub_streams:
|
||||||
if not nzb2media.ALLOWSUBS:
|
if not ALLOWSUBS:
|
||||||
break
|
break
|
||||||
if sub['index'] in s_mapped:
|
if sub['index'] in s_mapped:
|
||||||
continue
|
continue
|
||||||
if sub['codec_name'] in {'dvd_subtitle', 'VobSub'} and nzb2media.SCODEC == 'mov_text': # We can't convert these.
|
if sub['codec_name'] in {'dvd_subtitle', 'VobSub'} and SCODEC == 'mov_text': # We can't convert these.
|
||||||
continue
|
continue
|
||||||
_index = sub['index']
|
_index = sub['index']
|
||||||
map_cmd.extend(['-map', f'0:{_index}'])
|
map_cmd.extend(['-map', f'0:{_index}'])
|
||||||
s_mapped.extend([sub['index']])
|
s_mapped.extend([sub['index']])
|
||||||
if nzb2media.OUTPUTFASTSTART:
|
if OUTPUTFASTSTART:
|
||||||
other_cmd.extend(['-movflags', '+faststart'])
|
other_cmd.extend(['-movflags', '+faststart'])
|
||||||
if nzb2media.OTHEROPTS:
|
if OTHEROPTS:
|
||||||
other_cmd.extend(nzb2media.OTHEROPTS)
|
other_cmd.extend(OTHEROPTS)
|
||||||
command = [nzb2media.FFMPEG, '-loglevel', 'warning']
|
command = [nzb2media.tool.FFMPEG, '-loglevel', 'warning']
|
||||||
if nzb2media.HWACCEL:
|
if HWACCEL:
|
||||||
command.extend(['-hwaccel', 'auto'])
|
command.extend(['-hwaccel', 'auto'])
|
||||||
if nzb2media.GENERALOPTS:
|
if GENERALOPTS:
|
||||||
command.extend(nzb2media.GENERALOPTS)
|
command.extend(GENERALOPTS)
|
||||||
command.extend(['-i', input_file])
|
command.extend(['-i', input_file])
|
||||||
if nzb2media.SEMBED and os.path.isfile(file):
|
if SEMBED and os.path.isfile(file):
|
||||||
for subfile in get_subs(file):
|
for subfile in get_subs(file):
|
||||||
sub_details, result = get_video_details(subfile)
|
sub_details, result = get_video_details(subfile)
|
||||||
if not sub_details or not sub_details.get('streams'):
|
if not sub_details or not sub_details.get('streams'):
|
||||||
continue
|
continue
|
||||||
if nzb2media.SCODEC == 'mov_text':
|
if SCODEC == 'mov_text':
|
||||||
subcode = [stream['codec_name'] for stream in sub_details['streams']]
|
subcode = [stream['codec_name'] for stream in sub_details['streams']]
|
||||||
if set(subcode).intersection(['dvd_subtitle', 'VobSub']):
|
if set(subcode).intersection(['dvd_subtitle', 'VobSub']):
|
||||||
# We can't convert these.
|
# We can't convert these.
|
||||||
|
@ -517,10 +565,10 @@ def build_commands(file, new_dir, movie_name):
|
||||||
meta_cmd.extend([f'-metadata:s:s:{len(s_mapped) + num}', f'language={metlan.alpha3}'])
|
meta_cmd.extend([f'-metadata:s:s:{len(s_mapped) + num}', f'language={metlan.alpha3}'])
|
||||||
num += 1
|
num += 1
|
||||||
map_cmd.extend(['-map', f'{num}:0'])
|
map_cmd.extend(['-map', f'{num}:0'])
|
||||||
if not nzb2media.ALLOWSUBS or (not s_mapped and not num):
|
if not ALLOWSUBS or (not s_mapped and not num):
|
||||||
sub_cmd.extend(['-sn'])
|
sub_cmd.extend(['-sn'])
|
||||||
elif nzb2media.SCODEC:
|
elif SCODEC:
|
||||||
sub_cmd.extend(['-c:s', nzb2media.SCODEC])
|
sub_cmd.extend(['-c:s', SCODEC])
|
||||||
else:
|
else:
|
||||||
sub_cmd.extend(['-c:s', 'copy'])
|
sub_cmd.extend(['-c:s', 'copy'])
|
||||||
command.extend(map_cmd)
|
command.extend(map_cmd)
|
||||||
|
@ -531,7 +579,7 @@ def build_commands(file, new_dir, movie_name):
|
||||||
command.extend(other_cmd)
|
command.extend(other_cmd)
|
||||||
command.append(newfile_path)
|
command.append(newfile_path)
|
||||||
if platform.system() != 'Windows':
|
if platform.system() != 'Windows':
|
||||||
command = nzb2media.NICENESS + command
|
command = nzb2media.tool.NICENESS + command
|
||||||
return command, new_file
|
return command, new_file
|
||||||
|
|
||||||
|
|
||||||
|
@ -551,13 +599,13 @@ def extract_subs(file, newfile_path):
|
||||||
video_details, result = get_video_details(file)
|
video_details, result = get_video_details(file)
|
||||||
if not video_details:
|
if not video_details:
|
||||||
return
|
return
|
||||||
if nzb2media.SUBSDIR:
|
if SUBSDIR:
|
||||||
subdir = nzb2media.SUBSDIR
|
subdir = SUBSDIR
|
||||||
else:
|
else:
|
||||||
subdir = os.path.split(newfile_path)[0]
|
subdir = os.path.split(newfile_path)[0]
|
||||||
name = os.path.splitext(os.path.split(newfile_path)[1])[0]
|
name = os.path.splitext(os.path.split(newfile_path)[1])[0]
|
||||||
try:
|
try:
|
||||||
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['tags']['language'] in nzb2media.SLANGUAGES and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['tags']['language'] in SLANGUAGES and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
||||||
except Exception:
|
except Exception:
|
||||||
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
sub_streams = [item for item in video_details['streams'] if item['codec_type'] == 'subtitle' and item['codec_name'] != 'hdmv_pgs_subtitle' and item['codec_name'] != 'pgssub']
|
||||||
num = len(sub_streams)
|
num = len(sub_streams)
|
||||||
|
@ -573,9 +621,9 @@ def extract_subs(file, newfile_path):
|
||||||
output_file = os.path.join(subdir, f'{name}.{lan}.srt')
|
output_file = os.path.join(subdir, f'{name}.{lan}.srt')
|
||||||
if os.path.isfile(output_file):
|
if os.path.isfile(output_file):
|
||||||
output_file = os.path.join(subdir, f'{name}.{lan}.{ea_num}.srt')
|
output_file = os.path.join(subdir, f'{name}.{lan}.{ea_num}.srt')
|
||||||
command = [nzb2media.FFMPEG, '-loglevel', 'warning', '-i', file, '-vn', '-an', f'-codec:{idx}', 'srt', output_file]
|
command = [nzb2media.tool.FFMPEG, '-loglevel', 'warning', '-i', file, '-vn', '-an', f'-codec:{idx}', 'srt', output_file]
|
||||||
if platform.system() != 'Windows':
|
if platform.system() != 'Windows':
|
||||||
command = nzb2media.NICENESS + command
|
command = nzb2media.tool.NICENESS + command
|
||||||
log.info(f'Extracting {lan} subtitle from: {file}')
|
log.info(f'Extracting {lan} subtitle from: {file}')
|
||||||
print_cmd(command)
|
print_cmd(command)
|
||||||
result = 1 # set result to failed in case call fails.
|
result = 1 # set result to failed in case call fails.
|
||||||
|
@ -604,11 +652,11 @@ def process_list(iterable):
|
||||||
success = True
|
success = True
|
||||||
for item in iterable:
|
for item in iterable:
|
||||||
ext = os.path.splitext(item)[1].lower()
|
ext = os.path.splitext(item)[1].lower()
|
||||||
if ext in {'.iso', '.bin', '.img'} and ext not in nzb2media.IGNOREEXTENSIONS:
|
if ext in {'.iso', '.bin', '.img'} and ext not in IGNOREEXTENSIONS:
|
||||||
log.debug(f'Attempting to rip disk image: {item}')
|
log.debug(f'Attempting to rip disk image: {item}')
|
||||||
new_list.extend(rip_iso(item))
|
new_list.extend(rip_iso(item))
|
||||||
rem_list.append(item)
|
rem_list.append(item)
|
||||||
elif re.match('.+VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]', item) and '.vob' not in nzb2media.IGNOREEXTENSIONS:
|
elif re.match('.+VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]', item) and '.vob' not in IGNOREEXTENSIONS:
|
||||||
log.debug(f'Found VIDEO_TS image file: {item}')
|
log.debug(f'Found VIDEO_TS image file: {item}')
|
||||||
if not vts_path:
|
if not vts_path:
|
||||||
try:
|
try:
|
||||||
|
@ -616,7 +664,7 @@ def process_list(iterable):
|
||||||
except Exception:
|
except Exception:
|
||||||
vts_path = os.path.split(item)[0]
|
vts_path = os.path.split(item)[0]
|
||||||
rem_list.append(item)
|
rem_list.append(item)
|
||||||
elif re.match('.+BDMV[/\\]SOURCE[/\\][0-9]+[0-9].[Mm][Tt][Ss]', item) and '.mts' not in nzb2media.IGNOREEXTENSIONS:
|
elif re.match('.+BDMV[/\\]SOURCE[/\\][0-9]+[0-9].[Mm][Tt][Ss]', item) and '.mts' not in IGNOREEXTENSIONS:
|
||||||
log.debug(f'Found MTS image file: {item}')
|
log.debug(f'Found MTS image file: {item}')
|
||||||
if not mts_path:
|
if not mts_path:
|
||||||
try:
|
try:
|
||||||
|
@ -626,7 +674,7 @@ def process_list(iterable):
|
||||||
rem_list.append(item)
|
rem_list.append(item)
|
||||||
elif re.match('.+VIDEO_TS.', item) or re.match('.+VTS_[0-9][0-9]_[0-9].', item):
|
elif re.match('.+VIDEO_TS.', item) or re.match('.+VTS_[0-9][0-9]_[0-9].', item):
|
||||||
rem_list.append(item)
|
rem_list.append(item)
|
||||||
elif nzb2media.CONCAT and re.match('.+[cC][dD][0-9].', item):
|
elif CONCAT and re.match('.+[cC][dD][0-9].', item):
|
||||||
rem_list.append(item)
|
rem_list.append(item)
|
||||||
combine.append(item)
|
combine.append(item)
|
||||||
else:
|
else:
|
||||||
|
@ -654,6 +702,7 @@ def process_list(iterable):
|
||||||
|
|
||||||
|
|
||||||
def mount_iso(item): # Currently only supports Linux Mount when permissions allow.
|
def mount_iso(item): # Currently only supports Linux Mount when permissions allow.
|
||||||
|
global MOUNTED
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
log.error(f'No mounting options available under Windows for image file {item}')
|
log.error(f'No mounting options available under Windows for image file {item}')
|
||||||
return []
|
return []
|
||||||
|
@ -663,18 +712,18 @@ def mount_iso(item): # Currently only supports Linux Mount when permissions all
|
||||||
print_cmd(cmd)
|
print_cmd(cmd)
|
||||||
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
||||||
proc_out, proc_err = proc.communicate()
|
proc_out, proc_err = proc.communicate()
|
||||||
nzb2media.MOUNTED = mount_point # Allows us to verify this has been done and then cleanup.
|
MOUNTED = mount_point # Allows us to verify this has been done and then cleanup.
|
||||||
for root, _dirs, files in os.walk(mount_point):
|
for root, _dirs, files in os.walk(mount_point):
|
||||||
for file in files:
|
for file in files:
|
||||||
full_path = os.path.join(root, file)
|
full_path = os.path.join(root, file)
|
||||||
if re.match('.+VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]', full_path) and '.vob' not in nzb2media.IGNOREEXTENSIONS:
|
if re.match('.+VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]', full_path) and '.vob' not in IGNOREEXTENSIONS:
|
||||||
log.debug(f'Found VIDEO_TS image file: {full_path}')
|
log.debug(f'Found VIDEO_TS image file: {full_path}')
|
||||||
try:
|
try:
|
||||||
vts_path = re.match('(.+VIDEO_TS)', full_path).groups()[0]
|
vts_path = re.match('(.+VIDEO_TS)', full_path).groups()[0]
|
||||||
except Exception:
|
except Exception:
|
||||||
vts_path = os.path.split(full_path)[0]
|
vts_path = os.path.split(full_path)[0]
|
||||||
return combine_vts(vts_path)
|
return combine_vts(vts_path)
|
||||||
if re.match('.+BDMV[/\\]STREAM[/\\][0-9]+[0-9].[Mm]', full_path) and '.mts' not in nzb2media.IGNOREEXTENSIONS:
|
if re.match('.+BDMV[/\\]STREAM[/\\][0-9]+[0-9].[Mm]', full_path) and '.mts' not in IGNOREEXTENSIONS:
|
||||||
log.debug(f'Found MTS image file: {full_path}')
|
log.debug(f'Found MTS image file: {full_path}')
|
||||||
try:
|
try:
|
||||||
mts_path = re.match('(.+BDMV[/\\]STREAM)', full_path).groups()[0]
|
mts_path = re.match('(.+BDMV[/\\]STREAM)', full_path).groups()[0]
|
||||||
|
@ -689,7 +738,7 @@ def rip_iso(item):
|
||||||
new_files = []
|
new_files = []
|
||||||
failure_dir = 'failure'
|
failure_dir = 'failure'
|
||||||
# Mount the ISO in your OS and call combineVTS.
|
# Mount the ISO in your OS and call combineVTS.
|
||||||
if not nzb2media.SEVENZIP:
|
if not nzb2media.tool.SEVENZIP:
|
||||||
log.debug(f'No 7zip installed. Attempting to mount image file {item}')
|
log.debug(f'No 7zip installed. Attempting to mount image file {item}')
|
||||||
try:
|
try:
|
||||||
# Currently only works for Linux.
|
# Currently only works for Linux.
|
||||||
|
@ -698,7 +747,7 @@ def rip_iso(item):
|
||||||
log.error(f'Failed to mount and extract from image file {item}')
|
log.error(f'Failed to mount and extract from image file {item}')
|
||||||
new_files = [failure_dir]
|
new_files = [failure_dir]
|
||||||
return new_files
|
return new_files
|
||||||
cmd = [nzb2media.SEVENZIP, 'l', item]
|
cmd = [nzb2media.tool.SEVENZIP, 'l', item]
|
||||||
try:
|
try:
|
||||||
log.debug(f'Attempting to extract .vob or .mts from image file {item}')
|
log.debug(f'Attempting to extract .vob or .mts from image file {item}')
|
||||||
print_cmd(cmd)
|
print_cmd(cmd)
|
||||||
|
@ -720,7 +769,7 @@ def rip_iso(item):
|
||||||
break
|
break
|
||||||
if not concat:
|
if not concat:
|
||||||
break
|
break
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
combined.extend(concat)
|
combined.extend(concat)
|
||||||
continue
|
continue
|
||||||
name = f'{os.path.splitext(os.path.split(item)[1])[0]}.cd{title_set + 1}'
|
name = f'{os.path.splitext(os.path.split(item)[1])[0]}.cd{title_set + 1}'
|
||||||
|
@ -735,12 +784,12 @@ def rip_iso(item):
|
||||||
concat = []
|
concat = []
|
||||||
title_set += 1
|
title_set += 1
|
||||||
concat.append(mts_name)
|
concat.append(mts_name)
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
combined.extend(concat)
|
combined.extend(concat)
|
||||||
continue
|
continue
|
||||||
name = f'{os.path.splitext(os.path.split(item)[1])[0]}.cd{title_set}'
|
name = f'{os.path.splitext(os.path.split(item)[1])[0]}.cd{title_set}'
|
||||||
new_files.append({item: {'name': name, 'files': concat}})
|
new_files.append({item: {'name': name, 'files': concat}})
|
||||||
if nzb2media.CONCAT and combined:
|
if CONCAT and combined:
|
||||||
name = os.path.splitext(os.path.split(item)[1])[0]
|
name = os.path.splitext(os.path.split(item)[1])[0]
|
||||||
new_files.append({item: {'name': name, 'files': combined}})
|
new_files.append({item: {'name': name, 'files': combined}})
|
||||||
if not new_files:
|
if not new_files:
|
||||||
|
@ -772,12 +821,12 @@ def combine_vts(vts_path):
|
||||||
break
|
break
|
||||||
if not concat:
|
if not concat:
|
||||||
break
|
break
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
combined.extend(concat)
|
combined.extend(concat)
|
||||||
continue
|
continue
|
||||||
name = f'{name}.cd{title_set + 1}'
|
name = f'{name}.cd{title_set + 1}'
|
||||||
new_files.append({vts_path: {'name': name, 'files': concat}})
|
new_files.append({vts_path: {'name': name, 'files': concat}})
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
new_files.append({vts_path: {'name': name, 'files': combined}})
|
new_files.append({vts_path: {'name': name, 'files': combined}})
|
||||||
return new_files
|
return new_files
|
||||||
|
|
||||||
|
@ -799,13 +848,13 @@ def combine_mts(mts_path):
|
||||||
for mts_name in mts_list: # need to sort all files [1 - 998].mts in order
|
for mts_name in mts_list: # need to sort all files [1 - 998].mts in order
|
||||||
concat = []
|
concat = []
|
||||||
concat.append(os.path.join(mts_path, mts_name))
|
concat.append(os.path.join(mts_path, mts_name))
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
combined.extend(concat)
|
combined.extend(concat)
|
||||||
continue
|
continue
|
||||||
name = f'{name}.cd{num + 1}'
|
name = f'{name}.cd{num + 1}'
|
||||||
new_files.append({mts_path: {'name': name, 'files': concat}})
|
new_files.append({mts_path: {'name': name, 'files': concat}})
|
||||||
num += 1
|
num += 1
|
||||||
if nzb2media.CONCAT:
|
if CONCAT:
|
||||||
new_files.append({mts_path: {'name': name, 'files': combined}})
|
new_files.append({mts_path: {'name': name, 'files': combined}})
|
||||||
return new_files
|
return new_files
|
||||||
|
|
||||||
|
@ -833,12 +882,13 @@ def print_cmd(command):
|
||||||
|
|
||||||
|
|
||||||
def transcode_directory(dir_name):
|
def transcode_directory(dir_name):
|
||||||
if not nzb2media.FFMPEG:
|
global MOUNTED
|
||||||
|
if not nzb2media.tool.FFMPEG:
|
||||||
return 1, dir_name
|
return 1, dir_name
|
||||||
log.info('Checking for files to be transcoded')
|
log.info('Checking for files to be transcoded')
|
||||||
final_result = 0 # initialize as successful
|
final_result = 0 # initialize as successful
|
||||||
if nzb2media.OUTPUTVIDEOPATH:
|
if OUTPUTVIDEOPATH:
|
||||||
new_dir = nzb2media.OUTPUTVIDEOPATH
|
new_dir = OUTPUTVIDEOPATH
|
||||||
make_dir(new_dir)
|
make_dir(new_dir)
|
||||||
name = os.path.splitext(os.path.split(dir_name)[1])[0]
|
name = os.path.splitext(os.path.split(dir_name)[1])[0]
|
||||||
new_dir = os.path.join(new_dir, name)
|
new_dir = os.path.join(new_dir, name)
|
||||||
|
@ -846,17 +896,17 @@ def transcode_directory(dir_name):
|
||||||
else:
|
else:
|
||||||
new_dir = dir_name
|
new_dir = dir_name
|
||||||
movie_name = os.path.splitext(os.path.split(dir_name)[1])[0]
|
movie_name = os.path.splitext(os.path.split(dir_name)[1])[0]
|
||||||
file_list = nzb2media.list_media_files(dir_name, media=True, audio=False, meta=False, archives=False)
|
file_list = list_media_files(dir_name, media=True, audio=False, meta=False, archives=False)
|
||||||
file_list, rem_list, new_list, success = process_list(file_list)
|
file_list, rem_list, new_list, success = process_list(file_list)
|
||||||
if not success:
|
if not success:
|
||||||
return 1, dir_name
|
return 1, dir_name
|
||||||
for file in file_list:
|
for file in file_list:
|
||||||
if isinstance(file, str) and os.path.splitext(file)[1] in nzb2media.IGNOREEXTENSIONS:
|
if isinstance(file, str) and os.path.splitext(file)[1] in IGNOREEXTENSIONS:
|
||||||
continue
|
continue
|
||||||
command, file = build_commands(file, new_dir, movie_name)
|
command, file = build_commands(file, new_dir, movie_name)
|
||||||
newfile_path = command[-1]
|
newfile_path = command[-1]
|
||||||
# transcoding files may remove the original file, so make sure to extract subtitles first
|
# transcoding files may remove the original file, so make sure to extract subtitles first
|
||||||
if nzb2media.SEXTRACT and isinstance(file, str):
|
if SEXTRACT and isinstance(file, str):
|
||||||
extract_subs(file, newfile_path)
|
extract_subs(file, newfile_path)
|
||||||
try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason)
|
try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason)
|
||||||
os.remove(newfile_path)
|
os.remove(newfile_path)
|
||||||
|
@ -887,12 +937,12 @@ def transcode_directory(dir_name):
|
||||||
result = proc.returncode
|
result = proc.returncode
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error(f'Transcoding of video {newfile_path} has failed')
|
log.error(f'Transcoding of video {newfile_path} has failed')
|
||||||
if nzb2media.SUBSDIR and not result and isinstance(file, str):
|
if SUBSDIR and not result and isinstance(file, str):
|
||||||
for sub in get_subs(file):
|
for sub in get_subs(file):
|
||||||
name = os.path.splitext(os.path.split(file)[1])[0]
|
name = os.path.splitext(os.path.split(file)[1])[0]
|
||||||
subname = os.path.split(sub)[1]
|
subname = os.path.split(sub)[1]
|
||||||
newname = os.path.splitext(os.path.split(newfile_path)[1])[0]
|
newname = os.path.splitext(os.path.split(newfile_path)[1])[0]
|
||||||
newpath = os.path.join(nzb2media.SUBSDIR, subname.replace(name, newname))
|
newpath = os.path.join(SUBSDIR, subname.replace(name, newname))
|
||||||
if not os.path.isfile(newpath):
|
if not os.path.isfile(newpath):
|
||||||
os.rename(sub, newpath)
|
os.rename(sub, newpath)
|
||||||
if not result:
|
if not result:
|
||||||
|
@ -901,7 +951,7 @@ def transcode_directory(dir_name):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
log.info(f'Transcoding of video to {newfile_path} succeeded')
|
log.info(f'Transcoding of video to {newfile_path} succeeded')
|
||||||
if os.path.isfile(newfile_path) and (file in new_list or not nzb2media.DUPLICATE):
|
if os.path.isfile(newfile_path) and (file in new_list or not DUPLICATE):
|
||||||
try:
|
try:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -910,16 +960,16 @@ def transcode_directory(dir_name):
|
||||||
log.error(f'Transcoding of video to {newfile_path} failed with result {result}')
|
log.error(f'Transcoding of video to {newfile_path} failed with result {result}')
|
||||||
# this will be 0 (successful) it all are successful, else will return a positive integer for failure.
|
# this will be 0 (successful) it all are successful, else will return a positive integer for failure.
|
||||||
final_result = final_result + result
|
final_result = final_result + result
|
||||||
if nzb2media.MOUNTED: # In case we mounted an .iso file, unmount here.
|
if MOUNTED: # In case we mounted an .iso file, unmount here.
|
||||||
time.sleep(5) # play it safe and avoid failing to unmount.
|
time.sleep(5) # play it safe and avoid failing to unmount.
|
||||||
cmd = ['umount', '-l', nzb2media.MOUNTED]
|
cmd = ['umount', '-l', MOUNTED]
|
||||||
print_cmd(cmd)
|
print_cmd(cmd)
|
||||||
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
with subprocess.Popen(cmd, stdout=PIPE, stderr=DEVNULL) as proc:
|
||||||
proc_out, proc_err = proc.communicate()
|
proc_out, proc_err = proc.communicate()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
os.rmdir(nzb2media.MOUNTED)
|
os.rmdir(MOUNTED)
|
||||||
nzb2media.MOUNTED = None
|
MOUNTED = None
|
||||||
if not final_result and not nzb2media.DUPLICATE:
|
if not final_result and not DUPLICATE:
|
||||||
for file in rem_list:
|
for file in rem_list:
|
||||||
try:
|
try:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
|
@ -929,7 +979,235 @@ def transcode_directory(dir_name):
|
||||||
# this is an empty directory and we didn't transcode into it.
|
# this is an empty directory and we didn't transcode into it.
|
||||||
os.rmdir(new_dir)
|
os.rmdir(new_dir)
|
||||||
new_dir = dir_name
|
new_dir = dir_name
|
||||||
if not nzb2media.PROCESSOUTPUT and nzb2media.DUPLICATE:
|
if not PROCESSOUTPUT and DUPLICATE:
|
||||||
# We postprocess the original files to CP/SB
|
# We postprocess the original files to CP/SB
|
||||||
new_dir = dir_name
|
new_dir = dir_name
|
||||||
return final_result, new_dir
|
return final_result, new_dir
|
||||||
|
|
||||||
|
|
||||||
|
def configure_transcoder():
|
||||||
|
global MOUNTED
|
||||||
|
global GETSUBS
|
||||||
|
global TRANSCODE
|
||||||
|
global DUPLICATE
|
||||||
|
global CONCAT
|
||||||
|
global IGNOREEXTENSIONS
|
||||||
|
global OUTPUTFASTSTART
|
||||||
|
global GENERALOPTS
|
||||||
|
global OTHEROPTS
|
||||||
|
global OUTPUTQUALITYPERCENT
|
||||||
|
global OUTPUTVIDEOPATH
|
||||||
|
global PROCESSOUTPUT
|
||||||
|
global ALANGUAGE
|
||||||
|
global AINCLUDE
|
||||||
|
global SLANGUAGES
|
||||||
|
global SINCLUDE
|
||||||
|
global SEXTRACT
|
||||||
|
global SEMBED
|
||||||
|
global SUBSDIR
|
||||||
|
global VEXTENSION
|
||||||
|
global VCODEC
|
||||||
|
global VPRESET
|
||||||
|
global VFRAMERATE
|
||||||
|
global VBITRATE
|
||||||
|
global VRESOLUTION
|
||||||
|
global VCRF
|
||||||
|
global VLEVEL
|
||||||
|
global VCODEC_ALLOW
|
||||||
|
global ACODEC
|
||||||
|
global ACODEC_ALLOW
|
||||||
|
global ACHANNELS
|
||||||
|
global ABITRATE
|
||||||
|
global ACODEC2
|
||||||
|
global ACODEC2_ALLOW
|
||||||
|
global ACHANNELS2
|
||||||
|
global ABITRATE2
|
||||||
|
global ACODEC3
|
||||||
|
global ACODEC3_ALLOW
|
||||||
|
global ACHANNELS3
|
||||||
|
global ABITRATE3
|
||||||
|
global SCODEC
|
||||||
|
global BURN
|
||||||
|
global HWACCEL
|
||||||
|
global ALLOWSUBS
|
||||||
|
global DEFAULTS
|
||||||
|
MOUNTED = None
|
||||||
|
GETSUBS = int(nzb2media.CFG['Transcoder']['getSubs'])
|
||||||
|
TRANSCODE = int(nzb2media.CFG['Transcoder']['transcode'])
|
||||||
|
DUPLICATE = int(nzb2media.CFG['Transcoder']['duplicate'])
|
||||||
|
CONCAT = int(nzb2media.CFG['Transcoder']['concat'])
|
||||||
|
IGNOREEXTENSIONS = nzb2media.CFG['Transcoder']['ignoreExtensions']
|
||||||
|
if isinstance(IGNOREEXTENSIONS, str):
|
||||||
|
IGNOREEXTENSIONS = IGNOREEXTENSIONS.split(',')
|
||||||
|
OUTPUTFASTSTART = int(nzb2media.CFG['Transcoder']['outputFastStart'])
|
||||||
|
GENERALOPTS = nzb2media.CFG['Transcoder']['generalOptions']
|
||||||
|
if isinstance(GENERALOPTS, str):
|
||||||
|
GENERALOPTS = GENERALOPTS.split(',')
|
||||||
|
if GENERALOPTS == ['']:
|
||||||
|
GENERALOPTS = []
|
||||||
|
if '-fflags' not in GENERALOPTS:
|
||||||
|
GENERALOPTS.append('-fflags')
|
||||||
|
if '+genpts' not in GENERALOPTS:
|
||||||
|
GENERALOPTS.append('+genpts')
|
||||||
|
OTHEROPTS = nzb2media.CFG['Transcoder']['otherOptions']
|
||||||
|
if isinstance(OTHEROPTS, str):
|
||||||
|
OTHEROPTS = OTHEROPTS.split(',')
|
||||||
|
if OTHEROPTS == ['']:
|
||||||
|
OTHEROPTS = []
|
||||||
|
try:
|
||||||
|
OUTPUTQUALITYPERCENT = int(nzb2media.CFG['Transcoder']['outputQualityPercent'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
OUTPUTVIDEOPATH = nzb2media.CFG['Transcoder']['outputVideoPath']
|
||||||
|
PROCESSOUTPUT = int(nzb2media.CFG['Transcoder']['processOutput'])
|
||||||
|
ALANGUAGE = nzb2media.CFG['Transcoder']['audioLanguage']
|
||||||
|
AINCLUDE = int(nzb2media.CFG['Transcoder']['allAudioLanguages'])
|
||||||
|
SLANGUAGES = nzb2media.CFG['Transcoder']['subLanguages']
|
||||||
|
if isinstance(SLANGUAGES, str):
|
||||||
|
SLANGUAGES = SLANGUAGES.split(',')
|
||||||
|
if SLANGUAGES == ['']:
|
||||||
|
SLANGUAGES = []
|
||||||
|
SINCLUDE = int(nzb2media.CFG['Transcoder']['allSubLanguages'])
|
||||||
|
SEXTRACT = int(nzb2media.CFG['Transcoder']['extractSubs'])
|
||||||
|
SEMBED = int(nzb2media.CFG['Transcoder']['embedSubs'])
|
||||||
|
SUBSDIR = nzb2media.CFG['Transcoder']['externalSubDir']
|
||||||
|
VEXTENSION = nzb2media.CFG['Transcoder']['outputVideoExtension'].strip()
|
||||||
|
VCODEC = nzb2media.CFG['Transcoder']['outputVideoCodec'].strip()
|
||||||
|
VCODEC_ALLOW = nzb2media.CFG['Transcoder']['VideoCodecAllow'].strip()
|
||||||
|
if isinstance(VCODEC_ALLOW, str):
|
||||||
|
VCODEC_ALLOW = VCODEC_ALLOW.split(',')
|
||||||
|
if VCODEC_ALLOW == ['']:
|
||||||
|
VCODEC_ALLOW = []
|
||||||
|
VPRESET = nzb2media.CFG['Transcoder']['outputVideoPreset'].strip()
|
||||||
|
try:
|
||||||
|
VFRAMERATE = float(nzb2media.CFG['Transcoder']['outputVideoFramerate'].strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
VCRF = int(nzb2media.CFG['Transcoder']['outputVideoCRF'].strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
VLEVEL = nzb2media.CFG['Transcoder']['outputVideoLevel'].strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
VBITRATE = int((nzb2media.CFG['Transcoder']['outputVideoBitrate'].strip()).replace('k', '000'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
VRESOLUTION = nzb2media.CFG['Transcoder']['outputVideoResolution']
|
||||||
|
ACODEC = nzb2media.CFG['Transcoder']['outputAudioCodec'].strip()
|
||||||
|
ACODEC_ALLOW = nzb2media.CFG['Transcoder']['AudioCodecAllow'].strip()
|
||||||
|
if isinstance(ACODEC_ALLOW, str):
|
||||||
|
ACODEC_ALLOW = ACODEC_ALLOW.split(',')
|
||||||
|
if ACODEC_ALLOW == ['']:
|
||||||
|
ACODEC_ALLOW = []
|
||||||
|
try:
|
||||||
|
ACHANNELS = int(nzb2media.CFG['Transcoder']['outputAudioChannels'].strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ABITRATE = int((nzb2media.CFG['Transcoder']['outputAudioBitrate'].strip()).replace('k', '000'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
ACODEC2 = nzb2media.CFG['Transcoder']['outputAudioTrack2Codec'].strip()
|
||||||
|
ACODEC2_ALLOW = nzb2media.CFG['Transcoder']['AudioCodec2Allow'].strip()
|
||||||
|
if isinstance(ACODEC2_ALLOW, str):
|
||||||
|
ACODEC2_ALLOW = ACODEC2_ALLOW.split(',')
|
||||||
|
if ACODEC2_ALLOW == ['']:
|
||||||
|
ACODEC2_ALLOW = []
|
||||||
|
try:
|
||||||
|
ACHANNELS2 = int(nzb2media.CFG['Transcoder']['outputAudioTrack2Channels'].strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ABITRATE2 = int((nzb2media.CFG['Transcoder']['outputAudioTrack2Bitrate'].strip()).replace('k', '000'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
ACODEC3 = nzb2media.CFG['Transcoder']['outputAudioOtherCodec'].strip()
|
||||||
|
ACODEC3_ALLOW = nzb2media.CFG['Transcoder']['AudioOtherCodecAllow'].strip()
|
||||||
|
if isinstance(ACODEC3_ALLOW, str):
|
||||||
|
ACODEC3_ALLOW = ACODEC3_ALLOW.split(',')
|
||||||
|
if ACODEC3_ALLOW == ['']:
|
||||||
|
ACODEC3_ALLOW = []
|
||||||
|
try:
|
||||||
|
ACHANNELS3 = int(nzb2media.CFG['Transcoder']['outputAudioOtherChannels'].strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ABITRATE3 = int((nzb2media.CFG['Transcoder']['outputAudioOtherBitrate'].strip()).replace('k', '000'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
SCODEC = nzb2media.CFG['Transcoder']['outputSubtitleCodec'].strip()
|
||||||
|
BURN = int(nzb2media.CFG['Transcoder']['burnInSubtitle'].strip())
|
||||||
|
DEFAULTS = nzb2media.CFG['Transcoder']['outputDefault'].strip()
|
||||||
|
HWACCEL = int(nzb2media.CFG['Transcoder']['hwAccel'])
|
||||||
|
allow_subs = ['.mkv', '.mp4', '.m4v', 'asf', 'wma', 'wmv']
|
||||||
|
codec_alias = {'libx264': ['libx264', 'h264', 'h.264', 'AVC', 'MPEG-4'], 'libmp3lame': ['libmp3lame', 'mp3'], 'libfaac': ['libfaac', 'aac', 'faac']}
|
||||||
|
transcode_defaults = {
|
||||||
|
'iPad': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'iPad-1080p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1920:1080', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'iPad-720p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'Apple-TV': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'iPod': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '1280:720', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'iPhone': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '460:320', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'PS3': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'xbox': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'Roku-480p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'Roku-720p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'Roku-1080p': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 160000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
'mkv': {'VEXTENSION': '.mkv', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
||||||
|
'mkv-bluray': {'VEXTENSION': '.mkv', 'VCODEC': 'libx265', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'hevc', 'h265', 'libx265', 'h.265', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
||||||
|
'mp4-scene-release': {'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None, 'VCRF': 19, 'VLEVEL': '3.1', 'VRESOLUTION': None, 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'], 'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8, 'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None, 'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None, 'ACHANNELS3': 8, 'SCODEC': 'mov_text'},
|
||||||
|
'MKV-SD': {'VEXTENSION': '.mkv', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': '1200k', 'VCRF': None, 'VLEVEL': None, 'VRESOLUTION': '720: -1', 'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'], 'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2, 'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6, 'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None, 'SCODEC': 'mov_text'},
|
||||||
|
}
|
||||||
|
if DEFAULTS and DEFAULTS in transcode_defaults:
|
||||||
|
VEXTENSION = transcode_defaults[DEFAULTS]['VEXTENSION']
|
||||||
|
VCODEC = transcode_defaults[DEFAULTS]['VCODEC']
|
||||||
|
VPRESET = transcode_defaults[DEFAULTS]['VPRESET']
|
||||||
|
VFRAMERATE = transcode_defaults[DEFAULTS]['VFRAMERATE']
|
||||||
|
VBITRATE = transcode_defaults[DEFAULTS]['VBITRATE']
|
||||||
|
VRESOLUTION = transcode_defaults[DEFAULTS]['VRESOLUTION']
|
||||||
|
VCRF = transcode_defaults[DEFAULTS]['VCRF']
|
||||||
|
VLEVEL = transcode_defaults[DEFAULTS]['VLEVEL']
|
||||||
|
VCODEC_ALLOW = transcode_defaults[DEFAULTS]['VCODEC_ALLOW']
|
||||||
|
ACODEC = transcode_defaults[DEFAULTS]['ACODEC']
|
||||||
|
ACODEC_ALLOW = transcode_defaults[DEFAULTS]['ACODEC_ALLOW']
|
||||||
|
ACHANNELS = transcode_defaults[DEFAULTS]['ACHANNELS']
|
||||||
|
ABITRATE = transcode_defaults[DEFAULTS]['ABITRATE']
|
||||||
|
ACODEC2 = transcode_defaults[DEFAULTS]['ACODEC2']
|
||||||
|
ACODEC2_ALLOW = transcode_defaults[DEFAULTS]['ACODEC2_ALLOW']
|
||||||
|
ACHANNELS2 = transcode_defaults[DEFAULTS]['ACHANNELS2']
|
||||||
|
ABITRATE2 = transcode_defaults[DEFAULTS]['ABITRATE2']
|
||||||
|
ACODEC3 = transcode_defaults[DEFAULTS]['ACODEC3']
|
||||||
|
ACODEC3_ALLOW = transcode_defaults[DEFAULTS]['ACODEC3_ALLOW']
|
||||||
|
ACHANNELS3 = transcode_defaults[DEFAULTS]['ACHANNELS3']
|
||||||
|
ABITRATE3 = transcode_defaults[DEFAULTS]['ABITRATE3']
|
||||||
|
SCODEC = transcode_defaults[DEFAULTS]['SCODEC']
|
||||||
|
del transcode_defaults
|
||||||
|
if VEXTENSION in allow_subs:
|
||||||
|
ALLOWSUBS = 1
|
||||||
|
if not VCODEC_ALLOW and VCODEC:
|
||||||
|
VCODEC_ALLOW.extend([VCODEC])
|
||||||
|
for codec in VCODEC_ALLOW:
|
||||||
|
if codec in codec_alias:
|
||||||
|
extra = [item for item in codec_alias[codec] if item not in VCODEC_ALLOW]
|
||||||
|
VCODEC_ALLOW.extend(extra)
|
||||||
|
if not ACODEC_ALLOW and ACODEC:
|
||||||
|
ACODEC_ALLOW.extend([ACODEC])
|
||||||
|
for codec in ACODEC_ALLOW:
|
||||||
|
if codec in codec_alias:
|
||||||
|
extra = [item for item in codec_alias[codec] if item not in ACODEC_ALLOW]
|
||||||
|
ACODEC_ALLOW.extend(extra)
|
||||||
|
if not ACODEC2_ALLOW and ACODEC2:
|
||||||
|
ACODEC2_ALLOW.extend([ACODEC2])
|
||||||
|
for codec in ACODEC2_ALLOW:
|
||||||
|
if codec in codec_alias:
|
||||||
|
extra = [item for item in codec_alias[codec] if item not in ACODEC2_ALLOW]
|
||||||
|
ACODEC2_ALLOW.extend(extra)
|
||||||
|
if not ACODEC3_ALLOW and ACODEC3:
|
||||||
|
ACODEC3_ALLOW.extend([ACODEC3])
|
||||||
|
for codec in ACODEC3_ALLOW:
|
||||||
|
if codec in codec_alias:
|
||||||
|
extra = [item for item in codec_alias[codec] if item not in ACODEC3_ALLOW]
|
||||||
|
ACODEC3_ALLOW.extend(extra)
|
||||||
|
|
45
nzb2media/transmission.py
Normal file
45
nzb2media/transmission.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from transmission_rpc.client import Client as TransmissionClient
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
HOST = None
|
||||||
|
PORT = None
|
||||||
|
USERNAME = None
|
||||||
|
PASSWORD = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_transmission(config):
|
||||||
|
global HOST
|
||||||
|
global PORT
|
||||||
|
global USERNAME
|
||||||
|
global PASSWORD
|
||||||
|
|
||||||
|
HOST = config['TransmissionHost'] # localhost
|
||||||
|
PORT = int(config['TransmissionPort'])
|
||||||
|
USERNAME = config['TransmissionUSR'] # mysecretusr
|
||||||
|
PASSWORD = config['TransmissionPWD'] # mysecretpwr
|
||||||
|
|
||||||
|
|
||||||
|
def configure_client():
|
||||||
|
agent = 'transmission'
|
||||||
|
host = HOST
|
||||||
|
port = PORT
|
||||||
|
user = USERNAME
|
||||||
|
password = PASSWORD
|
||||||
|
log.debug(f'Connecting to {agent}: http://{host}:{port}')
|
||||||
|
try:
|
||||||
|
client = TransmissionClient(
|
||||||
|
host=host or '127.0.0.1',
|
||||||
|
port=port or 9091,
|
||||||
|
username=user,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
log.error('Failed to connect to Transmission')
|
||||||
|
else:
|
||||||
|
return client
|
|
@ -7,46 +7,62 @@ from subprocess import Popen
|
||||||
import nzb2media
|
import nzb2media
|
||||||
from nzb2media import transcoder
|
from nzb2media import transcoder
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
from nzb2media.auto_process.common import ProcessResult
|
||||||
from nzb2media.plugins.subtitles import import_subs
|
from nzb2media.subtitles import import_subs
|
||||||
from nzb2media.utils.files import list_media_files
|
from nzb2media.utils.files import list_media_files
|
||||||
from nzb2media.utils.paths import remove_dir
|
from nzb2media.utils.paths import remove_dir
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
MEDIA_EXTENSIONS = None
|
||||||
|
SCRIPT = None
|
||||||
|
PARAMETERS = None
|
||||||
|
SUCCESS_CODES = None
|
||||||
|
CLEAN = None
|
||||||
|
DELAY = None
|
||||||
|
RUN_ONCE = None
|
||||||
|
|
||||||
|
|
||||||
def external_script(output_destination, torrent_name, torrent_label, settings):
|
def external_script(output_destination, torrent_name, torrent_label, settings):
|
||||||
|
global MEDIA_EXTENSIONS
|
||||||
|
global SCRIPT
|
||||||
|
global PARAMETERS
|
||||||
|
global SUCCESS_CODES
|
||||||
|
global CLEAN
|
||||||
|
global RUN_ONCE
|
||||||
|
global DELAY
|
||||||
|
|
||||||
final_result = 0 # start at 0.
|
final_result = 0 # start at 0.
|
||||||
num_files = 0
|
num_files = 0
|
||||||
nzb2media.USER_SCRIPT_MEDIAEXTENSIONS = settings.get('user_script_mediaExtensions', '')
|
MEDIA_EXTENSIONS = settings.get('user_script_mediaExtensions', '')
|
||||||
try:
|
try:
|
||||||
if isinstance(nzb2media.USER_SCRIPT_MEDIAEXTENSIONS, str):
|
if isinstance(MEDIA_EXTENSIONS, str):
|
||||||
nzb2media.USER_SCRIPT_MEDIAEXTENSIONS = nzb2media.USER_SCRIPT_MEDIAEXTENSIONS.lower().split(',')
|
MEDIA_EXTENSIONS = MEDIA_EXTENSIONS.lower().split(',')
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error('user_script_mediaExtensions could not be set')
|
log.error('user_script_mediaExtensions could not be set')
|
||||||
nzb2media.USER_SCRIPT_MEDIAEXTENSIONS = []
|
MEDIA_EXTENSIONS = []
|
||||||
nzb2media.USER_SCRIPT = settings.get('user_script_path', '')
|
SCRIPT = settings.get('user_script_path', '')
|
||||||
if not nzb2media.USER_SCRIPT or nzb2media.USER_SCRIPT == 'None':
|
if not SCRIPT or SCRIPT == 'None':
|
||||||
# do nothing and return success. This allows the user an option to Link files only and not run a script.
|
# do nothing and return success. This allows the user an option to Link files only and not run a script.
|
||||||
return ProcessResult(status_code=0, message='No user script defined')
|
return ProcessResult(status_code=0, message='No user script defined')
|
||||||
nzb2media.USER_SCRIPT_PARAM = settings.get('user_script_param', '')
|
PARAMETERS = settings.get('user_script_param', '')
|
||||||
try:
|
try:
|
||||||
if isinstance(nzb2media.USER_SCRIPT_PARAM, str):
|
if isinstance(PARAMETERS, str):
|
||||||
nzb2media.USER_SCRIPT_PARAM = nzb2media.USER_SCRIPT_PARAM.split(',')
|
PARAMETERS = PARAMETERS.split(',')
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error('user_script_params could not be set')
|
log.error('user_script_params could not be set')
|
||||||
nzb2media.USER_SCRIPT_PARAM = []
|
PARAMETERS = []
|
||||||
nzb2media.USER_SCRIPT_SUCCESSCODES = settings.get('user_script_successCodes', 0)
|
SUCCESS_CODES = settings.get('user_script_successCodes', 0)
|
||||||
try:
|
try:
|
||||||
if isinstance(nzb2media.USER_SCRIPT_SUCCESSCODES, str):
|
if isinstance(SUCCESS_CODES, str):
|
||||||
nzb2media.USER_SCRIPT_SUCCESSCODES = nzb2media.USER_SCRIPT_SUCCESSCODES.split(',')
|
SUCCESS_CODES = SUCCESS_CODES.split(',')
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error('user_script_successCodes could not be set')
|
log.error('user_script_successCodes could not be set')
|
||||||
nzb2media.USER_SCRIPT_SUCCESSCODES = 0
|
SUCCESS_CODES = 0
|
||||||
nzb2media.USER_SCRIPT_CLEAN = int(settings.get('user_script_clean', 1))
|
CLEAN = int(settings.get('user_script_clean', 1))
|
||||||
nzb2media.USER_SCRIPT_RUNONCE = int(settings.get('user_script_runOnce', 1))
|
RUN_ONCE = int(settings.get('user_script_runOnce', 1))
|
||||||
if nzb2media.CHECK_MEDIA:
|
if nzb2media.CHECK_MEDIA:
|
||||||
for video in list_media_files(output_destination, media=True, audio=False, meta=False, archives=False):
|
for video in list_media_files(output_destination, audio=False, meta=False, archives=False):
|
||||||
if transcoder.is_video_good(video, 0):
|
if transcoder.is_video_good(video, 0):
|
||||||
import_subs(video)
|
import_subs(video)
|
||||||
else:
|
else:
|
||||||
|
@ -57,12 +73,12 @@ def external_script(output_destination, torrent_name, torrent_label, settings):
|
||||||
file_path = nzb2media.os.path.join(dirpath, file)
|
file_path = nzb2media.os.path.join(dirpath, file)
|
||||||
file_name, file_extension = os.path.splitext(file)
|
file_name, file_extension = os.path.splitext(file)
|
||||||
log.debug(f'Checking file {file} to see if this should be processed.')
|
log.debug(f'Checking file {file} to see if this should be processed.')
|
||||||
if file_extension in nzb2media.USER_SCRIPT_MEDIAEXTENSIONS or 'all' in nzb2media.USER_SCRIPT_MEDIAEXTENSIONS:
|
if file_extension in MEDIA_EXTENSIONS or 'all' in MEDIA_EXTENSIONS:
|
||||||
num_files += 1
|
num_files += 1
|
||||||
if nzb2media.USER_SCRIPT_RUNONCE == 1 and num_files > 1: # we have already run once, so just continue to get number of files.
|
if RUN_ONCE == 1 and num_files > 1: # we have already run once, so just continue to get number of files.
|
||||||
continue
|
continue
|
||||||
command = [nzb2media.USER_SCRIPT]
|
command = [SCRIPT]
|
||||||
for param in nzb2media.USER_SCRIPT_PARAM:
|
for param in PARAMETERS:
|
||||||
if param == 'FN':
|
if param == 'FN':
|
||||||
command.append(f'{file}')
|
command.append(f'{file}')
|
||||||
continue
|
continue
|
||||||
|
@ -76,7 +92,7 @@ def external_script(output_destination, torrent_name, torrent_label, settings):
|
||||||
command.append(f'{torrent_label}')
|
command.append(f'{torrent_label}')
|
||||||
continue
|
continue
|
||||||
if param == 'DN':
|
if param == 'DN':
|
||||||
if nzb2media.USER_SCRIPT_RUNONCE == 1:
|
if RUN_ONCE == 1:
|
||||||
command.append(f'{output_destination}')
|
command.append(f'{output_destination}')
|
||||||
else:
|
else:
|
||||||
command.append(f'{dirpath}')
|
command.append(f'{dirpath}')
|
||||||
|
@ -93,7 +109,7 @@ def external_script(output_destination, torrent_name, torrent_label, settings):
|
||||||
log.error(f'UserScript {command[0]} has failed')
|
log.error(f'UserScript {command[0]} has failed')
|
||||||
result = 1
|
result = 1
|
||||||
else:
|
else:
|
||||||
if str(res) in nzb2media.USER_SCRIPT_SUCCESSCODES:
|
if str(res) in SUCCESS_CODES:
|
||||||
# Linux returns 0 for successful.
|
# Linux returns 0 for successful.
|
||||||
log.info(f'UserScript {command[0]} was successfull')
|
log.info(f'UserScript {command[0]} was successfull')
|
||||||
result = 0
|
result = 0
|
||||||
|
@ -106,11 +122,11 @@ def external_script(output_destination, torrent_name, torrent_label, settings):
|
||||||
for _, _, filenames in os.walk(output_destination):
|
for _, _, filenames in os.walk(output_destination):
|
||||||
for file in filenames:
|
for file in filenames:
|
||||||
file_name, file_extension = os.path.splitext(file)
|
file_name, file_extension = os.path.splitext(file)
|
||||||
if file_extension in nzb2media.USER_SCRIPT_MEDIAEXTENSIONS or nzb2media.USER_SCRIPT_MEDIAEXTENSIONS == 'ALL':
|
if file_extension in MEDIA_EXTENSIONS or MEDIA_EXTENSIONS == 'ALL':
|
||||||
num_files_new += 1
|
num_files_new += 1
|
||||||
if nzb2media.USER_SCRIPT_CLEAN == 1 and not num_files_new and not final_result:
|
if CLEAN == 1 and not num_files_new and not final_result:
|
||||||
log.info(f'All files have been processed. Cleaning outputDirectory {output_destination}')
|
log.info(f'All files have been processed. Cleaning outputDirectory {output_destination}')
|
||||||
remove_dir(output_destination)
|
remove_dir(output_destination)
|
||||||
elif nzb2media.USER_SCRIPT_CLEAN == 1 and num_files_new:
|
elif CLEAN == 1 and num_files_new:
|
||||||
log.info(f'{num_files} files were processed, but {num_files_new} still remain. outputDirectory will not be cleaned.')
|
log.info(f'{num_files} files were processed, but {num_files_new} still remain. outputDirectory will not be cleaned.')
|
||||||
return ProcessResult(status_code=final_result, message='User Script Completed')
|
return ProcessResult(status_code=final_result, message='User Script Completed')
|
||||||
|
|
|
@ -3,11 +3,11 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from nzb2media import main_db
|
import nzb2media.databases
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
database = main_db.DBConnection()
|
database = nzb2media.databases.DBConnection()
|
||||||
|
|
||||||
|
|
||||||
def update_download_info_status(input_name, status):
|
def update_download_info_status(input_name, status):
|
||||||
|
|
|
@ -11,7 +11,7 @@ import guessit
|
||||||
import mediafile
|
import mediafile
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
from nzb2media import extractor
|
import nzb2media.tool
|
||||||
from nzb2media.utils.links import copy_link
|
from nzb2media.utils.links import copy_link
|
||||||
from nzb2media.utils.naming import is_sample
|
from nzb2media.utils.naming import is_sample
|
||||||
from nzb2media.utils.naming import sanitize_name
|
from nzb2media.utils.naming import sanitize_name
|
||||||
|
@ -153,7 +153,7 @@ def extract_files(src, dst=None, keep_archive=None):
|
||||||
if dir_path in extracted_folder and archive_name in extracted_archive:
|
if dir_path in extracted_folder and archive_name in extracted_archive:
|
||||||
continue # no need to extract this, but keep going to look for other archives and sub directories.
|
continue # no need to extract this, but keep going to look for other archives and sub directories.
|
||||||
try:
|
try:
|
||||||
if extractor.extract(input_file, dst or dir_path):
|
if nzb2media.tool.extract(input_file, dst or dir_path):
|
||||||
extracted_folder.append(dir_path)
|
extracted_folder.append(dir_path)
|
||||||
extracted_archive.append(archive_name)
|
extracted_archive.append(archive_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -8,6 +8,8 @@ import time
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.nzb
|
||||||
|
import nzb2media.torrent
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
@ -80,12 +82,12 @@ def server_responding(base_url):
|
||||||
def find_download(client_agent, download_id):
|
def find_download(client_agent, download_id):
|
||||||
log.debug(f'Searching for Download on {client_agent} ...')
|
log.debug(f'Searching for Download on {client_agent} ...')
|
||||||
if client_agent == 'utorrent':
|
if client_agent == 'utorrent':
|
||||||
torrents = nzb2media.TORRENT_CLASS.list()[1]['torrents']
|
torrents = nzb2media.torrent.CLASS.list()[1]['torrents']
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
if download_id in torrent:
|
if download_id in torrent:
|
||||||
return True
|
return True
|
||||||
if client_agent == 'transmission':
|
if client_agent == 'transmission':
|
||||||
torrents = nzb2media.TORRENT_CLASS.get_torrents()
|
torrents = nzb2media.torrent.CLASS.get_torrents()
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
torrent_hash = torrent.hashString
|
torrent_hash = torrent.hashString
|
||||||
if torrent_hash == download_id:
|
if torrent_hash == download_id:
|
||||||
|
@ -93,17 +95,17 @@ def find_download(client_agent, download_id):
|
||||||
if client_agent == 'deluge':
|
if client_agent == 'deluge':
|
||||||
return False
|
return False
|
||||||
if client_agent == 'qbittorrent':
|
if client_agent == 'qbittorrent':
|
||||||
torrents = nzb2media.TORRENT_CLASS.torrents()
|
torrents = nzb2media.torrent.CLASS.torrents()
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
if torrent['hash'] == download_id:
|
if torrent['hash'] == download_id:
|
||||||
return True
|
return True
|
||||||
if client_agent == 'sabnzbd':
|
if client_agent == 'sabnzbd':
|
||||||
if 'http' in nzb2media.SABNZBD_HOST:
|
if 'http' in nzb2media.nzb.SABNZBD_HOST:
|
||||||
base_url = f'{nzb2media.SABNZBD_HOST}:{nzb2media.SABNZBD_PORT}/api'
|
base_url = f'{nzb2media.nzb.SABNZBD_HOST}:{nzb2media.nzb.SABNZBD_PORT}/api'
|
||||||
else:
|
else:
|
||||||
base_url = f'http://{nzb2media.SABNZBD_HOST}:{nzb2media.SABNZBD_PORT}/api'
|
base_url = f'http://{nzb2media.nzb.SABNZBD_HOST}:{nzb2media.nzb.SABNZBD_PORT}/api'
|
||||||
url = base_url
|
url = base_url
|
||||||
params = {'apikey': nzb2media.SABNZBD_APIKEY, 'mode': 'get_files', 'output': 'json', 'value': download_id}
|
params = {'apikey': nzb2media.nzb.SABNZBD_APIKEY, 'mode': 'get_files', 'output': 'json', 'value': download_id}
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, params=params, verify=False, timeout=(30, 120))
|
response = requests.get(url, params=params, verify=False, timeout=(30, 120))
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
import nzb2media.torrent
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
@ -61,7 +62,7 @@ def parse_deluge(args):
|
||||||
input_hash = args[1]
|
input_hash = args[1]
|
||||||
input_id = args[1]
|
input_id = args[1]
|
||||||
try:
|
try:
|
||||||
input_category = nzb2media.TORRENT_CLASS.core.get_torrent_status(input_id, ['label']).get(b'label').decode()
|
input_category = nzb2media.torrent.CLASS.core.get_torrent_status(input_id, ['label']).get(b'label').decode()
|
||||||
except Exception:
|
except Exception:
|
||||||
input_category = ''
|
input_category = ''
|
||||||
return input_directory, input_name, input_category, input_hash, input_id
|
return input_directory, input_name, input_category, input_hash, input_id
|
||||||
|
@ -89,7 +90,7 @@ def parse_synods():
|
||||||
torrent_id = os.getenv('TR_TORRENT_ID')
|
torrent_id = os.getenv('TR_TORRENT_ID')
|
||||||
input_id = f'dbid_{torrent_id}'
|
input_id = f'dbid_{torrent_id}'
|
||||||
# res = nzb2media.TORRENT_CLASS.tasks_list(additional_param='detail')
|
# res = nzb2media.TORRENT_CLASS.tasks_list(additional_param='detail')
|
||||||
res = nzb2media.TORRENT_CLASS.tasks_info(input_id, additional_param='detail')
|
res = nzb2media.torrent.CLASS.tasks_info(input_id, additional_param='detail')
|
||||||
log.debug(f'result from syno {res}')
|
log.debug(f'result from syno {res}')
|
||||||
if res['success']:
|
if res['success']:
|
||||||
try:
|
try:
|
||||||
|
@ -176,7 +177,16 @@ def parse_qbittorrent(args):
|
||||||
|
|
||||||
|
|
||||||
def parse_args(client_agent, args):
|
def parse_args(client_agent, args):
|
||||||
clients = {'other': parse_other, 'rtorrent': parse_rtorrent, 'utorrent': parse_utorrent, 'deluge': parse_deluge, 'transmission': parse_transmission, 'qbittorrent': parse_qbittorrent, 'vuze': parse_vuze, 'synods': parse_synods}
|
clients = {
|
||||||
|
'other': parse_other,
|
||||||
|
'rtorrent': parse_rtorrent,
|
||||||
|
'utorrent': parse_utorrent,
|
||||||
|
'deluge': parse_deluge,
|
||||||
|
'transmission': parse_transmission,
|
||||||
|
'qbittorrent': parse_qbittorrent,
|
||||||
|
'vuze': parse_vuze,
|
||||||
|
'synods': parse_synods,
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
return clients[client_agent](args)
|
return clients[client_agent](args)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
# Silence errors on linux
|
|
||||||
# pylint: disable=import-error
|
|
||||||
# pylint: disable=no-name-in-module
|
|
||||||
from win32api import CloseHandle
|
|
||||||
from win32api import GetLastError
|
|
||||||
from win32event import CreateMutex
|
|
||||||
from winerror import ERROR_ALREADY_EXISTS
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
class WindowsProcess:
|
|
||||||
def __init__(self):
|
|
||||||
self.mutex = None
|
|
||||||
# {D0E858DF-985E-4907-B7FB-8D732C3FC3B9}
|
|
||||||
_path_str = os.fspath(nzb2media.PID_FILE).replace('\\', '/')
|
|
||||||
self.mutexname = f'nzbtomedia_{_path_str}'
|
|
||||||
self.create_mutex = CreateMutex
|
|
||||||
self.close_handle = CloseHandle
|
|
||||||
self.get_last_error = GetLastError
|
|
||||||
self.error_already_exists = ERROR_ALREADY_EXISTS
|
|
||||||
|
|
||||||
def alreadyrunning(self):
|
|
||||||
self.mutex = self.create_mutex(None, 0, self.mutexname)
|
|
||||||
self.lasterror = self.get_last_error()
|
|
||||||
if self.lasterror == self.error_already_exists:
|
|
||||||
self.close_handle(self.mutex)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.mutex:
|
|
||||||
self.close_handle(self.mutex)
|
|
||||||
|
|
||||||
|
|
||||||
class PosixProcess:
|
|
||||||
def __init__(self):
|
|
||||||
self.pidpath = nzb2media.PID_FILE
|
|
||||||
self.lock_socket = None
|
|
||||||
|
|
||||||
def alreadyrunning(self):
|
|
||||||
try:
|
|
||||||
self.lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
|
||||||
self.lock_socket.bind(f'\0{self.pidpath}')
|
|
||||||
self.lasterror = False
|
|
||||||
return self.lasterror
|
|
||||||
except OSError as error:
|
|
||||||
if 'Address already in use' in str(error):
|
|
||||||
self.lasterror = True
|
|
||||||
return self.lasterror
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if self.pidpath.exists():
|
|
||||||
# Make sure it is not a 'stale' pidFile
|
|
||||||
try:
|
|
||||||
pid = int(self.pidpath.read_text().strip())
|
|
||||||
except Exception:
|
|
||||||
pid = None
|
|
||||||
# Check list of running pids, if not running it is stale so overwrite
|
|
||||||
if isinstance(pid, int):
|
|
||||||
try:
|
|
||||||
os.kill(pid, 0)
|
|
||||||
self.lasterror = True
|
|
||||||
except OSError:
|
|
||||||
self.lasterror = False
|
|
||||||
else:
|
|
||||||
self.lasterror = False
|
|
||||||
else:
|
|
||||||
self.lasterror = False
|
|
||||||
if not self.lasterror:
|
|
||||||
# Write my pid into pidFile to keep multiple copies of program
|
|
||||||
# from running
|
|
||||||
self.pidpath.write_text(os.getpid())
|
|
||||||
return self.lasterror
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if not self.lasterror:
|
|
||||||
if self.lock_socket:
|
|
||||||
self.lock_socket.close()
|
|
||||||
if self.pidpath.is_file():
|
|
||||||
self.pidpath.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
# Alternative union syntax using | fails on Python < 3.10
|
|
||||||
# pylint: disable-next=consider-alternative-union-syntax
|
|
||||||
ProcessType = typing.Type[typing.Union[PosixProcess, WindowsProcess]]
|
|
||||||
if os.name == 'nt':
|
|
||||||
RunningProcess: ProcessType = WindowsProcess
|
|
||||||
else:
|
|
||||||
RunningProcess = PosixProcess
|
|
||||||
|
|
||||||
|
|
||||||
def restart():
|
|
||||||
install_type = nzb2media.version_check.CheckVersion().install_type
|
|
||||||
status = 0
|
|
||||||
popen_list = []
|
|
||||||
if install_type in {'git', 'source'}:
|
|
||||||
popen_list = [sys.executable, nzb2media.APP_FILENAME]
|
|
||||||
if popen_list:
|
|
||||||
popen_list += nzb2media.SYS_ARGV
|
|
||||||
log.info(f'Restarting nzbToMedia with {popen_list}')
|
|
||||||
with subprocess.Popen(popen_list, cwd=os.getcwd()) as proc:
|
|
||||||
proc.wait()
|
|
||||||
status = proc.returncode
|
|
||||||
os._exit(status)
|
|
|
@ -1,87 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
from nzb2media.torrent import deluge
|
|
||||||
from nzb2media.torrent import qbittorrent
|
|
||||||
from nzb2media.torrent import synology
|
|
||||||
from nzb2media.torrent import transmission
|
|
||||||
from nzb2media.torrent import utorrent
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
torrent_clients = {'deluge': deluge, 'qbittorrent': qbittorrent, 'transmission': transmission, 'utorrent': utorrent, 'synods': synology}
|
|
||||||
|
|
||||||
|
|
||||||
def create_torrent_class(client_agent) -> object | None:
|
|
||||||
if nzb2media.APP_NAME != 'TorrentToMedia.py':
|
|
||||||
return None # Skip loading Torrent for NZBs.
|
|
||||||
try:
|
|
||||||
agent = torrent_clients[client_agent]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
deluge.configure_client()
|
|
||||||
return agent.configure_client()
|
|
||||||
|
|
||||||
|
|
||||||
def pause_torrent(client_agent, input_hash, input_id, input_name):
|
|
||||||
log.debug(f'Stopping torrent {input_name} in {client_agent} while processing')
|
|
||||||
try:
|
|
||||||
if client_agent == 'utorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.stop(input_hash)
|
|
||||||
if client_agent == 'transmission' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.stop_torrent(input_id)
|
|
||||||
if client_agent == 'synods' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.pause_task(input_id)
|
|
||||||
if client_agent == 'deluge' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.core.pause_torrent([input_id])
|
|
||||||
if client_agent == 'qbittorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.pause(input_hash)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception:
|
|
||||||
log.warning(f'Failed to stop torrent {input_name} in {client_agent}')
|
|
||||||
|
|
||||||
|
|
||||||
def resume_torrent(client_agent, input_hash, input_id, input_name):
|
|
||||||
if nzb2media.TORRENT_RESUME != 1:
|
|
||||||
return
|
|
||||||
log.debug(f'Starting torrent {input_name} in {client_agent}')
|
|
||||||
try:
|
|
||||||
if client_agent == 'utorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.start(input_hash)
|
|
||||||
if client_agent == 'transmission' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.start_torrent(input_id)
|
|
||||||
if client_agent == 'synods' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.resume_task(input_id)
|
|
||||||
if client_agent == 'deluge' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.core.resume_torrent([input_id])
|
|
||||||
if client_agent == 'qbittorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.resume(input_hash)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception:
|
|
||||||
log.warning(f'Failed to start torrent {input_name} in {client_agent}')
|
|
||||||
|
|
||||||
|
|
||||||
def remove_torrent(client_agent, input_hash, input_id, input_name):
|
|
||||||
if nzb2media.DELETE_ORIGINAL == 1 or nzb2media.USE_LINK == 'move':
|
|
||||||
log.debug(f'Deleting torrent {input_name} from {client_agent}')
|
|
||||||
try:
|
|
||||||
if client_agent == 'utorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.removedata(input_hash)
|
|
||||||
nzb2media.TORRENT_CLASS.remove(input_hash)
|
|
||||||
if client_agent == 'transmission' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.remove_torrent(input_id, True)
|
|
||||||
if client_agent == 'synods' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.delete_task(input_id)
|
|
||||||
if client_agent == 'deluge' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.core.remove_torrent(input_id, True)
|
|
||||||
if client_agent == 'qbittorrent' and nzb2media.TORRENT_CLASS:
|
|
||||||
nzb2media.TORRENT_CLASS.delete_permanently(input_hash)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception:
|
|
||||||
log.warning(f'Failed to delete torrent {input_name} in {client_agent}')
|
|
||||||
else:
|
|
||||||
resume_torrent(client_agent, input_hash, input_id, input_name)
|
|
|
@ -4,17 +4,29 @@ import logging
|
||||||
|
|
||||||
from utorrent.client import UTorrentClient
|
from utorrent.client import UTorrentClient
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.addHandler(logging.NullHandler())
|
log.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
HOST = None
|
||||||
|
USERNAME = None
|
||||||
|
PASSWORD = None
|
||||||
|
|
||||||
|
|
||||||
|
def configure_utorrent(config):
|
||||||
|
global HOST
|
||||||
|
global USERNAME
|
||||||
|
global PASSWORD
|
||||||
|
|
||||||
|
HOST = config['uTorrentWEBui'] # http://localhost:8090/gui/
|
||||||
|
USERNAME = config['uTorrentUSR'] # mysecretusr
|
||||||
|
PASSWORD = config['uTorrentPWD'] # mysecretpwr
|
||||||
|
|
||||||
|
|
||||||
def configure_client():
|
def configure_client():
|
||||||
agent = 'utorrent'
|
agent = 'utorrent'
|
||||||
web_ui = nzb2media.UTORRENT_WEB_UI
|
web_ui = HOST
|
||||||
user = nzb2media.UTORRENT_USER
|
user = USERNAME
|
||||||
password = nzb2media.UTORRENT_PASSWORD
|
password = PASSWORD
|
||||||
log.debug(f'Connecting to {agent}: {web_ui}')
|
log.debug(f'Connecting to {agent}: {web_ui}')
|
||||||
try:
|
try:
|
||||||
client = UTorrentClient(web_ui, user, password)
|
client = UTorrentClient(web_ui, user, password)
|
|
@ -1,423 +0,0 @@
|
||||||
# Author: Nic Wolfe <nic@wolfeden.ca>
|
|
||||||
# Modified by: echel0n
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
import traceback
|
|
||||||
from subprocess import PIPE
|
|
||||||
from subprocess import STDOUT
|
|
||||||
from urllib.request import urlretrieve
|
|
||||||
|
|
||||||
import nzb2media
|
|
||||||
from nzb2media import github_api as github
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
class CheckVersion:
|
|
||||||
"""Version checker that runs in a thread with the SB scheduler."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.install_type = self.find_install_type()
|
|
||||||
self.installed_version = None
|
|
||||||
self.installed_branch = None
|
|
||||||
if self.install_type == 'git':
|
|
||||||
self.updater = GitUpdateManager()
|
|
||||||
elif self.install_type == 'source':
|
|
||||||
self.updater = SourceUpdateManager()
|
|
||||||
else:
|
|
||||||
self.updater = None
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.check_for_new_version()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def find_install_type():
|
|
||||||
"""Determine how this copy of SB was installed.
|
|
||||||
|
|
||||||
returns: type of installation. Possible values are:
|
|
||||||
'win': any compiled windows build
|
|
||||||
'git': running from source using git
|
|
||||||
'source': running from source without git
|
|
||||||
"""
|
|
||||||
# check if we're a windows build
|
|
||||||
if os.path.exists(os.path.join(nzb2media.APP_ROOT, '.git')):
|
|
||||||
install_type = 'git'
|
|
||||||
else:
|
|
||||||
install_type = 'source'
|
|
||||||
return install_type
|
|
||||||
|
|
||||||
def check_for_new_version(self, force=False):
|
|
||||||
"""Check the internet for a newer version.
|
|
||||||
|
|
||||||
returns: bool, True for new version or False for no new version.
|
|
||||||
force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced
|
|
||||||
"""
|
|
||||||
if not nzb2media.VERSION_NOTIFY and not force:
|
|
||||||
log.info('Version checking is disabled, not checking for the newest version')
|
|
||||||
return False
|
|
||||||
log.info(f'Checking if {self.install_type} needs an update')
|
|
||||||
if not self.updater.need_update():
|
|
||||||
nzb2media.NEWEST_VERSION_STRING = None
|
|
||||||
log.info('No update needed')
|
|
||||||
return False
|
|
||||||
self.updater.set_newest_text()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
if self.updater.need_update():
|
|
||||||
result = self.updater.update()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateManager:
|
|
||||||
@staticmethod
|
|
||||||
def get_github_repo_user():
|
|
||||||
return nzb2media.GIT_USER
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_github_repo():
|
|
||||||
return nzb2media.GIT_REPO
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_github_branch():
|
|
||||||
return nzb2media.GIT_BRANCH
|
|
||||||
|
|
||||||
|
|
||||||
class GitUpdateManager(UpdateManager):
|
|
||||||
def __init__(self):
|
|
||||||
self._git_path = self._find_working_git()
|
|
||||||
self.github_repo_user = self.get_github_repo_user()
|
|
||||||
self.github_repo = self.get_github_repo()
|
|
||||||
self.branch = self._find_git_branch()
|
|
||||||
self._cur_commit_hash = None
|
|
||||||
self._newest_commit_hash = None
|
|
||||||
self._num_commits_behind = 0
|
|
||||||
self._num_commits_ahead = 0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _git_error():
|
|
||||||
log.debug('Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
|
|
||||||
|
|
||||||
def _find_working_git(self):
|
|
||||||
test_cmd = 'version'
|
|
||||||
if nzb2media.GIT_PATH:
|
|
||||||
main_git = f'"{nzb2media.GIT_PATH}"'
|
|
||||||
else:
|
|
||||||
main_git = 'git'
|
|
||||||
log.debug(f'Checking if we can use git commands: {main_git} {test_cmd}')
|
|
||||||
output, err, exit_status = self._run_git(main_git, test_cmd)
|
|
||||||
if not exit_status:
|
|
||||||
log.debug(f'Using: {main_git}')
|
|
||||||
return main_git
|
|
||||||
log.debug(f'Not using: {main_git}')
|
|
||||||
# trying alternatives
|
|
||||||
alternative_git = []
|
|
||||||
# osx people who start SB from launchd have a broken path, so try a hail-mary attempt for them
|
|
||||||
if platform.system().lower() == 'darwin':
|
|
||||||
alternative_git.append('/usr/local/git/bin/git')
|
|
||||||
if platform.system().lower() == 'windows':
|
|
||||||
if main_git != main_git.lower():
|
|
||||||
alternative_git.append(main_git.lower())
|
|
||||||
if alternative_git:
|
|
||||||
log.debug('Trying known alternative git locations')
|
|
||||||
for cur_git in alternative_git:
|
|
||||||
log.debug(f'Checking if we can use git commands: {cur_git} {test_cmd}')
|
|
||||||
output, err, exit_status = self._run_git(cur_git, test_cmd)
|
|
||||||
if not exit_status:
|
|
||||||
log.debug(f'Using: {cur_git}')
|
|
||||||
return cur_git
|
|
||||||
log.debug(f'Not using: {cur_git}')
|
|
||||||
# Still haven't found a working git
|
|
||||||
log.debug('Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _run_git(git_path, args):
|
|
||||||
result = ''
|
|
||||||
proc_err = ''
|
|
||||||
if not git_path:
|
|
||||||
log.debug('No git specified, can\'t use git commands')
|
|
||||||
proc_status = 1
|
|
||||||
return result, proc_err, proc_status
|
|
||||||
cmd = f'{git_path} {args}'
|
|
||||||
try:
|
|
||||||
log.debug(f'Executing {cmd} with your shell in {nzb2media.APP_ROOT}')
|
|
||||||
with subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=True, cwd=nzb2media.APP_ROOT) as proc:
|
|
||||||
proc_out, proc_err = proc.communicate()
|
|
||||||
proc_status = proc.returncode
|
|
||||||
if nzb2media.LOG_GIT:
|
|
||||||
msg = proc_out.decode('utf-8').strip()
|
|
||||||
log.debug(f'git output: {msg}')
|
|
||||||
except OSError:
|
|
||||||
log.error(f'Command {cmd} didn\'t work')
|
|
||||||
proc_status = 1
|
|
||||||
proc_status = 128 if ('fatal:' in result) or proc_err else proc_status
|
|
||||||
if not proc_status:
|
|
||||||
log.debug(f'{cmd} : returned successful')
|
|
||||||
proc_status = 0
|
|
||||||
elif nzb2media.LOG_GIT and proc_status in {1, 128}:
|
|
||||||
log.debug(f'{cmd} returned : {result}')
|
|
||||||
else:
|
|
||||||
if nzb2media.LOG_GIT:
|
|
||||||
log.debug(f'{cmd} returned : {result}, treat as error for now')
|
|
||||||
proc_status = 1
|
|
||||||
return result, proc_err, proc_status
|
|
||||||
|
|
||||||
def _find_installed_version(self):
|
|
||||||
"""Attempt to find the currently installed version of Sick Beard.
|
|
||||||
|
|
||||||
Uses git show to get commit version.
|
|
||||||
Returns: True for success or False for failure
|
|
||||||
"""
|
|
||||||
output, err, exit_status = self._run_git(self._git_path, 'rev-parse HEAD')
|
|
||||||
if not exit_status and output:
|
|
||||||
cur_commit_hash = output.strip()
|
|
||||||
if not re.match('^[a-z0-9]+$', cur_commit_hash):
|
|
||||||
log.error('Output doesn\'t look like a hash, not using it')
|
|
||||||
return False
|
|
||||||
self._cur_commit_hash = cur_commit_hash
|
|
||||||
if self._cur_commit_hash:
|
|
||||||
nzb2media.NZBTOMEDIA_VERSION = self._cur_commit_hash
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _find_git_branch(self):
|
|
||||||
nzb2media.NZBTOMEDIA_BRANCH = self.get_github_branch()
|
|
||||||
branch_info, err, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD')
|
|
||||||
if not exit_status and branch_info:
|
|
||||||
branch = branch_info.strip().replace('refs/heads/', '', 1)
|
|
||||||
if branch:
|
|
||||||
nzb2media.NZBTOMEDIA_BRANCH = branch
|
|
||||||
nzb2media.GIT_BRANCH = branch
|
|
||||||
return nzb2media.GIT_BRANCH
|
|
||||||
|
|
||||||
def _check_github_for_update(self):
|
|
||||||
"""Check Github for a new version.
|
|
||||||
|
|
||||||
Uses git commands to check if there is a newer version than
|
|
||||||
the provided commit hash. If there is a newer version it
|
|
||||||
sets _num_commits_behind.
|
|
||||||
"""
|
|
||||||
self._newest_commit_hash = None
|
|
||||||
self._num_commits_behind = 0
|
|
||||||
self._num_commits_ahead = 0
|
|
||||||
# get all new info from github
|
|
||||||
output, err, exit_status = self._run_git(self._git_path, 'fetch origin')
|
|
||||||
if exit_status:
|
|
||||||
log.error('Unable to contact github, can\'t check for update')
|
|
||||||
return
|
|
||||||
# get latest commit_hash from remote
|
|
||||||
output, err, exit_status = self._run_git(self._git_path, 'rev-parse --verify --quiet \'@{upstream}\'')
|
|
||||||
if not exit_status and output:
|
|
||||||
cur_commit_hash = output.strip()
|
|
||||||
if not re.match('^[a-z0-9]+$', cur_commit_hash):
|
|
||||||
log.debug('Output doesn\'t look like a hash, not using it')
|
|
||||||
return
|
|
||||||
self._newest_commit_hash = cur_commit_hash
|
|
||||||
else:
|
|
||||||
log.debug('git didn\'t return newest commit hash')
|
|
||||||
return
|
|
||||||
# get number of commits behind and ahead (option --count not supported git < 1.7.2)
|
|
||||||
output, err, exit_status = self._run_git(self._git_path, 'rev-list --left-right \'@{upstream}\'...HEAD')
|
|
||||||
if not exit_status and output:
|
|
||||||
try:
|
|
||||||
self._num_commits_behind = int(output.count('<'))
|
|
||||||
self._num_commits_ahead = int(output.count('>'))
|
|
||||||
except Exception:
|
|
||||||
log.debug('git didn\'t return numbers for behind and ahead, not using it')
|
|
||||||
return
|
|
||||||
log.debug(f'cur_commit = {self._cur_commit_hash} % (newest_commit)= {self._newest_commit_hash}, num_commits_behind = {self._num_commits_behind}, num_commits_ahead = {self._num_commits_ahead}')
|
|
||||||
|
|
||||||
def set_newest_text(self):
|
|
||||||
if self._num_commits_ahead:
|
|
||||||
log.error(f'Local branch is ahead of {self.branch}. Automatic update not possible.')
|
|
||||||
elif self._num_commits_behind:
|
|
||||||
_plural = 's' if self._num_commits_behind > 1 else ''
|
|
||||||
log.info(f'There is a newer version available (you\'re {self._num_commits_behind} commit{_plural} behind)')
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def need_update(self):
|
|
||||||
if not self._find_installed_version():
|
|
||||||
log.error('Unable to determine installed version via git, please check your logs!')
|
|
||||||
return False
|
|
||||||
if not self._cur_commit_hash:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
self._check_github_for_update()
|
|
||||||
except Exception as error:
|
|
||||||
log.error(f'Unable to contact github, can\'t check for update: {error!r}')
|
|
||||||
return False
|
|
||||||
if self._num_commits_behind > 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Check git for a new version.
|
|
||||||
|
|
||||||
Calls git pull origin <branch> in order to update Sick Beard.
|
|
||||||
Returns a bool depending on the call's success.
|
|
||||||
"""
|
|
||||||
output, err, exit_status = self._run_git(self._git_path, f'pull origin {self.branch}')
|
|
||||||
if not exit_status:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class SourceUpdateManager(UpdateManager):
|
|
||||||
def __init__(self):
|
|
||||||
self.github_repo_user = self.get_github_repo_user()
|
|
||||||
self.github_repo = self.get_github_repo()
|
|
||||||
self.branch = self.get_github_branch()
|
|
||||||
self._cur_commit_hash = None
|
|
||||||
self._newest_commit_hash = None
|
|
||||||
self._num_commits_behind = 0
|
|
||||||
|
|
||||||
def _find_installed_version(self):
|
|
||||||
version_file = os.path.join(nzb2media.APP_ROOT, 'version.txt')
|
|
||||||
if not os.path.isfile(version_file):
|
|
||||||
self._cur_commit_hash = None
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
with open(version_file, encoding='utf-8') as fin:
|
|
||||||
self._cur_commit_hash = fin.read().strip(' \n\r')
|
|
||||||
except OSError as error:
|
|
||||||
log.debug(f'Unable to open \'version.txt\': {error}')
|
|
||||||
if not self._cur_commit_hash:
|
|
||||||
self._cur_commit_hash = None
|
|
||||||
else:
|
|
||||||
nzb2media.NZBTOMEDIA_VERSION = self._cur_commit_hash
|
|
||||||
|
|
||||||
def need_update(self):
|
|
||||||
self._find_installed_version()
|
|
||||||
try:
|
|
||||||
self._check_github_for_update()
|
|
||||||
except Exception as error:
|
|
||||||
log.error(f'Unable to contact github, can\'t check for update: {error!r}')
|
|
||||||
return False
|
|
||||||
if not self._cur_commit_hash or self._num_commits_behind > 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _check_github_for_update(self):
|
|
||||||
"""Check Github for a new version.
|
|
||||||
|
|
||||||
Uses pygithub to ask github if there is a newer version than
|
|
||||||
the provided commit hash. If there is a newer version it sets
|
|
||||||
Sick Beard's version text.
|
|
||||||
commit_hash: hash that we're checking against
|
|
||||||
"""
|
|
||||||
self._num_commits_behind = 0
|
|
||||||
self._newest_commit_hash = None
|
|
||||||
repository = github.GitHub(self.github_repo_user, self.github_repo, self.branch)
|
|
||||||
# try to get newest commit hash and commits behind directly by
|
|
||||||
# comparing branch and current commit
|
|
||||||
if self._cur_commit_hash:
|
|
||||||
branch_compared = repository.compare(base=self.branch, head=self._cur_commit_hash)
|
|
||||||
if 'base_commit' in branch_compared:
|
|
||||||
self._newest_commit_hash = branch_compared['base_commit']['sha']
|
|
||||||
if 'behind_by' in branch_compared:
|
|
||||||
self._num_commits_behind = int(branch_compared['behind_by'])
|
|
||||||
# fall back and iterate over last 100 (items per page in gh_api) commits
|
|
||||||
if not self._newest_commit_hash:
|
|
||||||
for cur_commit in repository.commits():
|
|
||||||
if not self._newest_commit_hash:
|
|
||||||
self._newest_commit_hash = cur_commit['sha']
|
|
||||||
if not self._cur_commit_hash:
|
|
||||||
break
|
|
||||||
if cur_commit['sha'] == self._cur_commit_hash:
|
|
||||||
break
|
|
||||||
# when _cur_commit_hash doesn't match anything _num_commits_behind == 100
|
|
||||||
self._num_commits_behind += 1
|
|
||||||
log.debug(f'cur_commit = {self._cur_commit_hash} % (newest_commit)= {self._newest_commit_hash}, num_commits_behind = {self._num_commits_behind}')
|
|
||||||
|
|
||||||
def set_newest_text(self):
|
|
||||||
# if we're up to date then don't set this
|
|
||||||
nzb2media.NEWEST_VERSION_STRING = None
|
|
||||||
if not self._cur_commit_hash:
|
|
||||||
log.error('Unknown current version number, don\'t know if we should update or not')
|
|
||||||
elif self._num_commits_behind > 0:
|
|
||||||
_plural = 's' if self._num_commits_behind > 1 else ''
|
|
||||||
log.info(f'There is a newer version available (you\'re {self._num_commits_behind} commit{_plural} behind)')
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Download and install latest source tarball from github."""
|
|
||||||
tar_download_url = f'https://github.com/{self.github_repo_user}/{self.github_repo}/tarball/{self.branch}'
|
|
||||||
version_path = os.path.join(nzb2media.APP_ROOT, 'version.txt')
|
|
||||||
try:
|
|
||||||
# prepare the update dir
|
|
||||||
sb_update_dir = os.path.join(nzb2media.APP_ROOT, 'sb-update')
|
|
||||||
if os.path.isdir(sb_update_dir):
|
|
||||||
log.info(f'Clearing out update folder {sb_update_dir} before extracting')
|
|
||||||
shutil.rmtree(sb_update_dir)
|
|
||||||
log.info(f'Creating update folder {sb_update_dir} before extracting')
|
|
||||||
os.makedirs(sb_update_dir)
|
|
||||||
# retrieve file
|
|
||||||
log.info(f'Downloading update from {tar_download_url!r}')
|
|
||||||
tar_download_path = os.path.join(sb_update_dir, 'nzbtomedia-update.tar')
|
|
||||||
urlretrieve(tar_download_url, tar_download_path)
|
|
||||||
if not os.path.isfile(tar_download_path):
|
|
||||||
log.error(f'Unable to retrieve new version from {tar_download_url}, can\'t update')
|
|
||||||
return False
|
|
||||||
if not tarfile.is_tarfile(tar_download_path):
|
|
||||||
log.error(f'Retrieved version from {tar_download_url} is corrupt, can\'t update')
|
|
||||||
return False
|
|
||||||
# extract to sb-update dir
|
|
||||||
log.info(f'Extracting file {tar_download_path}')
|
|
||||||
with tarfile.open(tar_download_path) as tar:
|
|
||||||
tar.extractall(sb_update_dir)
|
|
||||||
# delete .tar.gz
|
|
||||||
log.info(f'Deleting file {tar_download_path}')
|
|
||||||
os.remove(tar_download_path)
|
|
||||||
# find update dir name
|
|
||||||
update_dir_contents = [x for x in os.listdir(sb_update_dir) if os.path.isdir(os.path.join(sb_update_dir, x))]
|
|
||||||
if len(update_dir_contents) != 1:
|
|
||||||
log.error(f'Invalid update data, update failed: {update_dir_contents}')
|
|
||||||
return False
|
|
||||||
content_dir = os.path.join(sb_update_dir, update_dir_contents[0])
|
|
||||||
# walk temp folder and move files to main folder
|
|
||||||
log.info(f'Moving files from {content_dir} to {nzb2media.APP_ROOT}')
|
|
||||||
for dirname, _, filenames in os.walk(content_dir):
|
|
||||||
dirname = dirname[len(content_dir) + 1:]
|
|
||||||
for curfile in filenames:
|
|
||||||
old_path = os.path.join(content_dir, dirname, curfile)
|
|
||||||
new_path = os.path.join(nzb2media.APP_ROOT, dirname, curfile)
|
|
||||||
# Avoid DLL access problem on WIN32/64
|
|
||||||
# These files needing to be updated manually
|
|
||||||
# or find a way to kill the access from memory
|
|
||||||
if curfile in {'unrar.dll', 'unrar64.dll'}:
|
|
||||||
try:
|
|
||||||
os.chmod(new_path, stat.S_IWRITE)
|
|
||||||
os.remove(new_path)
|
|
||||||
os.renames(old_path, new_path)
|
|
||||||
except Exception as error:
|
|
||||||
log.debug(f'Unable to update {new_path}: {error}')
|
|
||||||
# Trash the updated file without moving in new path
|
|
||||||
os.remove(old_path)
|
|
||||||
continue
|
|
||||||
if os.path.isfile(new_path):
|
|
||||||
os.remove(new_path)
|
|
||||||
os.renames(old_path, new_path)
|
|
||||||
# update version.txt with commit hash
|
|
||||||
try:
|
|
||||||
with open(version_path, 'w', encoding='utf-8') as ver_file:
|
|
||||||
ver_file.write(self._newest_commit_hash)
|
|
||||||
except OSError as error:
|
|
||||||
log.error(f'Unable to write version file, update not complete: {error}')
|
|
||||||
return False
|
|
||||||
except Exception as error:
|
|
||||||
log.error(f'Error while trying to update: {error}')
|
|
||||||
log.debug(f'Traceback: {traceback.format_exc()}')
|
|
||||||
return False
|
|
||||||
return True
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'CouchPotato'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'CouchPotato'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'Gamez'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'Gamez'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'HeadPhones'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'HeadPhones'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'LazyLibrarian'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'LazyLibrarian'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'Lidarr'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'Lidarr'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,68 +1,7 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzb2media
|
from nzb2media.app import main
|
||||||
from nzb2media.processor import nzbget, sab, manual
|
|
||||||
from nzb2media.processor.nzb import process
|
|
||||||
from nzb2media.auto_process.common import ProcessResult
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
log.addHandler(logging.NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def main(args, section=None):
|
|
||||||
# Initialize the config
|
|
||||||
nzb2media.initialize(section)
|
|
||||||
|
|
||||||
log.info('#########################################################')
|
|
||||||
log.info(f'## ..::[{os.path.basename(__file__)}]::.. ##')
|
|
||||||
log.info('#########################################################')
|
|
||||||
|
|
||||||
# debug command line options
|
|
||||||
log.debug(f'Options passed into nzbToMedia: {args}')
|
|
||||||
|
|
||||||
# Post-Processing Result
|
|
||||||
result = ProcessResult(
|
|
||||||
message='',
|
|
||||||
status_code=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# NZBGet
|
|
||||||
if 'NZBOP_SCRIPTDIR' in os.environ:
|
|
||||||
result = nzbget.process()
|
|
||||||
# SABnzbd
|
|
||||||
elif 'SAB_SCRIPT' in os.environ:
|
|
||||||
result = sab.process_script()
|
|
||||||
# SABnzbd Pre 0.7.17
|
|
||||||
elif len(args) >= sab.MINIMUM_ARGUMENTS:
|
|
||||||
result = sab.process(args)
|
|
||||||
# Generic program
|
|
||||||
elif len(args) > 5 and args[5] == 'generic':
|
|
||||||
log.info('Script triggered from generic program')
|
|
||||||
result = process(args[1], input_name=args[2], input_category=args[3], download_id=args[4])
|
|
||||||
elif nzb2media.NZB_NO_MANUAL:
|
|
||||||
log.warning('Invalid number of arguments received from client, and no_manual set')
|
|
||||||
else:
|
|
||||||
manual.process()
|
|
||||||
|
|
||||||
if not result.status_code:
|
|
||||||
log.info(f'The {args[0]} script completed successfully.')
|
|
||||||
if result.message:
|
|
||||||
print(result.message + '!')
|
|
||||||
if 'NZBOP_SCRIPTDIR' in os.environ: # return code for nzbget v11
|
|
||||||
del nzb2media.MYAPP
|
|
||||||
return nzb2media.NZBGET_POSTPROCESS_SUCCESS
|
|
||||||
else:
|
|
||||||
log.error(f'A problem was reported in the {args[0]} script.')
|
|
||||||
if result.message:
|
|
||||||
print(result.message + '!')
|
|
||||||
if 'NZBOP_SCRIPTDIR' in os.environ: # return code for nzbget v11
|
|
||||||
del nzb2media.MYAPP
|
|
||||||
return nzb2media.NZBGET_POSTPROCESS_ERROR
|
|
||||||
del nzb2media.MYAPP
|
|
||||||
return result.status_code
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main(sys.argv))
|
section = ''
|
||||||
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'Mylar'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'Mylar'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'NzbDrone'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'NzbDrone'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'Radarr'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'Radarr'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'SiCKRAGE'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'SiCKRAGE'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'SickBeard'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'SickBeard'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nzbToMedia
|
from nzb2media.app import main
|
||||||
|
|
||||||
SECTION = 'Watcher3'
|
if __name__ == '__main__':
|
||||||
result = nzbToMedia.main(sys.argv, SECTION)
|
section = 'Watcher3'
|
||||||
sys.exit(result)
|
sys.exit(main(section=section))
|
||||||
|
|
|
@ -14,9 +14,28 @@ authors = [
|
||||||
{name = "Clinton Hall", email="fock_wulf@hotmail.com"}
|
{name = "Clinton Hall", email="fock_wulf@hotmail.com"}
|
||||||
]
|
]
|
||||||
dependencies =[
|
dependencies =[
|
||||||
'pywin32;platform_system=="Windows"'
|
'babelfish',
|
||||||
|
'beets',
|
||||||
|
'configobj',
|
||||||
|
'deluge-client@git+https://github.com/labrys/deluge.git@master',
|
||||||
|
'guessit',
|
||||||
|
'jaraco-windows ; sys.platform == "win32"',
|
||||||
|
'linktastic',
|
||||||
|
'mediafile',
|
||||||
|
'python-qbittorrent',
|
||||||
|
'pywin32 ; sys.platform == "win32"',
|
||||||
|
'pyxdg',
|
||||||
|
'rencode',
|
||||||
|
'requests',
|
||||||
|
'requests_oauthlib',
|
||||||
|
'setuptools',
|
||||||
|
'setuptools-scm',
|
||||||
|
'subliminal != 2.1.0',
|
||||||
|
'syno@git+https://github.com/labrys/syno.git@master',
|
||||||
|
'transmission-rpc@git+https://github.com/labrys/transmission-rpc@master',
|
||||||
|
'utorrent@git+https://github.com/labrys/utorrent.git@master',
|
||||||
]
|
]
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.8"
|
||||||
classifiers =[
|
classifiers =[
|
||||||
# complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
# complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
@ -30,7 +49,6 @@ classifiers =[
|
||||||
"Operating System :: Unix",
|
"Operating System :: Unix",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
|
|
@ -13,8 +13,8 @@ rencode
|
||||||
requests
|
requests
|
||||||
requests_oauthlib
|
requests_oauthlib
|
||||||
setuptools
|
setuptools
|
||||||
six
|
setuptools_scm
|
||||||
subliminal != 2.1.0
|
subliminal != 2.1.0
|
||||||
syno@git+https://github.com/labrys/syno.git@master
|
syno@git+https://github.com/labrys/syno.git@master
|
||||||
transmissionrpc
|
transmission-rpc
|
||||||
utorrent@git+https://github.com/labrys/utorrent.git@master
|
utorrent@git+https://github.com/labrys/utorrent.git@master
|
||||||
|
|
|
@ -44,19 +44,13 @@ def test_import_nzb():
|
||||||
import nzb2media.nzb
|
import nzb2media.nzb
|
||||||
assert nzb2media.nzb
|
assert nzb2media.nzb
|
||||||
|
|
||||||
import nzb2media.nzb.configuration
|
|
||||||
assert nzb2media.nzb.configuration
|
|
||||||
|
|
||||||
|
|
||||||
def test_import_plugins():
|
def test_import_plugins():
|
||||||
import nzb2media.plugins
|
import nzb2media.plex
|
||||||
assert nzb2media.plugins
|
assert nzb2media.plex
|
||||||
|
|
||||||
import nzb2media.plugins.plex
|
import nzb2media.subtitles
|
||||||
assert nzb2media.plugins.plex
|
assert nzb2media.subtitles
|
||||||
|
|
||||||
import nzb2media.plugins.subtitles
|
|
||||||
assert nzb2media.plugins.subtitles
|
|
||||||
|
|
||||||
|
|
||||||
def test_import_processor():
|
def test_import_processor():
|
||||||
|
@ -80,23 +74,20 @@ def test_import_torrent():
|
||||||
import nzb2media.torrent
|
import nzb2media.torrent
|
||||||
assert nzb2media.torrent
|
assert nzb2media.torrent
|
||||||
|
|
||||||
import nzb2media.torrent.configuration
|
import nzb2media.deluge
|
||||||
assert nzb2media.torrent.configuration
|
assert nzb2media.deluge
|
||||||
|
|
||||||
import nzb2media.torrent.deluge
|
import nzb2media.qbittorrent
|
||||||
assert nzb2media.torrent.deluge
|
assert nzb2media.qbittorrent
|
||||||
|
|
||||||
import nzb2media.torrent.qbittorrent
|
import nzb2media.synology
|
||||||
assert nzb2media.torrent.qbittorrent
|
assert nzb2media.synology
|
||||||
|
|
||||||
import nzb2media.torrent.synology
|
import nzb2media.transmission
|
||||||
assert nzb2media.torrent.synology
|
assert nzb2media.transmission
|
||||||
|
|
||||||
import nzb2media.torrent.transmission
|
import nzb2media.utorrent
|
||||||
assert nzb2media.torrent.transmission
|
assert nzb2media.utorrent
|
||||||
|
|
||||||
import nzb2media.torrent.utorrent
|
|
||||||
assert nzb2media.torrent.utorrent
|
|
||||||
|
|
||||||
|
|
||||||
def test_import_utils():
|
def test_import_utils():
|
||||||
|
@ -127,21 +118,12 @@ def test_import_utils():
|
||||||
import nzb2media.utils.network
|
import nzb2media.utils.network
|
||||||
assert nzb2media.utils.network
|
assert nzb2media.utils.network
|
||||||
|
|
||||||
import nzb2media.utils.nzb
|
|
||||||
assert nzb2media.utils.nzb
|
|
||||||
|
|
||||||
import nzb2media.utils.parsers
|
import nzb2media.utils.parsers
|
||||||
assert nzb2media.utils.parsers
|
assert nzb2media.utils.parsers
|
||||||
|
|
||||||
import nzb2media.utils.paths
|
import nzb2media.utils.paths
|
||||||
assert nzb2media.utils.paths
|
assert nzb2media.utils.paths
|
||||||
|
|
||||||
import nzb2media.utils.processes
|
|
||||||
assert nzb2media.utils.processes
|
|
||||||
|
|
||||||
import nzb2media.utils.torrent
|
|
||||||
assert nzb2media.utils.torrent
|
|
||||||
|
|
||||||
|
|
||||||
def test_import_nzb2media():
|
def test_import_nzb2media():
|
||||||
import nzb2media
|
import nzb2media
|
||||||
|
@ -153,12 +135,6 @@ def test_import_nzb2media():
|
||||||
import nzb2media.databases
|
import nzb2media.databases
|
||||||
assert nzb2media.databases
|
assert nzb2media.databases
|
||||||
|
|
||||||
import nzb2media.github_api
|
|
||||||
assert nzb2media.github_api
|
|
||||||
|
|
||||||
import nzb2media.main_db
|
|
||||||
assert nzb2media.main_db
|
|
||||||
|
|
||||||
import nzb2media.scene_exceptions
|
import nzb2media.scene_exceptions
|
||||||
assert nzb2media.scene_exceptions
|
assert nzb2media.scene_exceptions
|
||||||
|
|
||||||
|
@ -167,6 +143,3 @@ def test_import_nzb2media():
|
||||||
|
|
||||||
import nzb2media.user_scripts
|
import nzb2media.user_scripts
|
||||||
assert nzb2media.user_scripts
|
assert nzb2media.user_scripts
|
||||||
|
|
||||||
import nzb2media.version_check
|
|
||||||
assert nzb2media.version_check
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import nzb2media
|
||||||
|
|
||||||
def test_initial():
|
def test_initial():
|
||||||
nzb2media.initialize()
|
nzb2media.initialize()
|
||||||
del nzb2media.MYAPP
|
|
||||||
|
|
||||||
|
|
||||||
def test_core_parameters():
|
def test_core_parameters():
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import nzb2media
|
import nzb2media
|
||||||
from nzb2media import transcoder
|
from nzb2media import transcoder
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
sys.platform == 'win32' and sys.version_info < (3, 8),
|
|
||||||
reason='subprocess.Popen does not support pathlib.Path commands in Python 3.7',
|
|
||||||
)
|
|
||||||
def test_transcoder_check():
|
def test_transcoder_check():
|
||||||
assert transcoder.is_video_good(nzb2media.TEST_FILE, 1) is True
|
assert transcoder.is_video_good(nzb2media.TEST_FILE, 1) is True
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -4,12 +4,11 @@
|
||||||
envlist =
|
envlist =
|
||||||
clean,
|
clean,
|
||||||
check,
|
check,
|
||||||
{py37, py38, py39, py310, py311},
|
{py38, py39, py310, py311},
|
||||||
report
|
report
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py37: {env:TOXPYTHON:python3.7}
|
|
||||||
py38: {env:TOXPYTHON:python3.8}
|
py38: {env:TOXPYTHON:python3.8}
|
||||||
py39: {env:TOXPYTHON:python3.9}
|
py39: {env:TOXPYTHON:python3.9}
|
||||||
py310: {env:TOXPYTHON:python3.10}
|
py310: {env:TOXPYTHON:python3.10}
|
||||||
|
@ -20,22 +19,21 @@ setenv =
|
||||||
PYTHONUNBUFFERED=yes
|
PYTHONUNBUFFERED=yes
|
||||||
passenv =
|
passenv =
|
||||||
*
|
*
|
||||||
usedevelop = false
|
|
||||||
skip_install = true
|
|
||||||
deps =
|
deps =
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
-rrequirements.txt
|
|
||||||
commands =
|
commands =
|
||||||
{posargs:pytest -vvv -rA --cov --cov-report=term-missing --cov-branch tests}
|
{posargs:pytest -vvv -rA --cov --cov-report=term-missing --cov-branch tests}
|
||||||
|
|
||||||
[testenv:check]
|
[testenv:check]
|
||||||
deps =
|
deps =
|
||||||
pre-commit
|
pre-commit
|
||||||
|
mypy
|
||||||
skip_install = true
|
skip_install = true
|
||||||
commands =
|
commands =
|
||||||
pre-commit autoupdate
|
pre-commit autoupdate
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
mypy --install-types --non-interactive --ignore-missing-imports nzb2media
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue