├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── check-addon.yml │ └── publish-addon.yml ├── .gitignore ├── .pylintrc ├── Makefile ├── Readme.md ├── requirements.txt └── service.subtitles.rvm.addic7ed ├── License.txt ├── addic7ed ├── __init__.py ├── actions.py ├── addon.py ├── exception_logger.py ├── exceptions.py ├── parser.py ├── utils.py └── webclient.py ├── addon.xml ├── fanart.jpg ├── icon.png ├── main.py └── resources ├── language ├── resource.language.en_gb │ └── strings.po ├── resource.language.fr_fr │ └── strings.po ├── resource.language.nl_nl │ └── strings.po ├── resource.language.ru_ru │ └── strings.po └── resource.language.uk_ua │ └── strings.po └── settings.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Issue description:** 11 | 12 | 13 | **Kodi debug log:** 14 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/check-addon.yml: -------------------------------------------------------------------------------- 1 | name: Check addon 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.10 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: "3.10" 16 | - name: Install dependencies 17 | run: | 18 | pip install -q -r requirements.txt Pylint typing-extensions 19 | - name: Check with Pylint 20 | run: | 21 | pylint service.subtitles.rvm.addic7ed/addic7ed service.subtitles.rvm.addic7ed/main.py 22 | - name: Install addon checker 23 | run: | 24 | pip install -q kodi-addon-checker 25 | - name: Check with addon-checker 26 | run: | 27 | kodi-addon-checker --branch matrix 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-addon.yml: -------------------------------------------------------------------------------- 1 | name: Publish addon 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | - '!*-beta' 8 | 9 | jobs: 10 | publish: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python 3.8 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.8 20 | - name: Install addon checker 21 | run: | 22 | pip install -q kodi-addon-checker 23 | - name: Check with addon-checker 24 | run: | 25 | kodi-addon-checker --branch matrix 26 | - name: Install addon submitter 27 | run: | 28 | pip install -q git+https://github.com/xbmc/kodi-addon-submitter.git 29 | - name: Submit addon 30 | run: | 31 | submit-addon -r repo-scripts -b matrix -s --pull-request service.subtitles.rvm.addic7ed 32 | env: 33 | GH_USERNAME: romanvm 34 | GH_TOKEN: ${{ secrets.gh_token }} 35 | EMAIL: roman1972@gmail.com 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 4 | 5 | ## Directory-based project format 6 | .idea/ 7 | # if you remove the above rule, at least ignore user-specific stuff: 8 | # .idea/workspace.xml 9 | # .idea/tasks.xml 10 | # and these sensitive or high-churn files: 11 | # .idea/dataSources.ids 12 | # .idea/dataSources.xml 13 | # .idea/sqlDataSources.xml 14 | # .idea/dynamic.xml 15 | 16 | ## File-based project format 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | ## Additional for IntelliJ 22 | out/ 23 | 24 | # generated by mpeltonen/sbt-idea plugin 25 | .idea_modules/ 26 | 27 | # generated by JIRA plugin 28 | atlassian-ide-plugin.xml 29 | 30 | # generated by Crashlytics plugin (for Android Studio and Intellij) 31 | com_crashlytics_export_strings.xml 32 | 33 | *.py[cod] 34 | 35 | # C extensions 36 | *.so 37 | 38 | # Distribution / packaging 39 | .Python 40 | env/ 41 | build/ 42 | develop-eggs/ 43 | dist/ 44 | eggs/ 45 | #lib/ 46 | lib64/ 47 | parts/ 48 | sdist/ 49 | var/ 50 | *.egg-info/ 51 | .installed.cfg 52 | *.egg 53 | 54 | # PyInstaller 55 | # Usually these files are written by a python script from a template 56 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 57 | *.manifest 58 | *.spec 59 | 60 | # Installer logs 61 | pip-log.txt 62 | pip-delete-this-directory.txt 63 | 64 | # Unit test / coverage reports 65 | htmlcov/ 66 | .tox/ 67 | .coverage 68 | .cache 69 | nosetests.xml 70 | coverage.xml 71 | 72 | # Translations 73 | *.mo 74 | *.pot 75 | 76 | # Django stuff: 77 | *.log 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Arhcive files 86 | *.zip 87 | 88 | publish.py 89 | venv/ 90 | .venv/ 91 | .venv3/ 92 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=missing-module-docstring, 64 | missing-function-docstring, 65 | missing-class-docstring, 66 | invalid-name 67 | 68 | # Enable the message, report, category or checker with the given id(s). You can 69 | # either give multiple identifier separated by comma (,) or put this option 70 | # multiple time (only on the command line, not in the configuration file where 71 | # it should appear only once). See also the "--disable" option for examples. 72 | enable=c-extension-no-member 73 | 74 | 75 | [REPORTS] 76 | 77 | # Python expression which should return a score less than or equal to 10. You 78 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 79 | # which contain the number of messages in each category, as well as 'statement' 80 | # which is the total number of statements analyzed. This score is used by the 81 | # global evaluation report (RP0004). 82 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 83 | 84 | # Template used to display messages. This is a python new-style format string 85 | # used to format the message information. See doc for all details. 86 | #msg-template= 87 | 88 | # Set the output format. Available formats are text, parseable, colorized, json 89 | # and msvs (visual studio). You can also give a reporter class, e.g. 90 | # mypackage.mymodule.MyReporterClass. 91 | output-format=text 92 | 93 | # Tells whether to display a full report or only the messages. 94 | reports=no 95 | 96 | # Activate the evaluation score. 97 | score=yes 98 | 99 | 100 | [REFACTORING] 101 | 102 | # Maximum number of nested blocks for function / method body 103 | max-nested-blocks=5 104 | 105 | # Complete name of functions that never returns. When checking for 106 | # inconsistent-return-statements if a never returning function is called then 107 | # it will be considered as an explicit return statement and no message will be 108 | # printed. 109 | never-returning-functions=sys.exit 110 | 111 | 112 | [VARIABLES] 113 | 114 | # List of additional names supposed to be defined in builtins. Remember that 115 | # you should avoid defining new builtins when possible. 116 | additional-builtins= 117 | 118 | # Tells whether unused global variables should be treated as a violation. 119 | allow-global-unused-variables=yes 120 | 121 | # List of strings which can identify a callback function by name. A callback 122 | # name must start or end with one of those strings. 123 | callbacks=cb_, 124 | _cb 125 | 126 | # A regular expression matching the name of dummy variables (i.e. expected to 127 | # not be used). 128 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 129 | 130 | # Argument names that match this expression will be ignored. Default to name 131 | # with leading underscore. 132 | ignored-argument-names=_.*|^ignored_|^unused_ 133 | 134 | # Tells whether we should check for unused import in __init__ files. 135 | init-import=no 136 | 137 | # List of qualified module names which can have objects that can redefine 138 | # builtins. 139 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 140 | 141 | 142 | [MISCELLANEOUS] 143 | 144 | # List of note tags to take in consideration, separated by a comma. 145 | notes=FIXME, 146 | XXX, 147 | TODO 148 | 149 | # Regular expression of note tags to take in consideration. 150 | #notes-rgx= 151 | 152 | 153 | [SIMILARITIES] 154 | 155 | # Ignore comments when computing similarities. 156 | ignore-comments=yes 157 | 158 | # Ignore docstrings when computing similarities. 159 | ignore-docstrings=yes 160 | 161 | # Ignore imports when computing similarities. 162 | ignore-imports=no 163 | 164 | # Minimum lines number of a similarity. 165 | min-similarity-lines=4 166 | 167 | 168 | [STRING] 169 | 170 | # This flag controls whether inconsistent-quotes generates a warning when the 171 | # character used as a quote delimiter is used inconsistently within a module. 172 | check-quote-consistency=no 173 | 174 | # This flag controls whether the implicit-str-concat should generate a warning 175 | # on implicit string concatenation in sequences defined over several lines. 176 | check-str-concat-over-line-jumps=no 177 | 178 | 179 | [SPELLING] 180 | 181 | # Limits count of emitted suggestions for spelling mistakes. 182 | max-spelling-suggestions=4 183 | 184 | # Spelling dictionary name. Available dictionaries: none. To make it work, 185 | # install the python-enchant package. 186 | spelling-dict= 187 | 188 | # List of comma separated words that should not be checked. 189 | spelling-ignore-words= 190 | 191 | # A path to a file that contains the private dictionary; one word per line. 192 | spelling-private-dict-file= 193 | 194 | # Tells whether to store unknown words to the private dictionary (see the 195 | # --spelling-private-dict-file option) instead of raising a message. 196 | spelling-store-unknown-words=no 197 | 198 | 199 | [BASIC] 200 | 201 | # Naming style matching correct argument names. 202 | argument-naming-style=snake_case 203 | 204 | # Regular expression matching correct argument names. Overrides argument- 205 | # naming-style. 206 | #argument-rgx= 207 | 208 | # Naming style matching correct attribute names. 209 | attr-naming-style=snake_case 210 | 211 | # Regular expression matching correct attribute names. Overrides attr-naming- 212 | # style. 213 | #attr-rgx= 214 | 215 | # Bad variable names which should always be refused, separated by a comma. 216 | bad-names=foo, 217 | bar, 218 | baz, 219 | toto, 220 | tutu, 221 | tata 222 | 223 | # Bad variable names regexes, separated by a comma. If names match any regex, 224 | # they will always be refused 225 | bad-names-rgxs= 226 | 227 | # Naming style matching correct class attribute names. 228 | class-attribute-naming-style=any 229 | 230 | # Regular expression matching correct class attribute names. Overrides class- 231 | # attribute-naming-style. 232 | #class-attribute-rgx= 233 | 234 | # Naming style matching correct class names. 235 | class-naming-style=PascalCase 236 | 237 | # Regular expression matching correct class names. Overrides class-naming- 238 | # style. 239 | #class-rgx= 240 | 241 | # Naming style matching correct constant names. 242 | const-naming-style=UPPER_CASE 243 | 244 | # Regular expression matching correct constant names. Overrides const-naming- 245 | # style. 246 | #const-rgx= 247 | 248 | # Minimum line length for functions/classes that require docstrings, shorter 249 | # ones are exempt. 250 | docstring-min-length=-1 251 | 252 | # Naming style matching correct function names. 253 | function-naming-style=snake_case 254 | 255 | # Regular expression matching correct function names. Overrides function- 256 | # naming-style. 257 | #function-rgx= 258 | 259 | # Good variable names which should always be accepted, separated by a comma. 260 | good-names=i, 261 | j, 262 | k, 263 | ex, 264 | Run, 265 | _, 266 | fo, 267 | ex 268 | 269 | # Good variable names regexes, separated by a comma. If names match any regex, 270 | # they will always be accepted 271 | good-names-rgxs= 272 | 273 | # Include a hint for the correct naming format with invalid-name. 274 | include-naming-hint=no 275 | 276 | # Naming style matching correct inline iteration names. 277 | inlinevar-naming-style=any 278 | 279 | # Regular expression matching correct inline iteration names. Overrides 280 | # inlinevar-naming-style. 281 | #inlinevar-rgx= 282 | 283 | # Naming style matching correct method names. 284 | method-naming-style=snake_case 285 | 286 | # Regular expression matching correct method names. Overrides method-naming- 287 | # style. 288 | #method-rgx= 289 | 290 | # Naming style matching correct module names. 291 | module-naming-style=snake_case 292 | 293 | # Regular expression matching correct module names. Overrides module-naming- 294 | # style. 295 | #module-rgx= 296 | 297 | # Colon-delimited sets of names that determine each other's naming style when 298 | # the name regexes allow several styles. 299 | name-group= 300 | 301 | # Regular expression which should only match function or class names that do 302 | # not require a docstring. 303 | no-docstring-rgx=^_ 304 | 305 | # List of decorators that produce properties, such as abc.abstractproperty. Add 306 | # to this list to register other decorators that produce valid properties. 307 | # These decorators are taken in consideration only for invalid-name. 308 | property-classes=abc.abstractproperty 309 | 310 | # Naming style matching correct variable names. 311 | variable-naming-style=snake_case 312 | 313 | # Regular expression matching correct variable names. Overrides variable- 314 | # naming-style. 315 | #variable-rgx= 316 | 317 | 318 | [LOGGING] 319 | 320 | # The type of string formatting that logging methods do. `old` means using % 321 | # formatting, `new` is for `{}` formatting. 322 | logging-format-style=old 323 | 324 | # Logging modules to check that the string format arguments are in logging 325 | # function parameter format. 326 | logging-modules=logging 327 | 328 | 329 | [FORMAT] 330 | 331 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 332 | expected-line-ending-format= 333 | 334 | # Regexp for a line that is allowed to be longer than the limit. 335 | ignore-long-lines=^\s*(# )??$ 336 | 337 | # Number of spaces of indent required inside a hanging or continued line. 338 | indent-after-paren=4 339 | 340 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 341 | # tab). 342 | indent-string=' ' 343 | 344 | # Maximum number of characters on a single line. 345 | max-line-length=100 346 | 347 | # Maximum number of lines in a module. 348 | max-module-lines=1000 349 | 350 | # List of optional constructs for which whitespace checking is disabled. `dict- 351 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 352 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 353 | # `empty-line` allows space-only lines. 354 | #no-space-check=trailing-comma, 355 | # dict-separator 356 | 357 | # Allow the body of a class to be on the same line as the declaration if body 358 | # contains single statement. 359 | single-line-class-stmt=no 360 | 361 | # Allow the body of an if to be on the same line as the test if there is no 362 | # else. 363 | single-line-if-stmt=no 364 | 365 | 366 | [TYPECHECK] 367 | 368 | # List of decorators that produce context managers, such as 369 | # contextlib.contextmanager. Add to this list to register other decorators that 370 | # produce valid context managers. 371 | contextmanager-decorators=contextlib.contextmanager 372 | 373 | # List of members which are set dynamically and missed by pylint inference 374 | # system, and so shouldn't trigger E1101 when accessed. Python regular 375 | # expressions are accepted. 376 | generated-members= 377 | 378 | # Tells whether missing members accessed in mixin class should be ignored. A 379 | # mixin class is detected if its name ends with "mixin" (case insensitive). 380 | ignore-mixin-members=yes 381 | 382 | # Tells whether to warn about missing members when the owner of the attribute 383 | # is inferred to be None. 384 | ignore-none=yes 385 | 386 | # This flag controls whether pylint should warn about no-member and similar 387 | # checks whenever an opaque object is returned when inferring. The inference 388 | # can return multiple potential results while evaluating a Python object, but 389 | # some branches might not be evaluated, which results in partial inference. In 390 | # that case, it might be useful to still emit no-member and other checks for 391 | # the rest of the inferred objects. 392 | ignore-on-opaque-inference=yes 393 | 394 | # List of class names for which member attributes should not be checked (useful 395 | # for classes with dynamically set attributes). This supports the use of 396 | # qualified names. 397 | ignored-classes=optparse.Values,thread._local,_thread._local 398 | 399 | # List of module names for which member attributes should not be checked 400 | # (useful for modules/projects where namespaces are manipulated during runtime 401 | # and thus existing member attributes cannot be deduced by static analysis). It 402 | # supports qualified module names, as well as Unix pattern matching. 403 | ignored-modules= 404 | 405 | # Show a hint with possible names when a member name was not found. The aspect 406 | # of finding the hint is based on edit distance. 407 | missing-member-hint=yes 408 | 409 | # The minimum edit distance a name should have in order to be considered a 410 | # similar match for a missing member name. 411 | missing-member-hint-distance=1 412 | 413 | # The total number of similar names that should be taken in consideration when 414 | # showing a hint for a missing member. 415 | missing-member-max-choices=1 416 | 417 | # List of decorators that change the signature of a decorated function. 418 | signature-mutators= 419 | 420 | 421 | [IMPORTS] 422 | 423 | # List of modules that can be imported at any level, not just the top level 424 | # one. 425 | allow-any-import-level= 426 | 427 | # Allow wildcard imports from modules that define __all__. 428 | allow-wildcard-with-all=no 429 | 430 | # Analyse import fallback blocks. This can be used to support both Python 2 and 431 | # 3 compatible code, which means that the block might have code that exists 432 | # only in one or another interpreter, leading to false positives when analysed. 433 | analyse-fallback-blocks=no 434 | 435 | # Deprecated modules which should not be used, separated by a comma. 436 | deprecated-modules=optparse,tkinter.tix 437 | 438 | # Create a graph of external dependencies in the given file (report RP0402 must 439 | # not be disabled). 440 | ext-import-graph= 441 | 442 | # Create a graph of every (i.e. internal and external) dependencies in the 443 | # given file (report RP0402 must not be disabled). 444 | import-graph= 445 | 446 | # Create a graph of internal dependencies in the given file (report RP0402 must 447 | # not be disabled). 448 | int-import-graph= 449 | 450 | # Force import order to recognize a module as part of the standard 451 | # compatibility libraries. 452 | known-standard-library= 453 | 454 | # Force import order to recognize a module as part of a third party library. 455 | known-third-party=enchant 456 | 457 | # Couples of modules and preferred modules, separated by a comma. 458 | preferred-modules= 459 | 460 | 461 | [DESIGN] 462 | 463 | # Maximum number of arguments for function / method. 464 | max-args=5 465 | 466 | # Maximum number of attributes for a class (see R0902). 467 | max-attributes=7 468 | 469 | # Maximum number of boolean expressions in an if statement (see R0916). 470 | max-bool-expr=5 471 | 472 | # Maximum number of branch for function / method body. 473 | max-branches=12 474 | 475 | # Maximum number of locals for function / method body. 476 | max-locals=15 477 | 478 | # Maximum number of parents for a class (see R0901). 479 | max-parents=7 480 | 481 | # Maximum number of public methods for a class (see R0904). 482 | max-public-methods=20 483 | 484 | # Maximum number of return / yield for function / method body. 485 | max-returns=6 486 | 487 | # Maximum number of statements in function / method body. 488 | max-statements=50 489 | 490 | # Minimum number of public methods for a class (see R0903). 491 | min-public-methods=2 492 | 493 | 494 | [CLASSES] 495 | 496 | # List of method names used to declare (i.e. assign) instance attributes. 497 | defining-attr-methods=__init__, 498 | __new__, 499 | setUp, 500 | __post_init__ 501 | 502 | # List of member names, which should be excluded from the protected access 503 | # warning. 504 | exclude-protected=_asdict, 505 | _fields, 506 | _replace, 507 | _source, 508 | _make 509 | 510 | # List of valid names for the first argument in a class method. 511 | valid-classmethod-first-arg=cls 512 | 513 | # List of valid names for the first argument in a metaclass class method. 514 | valid-metaclass-classmethod-first-arg=cls 515 | 516 | 517 | [EXCEPTIONS] 518 | 519 | # Exceptions that will emit a warning when being caught. Defaults to 520 | # "BaseException, Exception". 521 | overgeneral-exceptions=builtins.BaseException, 522 | builtins.Exception 523 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | . .venv/bin/activate && \ 3 | pylint service.subtitles.rvm.addic7ed/addic7ed service.subtitles.rvm.addic7ed/main.py 4 | 5 | PHONY: lint 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # service.subtitles.rvm.addic7ed 2 | 3 | This ia a subtitles plugin for [Kodi](www.kodi.tv) mediacenter to download 4 | TV series subtitles from [addic7ed.com](http://www.addic7ed.com/) site. 5 | 6 | This is a completely independent project that I've created for my personal use. 7 | It has no relations to other Kodi plugins for addic7ed.com site that may exist. 8 | I use it regularly and try to maintain it in working state. 9 | 10 | **Warning**: Movie subtitles are not supported! For movies please use other plugins, 11 | e.g. opensubtitles. 12 | 13 | ## Important note! 14 | 15 | Starting from v.3.0.0 addon ID has been changed to **service.subtitles.rvm.addic7ed** 16 | to avoid name conflicts with similar addons that may exist. This means that 17 | v.3.0.0 must be installed from a repository or a .zip file as a new addon and 18 | previous versions of this addon must be uninstalled manually. 19 | 20 | ## Installation 21 | 22 | This addon is available in **Subtitles** section of the official Kodi addon 23 | repository and can be installed via Kodi Addon Manager. 24 | 25 | ## License 26 | 27 | [GPL v.3](http://www.gnu.org/licenses/gpl-3.0.en.html). 28 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Kodistubs 2 | bs4 3 | html5lib 4 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/License.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/service.addic7ed/66e34642390bf4872e9ac7266ec34627966daae7/service.subtitles.rvm.addic7ed/addic7ed/__init__.py -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/actions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import logging 17 | import os 18 | import re 19 | import shutil 20 | import sys 21 | from collections import namedtuple 22 | from urllib import parse as urlparse 23 | 24 | import xbmc 25 | import xbmcgui 26 | import xbmcplugin 27 | 28 | from addic7ed import parser 29 | from addic7ed.addon import ADDON, PROFILE, ICON, GettextEmulator 30 | from addic7ed.exceptions import NoSubtitlesReturned, ParseError, SubsSearchError, \ 31 | Add7ConnectionError 32 | from addic7ed.parser import parse_filename, normalize_showname, get_languages 33 | from addic7ed.utils import get_now_played 34 | from addic7ed.webclient import Session 35 | 36 | __all__ = ['router'] 37 | 38 | _ = GettextEmulator.gettext 39 | 40 | logger = logging.getLogger(__name__) 41 | 42 | TEMP_DIR = PROFILE / 'temp' 43 | HANDLE = int(sys.argv[1]) 44 | 45 | 46 | VIDEOFILE_EXTENSIONS = {'.avi', '.mkv', '.mp4', '.ts', '.m2ts', '.mov'} 47 | DIALOG = xbmcgui.Dialog() 48 | RELEASE_RE = re.compile(r'-(.*?)(?:\[.*?\])?\.') 49 | 50 | EpisodeData = namedtuple('EpisodeData', 51 | ['showname', 'season', 'episode', 'filename']) 52 | 53 | 54 | def _detect_synced_subs(subs_list, filename): 55 | """ 56 | Try to detect if subs from Addic7ed.com match the file being played 57 | 58 | :param subs_list: list or generator of subtitle items 59 | :param filename: the name of an episode videofile being played 60 | :return: the generator of subtitles "sync" property 61 | """ 62 | listing = [] 63 | for item in subs_list: 64 | release_match = RELEASE_RE.search(filename) 65 | if release_match is not None: 66 | release = release_match.group(1).lower() 67 | else: 68 | release = '' 69 | lowercase_version = item.version.lower() 70 | resync_pattern = rf'sync.+?{release}' 71 | synced = (release and 72 | release in lowercase_version and 73 | re.search(resync_pattern, lowercase_version, re.I) is None) 74 | listing.append((item, synced)) 75 | return listing 76 | 77 | 78 | def display_subs(subs_list, episode_url, filename): 79 | """ 80 | Display the list of found subtitles 81 | 82 | :param subs_list: the list or generator of tuples (subs item, synced) 83 | :param episode_url: the URL for the episode page on addic7ed.com. 84 | It is needed for downloading subs as 'Referer' HTTP header. 85 | :param filename: the name of the video-file being played. 86 | 87 | Each item in the displayed list is a ListItem instance with the following 88 | properties: 89 | 90 | - label: Kodi language name (e.g. 'English') 91 | - label2: a descriptive text for subs 92 | - thumbnailImage: a 2-letter language code (e.g. 'en') to display a country 93 | flag. 94 | - 'hearing_imp': if 'true' then 'CC' icon is displayed for the list item. 95 | - 'sync': if 'true' then 'SYNC' icon is displayed for the list item. 96 | - url: a plugin call URL for downloading selected subs. 97 | """ 98 | subs_list = sorted( 99 | _detect_synced_subs(subs_list, filename), 100 | key=lambda i: i[1], 101 | reverse=True 102 | ) 103 | for item, synced in subs_list: 104 | if item.unfinished: 105 | continue 106 | list_item = xbmcgui.ListItem(label=item.language, label2=item.version) 107 | list_item.setArt( 108 | {'thumb': xbmc.convertLanguage(item.language, xbmc.ISO_639_1)} 109 | ) 110 | if item.hi: 111 | list_item.setProperty('hearing_imp', 'true') 112 | if synced: 113 | list_item.setProperty('sync', 'true') 114 | url = '{}?{}'.format( # pylint: disable=consider-using-f-string 115 | sys.argv[0], 116 | urlparse.urlencode( 117 | {'action': 'download', 118 | 'link': item.link, 119 | 'ref': episode_url, 120 | 'filename': filename} 121 | ) 122 | ) 123 | xbmcplugin.addDirectoryItem(handle=HANDLE, url=url, listitem=list_item, 124 | isFolder=False) 125 | 126 | 127 | def download_subs(link, referrer, filename): 128 | """ 129 | Download selected subs 130 | 131 | :param link: str - a download link for the subs. 132 | :param referrer: str - a referer URL for the episode page 133 | (required by addic7ed.com). 134 | :param filename: str - the name of the video-file being played. 135 | 136 | The function must add a single ListItem instance with one property: 137 | label - the download location for subs. 138 | """ 139 | # Re-create a download location in a temporary folder 140 | if not PROFILE.exists(): 141 | PROFILE.mkdir() 142 | if TEMP_DIR.exists(): 143 | shutil.rmtree(str(TEMP_DIR)) 144 | TEMP_DIR.mkdir() 145 | # Combine a path where to download the subs 146 | filename = os.path.splitext(filename)[0] + '.srt' 147 | subspath = str(TEMP_DIR / filename) 148 | # Download the subs from addic7ed.com 149 | try: 150 | Session().download_subs(link, referrer, subspath) 151 | except Add7ConnectionError: 152 | logger.error('Unable to connect to addic7ed.com') 153 | DIALOG.notification(_('Error!'), _('Unable to connect to addic7ed.com.'), 'error') 154 | except NoSubtitlesReturned: 155 | DIALOG.notification(_('Error!'), _('Exceeded daily limit for subs downloads.'), 'error', 156 | 3000) 157 | logger.error('A HTML page returned instead of subtitles for link: %s', link) 158 | else: 159 | # Create a ListItem for downloaded subs and pass it 160 | # to the Kodi subtitles engine to move the downloaded subs file 161 | # from the temp folder to the designated 162 | # location selected by 'Subtitle storage location' option 163 | # in 'Settings > Video > Subtitles' section. 164 | # A 2-letter language code will be added to subs filename. 165 | list_item = xbmcgui.ListItem(label=subspath) 166 | xbmcplugin.addDirectoryItem(handle=HANDLE, 167 | url=subspath, 168 | listitem=list_item, 169 | isFolder=False) 170 | DIALOG.notification(_('Success!'), _('Subtitles downloaded.'), ICON, 3000, False) 171 | logger.info('Subs downloaded.') 172 | 173 | 174 | def extract_episode_data(): 175 | """ 176 | Extract episode data for searching 177 | 178 | :return: named tuple (showname, season, episode, filename) 179 | :raises ParseError: if cannot determine episode data 180 | """ 181 | now_played = get_now_played() 182 | logger.debug('Played file info: %s', now_played) 183 | showname = now_played['showtitle'] or xbmc.getInfoLabel('VideoPlayer.TVshowtitle') 184 | parsed = urlparse.urlparse(now_played['file']) 185 | filename = os.path.basename(parsed.path) 186 | if ADDON.getSetting('use_filename') == 'true' or not showname: 187 | # Try to get showname/season/episode data from 188 | # the filename if 'use_filename' setting is true 189 | # or if the video-file does not have library metadata. 190 | try: 191 | logger.debug('Using filename: %s', filename) 192 | showname, season, episode = parse_filename(filename) 193 | except ParseError: 194 | logger.debug('Filename %s failed. Trying ListItem.Label...', filename) 195 | try: 196 | filename = now_played['label'] 197 | logger.debug('Using filename: %s', filename) 198 | showname, season, episode = parse_filename(filename) 199 | except ParseError: 200 | logger.error('Unable to determine episode data for %s', filename) 201 | DIALOG.notification(_('Error!'), _('Unable to determine episode data.'), 202 | 'error', 3000) 203 | raise 204 | else: 205 | # Get get showname/season/episode data from 206 | # Kodi if the video-file is being played from 207 | # the TV-Shows library. 208 | season = str(now_played['season'] if now_played['season'] > -1 209 | else xbmc.getInfoLabel('VideoPlayer.Season')) 210 | season = season.zfill(2) 211 | episode = str(now_played['episode'] if now_played['episode'] > -1 212 | else xbmc.getInfoLabel('VideoPlayer.Episode')) 213 | episode = episode.zfill(2) 214 | if not os.path.splitext(filename)[1].lower() in VIDEOFILE_EXTENSIONS: 215 | filename = f'{showname}.{season}x{episode}.foo' 216 | logger.debug('Using library metadata: %s - %sx%s', showname, season, episode) 217 | return EpisodeData(showname, season, episode, filename) 218 | 219 | 220 | def search_subs(params): 221 | logger.info('Searching for subs...') 222 | languages = get_languages( 223 | urlparse.unquote_plus(params['languages']).split(',') 224 | ) 225 | # Search subtitles in Addic7ed.com. 226 | if params['action'] == 'search': 227 | try: 228 | episode_data = extract_episode_data() 229 | except ParseError: 230 | return 231 | # Create a search query string 232 | showname = normalize_showname(episode_data.showname) 233 | query = f'{showname} {episode_data.season}x{episode_data.episode}' 234 | filename = episode_data.filename 235 | else: 236 | # Get the query string typed on the on-screen keyboard 237 | query = params['searchstring'] 238 | filename = query 239 | if query: 240 | logger.debug('Search query: %s', query) 241 | try: 242 | results = parser.search_episode(query, languages) 243 | except Add7ConnectionError: 244 | logger.error('Unable to connect to addic7ed.com') 245 | DIALOG.notification(_('Error!'), _('Unable to connect to addic7ed.com.'), 'error') 246 | except SubsSearchError: 247 | logger.info('No subs for "%s" found.', query) 248 | else: 249 | if isinstance(results, list): 250 | logger.info('Multiple episodes found:\n%s', results) 251 | i = DIALOG.select( 252 | _('Select episode'), [item.title for item in results] 253 | ) 254 | if i >= 0: 255 | try: 256 | results = parser.get_episode(results[i].link, languages) 257 | except Add7ConnectionError: 258 | logger.error('Unable to connect to addic7ed.com') 259 | DIALOG.notification(_('Error!'), 260 | _('Unable to connect to addic7ed.com.'), 'error') 261 | return 262 | except SubsSearchError: 263 | logger.info('No subs found.') 264 | return 265 | else: 266 | logger.info('Episode selection cancelled.') 267 | return 268 | logger.info('Found subs for "%s"', query) 269 | display_subs(results.subtitles, results.episode_url, filename) 270 | 271 | 272 | def router(paramstring): 273 | """ 274 | Dispatch plugin functions depending on the call paramstring 275 | 276 | :param paramstring: URL-encoded plugin call parameters 277 | :type paramstring: str 278 | """ 279 | # Get plugin call params 280 | params = dict(urlparse.parse_qsl(paramstring)) 281 | if params['action'] in ('search', 'manualsearch'): 282 | # Search and display subs. 283 | search_subs(params) 284 | elif params['action'] == 'download': 285 | download_subs( 286 | params['link'], params['ref'], 287 | urlparse.unquote(params['filename']) 288 | ) 289 | xbmcplugin.endOfDirectory(HANDLE) 290 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/addon.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import hashlib 17 | import json 18 | import re 19 | from pathlib import Path 20 | 21 | import xbmcaddon 22 | from xbmcvfs import translatePath 23 | 24 | __all__ = ['ADDON_ID', 'ADDON', 'ADDON_VERSION', 'PATH', 'PROFILE', 'ICON', 'GettextEmulator'] 25 | 26 | ADDON = xbmcaddon.Addon() 27 | ADDON_ID = ADDON.getAddonInfo('id') 28 | ADDON_VERSION = ADDON.getAddonInfo('version') 29 | 30 | PATH = Path(translatePath(ADDON.getAddonInfo('path'))) 31 | PROFILE = Path(translatePath(ADDON.getAddonInfo('profile'))) 32 | ICON = str(PATH / 'icon.png') 33 | 34 | 35 | class GettextEmulator: 36 | """ 37 | Emulate GNU Gettext by mapping resource.language.en_gb UI strings to their numeric string IDs 38 | """ 39 | _instance = None 40 | 41 | class LocalizationError(Exception): # pylint: disable=missing-docstring 42 | pass 43 | 44 | def __new__(cls): 45 | if cls._instance is None: 46 | cls._instance = super().__new__(cls) 47 | return cls._instance 48 | 49 | def __init__(self): 50 | self._en_gb_string_po_path = (PATH / 'resources' / 'language' / 51 | 'resource.language.en_gb' / 'strings.po') 52 | if not self._en_gb_string_po_path.exists(): 53 | raise self.LocalizationError( 54 | 'Missing resource.language.en_gb strings.po localization file') 55 | if not PROFILE.exists(): 56 | PROFILE.mkdir() 57 | self._string_mapping_path = PROFILE / 'strings-map.json' 58 | self.strings_mapping = self._load_strings_mapping() 59 | 60 | def _load_strings_po(self): # pylint: disable=missing-docstring 61 | with self._en_gb_string_po_path.open('r', encoding='utf-8') as fo: 62 | return fo.read() 63 | 64 | def _load_strings_mapping(self): 65 | """ 66 | Load mapping of resource.language.en_gb UI strings to their IDs 67 | 68 | If a mapping file is missing or resource.language.en_gb strins.po file has been updated, 69 | a new mapping file is created. 70 | 71 | :return: UI strings mapping 72 | """ 73 | strings_po = self._load_strings_po() 74 | strings_po_md5 = hashlib.md5(strings_po.encode('utf-8')).hexdigest() 75 | try: 76 | with self._string_mapping_path.open('r', encoding='utf-8') as fo: 77 | mapping = json.load(fo) 78 | if mapping['md5'] != strings_po_md5: 79 | raise IOError('resource.language.en_gb strings.po has been updated') 80 | except (IOError, ValueError): 81 | strings_mapping = self._parse_strings_po(strings_po) 82 | mapping = { 83 | 'strings': strings_mapping, 84 | 'md5': strings_po_md5, 85 | } 86 | with self._string_mapping_path.open('w', encoding='utf-8') as fo: 87 | json.dump(mapping, fo) 88 | return mapping['strings'] 89 | 90 | @staticmethod 91 | def _parse_strings_po(strings_po): 92 | """ 93 | Parse resource.language.en_gb strings.po file contents into a mapping of UI strings 94 | to their numeric IDs. 95 | 96 | :param strings_po: the content of strings.po file as a text string 97 | :return: UI strings mapping 98 | """ 99 | id_string_pairs = re.findall(r'^msgctxt "#(\d+?)"\r?\nmsgid "(.*)"\r?$', strings_po, re.M) 100 | return {string: int(string_id) for string_id, string in id_string_pairs if string} 101 | 102 | @classmethod 103 | def gettext(cls, en_string: str) -> str: 104 | """ 105 | Return a localized UI string by a resource.language.en_gb source string 106 | 107 | :param en_string: resource.language.en_gb UI string 108 | :return: localized UI string 109 | """ 110 | emulator = cls() 111 | try: 112 | string_id = emulator.strings_mapping[en_string] 113 | except KeyError as exc: 114 | raise cls.LocalizationError( 115 | f'Unable to find "{en_string}" string in resource.language.en_gb/strings.po' 116 | ) from exc 117 | return ADDON.getLocalizedString(string_id) 118 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/exception_logger.py: -------------------------------------------------------------------------------- 1 | # (c) Roman Miroshnychenko 2023 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """Exception logger with extended diagnostic info""" 16 | 17 | import inspect 18 | import logging 19 | import sys 20 | from contextlib import contextmanager 21 | from platform import uname 22 | from pprint import pformat 23 | from typing import Any, Dict, Callable, Generator, Iterable, Optional 24 | 25 | import xbmc 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | def _format_vars(variables: Dict[str, Any]) -> str: 31 | """ 32 | Format variables dictionary 33 | 34 | :param variables: variables dict 35 | :return: formatted string with sorted ``var = val`` pairs 36 | """ 37 | var_list = [(var, val) for var, val in variables.items() 38 | if not (var.startswith('__') or var.endswith('__'))] 39 | var_list.sort(key=lambda i: i[0]) 40 | lines = [] 41 | for var, val in var_list: 42 | lines.append(f'{var} = {pformat(val)}') 43 | return '\n'.join(lines) 44 | 45 | 46 | def _format_code_context(frame_info: inspect.FrameInfo) -> str: 47 | context = '' 48 | if frame_info.code_context is not None: 49 | for i, line in enumerate(frame_info.code_context, frame_info.lineno - frame_info.index): 50 | if i == frame_info.lineno: 51 | context += f'{str(i).rjust(5)}:>{line}' 52 | else: 53 | context += f'{str(i).rjust(5)}: {line}' 54 | return context 55 | 56 | 57 | FRAME_INFO_TEMPLATE = """File: 58 | {file_path}:{lineno} 59 | ---------------------------------------------------------------------------------------------------- 60 | Code context: 61 | {code_context} 62 | ---------------------------------------------------------------------------------------------------- 63 | Local variables: 64 | {local_vars} 65 | ==================================================================================================== 66 | """ 67 | 68 | 69 | def _format_frame_info(frame_info: inspect.FrameInfo) -> str: 70 | return FRAME_INFO_TEMPLATE.format( 71 | file_path=frame_info.filename, 72 | lineno=frame_info.lineno, 73 | code_context=_format_code_context(frame_info), 74 | local_vars=_format_vars(frame_info.frame.f_locals) 75 | ) 76 | 77 | 78 | STACK_TRACE_TEMPLATE = """ 79 | #################################################################################################### 80 | Stack Trace 81 | ==================================================================================================== 82 | {stack_trace} 83 | ************************************* End of diagnostic info *************************************** 84 | """ 85 | 86 | 87 | def _format_stack_trace(frames: Iterable[inspect.FrameInfo]) -> str: 88 | stack_trace = '' 89 | for frame_info in frames: 90 | stack_trace += _format_frame_info(frame_info) 91 | return STACK_TRACE_TEMPLATE.format(stack_trace=stack_trace) 92 | 93 | 94 | EXCEPTION_TEMPLATE = """ 95 | #################################################################################################### 96 | Exception Diagnostic Info 97 | ---------------------------------------------------------------------------------------------------- 98 | Exception type : {exc_type} 99 | Exception message : {exc} 100 | System info : {system_info} 101 | Python version : {python_version} 102 | Kodi version : {kodi_version} 103 | sys.argv : {sys_argv} 104 | ---------------------------------------------------------------------------------------------------- 105 | sys.path: 106 | {sys_path} 107 | {stack_trace_info} 108 | """ 109 | 110 | 111 | def format_trace(frames_to_exclude: int = 1) -> str: 112 | """ 113 | Returns a pretty stack trace with code context and local variables 114 | 115 | Stack trace info includes the following: 116 | 117 | * File path and line number 118 | * Code fragment 119 | * Local variables 120 | 121 | It allows to inspect execution state at the point of this function call 122 | 123 | :param frames_to_exclude: How many top frames are excluded from the trace 124 | to skip unnecessary info. Since each function call creates a stack frame 125 | you need to exclude at least this function frame. 126 | """ 127 | frames = inspect.stack(5)[frames_to_exclude:] 128 | return _format_stack_trace(reversed(frames)) 129 | 130 | 131 | def format_exception(exc_obj: Optional[Exception] = None) -> str: 132 | """ 133 | Returns a pretty exception stack trace with code context and local variables 134 | 135 | :param exc_obj: exception object (optional) 136 | :raises ValueError: if no exception is being handled 137 | """ 138 | if exc_obj is None: 139 | _, exc_obj, _ = sys.exc_info() 140 | if exc_obj is None: 141 | raise ValueError('No exception is currently being handled') 142 | stack_trace = inspect.getinnerframes(exc_obj.__traceback__, context=5) 143 | stack_trace_info = _format_stack_trace(stack_trace) 144 | message = EXCEPTION_TEMPLATE.format( 145 | exc_type=exc_obj.__class__.__name__, 146 | exc=exc_obj, 147 | system_info=uname(), 148 | python_version=sys.version.replace('\n', ' '), 149 | kodi_version=xbmc.getInfoLabel('System.BuildVersion'), 150 | sys_argv=pformat(sys.argv), 151 | sys_path=pformat(sys.path), 152 | stack_trace_info=stack_trace_info 153 | ) 154 | return message 155 | 156 | 157 | @contextmanager 158 | def catch_exception(logger_func: Callable[[str], None] = logger.error 159 | ) -> Generator[None, None, None]: 160 | """ 161 | Diagnostic helper context manager 162 | 163 | It controls execution within its context and writes extended 164 | diagnostic info to the Kodi log if an unhandled exception 165 | happens within the context. The info includes the following items: 166 | 167 | - System info 168 | - Python version 169 | - Kodi version 170 | - Module path. 171 | - Stack trace including: 172 | * File path and line number where the exception happened 173 | * Code fragment where the exception has happened. 174 | * Local variables at the moment of the exception. 175 | 176 | After logging the diagnostic info the exception is re-raised. 177 | 178 | Example:: 179 | 180 | with catch_exception(): 181 | # Some risky code 182 | raise RuntimeError('Fatal error!') 183 | 184 | :param logger_func: logger function that accepts a single argument 185 | that is a log message. 186 | """ 187 | try: 188 | yield 189 | except Exception as exc: 190 | message = format_exception(exc) 191 | # pylint: disable=line-too-long logging-not-lazy 192 | logger_func('\n*********************************** Unhandled exception detected ***********************************\n' 193 | + message) 194 | raise 195 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | 17 | class Add7Exception(Exception): 18 | pass 19 | 20 | 21 | class ParseError(Add7Exception): 22 | pass 23 | 24 | 25 | class SubsSearchError(Add7Exception): 26 | pass 27 | 28 | 29 | class Add7ConnectionError(Add7Exception): 30 | pass 31 | 32 | 33 | class NoSubtitlesReturned(Add7Exception): 34 | pass 35 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/parser.py: -------------------------------------------------------------------------------- 1 | ## Copyright (C) 2013, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import re 17 | from collections import namedtuple 18 | 19 | from bs4 import BeautifulSoup 20 | 21 | from addic7ed.exceptions import SubsSearchError, ParseError 22 | from addic7ed.webclient import Session 23 | 24 | __all__ = [ 25 | 'search_episode', 26 | 'get_episode', 27 | 'parse_filename', 28 | 'normalize_showname', 29 | 'get_languages', 30 | ] 31 | 32 | session = Session() 33 | 34 | SubsSearchResult = namedtuple('SubsSearchResult', ['subtitles', 'episode_url']) 35 | EpisodeItem = namedtuple('EpisodeItem', ['title', 'link']) 36 | SubsItem = namedtuple('SubsItem', ['language', 'version', 'link', 'hi', 'unfinished']) 37 | LanguageData = namedtuple('LanguageData', ['kodi_lang', 'add7_lang']) 38 | 39 | serie_re = re.compile(r'^serie') 40 | version_re = re.compile(r'Version (.*?),') 41 | original_download_re = re.compile(r'^/original') 42 | updated_download_re = re.compile(r'^/updated') 43 | jointranslation_re = re.compile('^/jointranslation') 44 | spanish_re = re.compile(r'Spanish \(.*?\)') 45 | 46 | episode_patterns = ( 47 | re.compile(r'^(.*?)[ \.](?:\d*?[ \.])?s(\d+)[ \.]?e(\d+)\.', re.I | re.U), 48 | re.compile(r'^(.*?)[ \.](?:\d*?[ \.])?(\d+)x(\d+)\.', re.I | re.U), 49 | re.compile(r'^(.*?)[ \.](?:\d*?[ \.])?(\d{1,2}?)[ \.]?(\d{2})\.', re.I | re.U), 50 | ) 51 | # Convert show names from TheTVDB format to Addic7ed.com format 52 | # Keys must be all lowercase 53 | NAME_CONVERSIONS = { 54 | 'castle (2009)': 'castle', 55 | 'law & order: special victims unit': 'Law and order SVU', 56 | 'bodyguard (2018)': 'bodyguard', 57 | } 58 | 59 | 60 | def search_episode(query, languages=None): 61 | """ 62 | Search episode function. Accepts a TV show name, a season #, an episode # 63 | and language. Note that season and episode #s must be strings, not integers! 64 | For better search results relevance, season and episode #s 65 | should be 2-digit, e.g. 04. languages param must be a list of tuples 66 | ('Kodi language name', 'addic7ed language name') 67 | If search returns only 1 match, addic7ed.com redirects to the found episode 68 | page. In this case the function returns the list of available subs 69 | and an episode page URL. 70 | 71 | :param query: subs search query 72 | :param languages: the list of languages to search 73 | :return: search results as the list of potential episodes for multiple matches 74 | or the list of subtitles and episode page URL for a single match 75 | :raises: ConnectionError if addic7ed.com cannot be opened 76 | :raises: SubsSearchError if search returns no results 77 | """ 78 | if languages is None: 79 | languages = [LanguageData('English', 'English')] 80 | webpage = session.load_page('/search.php', 81 | params={'search': query, 'Submit': 'Search'}) 82 | soup = BeautifulSoup(webpage, 'html5lib') 83 | table = soup.find('table', 84 | {'class': 'tabel', 'align': 'center', 'width': '80%', 85 | 'border': '0'} 86 | ) 87 | if table is not None: 88 | results = list(parse_search_results(table)) 89 | if results: 90 | return results 91 | else: 92 | sub_cells = soup.find_all( 93 | 'table', 94 | {'width': '100%', 'border': '0', 95 | 'align': 'center', 'class': 'tabel95'} 96 | ) 97 | if sub_cells: 98 | return SubsSearchResult( 99 | parse_episode(sub_cells, languages), session.last_url 100 | ) 101 | raise SubsSearchError 102 | 103 | 104 | def parse_search_results(table): 105 | a_tags = table.find_all('a', href=serie_re) 106 | for tag in a_tags: 107 | yield EpisodeItem(tag.text, tag['href']) 108 | 109 | 110 | def get_episode(link, languages=None): 111 | if languages is None: 112 | languages = [LanguageData('English', 'English')] 113 | webpage = session.load_page('/' + link) 114 | soup = BeautifulSoup(webpage, 'html5lib') 115 | sub_cells = soup.find_all( 116 | 'table', 117 | {'width': '100%', 'border': '0', 'align': 'center', 'class': 'tabel95'} 118 | ) 119 | if not sub_cells: 120 | raise SubsSearchError 121 | return SubsSearchResult( 122 | parse_episode(sub_cells, languages), session.last_url 123 | ) 124 | 125 | 126 | def parse_episode(sub_cells, languages): 127 | """ 128 | Parse episode page. Accepts an episode page and a language. 129 | languages param must be a list of tuples 130 | ('Kodi language name', 'addic7ed language name') 131 | Returns the generator of available subs where each item is a named tuple 132 | with the following fields: 133 | 134 | - ``language``: subtitles language (Kodi) 135 | - ``version``: subtitles version (description on addic7ed.com) 136 | - ``link``: subtitles link 137 | - ``hi``: ``True`` for subs for hearing impaired, else ``False`` 138 | 139 | :param sub_cell: BS nodes with episode subtitles 140 | :param languages: the list of languages to search 141 | :return: generator function that yields :class:`SubsItem` items. 142 | """ 143 | for sub_cell in sub_cells: 144 | version = version_re.search( 145 | sub_cell.find('td', 146 | {'colspan': '3', 147 | 'align': 'center', 148 | 'class': 'NewsTitle'}).text 149 | ).group(1) 150 | works_with = sub_cell.find( 151 | 'td', {'class': 'newsDate', 'colspan': '3'} 152 | ).get_text(strip=True) 153 | if works_with: 154 | version += ', ' + works_with 155 | lang_cells = sub_cell.find_all('td', {'class': 'language'}) 156 | for lang_cell in lang_cells: 157 | for language in languages: 158 | if language.add7_lang in lang_cell.text: 159 | download_cell = lang_cell.find_next('td', {'colspan': '3'}) 160 | download_button = download_cell.find( 161 | 'a', 162 | class_='face-button', 163 | href=updated_download_re 164 | ) 165 | if download_button is None: 166 | download_button = download_cell.find( 167 | 'a', 168 | class_='face-button', 169 | href=original_download_re 170 | ) 171 | download_row = download_button.parent.parent 172 | info_row = download_row.find_next('tr') 173 | hi = info_row.find('img', title='Hearing Impaired') is not None 174 | unfinished = info_row.find('a', href=jointranslation_re) is not None 175 | yield SubsItem( 176 | language=language.kodi_lang, 177 | version=version, 178 | link=download_button['href'], 179 | hi=hi, 180 | unfinished=unfinished 181 | ) 182 | break 183 | 184 | 185 | def parse_filename(filename): 186 | """ 187 | Filename parser for extracting show name, season # and episode # from 188 | a filename. 189 | 190 | :param filename: episode filename 191 | :return: parsed showname, season and episode 192 | :raises ParseError: if the filename does not match any episode patterns 193 | """ 194 | filename = filename.replace(' ', '.') 195 | for regexp in episode_patterns: 196 | episode_data = regexp.search(filename) 197 | if episode_data is not None: 198 | showname = episode_data.group(1).replace('.', ' ') 199 | season = episode_data.group(2).zfill(2) 200 | episode = episode_data.group(3).zfill(2) 201 | return showname, season, episode 202 | raise ParseError 203 | 204 | 205 | def normalize_showname(showname): 206 | """ 207 | Normalize showname if there are differences 208 | between TheTVDB and Addic7ed 209 | 210 | :param showname: TV show name 211 | :return: normalized show name 212 | """ 213 | showname = showname.strip().lower() 214 | showname = NAME_CONVERSIONS.get(showname, showname) 215 | return showname.replace(':', '') 216 | 217 | 218 | def get_languages(languages_raw): 219 | """ 220 | Create the list of pairs of language names. 221 | The 1st item in a pair is used by Kodi. 222 | The 2nd item in a pair is used by 223 | the addic7ed web site parser. 224 | 225 | :param languages_raw: the list of subtitle languages from Kodi 226 | :return: the list of language pairs 227 | """ 228 | languages = [] 229 | for language in languages_raw: 230 | kodi_lang = language 231 | if 'English' in kodi_lang: 232 | add7_lang = 'English' 233 | elif kodi_lang == 'Portuguese (Brazil)': 234 | add7_lang = 'Portuguese (Brazilian)' 235 | elif spanish_re.search(kodi_lang) is not None: 236 | add7_lang = 'Spanish (Latin America)' 237 | else: 238 | add7_lang = language 239 | languages.append(LanguageData(kodi_lang, add7_lang)) 240 | return languages 241 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import json 17 | import logging 18 | import os 19 | 20 | import xbmc 21 | 22 | from addic7ed.addon import ADDON_ID, ADDON_VERSION 23 | from addic7ed.exception_logger import format_exception, format_trace 24 | 25 | __all__ = [ 26 | 'initialize_logging', 27 | 'get_now_played', 28 | ] 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | 33 | class KodiLogHandler(logging.Handler): 34 | """ 35 | Logging handler that writes to the Kodi log with correct levels 36 | 37 | It also adds {addon_id} and {addon_version} variables available to log format. 38 | """ 39 | LOG_FORMAT = '[{addon_id} v.{addon_version}] {filename}:{lineno} - {message}' 40 | LEVEL_MAP = { 41 | logging.NOTSET: xbmc.LOGNONE, 42 | logging.DEBUG: xbmc.LOGDEBUG, 43 | logging.INFO: xbmc.LOGINFO, 44 | logging.WARN: xbmc.LOGWARNING, 45 | logging.WARNING: xbmc.LOGWARNING, 46 | logging.ERROR: xbmc.LOGERROR, 47 | logging.CRITICAL: xbmc.LOGFATAL, 48 | } 49 | 50 | def emit(self, record): 51 | record.addon_id = ADDON_ID 52 | record.addon_version = ADDON_VERSION 53 | extended_trace_info = getattr(self, 'extended_trace_info', False) 54 | if extended_trace_info: 55 | if record.exc_info is not None: 56 | record.exc_text = format_exception(record.exc_info[1]) 57 | if record.stack_info is not None: 58 | record.stack_info = format_trace(7) 59 | message = self.format(record) 60 | kodi_log_level = self.LEVEL_MAP.get(record.levelno, xbmc.LOGDEBUG) 61 | xbmc.log(message, level=kodi_log_level) 62 | 63 | 64 | def initialize_logging(extended_trace_info=True): 65 | """ 66 | Initialize the root logger that writes to the Kodi log 67 | 68 | After initialization, you can use Python logging facilities as usual. 69 | 70 | :param extended_trace_info: write extended trace info when exc_info=True 71 | or stack_info=True parameters are passed to logging methods. 72 | """ 73 | handler = KodiLogHandler() 74 | # pylint: disable=attribute-defined-outside-init 75 | handler.extended_trace_info = extended_trace_info 76 | logging.basicConfig( 77 | format=KodiLogHandler.LOG_FORMAT, 78 | style='{', 79 | level=logging.DEBUG, 80 | handlers=[handler], 81 | force=True 82 | ) 83 | 84 | 85 | def get_now_played(): 86 | """ 87 | Get info about the currently played file via JSON-RPC 88 | 89 | :return: currently played item's data 90 | :rtype: dict 91 | """ 92 | request = json.dumps({ 93 | 'jsonrpc': '2.0', 94 | 'method': 'Player.GetItem', 95 | 'params': { 96 | 'playerid': 1, 97 | 'properties': ['showtitle', 'season', 'episode'] 98 | }, 99 | 'id': '1' 100 | }) 101 | response = xbmc.executeJSONRPC(request) 102 | item = json.loads(response)['result']['item'] 103 | path = xbmc.getInfoLabel('Window(10000).Property(videoinfo.current_path)') 104 | if path: 105 | item['file'] = os.path.basename(path) 106 | logger.debug("Using file path from addon: %s", item['file']) 107 | else: 108 | item['file'] = xbmc.Player().getPlayingFile() # It provides more correct result 109 | return item 110 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/addic7ed/webclient.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import logging 17 | 18 | import simple_requests as requests 19 | from xbmcvfs import File 20 | 21 | from addic7ed.exceptions import Add7ConnectionError, NoSubtitlesReturned 22 | 23 | __all__ = ['Session'] 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | SITE = 'https://www.addic7ed.com' 28 | HEADERS = { 29 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' 30 | '(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 31 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 32 | 'Host': SITE[8:], 33 | 'Accept-Charset': 'UTF-8', 34 | } 35 | 36 | 37 | class Session: 38 | """ 39 | Webclient Session class 40 | """ 41 | _instance = None 42 | 43 | def __new__(cls): 44 | if cls._instance is None: 45 | cls._instance = super().__new__(cls) 46 | return cls._instance 47 | 48 | def __init__(self): 49 | self.last_url = '' 50 | 51 | def _open_url(self, url, params, referer): 52 | logger.debug('Opening URL: %s', url) 53 | headers = HEADERS.copy() 54 | headers['Referer'] = referer 55 | try: 56 | response = requests.get(url, params=params, headers=headers, verify=False) 57 | except requests.RequestException as exc: 58 | logger.error('Unable to connect to Addic7ed.com!') 59 | raise Add7ConnectionError from exc 60 | logger.debug('Addic7ed.com returned page:\n%s', response.text) 61 | if not response.ok: 62 | logger.error('Addic7ed.com returned status: %s', response.status_code) 63 | raise Add7ConnectionError 64 | self.last_url = response.url 65 | return response 66 | 67 | def load_page(self, path, params=None): 68 | """ 69 | Load webpage by its relative path on the site 70 | 71 | :param path: relative path starting from '/' 72 | :param params: URL query params 73 | :return: webpage content as a Unicode string 74 | :raises ConnectionError: if unable to connect to the server 75 | """ 76 | response = self._open_url(SITE + path, params, referer=SITE + '/') 77 | self.last_url = response.url 78 | return response.text 79 | 80 | def download_subs(self, path, referer, filename='subtitles.srt'): 81 | """ 82 | Download subtitles by their URL 83 | 84 | :param path: relative path to .srt starting from '/' 85 | :param referer: referer page 86 | :param filename: subtitles filename 87 | :return: subtitles file contents as a byte string 88 | :raises ConnectionError: if unable to connect to the server 89 | :raises NoSubtitlesReturned: if a HTML page is returned instead of subtitles 90 | """ 91 | response = self._open_url(SITE + path, params=None, referer=referer) 92 | subtitles = response.content 93 | if subtitles[:9].lower() == b' 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Addic7ed.com Subtitles 16 | Субтитры Addic7ed.com 17 | Субтитри Addic7ed.com 18 | Sous-titres Addic7ed.com 19 | Addic7ed.com Ondertitels 20 | Subtitles service for Addic7ed.com. It supports only TV shows. 21 | Служба субтитров для сайта Addic7ed.com. Служба поддерживает только сериалы. 22 | Служба субтитрів для сайту Addic7ed.com. Служба підтримує лише серіали. 23 | Service de sous-titres pour Addic7ed.com. Fonctionne uniquement pour les séries TV. 24 | Ondertiteling dienst voor Addic7ed.com. Ondersteunt enkel TV series. 25 | all 26 | https://github.com/romanvm/service.addic7ed 27 | www.addic7ed.com 28 | GPL-3.0-only 29 | 30 | icon.png 31 | fanart.jpg 32 | 33 | 3.2.3: 34 | - Internal changes. 35 | true 36 | 37 | 38 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/service.addic7ed/66e34642390bf4872e9ac7266ec34627966daae7/service.subtitles.rvm.addic7ed/fanart.jpg -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/service.addic7ed/66e34642390bf4872e9ac7266ec34627966daae7/service.subtitles.rvm.addic7ed/icon.png -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/main.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019, Roman Miroshnychenko aka Roman V.M. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import sys 17 | 18 | from addic7ed.actions import router 19 | from addic7ed.exception_logger import catch_exception 20 | from addic7ed.utils import initialize_logging 21 | 22 | initialize_logging() 23 | 24 | if __name__ == '__main__': 25 | with catch_exception(): 26 | router(sys.argv[2][1:]) 27 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/language/resource.language.en_gb/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | msgid "" 3 | msgstr "" 4 | 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: en\n" 8 | 9 | 10 | msgctxt "#32000" 11 | msgid "Success!" 12 | msgstr "" 13 | 14 | msgctxt "#32001" 15 | msgid "Subtitles downloaded." 16 | msgstr "" 17 | 18 | msgctxt "#32002" 19 | msgid "Error!" 20 | msgstr "" 21 | 22 | msgctxt "#32003" 23 | msgid "Exceeded daily limit for subs downloads." 24 | msgstr "" 25 | 26 | msgctxt "#32004" 27 | msgid "Unable to download subtitles." 28 | msgstr "" 29 | 30 | msgctxt "#32005" 31 | msgid "Unable to connect to addic7ed.com." 32 | msgstr "" 33 | 34 | msgctxt "#32006" 35 | msgid "Unable to determine episode data." 36 | msgstr "" 37 | 38 | msgctxt "#32007" 39 | msgid "Always use filename for subs search" 40 | msgstr "" 41 | 42 | msgctxt "#32008" 43 | msgid "Select episode" 44 | msgstr "" 45 | 46 | msgctxt "#32009" 47 | msgid "Login error!" 48 | msgstr "" 49 | 50 | msgctxt "#32010" 51 | msgid "Login to Addic7ed.com" 52 | msgstr "" 53 | 54 | msgctxt "#32011" 55 | msgid "Username" 56 | msgstr "" 57 | 58 | msgctxt "#32012" 59 | msgid "Password" 60 | msgstr "" 61 | 62 | msgctxt "#32013" 63 | msgid "Clean cookies" 64 | msgstr "" 65 | 66 | msgctxt "#32014" 67 | msgid "This will logout you from Addic7ed.com." 68 | msgstr "" 69 | 70 | msgctxt "#32015" 71 | msgid "Are you sure?" 72 | msgstr "" 73 | 74 | msgctxt "#32016" 75 | msgid "Cookies removed successfully." 76 | msgstr "" 77 | 78 | msgctxt "#32017" 79 | msgid "Unable to remove cookies!" 80 | msgstr "" 81 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/language/resource.language.fr_fr/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | msgid "" 3 | msgstr "" 4 | 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: fr\n" 8 | 9 | 10 | msgctxt "#32000" 11 | msgid "Success!" 12 | msgstr "Succès!" 13 | 14 | msgctxt "#32001" 15 | msgid "Subtitles downloaded." 16 | msgstr "Sous-titres téléchargés." 17 | 18 | msgctxt "#32002" 19 | msgid "Error!" 20 | msgstr "Erreur!" 21 | 22 | msgctxt "#32003" 23 | msgid "Exceeded daily limit for subs downloads." 24 | msgstr "Limite journalière de téléchargements dépassée." 25 | 26 | msgctxt "#32004" 27 | msgid "Unable to download subtitles." 28 | msgstr "Erreur lors du téléchargement." 29 | 30 | msgctxt "#32005" 31 | msgid "Unable to connect to addic7ed.com." 32 | msgstr "Impossible de se connecter à addic7ed.com." 33 | 34 | msgctxt "#32006" 35 | msgid "Unable to determine episode data." 36 | msgstr "Impossible d'identifier les données de l'épisode." 37 | 38 | msgctxt "#32007" 39 | msgid "Always use filename for subs search" 40 | msgstr "Toujours utiliser le nom de fichier pour les recherches" 41 | 42 | msgctxt "#32008" 43 | msgid "Select episode" 44 | msgstr "Sélectionner l'épisode" 45 | 46 | msgctxt "#32009" 47 | msgid "Login error!" 48 | msgstr "Erreur de connexion!" 49 | 50 | msgctxt "#32010" 51 | msgid "Login to Addic7ed.com" 52 | msgstr "Connexion à Addic7ed.com" 53 | 54 | msgctxt "#32011" 55 | msgid "Username" 56 | msgstr "Nom utilisateur" 57 | 58 | msgctxt "#32012" 59 | msgid "Password" 60 | msgstr "Mot de passe" 61 | 62 | msgctxt "#32013" 63 | msgid "Clean cookies" 64 | msgstr "Effacer les cookies" 65 | 66 | msgctxt "#32014" 67 | msgid "This will logout you from Addic7ed.com." 68 | msgstr "Cela va vous déconnecter d'Addic7ed.com." 69 | 70 | msgctxt "#32015" 71 | msgid "Are you sure?" 72 | msgstr "Êtes-vous sûr(e)?" 73 | 74 | msgctxt "#32016" 75 | msgid "Cookies removed successfully." 76 | msgstr "Cookies effacés avec succès." 77 | 78 | msgctxt "#32017" 79 | msgid "Unable to remove cookies!" 80 | msgstr "Impossible d'effacer les cookies!" 81 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/language/resource.language.nl_nl/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | msgid "" 3 | msgstr "" 4 | 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: en\n" 8 | 9 | 10 | msgctxt "#32000" 11 | msgid "Success!" 12 | msgstr "Succes!" 13 | 14 | msgctxt "#32001" 15 | msgid "Subtitles downloaded." 16 | msgstr "Ondertiteling gedownload." 17 | 18 | msgctxt "#32002" 19 | msgid "Error!" 20 | msgstr "Fout!" 21 | 22 | msgctxt "#32003" 23 | msgid "Exceeded daily limit for subs downloads." 24 | msgstr "Dagelijkse ondertiteling download limiet overschreden." 25 | 26 | msgctxt "#32004" 27 | msgid "Unable to download subtitles." 28 | msgstr "Kan ondertiteling niet downloaden." 29 | 30 | msgctxt "#32005" 31 | msgid "Unable to connect to addic7ed.com." 32 | msgstr "Kan niet verbinden met addic7ed.com." 33 | 34 | msgctxt "#32006" 35 | msgid "Unable to determine episode data." 36 | msgstr "Kan de afleveringsinformatie niet bekomen." 37 | 38 | msgctxt "#32007" 39 | msgid "Always use filename for subs search" 40 | msgstr "Gebruik alijd de bestandsnaam voor opzoekingen" 41 | 42 | msgctxt "#32008" 43 | msgid "Select episode" 44 | msgstr "Selecteer aflevering" 45 | 46 | msgctxt "#32009" 47 | msgid "Login error!" 48 | msgstr "Fout tijdens aanmelden!" 49 | 50 | msgctxt "#32010" 51 | msgid "Login to Addic7ed.com" 52 | msgstr "Login bij Addic7ed.com" 53 | 54 | msgctxt "#32011" 55 | msgid "Username" 56 | msgstr "Gebruikersnaam" 57 | 58 | msgctxt "#32012" 59 | msgid "Password" 60 | msgstr "Wachtwoord" 61 | 62 | msgctxt "#32013" 63 | msgid "Clean cookies" 64 | msgstr "Kuis cookies op" 65 | 66 | msgctxt "#32014" 67 | msgid "This will logout you from Addic7ed.com." 68 | msgstr "Dit zal je afmelden van Addic7ed.com." 69 | 70 | msgctxt "#32015" 71 | msgid "Are you sure?" 72 | msgstr "Ben je zeker?" 73 | 74 | msgctxt "#32016" 75 | msgid "Cookies removed successfully." 76 | msgstr "Cookies opgekuist." 77 | 78 | msgctxt "#32017" 79 | msgid "Unable to remove cookies!" 80 | msgstr "Niet mogelijk om de cookies op te kuisen!" 81 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/language/resource.language.ru_ru/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | msgid "" 3 | msgstr "" 4 | 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: ru\n" 8 | 9 | 10 | msgctxt "#32000" 11 | msgid "Success!" 12 | msgstr "Готово!" 13 | 14 | msgctxt "#32001" 15 | msgid "Subtitles downloaded." 16 | msgstr "Субтитры загружены." 17 | 18 | msgctxt "#32002" 19 | msgid "Error!" 20 | msgstr "Ошибка!" 21 | 22 | msgctxt "#32003" 23 | msgid "Exceeded daily limit for subs downloads." 24 | msgstr "Превышен дневной лимит загрузки субитров." 25 | 26 | msgctxt "#32004" 27 | msgid "Unable to download subtitles." 28 | msgstr "Не удалось загрузить субтитры." 29 | 30 | msgctxt "#32005" 31 | msgid "Unable to connect to addic7ed.com." 32 | msgstr "Не удалось подключиться к addic7ed.com." 33 | 34 | msgctxt "#32006" 35 | msgid "Unable to determine episode data." 36 | msgstr "Не удалось определить данные серии." 37 | 38 | msgctxt "#32007" 39 | msgid "Always use filename for subs search" 40 | msgstr "Всегда искать субтитры по имени файла" 41 | 42 | msgctxt "#32008" 43 | msgid "Select episode" 44 | msgstr "Выберите серию" 45 | 46 | msgctxt "#32009" 47 | msgid "Login error!" 48 | msgstr "Ошибка входа." 49 | 50 | msgctxt "#32010" 51 | msgid "Login to Addic7ed.com" 52 | msgstr "Вход на Addic7ed.com" 53 | 54 | msgctxt "#32011" 55 | msgid "Username" 56 | msgstr "Имя пользователя" 57 | 58 | msgctxt "#32012" 59 | msgid "Password" 60 | msgstr "Пароль" 61 | 62 | msgctxt "#32013" 63 | msgid "Clean cookies" 64 | msgstr "Очистить куки" 65 | 66 | msgctxt "#32014" 67 | msgid "This will logout you from Addic7ed.com." 68 | msgstr "Это приведет к выходу с Addic7ed.com" 69 | 70 | msgctxt "#32015" 71 | msgid "Are you sure?" 72 | msgstr "Вы уверены?" 73 | 74 | msgctxt "#32016" 75 | msgid "Cookies removed successfully." 76 | msgstr "Куки успешно удалены." 77 | 78 | msgctxt "#32017" 79 | msgid "Unable to remove cookies!" 80 | msgstr "Не удалось удалить куки." 81 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/language/resource.language.uk_ua/strings.po: -------------------------------------------------------------------------------- 1 | # Kodi Media Center language file 2 | msgid "" 3 | msgstr "" 4 | 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Language: uk\n" 8 | 9 | 10 | msgctxt "#32000" 11 | msgid "Success!" 12 | msgstr "Готово!" 13 | 14 | msgctxt "#32001" 15 | msgid "Subtitles downloaded." 16 | msgstr "Субтитри завантажено." 17 | 18 | msgctxt "#32002" 19 | msgid "Error!" 20 | msgstr "Помилка!" 21 | 22 | msgctxt "#32003" 23 | msgid "Exceeded daily limit for subs downloads." 24 | msgstr "Перевищено щоденний ліміт завантаження субтитрів." 25 | 26 | msgctxt "#32004" 27 | msgid "Unable to download subtitles." 28 | msgstr "Не вдалося завантажити субтитри" 29 | 30 | msgctxt "#32005" 31 | msgid "Unable to connect to addic7ed.com." 32 | msgstr "Не вдалося підключится до addic7ed.com." 33 | 34 | msgctxt "#32006" 35 | msgid "Unable to determine episode data." 36 | msgstr "Не вдалося визначити дані серії." 37 | 38 | msgctxt "#32007" 39 | msgid "Always use filename for subs search" 40 | msgstr "Завжди шукати субтитри за іменем файлу" 41 | 42 | msgctxt "#32008" 43 | msgid "Select episode" 44 | msgstr "Виберіть серію" 45 | 46 | msgctxt "#32009" 47 | msgid "Login error!" 48 | msgstr "Помилка входу." 49 | 50 | msgctxt "#32010" 51 | msgid "Login to Addic7ed.com" 52 | msgstr "Вхід на Addic7ed.com" 53 | 54 | msgctxt "#32011" 55 | msgid "Username" 56 | msgstr "Ім\'я користувача" 57 | 58 | msgctxt "#32012" 59 | msgid "Password" 60 | msgstr "Пароль" 61 | 62 | msgctxt "#32013" 63 | msgid "Clean cookies" 64 | msgstr "Очистити кукі" 65 | 66 | msgctxt "#32014" 67 | msgid "This will logout you from Addic7ed.com." 68 | msgstr "Це призведе до виходу з Addic7ed.com." 69 | 70 | msgctxt "#32015" 71 | msgid "Are you sure?" 72 | msgstr "Ви впевнені?" 73 | 74 | msgctxt "#32016" 75 | msgid "Cookies removed successfully." 76 | msgstr "Кукі успішно видалені." 77 | 78 | msgctxt "#32017" 79 | msgid "Unable to remove cookies!" 80 | msgstr "Не вдалося видалити кукі." 81 | -------------------------------------------------------------------------------- /service.subtitles.rvm.addic7ed/resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------