Compare commits
272 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa318ac751 | ||
|
|
071ad16f83 | ||
|
|
eb44fc0ef7 | ||
|
|
f35239e1a4 | ||
|
|
5534878dcd | ||
|
|
f1b82cf89a | ||
|
|
4ae81b9f52 | ||
|
|
7af412e6c5 | ||
|
|
52baa2e8cc | ||
|
|
d48c159283 | ||
|
|
fae51bd9b2 | ||
|
|
9a6e16f505 | ||
|
|
44afd62989 | ||
|
|
69986cc40d | ||
|
|
9769a36c30 | ||
|
|
1621510b15 | ||
|
|
85f910dd48 | ||
|
|
e260124351 | ||
|
|
1eb2d21086 | ||
|
|
fedc67e1e2 | ||
|
|
c88a7b134f | ||
|
|
20dfc02624 | ||
|
|
2ca11e53be | ||
|
|
1b79794ede | ||
|
|
806de6910a | ||
|
|
ba30fc1613 | ||
|
|
cd5774bfd3 | ||
|
|
df9cd5fe48 | ||
|
|
76d189b8de | ||
|
|
0a0fd8a9a3 | ||
|
|
f78764d18a | ||
|
|
59b352df35 | ||
|
|
c241cc9df9 | ||
|
|
5ba6291fd9 | ||
|
|
c8715218c8 | ||
|
|
cc9caf3d54 | ||
|
|
6d298daea4 | ||
|
|
07ef6b6f41 | ||
|
|
6c8e193130 | ||
|
|
b7c31fa7a0 | ||
|
|
d25c7b351e | ||
|
|
90103b9fba | ||
|
|
7c39b06297 | ||
|
|
2ead20b2f7 | ||
|
|
c8821b1055 | ||
|
|
50b6f773f5 | ||
|
|
11705d46f2 | ||
|
|
3e96211433 | ||
|
|
0c3cefc0ff | ||
|
|
deab5fc2c2 | ||
|
|
9c68120447 | ||
|
|
6f3181a643 | ||
|
|
416b04fdec | ||
|
|
c2958445cb | ||
|
|
1176db8f41 | ||
|
|
136e22bb84 | ||
|
|
78b7f38f61 | ||
|
|
05ad58b46e | ||
|
|
8040349718 | ||
|
|
fca212edc3 | ||
|
|
7237fd2c63 | ||
|
|
26c02fdbac | ||
|
|
aa4a3531e9 | ||
|
|
4495129a66 | ||
|
|
791ed3fe32 | ||
|
|
b29688dc8d | ||
|
|
2936a12e25 | ||
|
|
213fa15ef3 | ||
|
|
80f25d016c | ||
|
|
a6ad9b5187 | ||
|
|
2f5765cc3f | ||
|
|
d08b5b7041 | ||
|
|
2772cf6389 | ||
|
|
6317aec577 | ||
|
|
43f576d1c5 | ||
|
|
d8493bc6bf | ||
|
|
96010b48c1 | ||
|
|
a803e007d0 | ||
|
|
0699bdd141 | ||
|
|
c5b1bb766b | ||
|
|
e0eff168bf | ||
|
|
1c88173b3e | ||
|
|
6c2e005cd7 | ||
|
|
08780edcc1 | ||
|
|
d86d785061 | ||
|
|
67cf11966b | ||
|
|
9c96cc4044 | ||
|
|
d1e4c1d09f | ||
|
|
944f3a363f | ||
|
|
79bd4b1925 | ||
|
|
8ea3351eee | ||
|
|
8d90ca0892 | ||
|
|
4bb7a95e73 | ||
|
|
13e553d818 | ||
|
|
92b8977797 | ||
|
|
a347255752 | ||
|
|
e8b329e688 | ||
|
|
d77d326950 | ||
|
|
9832e6b011 | ||
|
|
5bae9e72c9 | ||
|
|
dba2ee1556 | ||
|
|
357f28321c | ||
|
|
3402ef8d11 | ||
|
|
643e1d2ac8 | ||
|
|
f16ef03927 | ||
|
|
bf54539c39 | ||
|
|
e71714dbb8 | ||
|
|
a482172be4 | ||
|
|
6fbbd63ad6 | ||
|
|
6126d617f5 | ||
|
|
f507efdef7 | ||
|
|
7d4f50add1 | ||
|
|
201521a4d8 | ||
|
|
8c9289ef1c | ||
|
|
8ba1b0b0c0 | ||
|
|
7871422354 | ||
|
|
eb19d80b97 | ||
|
|
6888cbf7b8 | ||
|
|
5c1842877d | ||
|
|
0bf1d75e0c | ||
|
|
a3e3c33855 | ||
|
|
d5d5e7f74c | ||
|
|
8adfcbe20d | ||
|
|
f8f79ecc75 | ||
|
|
289f42392b | ||
|
|
71cea26ada | ||
|
|
5043a8db4e | ||
|
|
5d3199e306 | ||
|
|
37a132164f | ||
|
|
d02cad0fca | ||
|
|
3530d158a6 | ||
|
|
7eef3a30a8 | ||
|
|
36b23d0b19 | ||
|
|
816277512d | ||
|
|
bec7e06628 | ||
|
|
798a00ae3a | ||
|
|
c852d1e434 | ||
|
|
08a829ca97 | ||
|
|
1c18753511 | ||
|
|
6b1c3ccc39 | ||
|
|
bb207f1aee | ||
|
|
91cc66a8d1 | ||
|
|
cfbfb44307 | ||
|
|
8d29ffd7a7 | ||
|
|
44c5193f54 | ||
|
|
342eec8c26 | ||
|
|
4973404684 | ||
|
|
e45a59c184 | ||
|
|
6efa62ced2 | ||
|
|
a316a6e094 | ||
|
|
297b25a739 | ||
|
|
3aa4ee3548 | ||
|
|
e6e3c27371 | ||
|
|
bdf7298afa | ||
|
|
a81e125a29 | ||
|
|
a454eb16de | ||
|
|
50512e863d | ||
|
|
7ff8e12c4e | ||
|
|
a2815081d3 | ||
|
|
e14fa4b7de | ||
|
|
4c95caf957 | ||
|
|
4a242475c0 | ||
|
|
fb1348a306 | ||
|
|
1b3cea3042 | ||
|
|
264145c4b9 | ||
|
|
daea47bf38 | ||
|
|
2ff5a2e9fc | ||
|
|
fc690abee9 | ||
|
|
157a477a10 | ||
|
|
aa7a412a94 | ||
|
|
2832c21ffd | ||
|
|
3751794f81 | ||
|
|
5c3740b8a9 | ||
|
|
303f049004 | ||
|
|
a52ede2a3d | ||
|
|
f249eb7cae | ||
|
|
2d20dc4282 | ||
|
|
95f8dc8eb3 | ||
|
|
1ae1e824c0 | ||
|
|
72413ef3b4 | ||
|
|
d45c45a868 | ||
|
|
3b646da0a8 | ||
|
|
6121537216 | ||
|
|
2482f11a5a | ||
|
|
ade08f74a4 | ||
|
|
5eade9abe5 | ||
|
|
203986d54c | ||
|
|
8e8c376df3 | ||
|
|
8a6886c71d | ||
|
|
7f02ca1bca | ||
|
|
7df265b357 | ||
|
|
5427168f01 | ||
|
|
8ce7d851cc | ||
|
|
22d3a23099 | ||
|
|
b6dd0285a8 | ||
|
|
9d45d42efe | ||
|
|
6e6e753475 | ||
|
|
3b11c931d4 | ||
|
|
742fbb224f | ||
|
|
b35adac318 | ||
|
|
c33274709e | ||
|
|
89748156a6 | ||
|
|
c01a792e25 | ||
|
|
e4d0937782 | ||
|
|
524110dee9 | ||
|
|
ae480283a3 | ||
|
|
ccaf5878ae | ||
|
|
cb67d4b194 | ||
|
|
701ca68db7 | ||
|
|
cf9c3290b5 | ||
|
|
964994dd90 | ||
|
|
edbd3d37da | ||
|
|
c2d36e2ac2 | ||
|
|
b2c0caaa43 | ||
|
|
8155e77210 | ||
|
|
fdda442dc8 | ||
|
|
b962d9597b | ||
|
|
9e624d0db2 | ||
|
|
4f52ba2d4d | ||
|
|
c9a22c517c | ||
|
|
023cd5f720 | ||
|
|
8b2b03bf0a | ||
|
|
2a14caceeb | ||
|
|
0fd3687843 | ||
|
|
d1c205a588 | ||
|
|
21103ecaac | ||
|
|
2fcbbfce27 | ||
|
|
5e8eda0ea4 | ||
|
|
1e9ea80685 | ||
|
|
427ad71880 | ||
|
|
fbe2ebe98f | ||
|
|
8b8d61eacf | ||
|
|
7d07980a15 | ||
|
|
84829ba83d | ||
|
|
bb3e52d27f | ||
|
|
63d2a4cffc | ||
|
|
5c23ac47b2 | ||
|
|
a52a4a47f3 | ||
|
|
5f5328a280 | ||
|
|
dd81b1babf | ||
|
|
8adf8a2a05 | ||
|
|
4c8914ad8d | ||
|
|
cfe36f16f2 | ||
|
|
46a6d84101 | ||
|
|
2a5dd1c418 | ||
|
|
61253d7c9d | ||
|
|
d4b7262105 | ||
|
|
a644d4ffda | ||
|
|
84ea523d16 | ||
|
|
3ea93cbf13 | ||
|
|
3f4dc08dc7 | ||
|
|
ee8ae6f492 | ||
|
|
984c2dab54 | ||
|
|
d215d087b3 | ||
|
|
42187327d6 | ||
|
|
58a256c121 | ||
|
|
e3ec31ae99 | ||
|
|
f81301f62d | ||
|
|
b7601fda7b | ||
|
|
e3b205046b | ||
|
|
2c70cedaa6 | ||
|
|
cea6c532e0 | ||
|
|
2e28b5904d | ||
|
|
4288c032db | ||
|
|
04a5378a87 | ||
|
|
ca5918ded9 | ||
|
|
2e7737c1af | ||
|
|
441bb10624 | ||
|
|
9adb23b280 | ||
|
|
7b547b2bc8 | ||
|
|
4ec75ad266 | ||
|
|
7dcf68d2be |
@@ -1,22 +0,0 @@
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
steps:
|
||||
# On every PR, build the addon and make it available for download as an artifact
|
||||
- template: build.yml
|
||||
parameters:
|
||||
py_versions: [ 'py2', 'py3' ]
|
||||
|
||||
# When triggered by a tag, publish the built addon to the repo server
|
||||
- ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags') }}:
|
||||
- template: publish.yml
|
||||
parameters:
|
||||
py_versions: [ 'py2', 'py3' ]
|
||||
@@ -1,45 +0,0 @@
|
||||
parameters:
|
||||
python_versions : []
|
||||
|
||||
steps:
|
||||
- ${{ each py_version in parameters.py_versions }}:
|
||||
- task: usePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
|
||||
- checkout: self
|
||||
clean: true
|
||||
|
||||
- script: python3 -m pip install --user -r jellycon/requirements-dev.txt
|
||||
displayName: 'Install dev dependencies'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Create clean addon directory'
|
||||
inputs:
|
||||
sourceFolder: 'jellycon'
|
||||
cleanTargetFolder: true
|
||||
contents: |
|
||||
**/*
|
||||
!.ci/*
|
||||
!.git/**/*
|
||||
!.github/*
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/plugin.video.jellycon'
|
||||
|
||||
- script: python3 '$(Build.ArtifactStagingDirectory)/plugin.video.jellycon/build.py' --version ${{ py_version }} --target '$(Build.ArtifactStagingDirectory)/'
|
||||
displayName: 'Create ${{ py_version }} addon.xml'
|
||||
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Create ${{ py_version }} zip file'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/plugin.video.jellycon'
|
||||
includeRootFolder: True
|
||||
archiveType: 'zip'
|
||||
tarCompression: 'none'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/plugin.video.jellycon-${{ py_version }}.zip'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish ${{ py_version }} artifact'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/plugin.video.jellycon'
|
||||
artifactName: 'plugin.video.jellycon-${{ py_version }}-$(Build.BuildNumber)'
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
parameters:
|
||||
python_version : []
|
||||
|
||||
steps:
|
||||
- ${{ each py_version in parameters.py_versions }}:
|
||||
- task: CopyFilesOverSSH@0
|
||||
displayName: 'Upload to repo server'
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
sourceFolder: '$(Build.ArtifactStagingDirectory)'
|
||||
contents: 'plugin.video.jellycon-${{ py_version }}.zip'
|
||||
targetFolder: '/srv/repository/incoming/kodi'
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Add to Kodi repo'
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: 'python3 /usr/local/bin/kodirepo add /srv/repository/incoming/kodi/plugin.video.jellycon-${{ py_version }}.zip --datadir /srv/repository/releases/client/kodi/${{ py_version }}'
|
||||
failOnStdErr: false
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Clean up zip files'
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: 'rm /srv/repository/incoming/kodi/plugin.video.jellycon-${{ py_version }}.zip'
|
||||
@@ -1,70 +0,0 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def indent(elem, level=0):
|
||||
'''
|
||||
Nicely formats output xml with newlines and spaces
|
||||
https://stackoverflow.com/a/33956544
|
||||
'''
|
||||
i = "\n" + level*" "
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = i + " "
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
for elem in elem:
|
||||
indent(elem, level+1)
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
else:
|
||||
if level and (not elem.tail or not elem.tail.strip()):
|
||||
elem.tail = i
|
||||
|
||||
|
||||
try:
|
||||
py_version = sys.argv[1]
|
||||
except IndexError:
|
||||
print('No version specified')
|
||||
sys.exit(1)
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# Load template file
|
||||
with open('{dir_path}/template.xml'.format(**locals()), 'r') as f:
|
||||
tree = ET.parse(f)
|
||||
root = tree.getroot()
|
||||
|
||||
# Load version dependencies
|
||||
with open('{dir_path}/{py_version}.yaml'.format(**locals()), 'r') as f:
|
||||
deps = yaml.safe_load(f)
|
||||
|
||||
# Load version and changelog
|
||||
with open('jellyfin-kodi/release.yaml', 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
# Populate xml template
|
||||
for dep in deps:
|
||||
ET.SubElement(root.find('requires'), 'import', attrib=dep)
|
||||
|
||||
# Update version string
|
||||
addon_version = data.get('version')
|
||||
root.attrib['version'] = '{addon_version}+{py_version}'.format(**locals())
|
||||
|
||||
# Changelog
|
||||
date = datetime.today().strftime('%Y-%m-%d')
|
||||
changelog = data.get('changelog')
|
||||
for section in root.findall('extension'):
|
||||
news = section.findall('news')
|
||||
if news:
|
||||
news[0].text = 'v{addon_version} ({date}):\n{changelog}'.format(**locals())
|
||||
|
||||
# Format xml tree
|
||||
indent(root)
|
||||
|
||||
# Write addon.xml
|
||||
tree.write('jellyfin-kodi/addon.xml', encoding='utf-8', xml_declaration=True)
|
||||
16
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- ci
|
||||
- github-actions
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- pip
|
||||
- dependencies
|
||||
22
.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
_extends: jellyfin/jellyfin-meta-plugins
|
||||
|
||||
name-template: "Release $RESOLVED_VERSION"
|
||||
tag-template: "v$RESOLVED_VERSION"
|
||||
version-template: "$MAJOR.$MINOR.$PATCH"
|
||||
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
default: patch
|
||||
|
||||
template: |
|
||||
## :sparkles: What's New
|
||||
|
||||
$CHANGES
|
||||
12
.github/releasing.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# Releasing a new Version via GitHub Actions
|
||||
|
||||
0. (optional) label the PRs you want to include in this release (if you want to group them in the GH release based on topics). \
|
||||
Supported labels can be found in the Release Drafter [config-file](https://github.com/jellyfin/jellyfin-meta-plugins/blob/master/.github/release-drafter.yml) (currently inherited from `jellyfin/jellyfin-meta-plugins`)
|
||||
1. ensure you have merged the PRs you want to include in the release and that the so far drafted GitHub release has captured them
|
||||
2. Create a `release-prep` PR by manually triggering the 'Create Prepare-Release PR' Workflow from the Actions tab on GitHub
|
||||
3. check the newly created `Prepare for release vx.y.z` PR if updated the `release.yaml` properly (update it manually if need be)
|
||||
4. merge the `Prepare for release vx.y.z` and let the Actions triggered by doing that finis (should just be a couple of seconds)
|
||||
5. FINALLY, trigger the `Publish JellyCon` manually from the Actions tab on GitHub.
|
||||
1. this will release the up to that point drafted GitHub Release and tag the default branch accordingly
|
||||
2. this will package and deploy `JellyCon` in the new version to the deployment server and trigger the 'kodirepo' script on it
|
||||
6. Done, assuming everything ran successfully, you have now successfully published a new version! :tada:
|
||||
87
.github/tools/reformat_changelog.py
vendored
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3.8
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
from typing import Dict, List, Pattern, Union, TypedDict
|
||||
|
||||
from emoji.core import emojize, demojize, replace_emoji
|
||||
|
||||
|
||||
ITEM_FORMAT = "+ {title} (#{issue}) @{username}"
|
||||
OUTPUT_EMOJI = False
|
||||
|
||||
ITEM_PATTERN: Pattern = re.compile(
|
||||
r"^\s*(?P<old_listchar>[-*+])\s*(?P<title>.*?)\s*\(#(?P<issue>[0-9]+)\)\s*@(?P<username>[^\s]*)$"
|
||||
)
|
||||
|
||||
|
||||
class SectionType(TypedDict):
|
||||
title: str
|
||||
items: List[Dict[str, str]]
|
||||
|
||||
|
||||
def reformat(item_format: str, output_emoji: bool) -> None:
|
||||
data = [
|
||||
emojize(x.strip(), use_aliases=True, variant="emoji_type")
|
||||
for x in sys.stdin.readlines()
|
||||
if x.strip()
|
||||
]
|
||||
|
||||
sections = []
|
||||
|
||||
section: Union[SectionType, Dict] = {}
|
||||
for line in data:
|
||||
if line.startswith("## "):
|
||||
pass
|
||||
if line.startswith("### "):
|
||||
if section:
|
||||
sections.append(section)
|
||||
_section: SectionType = {
|
||||
"title": line.strip("# "),
|
||||
"items": [],
|
||||
}
|
||||
section = _section
|
||||
|
||||
m = ITEM_PATTERN.match(line)
|
||||
if m:
|
||||
gd = m.groupdict()
|
||||
section["items"].append(gd)
|
||||
|
||||
sections.append(section)
|
||||
|
||||
first = True
|
||||
|
||||
for section in sections:
|
||||
if not section:
|
||||
continue
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
print()
|
||||
|
||||
title = section["title"]
|
||||
if not output_emoji:
|
||||
title = replace_emoji(title).strip()
|
||||
|
||||
print(title)
|
||||
print("-" * len(title))
|
||||
|
||||
for item in section["items"]:
|
||||
formatted_item = item_format.format(**item)
|
||||
if not output_emoji:
|
||||
formatted_item = demojize(formatted_item)
|
||||
print(formatted_item)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--format", type=str, default=ITEM_FORMAT)
|
||||
|
||||
parser.add_argument("--no-emoji", dest="emoji", action="store_false")
|
||||
parser.add_argument("--emoji", dest="emoji", action="store_true")
|
||||
parser.set_defaults(emoji=OUTPUT_EMOJI)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
reformat(args.format, args.emoji)
|
||||
39
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Build JellyCon
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
py_version: [ 'py2', 'py3' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install pyyaml
|
||||
|
||||
- name: Create ${{ matrix.py_version }} addon.xml
|
||||
run: python build.py --version ${{ matrix.py_version }}
|
||||
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
retention-days: 14
|
||||
name: ${{ matrix.py_version }}-build-artifact
|
||||
path: |
|
||||
*.zip
|
||||
41
.github/workflows/codeql.yaml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: CodeQL Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '38 8 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellycon' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
version: ['2.7', '3.9']
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.version }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
72
.github/workflows/create-prepare-release-pr.yaml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Create Prepare-Release PR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
create_pr:
|
||||
name: "Create Pump Version PR"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Update Draft
|
||||
uses: release-drafter/release-drafter@v5.20.0
|
||||
id: draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Setup YQ
|
||||
uses: chrisdickinson/setup-yq@latest
|
||||
with:
|
||||
yq-version: v4.9.1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Parse Changlog
|
||||
run: |
|
||||
pip install emoji
|
||||
cat << EOF >> cl.md
|
||||
${{ steps.draft.outputs.body }}
|
||||
EOF
|
||||
TAG="${{ steps.draft.outputs.tag_name }}"
|
||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||
echo "YAML_CHANGELOG<<EOF" >> $GITHUB_ENV
|
||||
cat cl.md | python .github/tools/reformat_changelog.py --no-emoji >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
|
||||
cat cl.md | python .github/tools/reformat_changelog.py --emoji --format='+ #{issue} by @{username}' >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
rm cl.md
|
||||
|
||||
- name: Update release.yaml
|
||||
run: |
|
||||
yq eval '.version = env(VERSION) | .changelog = strenv(YAML_CHANGELOG) | .changelog style="literal"' -i release.yaml
|
||||
|
||||
- name: Commit Changes
|
||||
run: |
|
||||
git config user.name "jellyfin-bot"
|
||||
git config user.email "team@jellyfin.org"
|
||||
|
||||
git checkout -b prepare-${{ env.VERSION }}
|
||||
git commit -am "bump version to ${{ env.VERSION }}"
|
||||
|
||||
if [[ -z "$(git ls-remote --heads origin prepare-${{ env.VERSION }})" ]]; then
|
||||
git push origin prepare-${{ env.VERSION }}
|
||||
else
|
||||
git push -f origin prepare-${{ env.VERSION }}
|
||||
fi
|
||||
|
||||
- name: Create or Update PR
|
||||
uses: k3rnels-actions/pr-update@v1
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
pr_title: Prepare for release ${{ steps.draft.outputs.tag_name }}
|
||||
pr_source: prepare-${{ env.VERSION }}
|
||||
pr_labels: 'release-prep,skip-changelog'
|
||||
pr_body: |
|
||||
:robot: This is a generated PR to bump the `release.yaml` version and update the changelog.
|
||||
|
||||
---
|
||||
|
||||
${{ env.CHANGELOG }}
|
||||
64
.github/workflows/publish.yaml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Publish JellyCon
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
py_version: [ 'py2', 'py3' ]
|
||||
steps:
|
||||
- name: Update Draft
|
||||
uses: release-drafter/release-drafter@v5.20.0
|
||||
if: ${{ matrix.py_version == 'py3' }}
|
||||
with:
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install pyyaml
|
||||
|
||||
- name: Create ${{ matrix.py_version }} addon.xml
|
||||
run: python build.py --version ${{ matrix.py_version }}
|
||||
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
retention-days: 14
|
||||
name: ${{ matrix.py_version }}-build-artifact
|
||||
path: |
|
||||
*.zip
|
||||
|
||||
- name: Upload to repo server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
switches: -vrptz
|
||||
path: '*.zip'
|
||||
remote_path: /srv/repository/incoming/kodi
|
||||
remote_host: ${{ secrets.DEPLOY_HOST }}
|
||||
remote_user: ${{ secrets.DEPLOY_USER }}
|
||||
remote_key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- name: Add to Kodi repo and clean up
|
||||
uses: appleboy/ssh-action@v0.1.4
|
||||
with:
|
||||
host: ${{ secrets.DEPLOY_HOST }}
|
||||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_KEY }}
|
||||
script_stop: true
|
||||
script: |
|
||||
python3 /usr/local/bin/kodirepo add /srv/repository/incoming/kodi/plugin.video.jellycon+${{ matrix.py_version }}.zip --datadir /srv/repository/releases/client/kodi/${{ matrix.py_version }};
|
||||
rm /srv/repository/incoming/kodi/plugin.video.jellycon+${{ matrix.py_version }}.zip;
|
||||
16
.github/workflows/release-drafter.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
name: Update release draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update Release Draft
|
||||
uses: release-drafter/release-drafter@v5.20.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
49
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Test JellyCon
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
PR_TRIGGERED: ${{ github.event_name == 'pull_request' && github.repository == 'jellyfin/jellycon' }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
py_version: ['2.7', '3.9']
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.py_version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.py_version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -r requirements-dev.txt
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --output-file=flake8.output
|
||||
cat flake8.output
|
||||
|
||||
- name: Publish Test Atrifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
retention-days: 14
|
||||
name: ${{ matrix.py_version }}-test-results
|
||||
path: |
|
||||
flake8.output
|
||||
78
build.py
@@ -1,28 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def indent(elem, level=0):
|
||||
'''
|
||||
def indent(elem: ET.Element, level: int = 0) -> None:
|
||||
"""
|
||||
Nicely formats output xml with newlines and spaces
|
||||
https://stackoverflow.com/a/33956544
|
||||
'''
|
||||
i = "\n" + level*" "
|
||||
"""
|
||||
i = "\n" + level * " "
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = i + " "
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
for elem in elem:
|
||||
indent(elem, level+1)
|
||||
indent(elem, level + 1)
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
else:
|
||||
@@ -30,10 +30,10 @@ def indent(elem, level=0):
|
||||
elem.tail = i
|
||||
|
||||
|
||||
def create_addon_xml(config, source, py_version):
|
||||
'''
|
||||
def create_addon_xml(config: dict, source: str, py_version: str) -> None:
|
||||
"""
|
||||
Create addon.xml from template file
|
||||
'''
|
||||
"""
|
||||
# Load template file
|
||||
with open('{}/.config/template.xml'.format(source), 'r') as f:
|
||||
tree = ET.parse(f)
|
||||
@@ -63,21 +63,54 @@ def create_addon_xml(config, source, py_version):
|
||||
tree.write('{}/addon.xml'.format(source), encoding='utf-8', xml_declaration=True)
|
||||
|
||||
|
||||
def zip_files(py_version, source, target):
|
||||
'''
|
||||
def zip_files(py_version: str, source: str, target: str, dev: bool) -> None:
|
||||
"""
|
||||
Create installable addon zip archive
|
||||
'''
|
||||
archive_name = 'plugin.video.jellyfin+{}.zip'.format(py_version)
|
||||
"""
|
||||
archive_name = 'plugin.video.jellycon+{}.zip'.format(py_version)
|
||||
|
||||
with zipfile.ZipFile('{}/{}'.format(target, archive_name), 'w') as z:
|
||||
for root, dirs, files in os.walk(args.source):
|
||||
for filename in files:
|
||||
if 'plugin.video.jellyfin' not in filename and 'pyo' not in filename:
|
||||
file_path = os.path.join(root, filename)
|
||||
relative_path = os.path.join('plugin.video.jellyfin', os.path.relpath(file_path, source))
|
||||
for filename in filter(file_filter, files):
|
||||
file_path = os.path.join(root, filename)
|
||||
if dev or folder_filter(file_path):
|
||||
relative_path = os.path.join('plugin.video.jellycon', os.path.relpath(file_path, source))
|
||||
z.write(file_path, relative_path)
|
||||
|
||||
|
||||
def file_filter(file_name: str) -> bool:
|
||||
"""
|
||||
True if file_name is meant to be included
|
||||
"""
|
||||
return (
|
||||
not (file_name.startswith('plugin.video.jellycon') and file_name.endswith('.zip'))
|
||||
and not file_name.endswith('.pyo')
|
||||
and not file_name.endswith('.pyc')
|
||||
and not file_name.endswith('.pyd')
|
||||
)
|
||||
|
||||
|
||||
def folder_filter(folder_name: str) -> bool:
|
||||
"""
|
||||
True if folder_name is meant to be included
|
||||
"""
|
||||
filters = [
|
||||
'.ci',
|
||||
'.git',
|
||||
'.github',
|
||||
'.config',
|
||||
'.mypy_cache',
|
||||
'.pytest_cache',
|
||||
'__pycache__',
|
||||
]
|
||||
for f in filters:
|
||||
if f in folder_name.split(os.path.sep):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Build flags:')
|
||||
parser.add_argument(
|
||||
@@ -96,13 +129,16 @@ if __name__ == '__main__':
|
||||
type=Path,
|
||||
default=Path(__file__).absolute().parent)
|
||||
|
||||
parser.add_argument('--dev', dest='dev', action='store_true')
|
||||
parser.set_defaults(dev=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load config file
|
||||
config_path = os.path.join(args.source, 'release.yaml')
|
||||
with open(config_path, 'r') as fh:
|
||||
config = yaml.safe_load(fh)
|
||||
release_config = yaml.safe_load(fh)
|
||||
|
||||
create_addon_xml(config, args.source, args.version)
|
||||
create_addon_xml(release_config, args.source, args.version)
|
||||
|
||||
zip_files(args.version, args.source, args.target)
|
||||
zip_files(args.version, args.source, args.target, args.dev)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import xbmcaddon
|
||||
|
||||
from resources.lib.loghandler import LazyLogger
|
||||
from resources.lib.lazylogger import LazyLogger
|
||||
from resources.lib.functions import main_entry_point
|
||||
from resources.lib.tracking import set_timing_enabled
|
||||
|
||||
@@ -16,6 +16,3 @@ if log_timing_data:
|
||||
log.debug("About to enter mainEntryPoint()")
|
||||
|
||||
main_entry_point()
|
||||
|
||||
# clear done and exit.
|
||||
# sys.modules.clear()
|
||||
|
||||
BIN
fanart.jpg
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 32 KiB |
BIN
icon.png
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
BIN
kodi.png
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 15 KiB |
61
release.yaml
@@ -1,7 +1,60 @@
|
||||
version: '0.4.3'
|
||||
changelog: |
|
||||
- #62 - Change unicode to str
|
||||
- #63 - Fix displaying public user list
|
||||
version: '0.5.0'
|
||||
changelog: |-
|
||||
New features and improvements
|
||||
-----------------------------
|
||||
+ Make sure manual login shows when connecting to a 10.7 server (#167) @mcarlton00
|
||||
+ Add genres and alpha picker to music (#161) @mcarlton00
|
||||
+ Add button to the bitrate selector (#160) @mcarlton00
|
||||
+ Add quick connect authentication (#159) @mcarlton00
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
+ Fix playing all files (#162) @mcarlton00
|
||||
+ Ensure server is present in API requests (#157) @mcarlton00
|
||||
+ Fix errors when stopping transcoded playback (#156) @mcarlton00
|
||||
+ Fix content update checks (#155) @mcarlton00
|
||||
+ Fix stale data after storage migration (#153) @mcarlton00
|
||||
+ Fix legacy skin shortcuts (#150) @mcarlton00
|
||||
+ Use timezone when calculating last activity date (#147) @mcarlton00
|
||||
+ Fix live tv playback (#144) @mcarlton00
|
||||
+ Fix browsing the Live TV programs menu (#145) @mcarlton00
|
||||
+ Force login if no saved credentials (#139) @mcarlton00
|
||||
+ Fix movie recommendations (#132) @mcarlton00
|
||||
+ Fix remote control (#134) @mcarlton00
|
||||
+ Don't throw errors when non-JellyCon content is playing (#135) @mcarlton00
|
||||
+ Fallback to empty lists instead of failing (#136) @mcarlton00
|
||||
+ Make widgets respect episode name format setting (#137) @mcarlton00
|
||||
+ Make requests respect the verify certificate setting (#131) @mcarlton00
|
||||
+ Fix manual user login (#123) @mcarlton00
|
||||
+ Rework user data storage (#119) @mcarlton00
|
||||
|
||||
Code or Repo Maintenance
|
||||
------------------------
|
||||
+ Use offscreen option when generating all listitems (#168) @mcarlton00
|
||||
+ Remove safe delete code (#166) @mcarlton00
|
||||
+ Address flake8 warnings (#165) @mcarlton00
|
||||
+ Simplify url param dict definitions (#164) @mcarlton00
|
||||
+ Optimize loops while building menus (#163) @mcarlton00
|
||||
+ Verify certificates by default (#154) @mcarlton00
|
||||
+ Rework live tv playback (#148) @mcarlton00
|
||||
+ Remove trakt integration (#133) @mcarlton00
|
||||
+ Copy translate_path from Jf4Kodi, fix LazyLogger (#130) @oddstr13
|
||||
+ Rework the network stack (#124) @mcarlton00
|
||||
+ Rework user data storage (#119) @mcarlton00
|
||||
+ Code reorganizing (#111) @mcarlton00
|
||||
+ Remove unused and commented out code (#109) @mcarlton00
|
||||
|
||||
CI & build changes
|
||||
------------------
|
||||
+ Bump release-drafter/release-drafter from 5.19.0 to 5.20.0 (#152) @dependabot
|
||||
+ Bump github/codeql-action from 1 to 2 (#143) @dependabot
|
||||
+ Bump actions/upload-artifact from 2 to 3 (#141) @dependabot
|
||||
+ Bump release-drafter/release-drafter from 5.18.1 to 5.19.0 (#138) @dependabot
|
||||
+ Bump actions/checkout from 2 to 3 (#127) @dependabot
|
||||
+ Bump actions/setup-python from 2 to 3 (#126) @dependabot
|
||||
+ Bump release-drafter/release-drafter from 5.17.5 to 5.18.1 (#122) @dependabot
|
||||
+ Bump release-drafter/release-drafter from 5.15.0 to 5.17.5 (#118) @dependabot
|
||||
+ Bump burnett01/rsync-deployments from 5.1 to 5.2 (#113) @dependabot
|
||||
dependencies:
|
||||
py2:
|
||||
- addon: 'xbmc.python'
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
pyyaml
|
||||
setuptools >= 44.1.1 # Old setuptools causes script.module.addon.signals to fail installing
|
||||
six >= 1.13
|
||||
python-dateutil >= 2.8.1
|
||||
requests >= 2.22
|
||||
futures >= 2.2; python_version < '3.0'
|
||||
|
||||
Kodistubs ~= 18.0; python_version < '3.0'
|
||||
Kodistubs ~= 19.0; python_version >= '3.6'
|
||||
|
||||
git+https://github.com/romanvm/kodi.six
|
||||
git+https://github.com/ruuk/script.module.addon.signals
|
||||
|
||||
flake8 >= 3.8
|
||||
flake8-import-order >= 0.18
|
||||
websocket-client >= 0.57.0
|
||||
|
||||
747
resources/language/resource.language.ar/strings.po
Normal file
@@ -0,0 +1,747 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-06-05 14:22+0000\n"
|
||||
"Last-Translator: egymoh <egymoh2@hotmail.com>\n"
|
||||
"Language-Team: Arabic <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/ar/>\n"
|
||||
"Language: ar\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
|
||||
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "البرامج"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "القنوات"
|
||||
|
||||
msgctxt "#30359"
|
||||
msgid "Building full image list"
|
||||
msgstr "بناء قائمة كاملة بالصور"
|
||||
|
||||
msgctxt "#30358"
|
||||
msgid "Retreiving remote image list"
|
||||
msgstr "استعادة قائمة الصور البعيدة"
|
||||
|
||||
msgctxt "#30357"
|
||||
msgid "Processing existing image list"
|
||||
msgstr "معالجة قائمة الصور الموجودة"
|
||||
|
||||
msgctxt "#30356"
|
||||
msgid "Loading existing image list"
|
||||
msgstr "تحميل قائمة الصور الموجودة"
|
||||
|
||||
msgctxt "#30355"
|
||||
msgid "Kodi Settings->Services->Allow remote control via HTTP"
|
||||
msgstr "إعدادات Kodi-> الخدمات-> السماح بالتحكم عن بعد عبر HTTP"
|
||||
|
||||
msgctxt "#30354"
|
||||
msgid "Go To Series"
|
||||
msgstr "انتقل إلى السلسلة"
|
||||
|
||||
msgctxt "#30321"
|
||||
msgid " - Album Artists"
|
||||
msgstr "فنانو الألبوم"
|
||||
|
||||
msgctxt "#30319"
|
||||
msgid "Music - All Album Artists"
|
||||
msgstr "كل فناني الألبوم"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30351"
|
||||
msgid "Music - Recently Played"
|
||||
msgstr "لعبت مؤخرا"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "أضيف مؤخرا"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30349"
|
||||
msgid " - Recently Played"
|
||||
msgstr "لعبت مؤخرا"
|
||||
|
||||
msgctxt "#30348"
|
||||
msgid "Add user ratings"
|
||||
msgstr "إضافة تقييمات المستخدم"
|
||||
|
||||
msgctxt "#30347"
|
||||
msgid "Getting Existing Images"
|
||||
msgstr "الحصول على الصور الموجودة"
|
||||
|
||||
msgctxt "#30346"
|
||||
msgid "Deleteing Cached Images"
|
||||
msgstr "حذف الصور التخزين المؤقتة"
|
||||
|
||||
msgctxt "#30345"
|
||||
msgid "Cache Jellyfin server data requests"
|
||||
msgstr "ذاكرة التخزين المؤقت لطلبات بيانات خادم Jellyfin"
|
||||
|
||||
msgctxt "#30344"
|
||||
msgid "Number of images removed from cache"
|
||||
msgstr "عدد الصور التي تمت إزالتها من ذاكرة التخزين المؤقت"
|
||||
|
||||
msgctxt "#30343"
|
||||
msgid "Changes Require Kodi Restart"
|
||||
msgstr "تتطلب التغييرات إعادة تشغيل Kodi"
|
||||
|
||||
msgctxt "#30342"
|
||||
msgid "New content check interval (0 = disabled)"
|
||||
msgstr "فاصل زمني لفحص المحتوى الجديد (0 = معطل)"
|
||||
|
||||
msgctxt "#30341"
|
||||
msgid "Background image update interval (0 = disabled)"
|
||||
msgstr "الفاصل الزمني لتحديث صورة الخلفية (0 = معطل)"
|
||||
|
||||
msgctxt "#30340"
|
||||
msgid "Group movies into collections"
|
||||
msgstr "تجميع الأفلام في مجموعات"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "شخص"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "البوم"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "أغنية"
|
||||
|
||||
msgctxt "#30334"
|
||||
msgid "Use JellyCon context menu"
|
||||
msgstr "استخدام قائمة سياق JellyCon"
|
||||
|
||||
msgctxt "#30332"
|
||||
msgid "Stop media playback on screensaver activation"
|
||||
msgstr "إيقاف تشغيل الوسائط عند تنشيط شاشة التوقف"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "أفلام لكل صفحة"
|
||||
|
||||
msgctxt "#30330"
|
||||
msgid "Show change user dialog"
|
||||
msgstr "إظهار مربع حوار تغيير المستخدم"
|
||||
|
||||
msgctxt "#30329"
|
||||
msgid "Screensaver"
|
||||
msgstr "حافظة الشاشة"
|
||||
|
||||
msgctxt "#30328"
|
||||
msgid "Show empty folders (shows, seasons, collections)"
|
||||
msgstr "إظهار المجلدات الفارغة (المسلسلات، المواسم ، المجموعات)"
|
||||
|
||||
msgctxt "#30327"
|
||||
msgid "Go To Season"
|
||||
msgstr "اذهب إلى الموسم"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "الأنواع"
|
||||
|
||||
msgctxt "#30322"
|
||||
msgid "Auto resume"
|
||||
msgstr "استئناف تلقائي"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "ألبومات"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "موسيقى - ألبومات"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "تشغيل الكل"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "خطأ في الإتصال"
|
||||
|
||||
msgctxt "#30315"
|
||||
msgid "Suppress notifications for connection errors"
|
||||
msgstr "قم بإيقاف الأشعارات الخاصة بأخطاء الاتصال"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "تشغيل"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "قائمة"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "الكل -"
|
||||
|
||||
msgctxt "#30311"
|
||||
msgid "Library - "
|
||||
msgstr "مكتبة -"
|
||||
|
||||
msgctxt "#30310"
|
||||
msgid "Enable Jellyfin remote control"
|
||||
msgstr "تفعيل جهاز التحكم عن بعد Jellyfin"
|
||||
|
||||
msgctxt "#30309"
|
||||
msgid "Select Media Source"
|
||||
msgstr "حدد مصدر الوسائط"
|
||||
|
||||
msgctxt "#30308"
|
||||
msgid "Select Trailer"
|
||||
msgstr "حدد المقطع الدعائي"
|
||||
|
||||
msgctxt "#30307"
|
||||
msgid "Play Trailer"
|
||||
msgstr "تشغيل المقطع الدعائي"
|
||||
|
||||
msgctxt "#30306"
|
||||
msgid "Playback starting"
|
||||
msgstr "بدء التشغيل"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "لم يتم العثور عليه"
|
||||
|
||||
msgctxt "#30304"
|
||||
msgid "Cached Jellyfin images : "
|
||||
msgstr "صور Jellyfin المخزنة مؤقتًا:"
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "Missing Jellyfin images : "
|
||||
msgstr "صور Jellyfin المفقودة:"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Existing images : "
|
||||
msgstr "الصور الموجودة:"
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Caching Images"
|
||||
msgstr "صور التخزين المؤقت"
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "Cache all Jellyfin images as local Kodi images?"
|
||||
msgstr "تخزين جميع صور Jellyfin مؤقتًا كصور Kodi محلية؟"
|
||||
|
||||
msgctxt "#30299"
|
||||
msgid "Cache Images"
|
||||
msgstr "صور مخبأة"
|
||||
|
||||
msgctxt "#30298"
|
||||
msgid "Deleting Kodi Images"
|
||||
msgstr "حذف صور Kodi"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "هل تريد حذف الصور غير المستخدمة؟"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
msgctxt "#30295"
|
||||
msgid "To use this feature you need HTTP control enabled"
|
||||
msgstr "لاستخدام هذه الميزة تحتاج إلى تمكين تحكم HTTP"
|
||||
|
||||
msgctxt "#30294"
|
||||
msgid "Notice"
|
||||
msgstr "ملاحظة"
|
||||
|
||||
msgctxt "#30293"
|
||||
msgid "Cache images"
|
||||
msgstr "صور ذاكرة التخزين المؤقت"
|
||||
|
||||
msgctxt "#30292"
|
||||
msgid "Select Subtitle Stream"
|
||||
msgstr "حدد الترجمة"
|
||||
|
||||
msgctxt "#30291"
|
||||
msgid "Select Audio Stream"
|
||||
msgstr "حدد الصوت"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "الكل"
|
||||
|
||||
msgctxt "#30289"
|
||||
msgid "TV Shows - Genres"
|
||||
msgstr "مسلسلات - الأنواع"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "الأحدث"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "مسلسلات - الأحدث"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "أفلام - لم تتم مشاهدتها"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "لم تتم مشاهدتها"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "تشغيل الحلقة التالية؟"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "لم يتم اكتشاف خوادم Jellyfin على شبكتك المحلية."
|
||||
|
||||
msgctxt "#30281"
|
||||
msgid "Refresh Cached Images"
|
||||
msgstr "قم بتحديث الصور المخزنة مؤقتًا"
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "عنوان مفقود"
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "مسلسلات - لم تتم مشاهدته"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "القادم"
|
||||
|
||||
msgctxt "#30277"
|
||||
msgid "JellyCon needs to prompt for resume on partily played items, Kodi can also prompt, this can cause a double prompt. Do you want to remove the double prompt?"
|
||||
msgstr ""
|
||||
"يحتاج JellyCon إلى المطالبة بالاستئناف على العناصر التي يتم تشغيلها جزئيًا ، "
|
||||
"ويمكن لـ Kodi أيضًا المطالبة ، وقد يتسبب ذلك في مطالبة مزدوجة. هل تريد إزالة "
|
||||
"المطالبة المزدوجة؟"
|
||||
|
||||
msgctxt "#30276"
|
||||
msgid "Extra Resume Prompt Detected"
|
||||
msgstr "تم اكتشاف استئناف إضافي"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "تحويل اجباري"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
msgctxt "#30273"
|
||||
msgid "Unset Favourite"
|
||||
msgstr "عدم تحديد المفضلة"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "تعيين المفضلة"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "حدد لم تتم المشاهدة"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "حدد تمت المشاهدة"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "أفلام - عشوائية"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "أضيف مؤخرا"
|
||||
|
||||
msgctxt "#30267"
|
||||
msgid " - In Progress"
|
||||
msgstr "في تقدم"
|
||||
|
||||
msgctxt "#30266"
|
||||
msgid "Movies - Pages"
|
||||
msgstr "أفلام - صفحات"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "الحلقات - التالي"
|
||||
|
||||
msgctxt "#30264"
|
||||
msgid "Episodes - In Progress"
|
||||
msgstr "الحلقات - قيد التقدم"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "الحلقات - المضافة حديثًا"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "مسلسلات - المفضلة"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "مسلسلات"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "أفلام - المفضلة"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "أفلام - قيد التحميل"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "أفلام - أضيفت مؤخرًا"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "أفلام"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "مسلسلات - من الألف إلى الياء"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "إظهار إعدادات الوظائف الإضافية"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "أفلام - من الألف إلى الياء"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "أفلام - الأنواع"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "غير معروف"
|
||||
|
||||
msgctxt "#30247"
|
||||
msgid "Custom Widget Content"
|
||||
msgstr "محتوى القطعة المخصص"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "بحث"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "فرض تحويل mpeg4"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "فرض تحويل الشفرة msmpeg4v3 (divx)"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "فرض تحويل mpeg2"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "خيارات التشغيل"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "ابدأ من البداية"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "فرض تحويل الشفرة H265 (HEVC)"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "حلقات"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "أفلام"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "مسلسلات"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "تفاعل"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "حجم الصفحة والتصفية"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "تخطيط العنصر"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "مطالبة بحذف الفيلم بعد٪"
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "الموافقه قبل التشغيل"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "تشغيل الحلقة التالية بعد٪"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "مطالبة بحذف الحلقة بعد٪"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "تفاصيل العنصر"
|
||||
|
||||
msgctxt "#30215"
|
||||
msgid "On playback stop (100% = disabled)"
|
||||
msgstr "توقف التشغيل (100٪ = معطل)"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "أحداث"
|
||||
|
||||
msgctxt "#30213"
|
||||
msgid "Video force 8 bit"
|
||||
msgstr "قوة الفيديو 8 بت"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "أقصى عرض للفيديو"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "خيارات التحويل"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "مسار الملف المباشر"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "تشغيل"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "نوع التشغيل"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "غير قادر على الإتصال بالسيرفر"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "خطأ في الرابط"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "اشمل الاشخاص"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "تضمين معلومات الوسائط"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "تضمين المقدمة"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "اختر المستخدم"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "العنوان:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "عنوان الخادم المحدد"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "حدد الخادم"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "أضف (cc) إذا كان العنوان الفرعي متاحًا"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "لم يتم تعيين نوع الوسائط"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "خطأ"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "عنصر المعالجة:"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "انتهئ"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "عند الاستئناف"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "إظهار تقدم التحميل"
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "إضافة نسبة الاستئناف إلى الأسماء"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "الرجوع الى الوراء بثواني"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "استرجاع البيانات"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "تحميل المحتوى"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "خدمات"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "واجهه المستخدم"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "تحذير: سيؤدي هذا الإجراء إلى حذف ملفات الوسائط من الخادم."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "تأكيد الحذف؟"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "غير متاح"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "في انتظار حذف الخادم"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "حذف"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "اسم المستخدم لم يتم العثور عليه"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "اسم المستخدم / كلمة المرور غير صحيحة"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "تفعيل تسجيل التصحيح"
|
||||
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "حدد الإجراء القطعة"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "كلمة المرور:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "اسم المستخدم:"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "إخفاء تفاصيل الحلقة التي لم تتم مشاهدتها"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "متقدم"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "إظهار جميع الحلقات"
|
||||
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "تسطيح موسم واحد"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "تنسيق اسم الحلقة"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "عدد العناصر المراد إظهارها في القوائم المصفاة"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "إظهار العملاء المتصلين"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "عرض اسم الجهاز"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "تسجيل بيانات التوقيت"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "جيليفن"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[تغيير المستخدم]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[كشف الخادم المحلي]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "عدد ملفات تعريف الأداء المطلوب التقاطها"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "كلمة المرور (سامبا)"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "اسم المستخدم (سامبا)"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "كلمة المرور"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "تحقق من شهادة HTTPS"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "شبكة"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "أستضافة"
|
||||
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "الحد الأقصى لمعدل نقل البيانات (Kbps)"
|
||||
60
resources/language/resource.language.cs/strings.po
Normal file
@@ -0,0 +1,60 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-06-09 21:22+0000\n"
|
||||
"Last-Translator: DJSweder <djsweder@gmail.com>\n"
|
||||
"Language-Team: Czech <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/cs/>\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Zobrazované jméno zařízení"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Zaznamenat časové údaje"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Změnit uživatele]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Zjistit místní server]"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Samba heslo"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Samba uživatelské jméno"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Uživatelské jméno"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Ověř HTTPS certifikát"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Hostitel"
|
||||
418
resources/language/resource.language.cy/strings.po
Normal file
@@ -0,0 +1,418 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-05-08 11:13+0000\n"
|
||||
"Last-Translator: Rhodri <rhodrilld@gmail.com>\n"
|
||||
"Language-Team: Welsh <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/cy/>\n"
|
||||
"Language: cy\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=(n==0) ? 0 : (n==1) ? 1 : (n==2) ? 2 : "
|
||||
"(n==3) ? 3 :(n==6) ? 4 : 5;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Cyfrinair"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Enw defnyddiwr"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Gwiriwch dystysgrif HTTPS"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Gwesteiwr"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Dangos bob pennod"
|
||||
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "Gwastadu tymor sengl"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "Fformat enw pennod wedi'i hidlo"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Nifer yr eitemau i'w dangos mewn rhestrau wedi'u hidlo"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Dangos cleients cysylltiedig"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Enw arddangos dyfais"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Logio data amseru"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Newid defnyddiwr]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Canfod gweinydd lleol]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Nifer y proffiliau perfformiad i'w gynhyrchu"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Cyfrinair Samba"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Enw defnyddiwr Samba"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Cyfeiriad:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Cyfeiriad Gweinydd a ddewiswyd"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Dewis Gweinydd"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Gwall"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Prosesi'r eitem:"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Wedi gorffen"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "Ar barhad"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Adalw Data"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Llwytho Cynnwys"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Gwasanaethau"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr ""
|
||||
"Rhybudd: Bydd y weithred hon yn dileu'r ffeiliau cyfryngau o'r gweinydd."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Cadarnhau dileu?"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "Aros i'r gweinydd i'w ddileu"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Wrthi'n dileu"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Enw defnyddiwr ddim yn bodoli"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Enw defnyddiwr/Cyfrinair anghywir"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Cyfrinair:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Enw defnyddiwr:"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Uwchraddol"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Gwybodaeth Eitem"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "gwall URL"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Dewis Defnyddiwr"
|
||||
|
||||
msgctxt "#30429"
|
||||
msgid "Genre"
|
||||
msgstr "Genre"
|
||||
|
||||
msgctxt "#30430"
|
||||
msgid "Label"
|
||||
msgstr "Label"
|
||||
|
||||
msgctxt "#30426"
|
||||
msgid "Title"
|
||||
msgstr "Teitl"
|
||||
|
||||
msgctxt "#30425"
|
||||
msgid "Year"
|
||||
msgstr "Blwyddyn"
|
||||
|
||||
msgctxt "#30401"
|
||||
msgid "Info"
|
||||
msgstr "Gwybodaeth"
|
||||
|
||||
msgctxt "#30399"
|
||||
msgid "Hide"
|
||||
msgstr "Cuddio"
|
||||
|
||||
msgctxt "#30392"
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgctxt "#30391"
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgctxt "#30380"
|
||||
msgid "Never"
|
||||
msgstr "Byth"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Person"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Albwm"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Cân"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Chwarae"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Dewislen"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Dileu"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Dileu"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Ffilmiau"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Anhysbys"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Chwilio"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Pennodau"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Ffilmiau"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Chwarae'r bennod nesaf ar ôl %"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Digwyddiadau"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Opsiynau trawsgodio"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Methu cysylltu â'r gweinydd"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Cynnwys pobl"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Cynnwys plot"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Ychwanegu (cc) os oes is-deitlau ar gael"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "Neidio yn ôl eiliadau"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Rhyngwyneb"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Actifadu logio dadfygio"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Cuddio manylion penodau heb eu gwylio"
|
||||
|
||||
msgctxt "#30381"
|
||||
msgid "More than one"
|
||||
msgstr "Mwy na un"
|
||||
|
||||
msgctxt "#30362"
|
||||
msgid " - Recordings"
|
||||
msgstr "- Recordiadau"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "- Rhaglenni"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "- Sianeli"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "- Genres"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "Gwall cysylltiad"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "Pobeth -"
|
||||
|
||||
msgctxt "#30311"
|
||||
msgid "Library - "
|
||||
msgstr "Llyfrgell -"
|
||||
|
||||
msgctxt "#30294"
|
||||
msgid "Notice"
|
||||
msgstr "Rhybydd"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Pobeth"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- Diweddaraf"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- Heb ei wylio"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Chwarae'r bennod nesaf?"
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "Teitl ar goll"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- Nesaf"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Ffilmiau - Heb ei wylio"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Ffilmiau - A-Z"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Ffilmiau - Genres"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Ffilmiau - Ffefrynnau"
|
||||
|
||||
msgctxt "#30415"
|
||||
msgid " - Favorite Collections"
|
||||
msgstr "- Hoff Gasgliadau"
|
||||
|
||||
msgctxt "#30414"
|
||||
msgid " - Favorites"
|
||||
msgstr "- Ffefrynnau"
|
||||
|
||||
msgctxt "#30289"
|
||||
msgid "TV Shows - Genres"
|
||||
msgstr "Rhaglenni teledu - Genres"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "Rhaglenni teledu - Diweddaraf"
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "Rhaglenni teledu - Heb ei wylio"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "Rhaglenni teledu - Ffefrynnau"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "Rhaglenni teledu"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "Rhaglenni teledu - A-Z"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "Rhaglenni teledu"
|
||||
1107
resources/language/resource.language.de/strings.po
Normal file
2
resources/language/resource.language.el/strings.po
Normal file
@@ -0,0 +1,2 @@
|
||||
msgid ""
|
||||
msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit"
|
||||
2
resources/language/resource.language.enm/strings.po
Normal file
@@ -0,0 +1,2 @@
|
||||
msgid ""
|
||||
msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit"
|
||||
1099
resources/language/resource.language.eo/strings.po
Normal file
1091
resources/language/resource.language.es/strings.po
Normal file
763
resources/language/resource.language.et/strings.po
Normal file
@@ -0,0 +1,763 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-01-03 18:05+0000\n"
|
||||
"Last-Translator: rimasx <riks_12@hot.ee>\n"
|
||||
"Language-Team: Estonian <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/et/>\n"
|
||||
"Language: et\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.5.2\n"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Kuva kõik episoodid"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Filtreeritud loendites kuvatavate üksuste arv"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Kuva ühendatud kliendid"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Seadme kuvatav nimi"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Logi ajakirjed"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Muuda kasutajat]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Tuvasta kohalik server]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Jäädvustavate jõudlusprofiilide arv"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Samba parool"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Samba kasutajanimi"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Parool"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Kasutajanimi"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Kinnita HTTPS sertifikaat"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30366"
|
||||
msgid "Manually enter user details"
|
||||
msgstr "Sisesta kasutaja andmed käsitsi"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "Transkoodi jõuga mpeg4"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "Transkoodi jõuga msmpeg4v3 (divx)"
|
||||
|
||||
msgctxt "#30365"
|
||||
msgid "Manual Login"
|
||||
msgstr "Käsitsi sisselogimine"
|
||||
|
||||
msgctxt "#30364"
|
||||
msgid "Do you want to save the password?"
|
||||
msgstr "Kas soovid parooli salvestada?"
|
||||
|
||||
msgctxt "#30363"
|
||||
msgid "Save Password?"
|
||||
msgstr "Kas salvestada parool?"
|
||||
|
||||
msgctxt "#30362"
|
||||
msgid " - Recordings"
|
||||
msgstr "- salvestised"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "- saated"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "- kanalid"
|
||||
|
||||
msgctxt "#30359"
|
||||
msgid "Building full image list"
|
||||
msgstr "Täieliku piltide loendi koostamine"
|
||||
|
||||
msgctxt "#30358"
|
||||
msgid "Retreiving remote image list"
|
||||
msgstr "Kaugpiltide loendi vastuvõtt"
|
||||
|
||||
msgctxt "#30357"
|
||||
msgid "Processing existing image list"
|
||||
msgstr "Olemasoleva piltide loendi töötlemine"
|
||||
|
||||
msgctxt "#30356"
|
||||
msgid "Loading existing image list"
|
||||
msgstr "Olemasoleva piltide loendi laadimine"
|
||||
|
||||
msgctxt "#30355"
|
||||
msgid "Kodi Settings->Services->Allow remote control via HTTP"
|
||||
msgstr "Kodi seaded-> Teenused-> Luba kaugjuhtimine HTTP kaudu"
|
||||
|
||||
msgctxt "#30354"
|
||||
msgid "Go To Series"
|
||||
msgstr "Ava seriaal"
|
||||
|
||||
msgctxt "#30353"
|
||||
msgid " - Frequently Played"
|
||||
msgstr "- sageli esitatud"
|
||||
|
||||
msgctxt "#30321"
|
||||
msgid " - Album Artists"
|
||||
msgstr "– albumi esitajad"
|
||||
|
||||
msgctxt "#30319"
|
||||
msgid "Music - All Album Artists"
|
||||
msgstr "Muusika – kõik albumi esitajad"
|
||||
|
||||
msgctxt "#30352"
|
||||
msgid "Music - Frequently Played"
|
||||
msgstr "Muusika – sageli esitatud"
|
||||
|
||||
msgctxt "#30327"
|
||||
msgid "Go To Season"
|
||||
msgstr "Ava hooaeg"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "Faili otserada"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "Episoodid – viimati lisatud"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Filmid – viimati lisatud"
|
||||
|
||||
msgctxt "#30349"
|
||||
msgid " - Recently Played"
|
||||
msgstr "- viimati esitatud"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- viimati lisatud"
|
||||
|
||||
msgctxt "#30351"
|
||||
msgid "Music - Recently Played"
|
||||
msgstr "Muusika – viimati esitatud"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "Muusika – viimati lisatud"
|
||||
|
||||
msgctxt "#30348"
|
||||
msgid "Add user ratings"
|
||||
msgstr "Lisa kasutajahinded"
|
||||
|
||||
msgctxt "#30347"
|
||||
msgid "Getting Existing Images"
|
||||
msgstr "Olemasolevate piltide hankimine"
|
||||
|
||||
msgctxt "#30346"
|
||||
msgid "Deleteing Cached Images"
|
||||
msgstr "Vahemällu salvestatud piltide kustutamine"
|
||||
|
||||
msgctxt "#30345"
|
||||
msgid "Cache Jellyfin server data requests"
|
||||
msgstr "Salvesta Jellyfin serveri andmepäringud vahemällu"
|
||||
|
||||
msgctxt "#30344"
|
||||
msgid "Number of images removed from cache"
|
||||
msgstr "Vahemälust eemaldatud piltide arv"
|
||||
|
||||
msgctxt "#30343"
|
||||
msgid "Changes Require Kodi Restart"
|
||||
msgstr "Muudatused nõuavad Kodi taaskäivitamist"
|
||||
|
||||
msgctxt "#30342"
|
||||
msgid "New content check interval (0 = disabled)"
|
||||
msgstr "Uus sisu kontrollimise intervall (0 = keelatud)"
|
||||
|
||||
msgctxt "#30341"
|
||||
msgid "Background image update interval (0 = disabled)"
|
||||
msgstr "Taustapildi värskendamise intervall (0 = keelatud)"
|
||||
|
||||
msgctxt "#30340"
|
||||
msgid "Group movies into collections"
|
||||
msgstr "Rühmita filmid kogumikesse"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Isik"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Album"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Lugu"
|
||||
|
||||
msgctxt "#30334"
|
||||
msgid "Use JellyCon context menu"
|
||||
msgstr "Kasuta JellyCon kontekstimenüüd"
|
||||
|
||||
msgctxt "#30333"
|
||||
msgid "Cache artwork in the background"
|
||||
msgstr "Salvesta pildid vahemällu taustal"
|
||||
|
||||
msgctxt "#30332"
|
||||
msgid "Stop media playback on screensaver activation"
|
||||
msgstr "Peata meedia taasesitus ekraanisäästja aktiveerimisel"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "Filme lehel"
|
||||
|
||||
msgctxt "#30330"
|
||||
msgid "Show change user dialog"
|
||||
msgstr "Kuva kasutaja muutmise dialoog"
|
||||
|
||||
msgctxt "#30329"
|
||||
msgid "Screensaver"
|
||||
msgstr "Ekraanisäästja"
|
||||
|
||||
msgctxt "#30328"
|
||||
msgid "Show empty folders (shows, seasons, collections)"
|
||||
msgstr "Kuva tühjad kaustad (saated, hooajad, kogumikud)"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "- žanrid"
|
||||
|
||||
msgctxt "#30322"
|
||||
msgid "Auto resume"
|
||||
msgstr "Automaatne jätkamine"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "- albumid"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "Muusika – albumid"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "Esita kõik"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "Ühenduse viga"
|
||||
|
||||
msgctxt "#30315"
|
||||
msgid "Suppress notifications for connection errors"
|
||||
msgstr "Lülita ühenduse veateavitused välja"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Esita"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Menüü"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "Kõik -"
|
||||
|
||||
msgctxt "#30311"
|
||||
msgid "Library - "
|
||||
msgstr "Meediakogu -"
|
||||
|
||||
msgctxt "#30310"
|
||||
msgid "Enable Jellyfin remote control"
|
||||
msgstr "Luba Jellyfini kaugjuhtimine"
|
||||
|
||||
msgctxt "#30309"
|
||||
msgid "Select Media Source"
|
||||
msgstr "Vali meedia allikas"
|
||||
|
||||
msgctxt "#30308"
|
||||
msgid "Select Trailer"
|
||||
msgstr "Vali treiler"
|
||||
|
||||
msgctxt "#30307"
|
||||
msgid "Play Trailer"
|
||||
msgstr "Esita treiler"
|
||||
|
||||
msgctxt "#30306"
|
||||
msgid "Playback starting"
|
||||
msgstr "Taasesitus algab"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "Ei leitud"
|
||||
|
||||
msgctxt "#30304"
|
||||
msgid "Cached Jellyfin images : "
|
||||
msgstr "Vahemällu salvestatud Jellyfini pildid:"
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "Missing Jellyfin images : "
|
||||
msgstr "Puuduvad Jellyfini pildid:"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Existing images : "
|
||||
msgstr "Olemasolevad pildid:"
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Caching Images"
|
||||
msgstr "Piltide vahemällu salvestamine"
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "Cache all Jellyfin images as local Kodi images?"
|
||||
msgstr "Kas salvestada kõik Jellyfini pildid vahemällu kohalike Kodi piltidena?"
|
||||
|
||||
msgctxt "#30299"
|
||||
msgid "Cache Images"
|
||||
msgstr "Pildid vahemällu"
|
||||
|
||||
msgctxt "#30298"
|
||||
msgid "Deleting Kodi Images"
|
||||
msgstr "Kodi piltide kustutamine"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "Kas kustutada kasutamata pildid?"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Kustuta"
|
||||
|
||||
msgctxt "#30295"
|
||||
msgid "To use this feature you need HTTP control enabled"
|
||||
msgstr "Selle funktsiooni kasutamiseks peab HTTP juhtimine olema lubatud"
|
||||
|
||||
msgctxt "#30294"
|
||||
msgid "Notice"
|
||||
msgstr "Märkus"
|
||||
|
||||
msgctxt "#30293"
|
||||
msgid "Cache images"
|
||||
msgstr "Pildid vahemällu"
|
||||
|
||||
msgctxt "#30292"
|
||||
msgid "Select Subtitle Stream"
|
||||
msgstr "Vali subtiitrirada"
|
||||
|
||||
msgctxt "#30291"
|
||||
msgid "Select Audio Stream"
|
||||
msgstr "Vali heliriba"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Kõik"
|
||||
|
||||
msgctxt "#30289"
|
||||
msgid "TV Shows - Genres"
|
||||
msgstr "Sarjad - žanrid"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- uued"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "Sarjad - uued"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Filmid - vaatamata"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- vaatamata"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Kas esitada järgmine episood?"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "Kohtvõrgus ei tuvastatud Jellyfini servereid."
|
||||
|
||||
msgctxt "#30281"
|
||||
msgid "Refresh Cached Images"
|
||||
msgstr "Värskenda vahemällu salvestatud pilte"
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "Puuduv pealkiri"
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "Sarjad - vaatamata"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- järgmisena"
|
||||
|
||||
msgctxt "#30277"
|
||||
msgid "JellyCon needs to prompt for resume on partily played items, Kodi can also prompt, this can cause a double prompt. Do you want to remove the double prompt?"
|
||||
msgstr ""
|
||||
"JellyCon peab küsima jätkamist osaliselt esitatud üksuste puhul. Kodi võib "
|
||||
"samuti küsida ja see võib põhjustada päringu. Kas soovid topeltpäringu "
|
||||
"eemaldada?"
|
||||
|
||||
msgctxt "#30276"
|
||||
msgid "Extra Resume Prompt Detected"
|
||||
msgstr "Tuvastati täiendav jätkamise päring"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "Sunnitud transkoodimine"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Kustuta"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "Määra lemmikuks"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Märgi mittevaadatuks"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Märgi vaadatuks"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Filmid – juhuslikud"
|
||||
|
||||
msgctxt "#30267"
|
||||
msgid " - In Progress"
|
||||
msgstr "- pooleli"
|
||||
|
||||
msgctxt "#30266"
|
||||
msgid "Movies - Pages"
|
||||
msgstr "Filmid – lehed"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "Episoodid – järgmine"
|
||||
|
||||
msgctxt "#30264"
|
||||
msgid "Episodes - In Progress"
|
||||
msgstr "Episoodid – pooleli"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "Sarjad - lemmikud"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "Sarjad"
|
||||
|
||||
msgctxt "#30260"
|
||||
msgid "BoxSets"
|
||||
msgstr "Kogumikud"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Filmid – lemmikud"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "Filmid – pooleli"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Filmid"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "Sarjad - A-Ü"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Kuva lisamooduli seaded"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Filmid – A-Ü"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Filmid – žanrid"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Teadmata"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Otsi"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "Transkoodi jõuga mpeg2"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "Taasesituse striimimise valikud"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Alusta algusest"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "Transkoodi jõuga h265 (hevc)"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Episoodid"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Filmid"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "Sarjad"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "Lehekülje suurus ja filtreerimine"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Üksuse paigutus"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "Paku filmi kustutamist pärast %"
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "- Küsi enne esitust"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Esita järgmine episood pärast %"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "Paku episoodi kustutamist pärast %"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Üksuse üksikasjad"
|
||||
|
||||
msgctxt "#30215"
|
||||
msgid "On playback stop (100% = disabled)"
|
||||
msgstr "Taasesituse peatamisel (100% = keelatud)"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Sündmused"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "Video maksimaalne laius"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Transkoodimise valikud"
|
||||
|
||||
msgctxt "#30210"
|
||||
msgid "HTTP direct stream"
|
||||
msgstr "HTTP otsevoog"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Peremeesmasin"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "Kaasa meediavoo teave"
|
||||
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "Voo maksimaalne bitikiirus (Kbps)"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Taasesitus"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Taasesituse tüüp"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Serveriga ei saa ühendust"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "URL viga"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Kaasa inimesed"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Kaasa süžee"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Vali kasutaja"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Aadress:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Valitud serveri aadress"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Vali server"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Lisa (cc) subtiitrite olemasolul"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "Meediatüüp määramata"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Viga"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Üksuse töötlemine:"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Tehtud"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "Jätkamisel"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "Kuva laadimise edenemine"
|
||||
|
||||
msgctxt "#30116"
|
||||
msgid "Add unwatched counts to names"
|
||||
msgstr "Lisa nimedele vaatamata arv"
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "Lisa nimedele jätkamise protsent"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "Tagasihüpe (sek)"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Andmete toomine"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Sisu laadimine"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Teenused"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Liides"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "Hoiatus: see toiming kustutab meediafailid serverist."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Kas kinnitada kustutamine?"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "Pole saadaval"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "Serveri kustutamise ootel"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Kustutamine"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Kasutajanime ei leitud"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Vale kasutajanimi/parool"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Luba silumislogimine"
|
||||
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "Vidina toiming üksuse valikul"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Parool:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Kasutajanimi:"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Peida vaatamata episoodi üksikasjad"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Täpsem"
|
||||
1101
resources/language/resource.language.fi/strings.po
Normal file
929
resources/language/resource.language.fr/strings.po
Normal file
@@ -0,0 +1,929 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-05-20 04:22+0000\n"
|
||||
"Last-Translator: MoFanFr <hennebelle.benoit@gmail.com>\n"
|
||||
"Language-Team: French <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/fr/>\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Nombre d'éléments à afficher dans les listes filtrées"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Afficher les clients connectés"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Nom d'affichage de l'appareil"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Enregistrer les données de synchronisation"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Changer d'utilisateur]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Détecter le serveur local]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Nombre de profils de performances à capturer"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Hôte"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Nom d'utilisateur introuvable"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Mot de passe :"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Mot de passe Samba"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Nom d'utilisateur Samba"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Nom d'utilisateur"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Vérifier le certificat HTTPS"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Utilisateur/Mot de passe incorrect"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Activer le débogage dans le journal d’évènements"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Nom d'utilisateur :"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Cacher les détails des épisodes non-visionnés"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Avancés"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Afficher tous les épisodes"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Options de transcodage"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "Chemin d'accès du fichier"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Impossible de se connecter au serveur"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "Erreur d'URL"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Sélectionner l'utilisateur"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr ""
|
||||
"Avertissement : Cette action va supprimer tous les fichier multimédias "
|
||||
"serveur."
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Sélectionner l'adresse du serveur"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Sélectionner le serveur"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Services"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Interface"
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Confirmer la suppression ?"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Suppression"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Évènement"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "Largeur vidéo max"
|
||||
|
||||
msgctxt "#30210"
|
||||
msgid "HTTP direct stream"
|
||||
msgstr "Stream direct HTTP"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "Bitrate maximum du flux (Kbps)"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Lecture"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "Inclure info du flux media"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Inclure l'intrigue"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Ajouter (cc) si sous-titre disponible"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "Type de média non configurer"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Fait"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "Afficher la progression du chargement"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Récupération des données"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Chargement du contenu"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "En attente de suppression par le serveur"
|
||||
|
||||
msgctxt "#30306"
|
||||
msgid "Playback starting"
|
||||
msgstr "Démarrage de la lecture"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "Non trouvé"
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Caching Images"
|
||||
msgstr "Mise en cache des images"
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "Cache all Jellyfin images as local Kodi images?"
|
||||
msgstr ""
|
||||
"Mettre en cache toutes les images Jellyfin en tant qu'images locales Kodi ?"
|
||||
|
||||
msgctxt "#30299"
|
||||
msgid "Cache Images"
|
||||
msgstr "Images en cache"
|
||||
|
||||
msgctxt "#30298"
|
||||
msgid "Deleting Kodi Images"
|
||||
msgstr "Suppression des images de Kodi en cours"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "Supprimer les images non-utilisées ?"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
msgctxt "#30295"
|
||||
msgid "To use this feature you need HTTP control enabled"
|
||||
msgstr "Pour utiliser cette fonctionnalité vous devez activer le contrôle HTTP"
|
||||
|
||||
msgctxt "#30293"
|
||||
msgid "Cache images"
|
||||
msgstr "Images en cache"
|
||||
|
||||
msgctxt "#30292"
|
||||
msgid "Select Subtitle Stream"
|
||||
msgstr "Choisir le flux des sous-titres"
|
||||
|
||||
msgctxt "#30291"
|
||||
msgid "Select Audio Stream"
|
||||
msgstr "Choisir le flux audio"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Tous"
|
||||
|
||||
msgctxt "#30289"
|
||||
msgid "TV Shows - Genres"
|
||||
msgstr "Séries TV - Genres"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "Séries TV - Derniers"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Films - Non-vus"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Lire l'épisode suivant ?"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "Aucun serveur Jellyfin détecté sur le réseau local."
|
||||
|
||||
msgctxt "#30281"
|
||||
msgid "Refresh Cached Images"
|
||||
msgstr "Actualiser les images en cache"
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "Titre manquant"
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "Séries TV - Non-vus"
|
||||
|
||||
msgctxt "#30277"
|
||||
msgid "JellyCon needs to prompt for resume on partily played items, Kodi can also prompt, this can cause a double prompt. Do you want to remove the double prompt?"
|
||||
msgstr ""
|
||||
"JellyCon doit demander la reprise des éléments partiellement lus, Kodi peut "
|
||||
"également le demander, cela peut provoquer une double invite. Voulez-vous "
|
||||
"supprimer la double invite ?"
|
||||
|
||||
msgctxt "#30276"
|
||||
msgid "Extra Resume Prompt Detected"
|
||||
msgstr "Invitation supplémentaire à reprendre la lecture détectée"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "Forcer le transcodage"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
msgctxt "#30273"
|
||||
msgid "Unset Favourite"
|
||||
msgstr "Retirer des favoris"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "Ajouter en favori"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Noter comme non-vu"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Noter comme vu"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Films - Aléatoires"
|
||||
|
||||
msgctxt "#30266"
|
||||
msgid "Movies - Pages"
|
||||
msgstr "Films - Pages"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "Épisodes - À suivre"
|
||||
|
||||
msgctxt "#30264"
|
||||
msgid "Episodes - In Progress"
|
||||
msgstr "Épisodes - En cours"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "Épisodes - Ajoutés récemment"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "Séries TV - Favoris"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "Séries TV"
|
||||
|
||||
msgctxt "#30260"
|
||||
msgid "BoxSets"
|
||||
msgstr "Coffrets"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Films - Favoris"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "Films - En cours"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Films - Ajoutés récemment"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Films"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "Séries TV - A-Z"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Afficher les paramètres du module"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Films - A-Z"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Films - Genres"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Inconnu"
|
||||
|
||||
msgctxt "#30247"
|
||||
msgid "Custom Widget Content"
|
||||
msgstr "Contenu de widget personnalisé"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "Forcer le transcodage mpeg4"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "Forcer le transcodage msmpeg4v3 (divx)"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "Forcer le transcodage mpeg2"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "Options du flux de lecture"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Lire depuis le début"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "Forcer le transcodage h265 (hevc)"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Épisodes"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Films"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "Séries TV"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "Intéraction"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "Taille et filtre de la page"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Disposition de l'élément"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "Proposer de supprimer le film après %"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Lire l'épisode suivant après %"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "Proposer de supprimer l'épisode après %"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Détails de l'élément"
|
||||
|
||||
msgctxt "#30215"
|
||||
msgid "On playback stop (100% = disabled)"
|
||||
msgstr "À l'arrêt de la lecture (100% = désactivé)"
|
||||
|
||||
msgctxt "#30213"
|
||||
msgid "Video force 8 bit"
|
||||
msgstr "Forcer le mode vidéo 8-bit"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Type de lecture"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Inclure les personnes"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "À la reprise de la lecture"
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "Ajouter le pourcentage de continuation aux noms"
|
||||
|
||||
msgctxt "#30116"
|
||||
msgid "Add unwatched counts to names"
|
||||
msgstr "Ajouter un compteur d'éléments non-vus aux noms"
|
||||
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "Aplatir une seule saison"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "Format de nom d'épisode filtré"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "Revenir en arrière"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- Ajouté récemment"
|
||||
|
||||
msgctxt "#30267"
|
||||
msgid " - In Progress"
|
||||
msgstr "- En cours"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Adresse :"
|
||||
|
||||
msgctxt "#30437"
|
||||
msgid "Playback options"
|
||||
msgstr "Options de lecture"
|
||||
|
||||
msgctxt "#30431"
|
||||
msgid "Seasons"
|
||||
msgstr "Saisons"
|
||||
|
||||
msgctxt "#30429"
|
||||
msgid "Genre"
|
||||
msgstr "Genre"
|
||||
|
||||
msgctxt "#30428"
|
||||
msgid "Rating"
|
||||
msgstr "Critique"
|
||||
|
||||
msgctxt "#30427"
|
||||
msgid "Added"
|
||||
msgstr "Ajouté"
|
||||
|
||||
msgctxt "#30426"
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
msgctxt "#30425"
|
||||
msgid "Year"
|
||||
msgstr "Année"
|
||||
|
||||
msgctxt "#30424"
|
||||
msgid "Default"
|
||||
msgstr "Défaut"
|
||||
|
||||
msgctxt "#30423"
|
||||
msgid "NotSet"
|
||||
msgstr "Non-défini"
|
||||
|
||||
msgctxt "#30422"
|
||||
msgid "Sorting"
|
||||
msgstr "Tri"
|
||||
|
||||
msgctxt "#30421"
|
||||
msgid "Views"
|
||||
msgstr "Vues"
|
||||
|
||||
msgctxt "#30420"
|
||||
msgid "Audio max channels"
|
||||
msgstr "Nombre maximal de canaux audio"
|
||||
|
||||
msgctxt "#30419"
|
||||
msgid "Audio codec"
|
||||
msgstr "Codec audio"
|
||||
|
||||
msgctxt "#30417"
|
||||
msgid "You do not have permision to delete this item"
|
||||
msgstr "Vous n'avez pas les permissions pour supprimer cet élément"
|
||||
|
||||
msgctxt "#30415"
|
||||
msgid " - Favorite Collections"
|
||||
msgstr "- Collections Favorites"
|
||||
|
||||
msgctxt "#30414"
|
||||
msgid " - Favorites"
|
||||
msgstr "- Favoris"
|
||||
|
||||
msgctxt "#30412"
|
||||
msgid " - Decades"
|
||||
msgstr "- Décennies"
|
||||
|
||||
msgctxt "#30411"
|
||||
msgid " - Years"
|
||||
msgstr "- Années"
|
||||
|
||||
msgctxt "#30410"
|
||||
msgid " - Collections"
|
||||
msgstr "- Collections"
|
||||
|
||||
msgctxt "#30407"
|
||||
msgid "Global Lists"
|
||||
msgstr "Listes globales"
|
||||
|
||||
msgctxt "#30406"
|
||||
msgid "Jellyfin Libraries"
|
||||
msgstr "Bibliothèques Jellyfin"
|
||||
|
||||
msgctxt "#30405"
|
||||
msgid " - Show All"
|
||||
msgstr "- Tout afficher"
|
||||
|
||||
msgctxt "#30404"
|
||||
msgid " - A-Z"
|
||||
msgstr "- A - Z"
|
||||
|
||||
msgctxt "#30403"
|
||||
msgid "Movies - Recommendations"
|
||||
msgstr "Films - Recommendations"
|
||||
|
||||
msgctxt "#30402"
|
||||
msgid "Add to Kodi Playlist"
|
||||
msgstr "Ajouter à la liste de lecture Kodi"
|
||||
|
||||
msgctxt "#30401"
|
||||
msgid "Info"
|
||||
msgstr "Informations"
|
||||
|
||||
msgctxt "#30399"
|
||||
msgid "Hide"
|
||||
msgstr "Cacher"
|
||||
|
||||
msgctxt "#30398"
|
||||
msgid "Refresh Jellyfin Metadata"
|
||||
msgstr "Rafraîchir les métadonnées Jellyfin"
|
||||
|
||||
msgctxt "#30397"
|
||||
msgid " - Pages"
|
||||
msgstr "- Pages"
|
||||
|
||||
msgctxt "#30392"
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgctxt "#30391"
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgctxt "#30390"
|
||||
msgid "Protocol"
|
||||
msgstr "Protocole"
|
||||
|
||||
msgctxt "#30389"
|
||||
msgid "User details"
|
||||
msgstr "Détails utilisateur"
|
||||
|
||||
msgctxt "#30386"
|
||||
msgid "Unused Jellyfin images : "
|
||||
msgstr "Images Jellyfin inutilisées :"
|
||||
|
||||
msgctxt "#30383"
|
||||
msgid "System - "
|
||||
msgstr "Système -"
|
||||
|
||||
msgctxt "#30382"
|
||||
msgid "Always"
|
||||
msgstr "Toujours"
|
||||
|
||||
msgctxt "#30381"
|
||||
msgid "More than one"
|
||||
msgstr "Plus d'un(e)"
|
||||
|
||||
msgctxt "#30380"
|
||||
msgid "Never"
|
||||
msgstr "Jamais"
|
||||
|
||||
msgctxt "#30372"
|
||||
msgid "Server URL"
|
||||
msgstr "URL du serveur"
|
||||
|
||||
msgctxt "#30365"
|
||||
msgid "Manual Login"
|
||||
msgstr "Connexion manuelle"
|
||||
|
||||
msgctxt "#30364"
|
||||
msgid "Do you want to save the password?"
|
||||
msgstr "Voulez-vous sauver le mot de passe ?"
|
||||
|
||||
msgctxt "#30363"
|
||||
msgid "Save Password?"
|
||||
msgstr "Sauver le mot de passe ?"
|
||||
|
||||
msgctxt "#30362"
|
||||
msgid " - Recordings"
|
||||
msgstr "- Enregistrements"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "- Programmes"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "- Canaux"
|
||||
|
||||
msgctxt "#30353"
|
||||
msgid " - Frequently Played"
|
||||
msgstr "- Fréquemment lus"
|
||||
|
||||
msgctxt "#30321"
|
||||
msgid " - Album Artists"
|
||||
msgstr "- Artistes de l'album"
|
||||
|
||||
msgctxt "#30352"
|
||||
msgid "Music - Frequently Played"
|
||||
msgstr "Musique - Joués fréquemment"
|
||||
|
||||
msgctxt "#30351"
|
||||
msgid "Music - Recently Played"
|
||||
msgstr "Musique - Joués récemment"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "Musique - Ajoutés récemment"
|
||||
|
||||
msgctxt "#30349"
|
||||
msgid " - Recently Played"
|
||||
msgstr "- Lus récemment"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30347"
|
||||
msgid "Getting Existing Images"
|
||||
msgstr "Récupération des images existantes"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30346"
|
||||
msgid "Deleteing Cached Images"
|
||||
msgstr "Suppression des images en cache"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30345"
|
||||
msgid "Cache Jellyfin server data requests"
|
||||
msgstr "Garder les requêtes de données serveur de Jellyfin en cache"
|
||||
|
||||
msgctxt "#30344"
|
||||
msgid "Number of images removed from cache"
|
||||
msgstr "Nombre d'images enlevées du cache"
|
||||
|
||||
msgctxt "#30342"
|
||||
msgid "New content check interval (0 = disabled)"
|
||||
msgstr "Intervalle de vérification de nouveau contenu (0 = désactivé)"
|
||||
|
||||
msgctxt "#30322"
|
||||
msgid "Auto resume"
|
||||
msgstr "Reprise automatique"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Lecture"
|
||||
|
||||
msgctxt "#30294"
|
||||
msgid "Notice"
|
||||
msgstr "Annonce"
|
||||
|
||||
msgctxt "#30340"
|
||||
msgid "Group movies into collections"
|
||||
msgstr "Grouper les films en collections"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Personne"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Album"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Chanson"
|
||||
|
||||
msgctxt "#30334"
|
||||
msgid "Use JellyCon context menu"
|
||||
msgstr "Utiliser le menu contextuel JellyCon"
|
||||
|
||||
msgctxt "#30332"
|
||||
msgid "Stop media playback on screensaver activation"
|
||||
msgstr ""
|
||||
"Arrêter la lecture du média lors de l'activation de l'économiseur d'écran"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "Films par page"
|
||||
|
||||
msgctxt "#30330"
|
||||
msgid "Show change user dialog"
|
||||
msgstr "Afficher le dialogue de changement d'utilisateur"
|
||||
|
||||
msgctxt "#30329"
|
||||
msgid "Screensaver"
|
||||
msgstr "Économiseur d'écran"
|
||||
|
||||
msgctxt "#30328"
|
||||
msgid "Show empty folders (shows, seasons, collections)"
|
||||
msgstr "Afficher les dossiers vides (séries, saisons, collections)"
|
||||
|
||||
msgctxt "#30327"
|
||||
msgid "Go To Season"
|
||||
msgstr "Aller à la saison"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "- Genres"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "- Albums"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "Musique - Albums"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "Lire tout"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "Erreur de connexion"
|
||||
|
||||
msgctxt "#30315"
|
||||
msgid "Suppress notifications for connection errors"
|
||||
msgstr "Cacher les notifications pour des erreurs de connexion"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "Tout -"
|
||||
|
||||
msgctxt "#30311"
|
||||
msgid "Library - "
|
||||
msgstr "Médiathèque -"
|
||||
|
||||
msgctxt "#30310"
|
||||
msgid "Enable Jellyfin remote control"
|
||||
msgstr "Activer le contrôle à distance Jellyfin"
|
||||
|
||||
msgctxt "#30309"
|
||||
msgid "Select Media Source"
|
||||
msgstr "Sélectionner la source de média"
|
||||
|
||||
msgctxt "#30308"
|
||||
msgid "Select Trailer"
|
||||
msgstr "Sélectionner la bande-annonce"
|
||||
|
||||
msgctxt "#30307"
|
||||
msgid "Play Trailer"
|
||||
msgstr "Lire la bande-annonce"
|
||||
|
||||
msgctxt "#30304"
|
||||
msgid "Cached Jellyfin images : "
|
||||
msgstr "Images Jellyfin en cache :"
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "Missing Jellyfin images : "
|
||||
msgstr "Images Jellyfin manquantes :"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Existing images : "
|
||||
msgstr "Images existantes :"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- Derniers"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- Non-vus"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- À suivre"
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "- Afficher avant de lire"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Traitement de l'élément :"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30356"
|
||||
msgid "Loading existing image list"
|
||||
msgstr "Chargement de la liste des images existantes"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30354"
|
||||
msgid "Go To Series"
|
||||
msgstr "Voir les Séries"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30319"
|
||||
msgid "Music - All Album Artists"
|
||||
msgstr "Musique - Tous les Albums par Artistes"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30343"
|
||||
msgid "Changes Require Kodi Restart"
|
||||
msgstr "Les modifications nécessite le redémarrage de Kodi"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30341"
|
||||
msgid "Background image update interval (0 = disabled)"
|
||||
msgstr "Intervalle de mise à jours des imades en arrière plan (0=désactivé)"
|
||||
|
||||
msgctxt "#30333"
|
||||
msgid "Cache artwork in the background"
|
||||
msgstr "Mise en cache des illustrations en arrière plan"
|
||||
352
resources/language/resource.language.hi/strings.po
Normal file
@@ -0,0 +1,352 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-05-20 04:22+0000\n"
|
||||
"Last-Translator: Sherlock <aggybooy2@gmail.com>\n"
|
||||
"Language-Team: Hindi <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/hi/>\n"
|
||||
"Language: hi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30407"
|
||||
msgid "Global Lists"
|
||||
msgstr "वैश्विक सूची"
|
||||
|
||||
msgctxt "#30408"
|
||||
msgid "Custom Widgets"
|
||||
msgstr "कस्टम विड्जेट"
|
||||
|
||||
msgctxt "#30409"
|
||||
msgid "Add-on Actions"
|
||||
msgstr "अतिरिक्त कार्य"
|
||||
|
||||
msgctxt "#30410"
|
||||
msgid " - Collections"
|
||||
msgstr "संग्रह"
|
||||
|
||||
msgctxt "#30417"
|
||||
msgid "You do not have permision to delete this item"
|
||||
msgstr "आपके पास इस वस्तु को मिटाने की अनुमति नहीं है"
|
||||
|
||||
msgctxt "#30416"
|
||||
msgid "HTTP timeout seconds"
|
||||
msgstr "एचटीटीपी के समय समाप्ति (सेकड़ो में)"
|
||||
|
||||
msgctxt "#30413"
|
||||
msgid " - Tags"
|
||||
msgstr "चिप्पी"
|
||||
|
||||
msgctxt "#30418"
|
||||
msgid "Audio bitrate (Kbits)"
|
||||
msgstr "ऑडियो बितरते (किलो बिट प्रति घंटा)"
|
||||
|
||||
msgctxt "#30419"
|
||||
msgid "Audio codec"
|
||||
msgstr "ऑडियो कोडेक"
|
||||
|
||||
msgctxt "#30420"
|
||||
msgid "Audio max channels"
|
||||
msgstr "अधिकतम ऑडियो चैनल"
|
||||
|
||||
msgctxt "#30421"
|
||||
msgid "Views"
|
||||
msgstr "कितनी बार देखा गया"
|
||||
|
||||
msgctxt "#30422"
|
||||
msgid "Sorting"
|
||||
msgstr "छंटाई"
|
||||
|
||||
msgctxt "#30423"
|
||||
msgid "NotSet"
|
||||
msgstr "नहीं लगाया गया"
|
||||
|
||||
msgctxt "#30424"
|
||||
msgid "Default"
|
||||
msgstr "पहले से चुना हुआ"
|
||||
|
||||
msgctxt "#30425"
|
||||
msgid "Year"
|
||||
msgstr "साल"
|
||||
|
||||
msgctxt "#30434"
|
||||
msgid "Force transcode stream bitrate (Kbits)"
|
||||
msgstr "जबरदस्ती बिट्रेट तय करे (किलो बिट प्रति सेकंड)"
|
||||
|
||||
msgctxt "#30436"
|
||||
msgid "Speed test data size (MB)"
|
||||
msgstr "इंटरनेट की रफ़्तार नापने के लिए डाटा का माप (मेगा बाईट)"
|
||||
|
||||
msgctxt "#30433"
|
||||
msgid "Allow direct file playback"
|
||||
msgstr "सीधे फाइल से प्लेबैक की अनुमति दें"
|
||||
|
||||
msgctxt "#30437"
|
||||
msgid "Playback options"
|
||||
msgstr "चलने के विकल्प दिखाएं"
|
||||
|
||||
msgctxt "#30439"
|
||||
msgid "Show play next episode at time left"
|
||||
msgstr "कितने समय पहले अगले अध्याय पर जाने का बटन दिखाएं"
|
||||
|
||||
msgctxt "#30438"
|
||||
msgid "Play cinema intros"
|
||||
msgstr "सिनेमा उपक्षेप चलायें"
|
||||
|
||||
msgctxt "#30441"
|
||||
msgid "Use cached widget data"
|
||||
msgstr "पुराणी विद्गट जानकारी इस्तेमाल करें"
|
||||
|
||||
msgctxt "#30442"
|
||||
msgid "Simple new content check"
|
||||
msgstr "नए कंटेंट के लिए चेक करें"
|
||||
|
||||
msgctxt "#30435"
|
||||
msgid "Connection speed test"
|
||||
msgstr "कनेक्शन की स्पीड नापें"
|
||||
|
||||
msgctxt "#30440"
|
||||
msgid "Play next"
|
||||
msgstr "अगला चलाएं"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "कुछ सेकंड पीछे जाएं"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "देता प्राप्त कर रहे है"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "कंटेंट लोड हो रहा है"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "डीबग सूचि बनाएं"
|
||||
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "विद्गट वास्तु का कार्य चुनें"
|
||||
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "सिर्फ एक सत्र वाले शो के लिए सत्र पेज हटाएं"
|
||||
|
||||
msgctxt "#30415"
|
||||
msgid " - Favorite Collections"
|
||||
msgstr "पसंदीदा संग्रह"
|
||||
|
||||
msgctxt "#30414"
|
||||
msgid " - Favorites"
|
||||
msgstr "पसंदीदा"
|
||||
|
||||
msgctxt "#30411"
|
||||
msgid " - Years"
|
||||
msgstr "साल"
|
||||
|
||||
msgctxt "#30399"
|
||||
msgid "Hide"
|
||||
msgstr "छुपाएँ"
|
||||
|
||||
msgctxt "#30412"
|
||||
msgid " - Decades"
|
||||
msgstr "दशक"
|
||||
|
||||
msgctxt "#30432"
|
||||
msgid "Hide watched items in lists"
|
||||
msgstr "देखि हुईं वस्तुएं हटाएं"
|
||||
|
||||
msgctxt "#30431"
|
||||
msgid "Seasons"
|
||||
msgstr "सत्र"
|
||||
|
||||
msgctxt "#30430"
|
||||
msgid "Label"
|
||||
msgstr "लेबल"
|
||||
|
||||
msgctxt "#30427"
|
||||
msgid "Added"
|
||||
msgstr "जोड़ा हुआ"
|
||||
|
||||
msgctxt "#30426"
|
||||
msgid "Title"
|
||||
msgstr "शीर्षक"
|
||||
|
||||
msgctxt "#30428"
|
||||
msgid "Rating"
|
||||
msgstr "रेटिंग"
|
||||
|
||||
msgctxt "#30429"
|
||||
msgid "Genre"
|
||||
msgstr "शैली"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "छठें हुएं अध्यायों के नामों का प्रारूप"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "छांटी हुई सूचि में कितने वास्तु दिखाए"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "समय की जानकारी लिखें"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "उपकरण का नाम"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "जुड़े हुए क्लाइंट्स दिखाएं"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "अंधेके एपिसोडों का विवरण हटाएँ"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "सारे एपिसोड दिखाएं"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "पासवर्ड :"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "खोज"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "अध्याय"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "टीवी शो"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "फ़िल्म"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "वास्तु का विवरण"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "पता :"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "गलत उपयोगरता का नाम / पासवर्ड"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "उपयोगकर्ता का नाम नहीं पाया गया"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "उन्नत"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "चेतावनी : यह कार्य आपकी मिडिया मिटा देगा |"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "समस्या"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "खत्म"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "कहानी जोड़ें"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "उपयोगकर्ता चुनें"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "चुनें हुए सर्वर का पता"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "सर्वर चुनें"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "सेवाएं"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "अंतराफलक"
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "क्या आप पक्का मिटाना चाहतें हैं?"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "उपलब्ध नहीं है"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "सर्वर द्वारा हटाए जानें का इंतज़ार कर रहें हैं"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "हटा रहें हैं"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "उपयोगकर्ता का नाम :"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "कितने कार्य प्रोफाइल रखने हैं"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "स्थानीय सर्वर का पता करें"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "उपयोगकर्ता बदलें"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "जेलीफिन"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "साम्बा में पासवर्ड"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "साम्बा में उपयोगकर्ता का नाम"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "पासवर्ड"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "उपयोगकर्ता का नाम"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "HTTPS प्रमाणपत्र की जांच करें"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "द्वार"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "आतिथेय"
|
||||
1101
resources/language/resource.language.hu/strings.po
Normal file
484
resources/language/resource.language.id/strings.po
Normal file
@@ -0,0 +1,484 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-05-20 04:22+0000\n"
|
||||
"Last-Translator: liimee <git.taaa@fedora.email>\n"
|
||||
"Language-Team: Indonesian <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/id/>\n"
|
||||
"Language: id\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30414"
|
||||
msgid " - Favorites"
|
||||
msgstr "- Favorit"
|
||||
|
||||
msgctxt "#30440"
|
||||
msgid "Play next"
|
||||
msgstr "Putar berikutnya"
|
||||
|
||||
msgctxt "#30431"
|
||||
msgid "Seasons"
|
||||
msgstr "Musim"
|
||||
|
||||
msgctxt "#30430"
|
||||
msgid "Label"
|
||||
msgstr "Label"
|
||||
|
||||
msgctxt "#30427"
|
||||
msgid "Added"
|
||||
msgstr "Ditambahkan"
|
||||
|
||||
msgctxt "#30426"
|
||||
msgid "Title"
|
||||
msgstr "Judul"
|
||||
|
||||
msgctxt "#30425"
|
||||
msgid "Year"
|
||||
msgstr "Tahun"
|
||||
|
||||
msgctxt "#30417"
|
||||
msgid "You do not have permision to delete this item"
|
||||
msgstr "Anda tidak memiliki izin untuk menghapus item ini"
|
||||
|
||||
msgctxt "#30405"
|
||||
msgid " - Show All"
|
||||
msgstr "- Tampilkan Semua"
|
||||
|
||||
msgctxt "#30404"
|
||||
msgid " - A-Z"
|
||||
msgstr "- A-Z"
|
||||
|
||||
msgctxt "#30403"
|
||||
msgid "Movies - Recommendations"
|
||||
msgstr "Film - Disarankan"
|
||||
|
||||
msgctxt "#30402"
|
||||
msgid "Add to Kodi Playlist"
|
||||
msgstr "Tambahkan ke Daftar Putar Kodi"
|
||||
|
||||
msgctxt "#30401"
|
||||
msgid "Info"
|
||||
msgstr "Informasi"
|
||||
|
||||
msgctxt "#30399"
|
||||
msgid "Hide"
|
||||
msgstr "Sembunyikan"
|
||||
|
||||
msgctxt "#30398"
|
||||
msgid "Refresh Jellyfin Metadata"
|
||||
msgstr "Muat Ulang Metadata Jellyfin"
|
||||
|
||||
msgctxt "#30392"
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgctxt "#30391"
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgctxt "#30390"
|
||||
msgid "Protocol"
|
||||
msgstr "Protokol"
|
||||
|
||||
msgctxt "#30389"
|
||||
msgid "User details"
|
||||
msgstr "Keterangan pengguna"
|
||||
|
||||
msgctxt "#30388"
|
||||
msgid "Server details"
|
||||
msgstr "Keterangan server"
|
||||
|
||||
msgctxt "#30382"
|
||||
msgid "Always"
|
||||
msgstr "Selalu"
|
||||
|
||||
msgctxt "#30380"
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
|
||||
msgctxt "#30381"
|
||||
msgid "More than one"
|
||||
msgstr "Lebih dari satu"
|
||||
|
||||
msgctxt "#30383"
|
||||
msgid "System - "
|
||||
msgstr "Sistem -"
|
||||
|
||||
msgctxt "#30377"
|
||||
msgid "Sending request"
|
||||
msgstr "Mengirim permintaan"
|
||||
|
||||
msgctxt "#30376"
|
||||
msgid "Checking server url"
|
||||
msgstr "Memeriksa url server"
|
||||
|
||||
msgctxt "#30374"
|
||||
msgid "Sending request"
|
||||
msgstr "Mengirim permintaan"
|
||||
|
||||
msgctxt "#30373"
|
||||
msgid "Scanning for local servers"
|
||||
msgstr "Memindai server lokal"
|
||||
|
||||
msgctxt "#30372"
|
||||
msgid "Server URL"
|
||||
msgstr "URL Server"
|
||||
|
||||
msgctxt "#30371"
|
||||
msgid "Could not connect to the URL you entered, do you want to try again?"
|
||||
msgstr "Tidak dapat menghubungi URL yang dimasukkan, apa Anda ingin coba lagi?"
|
||||
|
||||
msgctxt "#30364"
|
||||
msgid "Do you want to save the password?"
|
||||
msgstr "Apakah Anda ingin menyimpan kata sandi nya?"
|
||||
|
||||
msgctxt "#30363"
|
||||
msgid "Save Password?"
|
||||
msgstr "Simpan Kata Sandi?"
|
||||
|
||||
msgctxt "#30362"
|
||||
msgid " - Recordings"
|
||||
msgstr "- Rekaman"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "- Program"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "- Saluran"
|
||||
|
||||
msgctxt "#30353"
|
||||
msgid " - Frequently Played"
|
||||
msgstr "Sering diputar"
|
||||
|
||||
msgctxt "#30352"
|
||||
msgid "Music - Frequently Played"
|
||||
msgstr "Musik - Sering Diputar"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "Musik - Baru Ditambahkan"
|
||||
|
||||
msgctxt "#30341"
|
||||
msgid "Background image update interval (0 = disabled)"
|
||||
msgstr "Interval pembaruan gambar latar belakang (0 = nonaktif)"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Orang"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Album"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Lagu"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "Film per halaman"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "- Album"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "Musik - Album"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "Putar Semua"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Putar"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "Semua -"
|
||||
|
||||
msgctxt "#30309"
|
||||
msgid "Select Media Source"
|
||||
msgstr "Pilih Sumber Media"
|
||||
|
||||
msgctxt "#30306"
|
||||
msgid "Playback starting"
|
||||
msgstr "Pemutaran dimulai"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "Tak Ditemukan"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "Hapus gambar yang tidak digunakan?"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Hapus"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- Terbaru"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "Acara TV - Terbaru"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Film - Belum Ditonton"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- Belum Ditonton"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Putar Episode Berikutnya?"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "Tak ada server Jellyfin yang terdeteksi pada jaringan lokal Anda."
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "Acara TV - Belum Ditonton"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- Berikutnya"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Hapus"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Tandai Belum Ditonton"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Tandai Sudah Ditonton"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Film - Acak"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- Baru Ditambahkan"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "Episode - Berikutnya"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "Episode - Baru Ditambahkan"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "Acara TV - Favorit"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "Acara TV"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Film - Favorit"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Film - Baru Ditambahkan"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Film"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "Acara TV - A-Z"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Tampilkan pengaturan add-on"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Film - A-Z"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Cari"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Mulai dari awal"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Episode"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Film"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "Acara TV"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "Interaksi"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Tata Letak Item"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Keterangan Item"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Pemutaran"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Jenis pemutaran"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Tidak dapat terhubung dengan server"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "Kesalahan pada URL"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Pilih Pengguna"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Alamat:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Alamat Server Terpilih"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Pilih Server"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Tambahkan (cc) apabila subtitel tersedia"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Kesalahan"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Memproses Item:"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Menerima Data"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Layanan"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Antarmuka"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "Peringatan: Tindakan ini akan menghapus berkas media dari server."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Konfirmasi penghapusan?"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Menghapus"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Nama pengguna tidak ditemukan"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Nama Pengguna/Kata Sandi Salah"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Nama Pengguna:"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Kata Sandi:"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Sembunyikan keterangan episode yang belum ditonton"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Lanjutan"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Tampilkan semua episode"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Verifikasi sertifikat HTTPS"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Tampilkan klien yang terhubung"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Kata sandi Samba"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Nama pengguna Samba"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Kata sandi"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Nama pengguna"
|
||||
428
resources/language/resource.language.it/strings.po
Normal file
@@ -0,0 +1,428 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2021-12-02 18:05+0000\n"
|
||||
"Last-Translator: Alfonso Scarpino <alfonso.scarpino@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/it/>\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.5.2\n"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "Mostra avanzamento del caricamento"
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "Aggiungi percentuale guardata ai nomi"
|
||||
|
||||
msgctxt "#30116"
|
||||
msgid "Add unwatched counts to names"
|
||||
msgstr "Aggiungi il contatore non guardati ai nomi"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Recupero dati in corso"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Caricamento contenuto in corso"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Servizi"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Interfaccia"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "Attenzione: questa operazione eliminerà i file multimediali dal server."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Confermi l'eliminazione?"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "N/D"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "In attesa di eliminazione sul server"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Eliminazione in corso"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Nome utente sconosciuto"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Nome utente/Password errati"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Attiva log a debug"
|
||||
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "Azione di selezione elemento widget"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Password:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Nome utente:"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Nascondi dettagli episodi non guardati"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Avanzate"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Mostra elemento tutti gli episodi"
|
||||
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "Appiattisci stagione unica"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "Formato nome episodio filtrato"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Numero di elementi da mostrare nelle liste filtrate"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Mostra client connessi"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Nome dispositivo visualizzato"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Dati di temporizzazione dei log"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Cambia utente]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Rileva server locale]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Numero di profili di performance da acquisire"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Password Samba"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Nome utente Samba"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Nome utente"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Verifica certificato HTTPS"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Porta"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Host"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Riproduzione"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Tipo riproduzione"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Impossibile connettersi al server"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "Errore URL"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Includi persone"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "Includi info flusso multimediale"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Includi trama"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Seleziona Utente"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Indirizzo:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Indirizzo server selezionato"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Seleziona Server"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Aggiungi (cc) se sono disponibili i sottotitoli"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "Nessun formato media impostato"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Errore"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Elaborazione Elemento:"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Finito"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "Forza transcodifica"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
msgctxt "#30273"
|
||||
msgid "Unset Favourite"
|
||||
msgstr "Rimuovi dai preferiti"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "Aggiungi ai preferiti"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Segna come non guardato"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Segna come guardato"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Film - Casuale"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- Aggiunti di recente"
|
||||
|
||||
msgctxt "#30267"
|
||||
msgid " - In Progress"
|
||||
msgstr "- In corso"
|
||||
|
||||
msgctxt "#30266"
|
||||
msgid "Movies - Pages"
|
||||
msgstr "Film - Pagine"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "Episodi - Prossimo"
|
||||
|
||||
msgctxt "#30264"
|
||||
msgid "Episodes - In Progress"
|
||||
msgstr "Episodi - In corso"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "Episodi - Aggiunti di recente"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "Serie TV - Preferiti"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "Serie TV"
|
||||
|
||||
msgctxt "#30260"
|
||||
msgid "BoxSets"
|
||||
msgstr "Collezioni"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Film - Preferiti"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "Film - In corso"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Film - Aggiunti di recente"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Film"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "Serie TV - A-Z"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Mostra impostazioni add-on"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Film - A-Z"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Film - Generi"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Sconosciuto"
|
||||
|
||||
msgctxt "#30247"
|
||||
msgid "Custom Widget Content"
|
||||
msgstr "Contenuto personalizzato widget"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "Forza transcodifica mpeg4"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "Forza transcodifica msmpeg4v3 (divx)"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "Forza transcodifica mpeg2"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "Opzioni di riproduzione flusso"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Ricomincia dall'inizio"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "Forza transcodifica h265 (hevc)"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Episodi"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Film"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "Serie TV"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "Interazione"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "Dimensione pagina e filtri"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Disposizione elemento"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "Chiedi se eliminare il film dopo %"
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "- Chiedi prima di riprodurre"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Riproduci il prossimo episodio dopo %"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "Chiedi se eliminare l'episodio dopo %"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Dettagli elemento"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Eventi"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "Larghezza massima video"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Opzioni di transcodifica"
|
||||
|
||||
msgctxt "#30210"
|
||||
msgid "HTTP direct stream"
|
||||
msgstr "Flusso diretto HTTP"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "Percorso diretto del file"
|
||||
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "Bitrate massimo del flusso (Kbits)"
|
||||
1103
resources/language/resource.language.kk/strings.po
Normal file
536
resources/language/resource.language.nl/strings.po
Normal file
@@ -0,0 +1,536 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-06-01 17:51+0000\n"
|
||||
"Last-Translator: B v H <bobsieflopsie@outlook.com>\n"
|
||||
"Language-Team: Dutch <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/nl/>\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Films - Onlangs Toegevoegd"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Films"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "TV Shows - A-Z"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Toon add-on instellingen"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Films - A-Z"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Films - Genres"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Onbekend"
|
||||
|
||||
msgctxt "#30247"
|
||||
msgid "Custom Widget Content"
|
||||
msgstr "Custom Widget Inhoud"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Zoek"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "Forceer transcoderen mpeg4"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "Forceer transcoderen msmpeg4v3 (divx)"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "Forceer transcoderen mpeg2"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "Afspeelstreamopties"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Speel vanaf begin"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "Forceer transcoderen h265 (hevc)"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Afleveringen"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Films"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "TV Shows"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "Interactie"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Item Details"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "Paginagrootte en Filteren"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Item Layout"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "Vraag om film te verwijderen na %"
|
||||
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "- Vraag voor afspelen"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Speel volgende aflevering na %"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "Vraag om aflevering te verwijderen na %"
|
||||
|
||||
msgctxt "#30215"
|
||||
msgid "On playback stop (100% = disabled)"
|
||||
msgstr "Bij stoppen playback (100% = uitgeschakeld)"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Events"
|
||||
|
||||
msgctxt "#30213"
|
||||
msgid "Video force 8 bit"
|
||||
msgstr "Forceer 8 bit video"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "Maximale videobreedte"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Transcode opties"
|
||||
|
||||
msgctxt "#30210"
|
||||
msgid "HTTP direct stream"
|
||||
msgstr "HTTP direct stream"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "Directe bestandslocatie"
|
||||
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "Maximale stream bitrate (Kbps)"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Afspelen"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Afspeeltype"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Kan niet verbinden met de server"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "URL error"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Mensen toevoegen"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "Mediastream informatie toevoegen"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Plot toevoegen"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Selecteer gebruiker"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Adres:"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Adres geselecteerde server"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Selecteer server"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Voeg (cc) toe als ondertiteling beschikbaar is"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "Geen mediatype ingesteld"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Item verwerken:"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Gereed"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "Bij hervatten"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "Toon laadvoortgang"
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "Voeg percentage bekeken to aan namen"
|
||||
|
||||
msgctxt "#30116"
|
||||
msgid "Add unwatched counts to names"
|
||||
msgstr "Voeg hoeveelheid niet bekeken to aan namen"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "Spring seconden terug"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Data Ophalen"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Content aan het laden"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Services"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Interface"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "Waarschuwing: Deze stap zal mediabestanden van de server verwijderen."
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Bevestig verwijderen?"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "Wachten op server om te wissen"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Bezig met verwijderen"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Gebruikersnaam niet gevonden"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Onjuiste gebruikersnaam/wachtwoord"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Schakel debug logging in"
|
||||
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "Widget item selecteer actie"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Wachtwoord:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Gebruikersnaam:"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Verberg details van niet bekeken afleveringen"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Geavanceerd"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Toon alle afleveringen"
|
||||
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "Naamformat gefilterde aflevering"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Aantal weer te geven items in gefilterde lijsten"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Toon verbonden clients"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Weergavenaam apparaat"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Log timing gegevens"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Wijzig gebruiker]"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Detecteer lokale server]"
|
||||
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Aantal vast te leggen prestatieprofielen"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Samba wachtwoord"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Samba gebruikersnaam"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Gebruikersnaam"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Verifieer HTTPS certificaat"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Poort"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Host"
|
||||
|
||||
msgctxt "#30352"
|
||||
msgid "Music - Frequently Played"
|
||||
msgstr "Muziek - Vaak Afgespeeld"
|
||||
|
||||
msgctxt "#30351"
|
||||
msgid "Music - Recently Played"
|
||||
msgstr "Muziek - Recent Afgespeeld"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "Muziek - Recent Toegevoegd"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Persoon"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Album"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Nummer"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "Films per pagina"
|
||||
|
||||
msgctxt "#30327"
|
||||
msgid "Go To Season"
|
||||
msgstr "Ga Naar Seizoen"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "- Genres"
|
||||
|
||||
msgctxt "#30322"
|
||||
msgid "Auto resume"
|
||||
msgstr "Automatisch Hervatten"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "- Albums"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "Muziek - Albums"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "Speel Alles Af"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "Verbindingsfout"
|
||||
|
||||
msgctxt "#30315"
|
||||
msgid "Suppress notifications for connection errors"
|
||||
msgstr "Meldingen van verbindingsfouten onderdrukken"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Speel Af"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
msgctxt "#30308"
|
||||
msgid "Select Trailer"
|
||||
msgstr "Selecteer Trailer"
|
||||
|
||||
msgctxt "#30307"
|
||||
msgid "Play Trailer"
|
||||
msgstr "Speel Trailer Af"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "Niet Gevonden"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Existing images : "
|
||||
msgstr "Bestaande Afbeeldingen:"
|
||||
|
||||
msgctxt "#30298"
|
||||
msgid "Deleting Kodi Images"
|
||||
msgstr "Kodi Afbeeldingen Verwijderen"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "Verwijder niet gebruikte afbeeldingen?"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Alles"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- Nieuwste"
|
||||
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Films - Niet Bekeken"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- Niet Bekeken"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Speel Volgende Aflevering Af?"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "Geen Jellyfin Servers Gevonden Op Uw Locale Netwerk."
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "Missende Titel"
|
||||
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- Volgende"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "Forceer Transcoderen"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
msgctxt "#30273"
|
||||
msgid "Unset Favourite"
|
||||
msgstr "Demarkeer Favoriet"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "Markeer Favoriet"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Markeer Niet Bekeken"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Markeer Bekeken"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Films - Willekeurig"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- Recent Toegevoegd"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Films - Favorieten"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "Films - In Uitvoering"
|
||||
1091
resources/language/resource.language.pl/strings.po
Normal file
1100
resources/language/resource.language.ru/strings.po
Normal file
917
resources/language/resource.language.sv/strings.po
Normal file
@@ -0,0 +1,917 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-06-04 10:22+0000\n"
|
||||
"Last-Translator: hogenf <hogen.fasth@gmail.com>\n"
|
||||
"Language-Team: Swedish <https://translate.jellyfin.org/projects/jellycon/"
|
||||
"jellycon/sv/>\n"
|
||||
"Language: sv\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30283"
|
||||
msgid "Play Next Episode?"
|
||||
msgstr "Spela Nästa Avsnitt?"
|
||||
|
||||
msgctxt "#30240"
|
||||
msgid "Force transcode msmpeg4v3 (divx)"
|
||||
msgstr "Tvinga msmpeg4v3 (divx) transkoding"
|
||||
|
||||
msgctxt "#30218"
|
||||
msgid "Play next episode after %"
|
||||
msgstr "Spela nästa avsnitt efter %"
|
||||
|
||||
msgctxt "#30239"
|
||||
msgid "Force transcode mpeg2"
|
||||
msgstr "Tvinga mpeg2 transkoding"
|
||||
|
||||
msgctxt "#30224"
|
||||
msgid "Interaction"
|
||||
msgstr "Interaktion"
|
||||
|
||||
msgctxt "#30213"
|
||||
msgid "Video force 8 bit"
|
||||
msgstr "Video tvinga 8 bit"
|
||||
|
||||
msgctxt "#30236"
|
||||
msgid "Force transcode h265 (hevc)"
|
||||
msgstr "Tvinga h265 (hevc) transkoding"
|
||||
|
||||
msgctxt "#30254"
|
||||
msgid "Show add-on settings"
|
||||
msgstr "Visa tilläggs inställningar"
|
||||
|
||||
msgctxt "#30223"
|
||||
msgid "Page Size and Filtering"
|
||||
msgstr "Sido Storlek och Filtrering"
|
||||
|
||||
msgctxt "#30212"
|
||||
msgid "Video max width"
|
||||
msgstr "Video max bredd"
|
||||
|
||||
msgctxt "#30211"
|
||||
msgid "Transcode options"
|
||||
msgstr "Transkoding alternativ"
|
||||
|
||||
msgctxt "#30163"
|
||||
msgid "Add (cc) if subtitle is available"
|
||||
msgstr "Lägg till (cc) om undertexter är tillgängliga"
|
||||
|
||||
msgctxt "#30092"
|
||||
msgid "Warning: This action will delete the media files from the server."
|
||||
msgstr "Varning: Detta kommer radera media filerna från servern."
|
||||
|
||||
msgctxt "#30373"
|
||||
msgid "Scanning for local servers"
|
||||
msgstr "Skannar efter lokala servrar"
|
||||
|
||||
msgctxt "#30365"
|
||||
msgid "Manual Login"
|
||||
msgstr "Manuell Inloggning"
|
||||
|
||||
msgctxt "#30372"
|
||||
msgid "Server URL"
|
||||
msgstr "Server URL"
|
||||
|
||||
msgctxt "#30364"
|
||||
msgid "Do you want to save the password?"
|
||||
msgstr "Vill du spara lösenordet?"
|
||||
|
||||
msgctxt "#30350"
|
||||
msgid "Music - Recently Added"
|
||||
msgstr "Musik - Nyligen Tillagt"
|
||||
|
||||
msgctxt "#30360"
|
||||
msgid " - Channels"
|
||||
msgstr "- Kanaler"
|
||||
|
||||
msgctxt "#30353"
|
||||
msgid " - Frequently Played"
|
||||
msgstr "- Ofta Spelat"
|
||||
|
||||
msgctxt "#30349"
|
||||
msgid " - Recently Played"
|
||||
msgstr "- Nyligen Spelat"
|
||||
|
||||
msgctxt "#30351"
|
||||
msgid "Music - Recently Played"
|
||||
msgstr "Musik - Nyligen Spelat"
|
||||
|
||||
msgctxt "#30339"
|
||||
msgid "Person"
|
||||
msgstr "Person"
|
||||
|
||||
msgctxt "#30348"
|
||||
msgid "Add user ratings"
|
||||
msgstr "Lägg till användar betyg"
|
||||
|
||||
msgctxt "#30321"
|
||||
msgid " - Album Artists"
|
||||
msgstr "- Album Artister"
|
||||
|
||||
msgctxt "#30338"
|
||||
msgid "Album"
|
||||
msgstr "Album"
|
||||
|
||||
msgctxt "#30328"
|
||||
msgid "Show empty folders (shows, seasons, collections)"
|
||||
msgstr "Visa tomma mappar (serier, säsonger, samlingar)"
|
||||
|
||||
msgctxt "#30287"
|
||||
msgid "TV Shows - Latest"
|
||||
msgstr "TV Serier - Senaste"
|
||||
|
||||
msgctxt "#30280"
|
||||
msgid "Missing Title"
|
||||
msgstr "Saknar titel"
|
||||
|
||||
msgctxt "#30289"
|
||||
msgid "TV Shows - Genres"
|
||||
msgstr "TV Serier - Genrer"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Existing images : "
|
||||
msgstr "Existerande bilder:"
|
||||
|
||||
msgctxt "#30305"
|
||||
msgid "Not Found"
|
||||
msgstr "Hittades Inte"
|
||||
|
||||
msgctxt "#30316"
|
||||
msgid "Connection Error"
|
||||
msgstr "Anslutnings Error"
|
||||
|
||||
msgctxt "#30310"
|
||||
msgid "Enable Jellyfin remote control"
|
||||
msgstr "Aktivera Jellyfin fjärrkontrol"
|
||||
|
||||
msgctxt "#30325"
|
||||
msgid " - Genres"
|
||||
msgstr "- Genrer"
|
||||
|
||||
msgctxt "#30337"
|
||||
msgid "Song"
|
||||
msgstr "Låt"
|
||||
|
||||
msgctxt "#30346"
|
||||
msgid "Deleteing Cached Images"
|
||||
msgstr "Raderar Cachade Bilder"
|
||||
|
||||
msgctxt "#30354"
|
||||
msgid "Go To Series"
|
||||
msgstr "Gå Till Serie"
|
||||
|
||||
msgctxt "#30361"
|
||||
msgid " - Programs"
|
||||
msgstr "- Program"
|
||||
|
||||
msgctxt "#30362"
|
||||
msgid " - Recordings"
|
||||
msgstr "- Inspelningar"
|
||||
|
||||
msgctxt "#30363"
|
||||
msgid "Save Password?"
|
||||
msgstr "Spara Lösenord?"
|
||||
|
||||
msgctxt "#30374"
|
||||
msgid "Sending request"
|
||||
msgstr "Skickar begäran"
|
||||
|
||||
msgctxt "#30386"
|
||||
msgid "Unused Jellyfin images : "
|
||||
msgstr "Oanvända Jellyfin bilder:"
|
||||
|
||||
msgctxt "#30395"
|
||||
msgid "Clear cached server data"
|
||||
msgstr "Töm cachad server data"
|
||||
|
||||
msgctxt "#30394"
|
||||
msgid "Cache files deleted"
|
||||
msgstr "Cache filer raderade"
|
||||
|
||||
msgctxt "#30393"
|
||||
msgid "Clear Cache Result"
|
||||
msgstr "Töm Cache Resultat"
|
||||
|
||||
msgctxt "#30392"
|
||||
msgid "HTTPS"
|
||||
msgstr "HTTPS"
|
||||
|
||||
msgctxt "#30391"
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "On resume"
|
||||
msgstr "Vid återupptagning"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "Kunde inte ansluta till servern"
|
||||
|
||||
msgctxt "#30216"
|
||||
msgid "Item Details"
|
||||
msgstr "Objekt Detaljer"
|
||||
|
||||
msgctxt "#30183"
|
||||
msgid "Include people"
|
||||
msgstr "Inkludera personer"
|
||||
|
||||
msgctxt "#30182"
|
||||
msgid "Include media stream info"
|
||||
msgstr "Inkludera media stream information"
|
||||
|
||||
msgctxt "#30181"
|
||||
msgid "Include plot"
|
||||
msgstr "Inkludera intrig"
|
||||
|
||||
msgctxt "#30214"
|
||||
msgid "Events"
|
||||
msgstr "Evenemang"
|
||||
|
||||
msgctxt "#30222"
|
||||
msgid "Item Layout"
|
||||
msgstr "Objekt Layout"
|
||||
|
||||
msgctxt "#30241"
|
||||
msgid "Force transcode mpeg4"
|
||||
msgstr "Tvinga mpeg4 transkodning"
|
||||
|
||||
msgctxt "#30260"
|
||||
msgid "BoxSets"
|
||||
msgstr "BoxSet"
|
||||
|
||||
msgctxt "#30259"
|
||||
msgid "Movies - Favorites"
|
||||
msgstr "Filmer - Favoriter"
|
||||
|
||||
msgctxt "#30258"
|
||||
msgid "Movies - In Progress"
|
||||
msgstr "Filmer - Pågående"
|
||||
|
||||
msgctxt "#30266"
|
||||
msgid "Movies - Pages"
|
||||
msgstr "Filmer - Sidor"
|
||||
|
||||
msgctxt "#30265"
|
||||
msgid "Episodes - Next Up"
|
||||
msgstr "Avsnitt - Härnäst"
|
||||
|
||||
msgctxt "#30264"
|
||||
msgid "Episodes - In Progress"
|
||||
msgstr "Avsnitt - Pågående"
|
||||
|
||||
msgctxt "#30263"
|
||||
msgid "Episodes - Recently Added"
|
||||
msgstr "Avsnitt - Nyligen Tillagt"
|
||||
|
||||
msgctxt "#30275"
|
||||
msgid "Force Transcode"
|
||||
msgstr "Tvinga Transkoding"
|
||||
|
||||
msgctxt "#30273"
|
||||
msgid "Unset Favourite"
|
||||
msgstr "Ta Bort Som Favorit"
|
||||
|
||||
msgctxt "#30272"
|
||||
msgid "Set Favourite"
|
||||
msgstr "Sätt Som Favorit"
|
||||
|
||||
msgctxt "#30271"
|
||||
msgid "Mark Unwatched"
|
||||
msgstr "Markera Som Ej Sedd"
|
||||
|
||||
msgctxt "#30270"
|
||||
msgid "Mark Watched"
|
||||
msgstr "Markera Som Sedd"
|
||||
|
||||
msgctxt "#30269"
|
||||
msgid "Movies - Random"
|
||||
msgstr "Filmer - Slumpmässig"
|
||||
|
||||
msgctxt "#30268"
|
||||
msgid " - Recently Added"
|
||||
msgstr "- Nyligen Tillagt"
|
||||
|
||||
msgctxt "#30267"
|
||||
msgid " - In Progress"
|
||||
msgstr "- Pågående"
|
||||
|
||||
msgctxt "#30021"
|
||||
msgid "Show all episodes item"
|
||||
msgstr "Visa alla avsnitt"
|
||||
|
||||
msgctxt "#30262"
|
||||
msgid "TV Shows - Favorites"
|
||||
msgstr "TV Serier - Favoriter"
|
||||
|
||||
msgctxt "#30251"
|
||||
msgid "Movies - Genres"
|
||||
msgstr "Filmer - Genrer"
|
||||
|
||||
msgctxt "#30237"
|
||||
msgid "Start from beginning"
|
||||
msgstr "Starta från början"
|
||||
|
||||
msgctxt "#30252"
|
||||
msgid "Movies - A-Z"
|
||||
msgstr "Filmer - A-Z"
|
||||
|
||||
msgctxt "#30261"
|
||||
msgid "TV Shows"
|
||||
msgstr "TV Serier"
|
||||
|
||||
msgctxt "#30255"
|
||||
msgid "TV Shows - A-Z"
|
||||
msgstr "TV Serier - A-Z"
|
||||
|
||||
msgctxt "#30257"
|
||||
msgid "Movies - Recently Added"
|
||||
msgstr "Filmer - Nyligen Tillagda"
|
||||
|
||||
msgctxt "#30256"
|
||||
msgid "Movies"
|
||||
msgstr "Filmer"
|
||||
|
||||
msgctxt "#30246"
|
||||
msgid "Search"
|
||||
msgstr "Sök"
|
||||
|
||||
msgctxt "#30229"
|
||||
msgid "TV Shows"
|
||||
msgstr "TV Serier"
|
||||
|
||||
msgctxt "#30231"
|
||||
msgid "Movies"
|
||||
msgstr "Filmer"
|
||||
|
||||
msgctxt "#30235"
|
||||
msgid "Episodes"
|
||||
msgstr "Avsnitt"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "Uppspelnings typ"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "Uppspelning"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "Värd"
|
||||
|
||||
msgctxt "#30290"
|
||||
msgid "All"
|
||||
msgstr "Alla"
|
||||
|
||||
msgctxt "#30274"
|
||||
msgid "Delete"
|
||||
msgstr "Radera"
|
||||
|
||||
msgctxt "#30288"
|
||||
msgid " - Latest"
|
||||
msgstr "- Senaste"
|
||||
|
||||
msgctxt "#30296"
|
||||
msgid "Delete"
|
||||
msgstr "Radera"
|
||||
|
||||
msgctxt "#30309"
|
||||
msgid "Select Media Source"
|
||||
msgstr "Välj Media Källa"
|
||||
|
||||
msgctxt "#30322"
|
||||
msgid "Auto resume"
|
||||
msgstr "Återuppta automatiskt"
|
||||
|
||||
msgctxt "#30320"
|
||||
msgid " - Albums"
|
||||
msgstr "- Album"
|
||||
|
||||
msgctxt "#30318"
|
||||
msgid "Music - Albums"
|
||||
msgstr "Musik - Album"
|
||||
|
||||
msgctxt "#30317"
|
||||
msgid "Play All"
|
||||
msgstr "Spela Alla"
|
||||
|
||||
msgctxt "#30306"
|
||||
msgid "Playback starting"
|
||||
msgstr "Uppspelning startar"
|
||||
|
||||
msgctxt "#30312"
|
||||
msgid "All - "
|
||||
msgstr "Alla -"
|
||||
|
||||
msgctxt "#30311"
|
||||
msgid "Library - "
|
||||
msgstr "Bibliotek -"
|
||||
|
||||
msgctxt "#30308"
|
||||
msgid "Select Trailer"
|
||||
msgstr "Välj Trailer"
|
||||
|
||||
msgctxt "#30307"
|
||||
msgid "Play Trailer"
|
||||
msgstr "Spela Trailer"
|
||||
|
||||
msgctxt "#30313"
|
||||
msgid "Menu"
|
||||
msgstr "Meny"
|
||||
|
||||
msgctxt "#30331"
|
||||
msgid "Movies per page"
|
||||
msgstr "Filmer per sida"
|
||||
|
||||
msgctxt "#30314"
|
||||
msgid "Play"
|
||||
msgstr "Spela"
|
||||
|
||||
msgctxt "#30329"
|
||||
msgid "Screensaver"
|
||||
msgstr "Skärmsläckare"
|
||||
|
||||
msgctxt "#30402"
|
||||
msgid "Add to Kodi Playlist"
|
||||
msgstr "Lägg till i Kodi Spellista"
|
||||
|
||||
msgctxt "#30397"
|
||||
msgid " - Pages"
|
||||
msgstr "- Sidor"
|
||||
|
||||
msgctxt "#30383"
|
||||
msgid "System - "
|
||||
msgstr "System -"
|
||||
|
||||
msgctxt "#30381"
|
||||
msgid "More than one"
|
||||
msgstr "Mer än en gång"
|
||||
|
||||
msgctxt "#30377"
|
||||
msgid "Sending request"
|
||||
msgstr "Skickar förfrågan"
|
||||
|
||||
msgctxt "#30410"
|
||||
msgid " - Collections"
|
||||
msgstr "- Samlingar"
|
||||
|
||||
msgctxt "#30399"
|
||||
msgid "Hide"
|
||||
msgstr "Göm"
|
||||
|
||||
msgctxt "#30382"
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
|
||||
msgctxt "#30380"
|
||||
msgid "Never"
|
||||
msgstr "Aldrig"
|
||||
|
||||
msgctxt "#30431"
|
||||
msgid "Seasons"
|
||||
msgstr "Säsonger"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "Välj Server"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "URL error"
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "Jump back seconds"
|
||||
msgstr "Hoppa tillbaka sekunder"
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Done"
|
||||
msgstr "Färdig"
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "No Media Type Set"
|
||||
msgstr "Ingen Media Typ Satt"
|
||||
|
||||
msgctxt "#30389"
|
||||
msgid "User details"
|
||||
msgstr "Användar Detaljer"
|
||||
|
||||
msgctxt "#30404"
|
||||
msgid " - A-Z"
|
||||
msgstr "- A-Z"
|
||||
|
||||
msgctxt "#30413"
|
||||
msgid " - Tags"
|
||||
msgstr "- Etiketter"
|
||||
|
||||
msgctxt "#30422"
|
||||
msgid "Sorting"
|
||||
msgstr "Sorterar"
|
||||
|
||||
msgctxt "#30441"
|
||||
msgid "Use cached widget data"
|
||||
msgstr "Använd cachad widget data"
|
||||
|
||||
msgctxt "#30440"
|
||||
msgid "Play next"
|
||||
msgstr "Spela härnest"
|
||||
|
||||
msgctxt "#30250"
|
||||
msgid "Unknown"
|
||||
msgstr "Okänd"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "Vald Server Adress"
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Services"
|
||||
msgstr "Tjänster"
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "Bekräfta radering?"
|
||||
|
||||
msgctxt "#30052"
|
||||
msgid "Deleting"
|
||||
msgstr "Raderar"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "Avancerat"
|
||||
|
||||
msgctxt "#30011"
|
||||
msgid "[Detect local server]"
|
||||
msgstr "[Upptäck lokal server]"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "Verifiera HTTPS certifikat"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgctxt "#30327"
|
||||
msgid "Go To Season"
|
||||
msgstr "Gå Till Säsong"
|
||||
|
||||
msgctxt "#30437"
|
||||
msgid "Playback options"
|
||||
msgstr "Uppspelnings alternativ"
|
||||
|
||||
msgctxt "#30433"
|
||||
msgid "Allow direct file playback"
|
||||
msgstr "Tillåt direkt fil uppspelning"
|
||||
|
||||
msgctxt "#30432"
|
||||
msgid "Hide watched items in lists"
|
||||
msgstr "Dölj visade objekt i listor"
|
||||
|
||||
msgctxt "#30430"
|
||||
msgid "Label"
|
||||
msgstr "Etikett"
|
||||
|
||||
msgctxt "#30429"
|
||||
msgid "Genre"
|
||||
msgstr "Genre"
|
||||
|
||||
msgctxt "#30427"
|
||||
msgid "Added"
|
||||
msgstr "Tillagd"
|
||||
|
||||
msgctxt "#30426"
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
msgctxt "#30425"
|
||||
msgid "Year"
|
||||
msgstr "År"
|
||||
|
||||
msgctxt "#30428"
|
||||
msgid "Rating"
|
||||
msgstr "Betyg"
|
||||
|
||||
msgctxt "#30417"
|
||||
msgid "You do not have permision to delete this item"
|
||||
msgstr "Du har inte behörighet att ta bort detta objekt"
|
||||
|
||||
msgctxt "#30412"
|
||||
msgid " - Decades"
|
||||
msgstr "- Decennier"
|
||||
|
||||
msgctxt "#30421"
|
||||
msgid "Views"
|
||||
msgstr "Visningar"
|
||||
|
||||
msgctxt "#30406"
|
||||
msgid "Jellyfin Libraries"
|
||||
msgstr "Jellyfin Bibliotek"
|
||||
|
||||
msgctxt "#30388"
|
||||
msgid "Server details"
|
||||
msgstr "Server Detaljer"
|
||||
|
||||
msgctxt "#30403"
|
||||
msgid "Movies - Recommendations"
|
||||
msgstr "Filmer - Rekommendationer"
|
||||
|
||||
msgctxt "#30405"
|
||||
msgid " - Show All"
|
||||
msgstr "- Visa Alla"
|
||||
|
||||
msgctxt "#30390"
|
||||
msgid "Protocol"
|
||||
msgstr "Protokol"
|
||||
|
||||
msgctxt "#30401"
|
||||
msgid "Info"
|
||||
msgstr "Information"
|
||||
|
||||
msgctxt "#30411"
|
||||
msgid " - Years"
|
||||
msgstr "- År"
|
||||
|
||||
msgctxt "#30424"
|
||||
msgid "Default"
|
||||
msgstr "Standard"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "Välj användare"
|
||||
|
||||
msgctxt "#30169"
|
||||
msgid "Address: "
|
||||
msgstr "Adress:"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "Användargränssnitt"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Retrieving Data"
|
||||
msgstr "Hämtar data"
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Loading Content"
|
||||
msgstr "Laddar Innehåll"
|
||||
|
||||
msgctxt "#30045"
|
||||
msgid "Username not found"
|
||||
msgstr "Användarnamn kunde inte hittas"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[Ändra användare]"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "Lösenord:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "Användarnamn:"
|
||||
|
||||
msgctxt "#30017"
|
||||
msgid "Show connected clients"
|
||||
msgstr "Visa anslutna enheter"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Samba lösenord"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Samba användarnamn"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "Lösenord"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "Användarnamn"
|
||||
|
||||
msgctxt "#30332"
|
||||
msgid "Stop media playback on screensaver activation"
|
||||
msgstr "Stoppa mediauppspelning vid aktivering av skärmsläckare"
|
||||
|
||||
msgctxt "#30330"
|
||||
msgid "Show change user dialog"
|
||||
msgstr "Visa ändra användardialogrutan"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30315"
|
||||
msgid "Suppress notifications for connection errors"
|
||||
msgstr "Undertryck aviseringar för anslutningsfel"
|
||||
|
||||
msgctxt "#30304"
|
||||
msgid "Cached Jellyfin images : "
|
||||
msgstr "Cachade Jellyfin-bilder:"
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "Missing Jellyfin images : "
|
||||
msgstr "Saknade Jellyfin-bilder:"
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Caching Images"
|
||||
msgstr "Cacha bilder"
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "Cache all Jellyfin images as local Kodi images?"
|
||||
msgstr "Cache alla Jellyfin-bilder som lokala Kodi-bilder?"
|
||||
|
||||
msgctxt "#30299"
|
||||
msgid "Cache Images"
|
||||
msgstr "Cachebilder"
|
||||
|
||||
msgctxt "#30298"
|
||||
msgid "Deleting Kodi Images"
|
||||
msgstr "Ta bort Kodi-bilder"
|
||||
|
||||
msgctxt "#30297"
|
||||
msgid "Delete unused images?"
|
||||
msgstr "Ta bort oanvända bilder?"
|
||||
|
||||
msgctxt "#30295"
|
||||
msgid "To use this feature you need HTTP control enabled"
|
||||
msgstr "För att använda den här funktionen måste du aktivera HTTP-kontroll"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30294"
|
||||
msgid "Notice"
|
||||
msgstr "Notis"
|
||||
|
||||
msgctxt "#30293"
|
||||
msgid "Cache images"
|
||||
msgstr "Cachebilder"
|
||||
|
||||
msgctxt "#30292"
|
||||
msgid "Select Subtitle Stream"
|
||||
msgstr "Välj undertextström"
|
||||
|
||||
msgctxt "#30291"
|
||||
msgid "Select Audio Stream"
|
||||
msgstr "Välj ljudström"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30118"
|
||||
msgid "Add resume percent to names"
|
||||
msgstr "Lägg till fortsättningsprocent i namn"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30116"
|
||||
msgid "Add unwatched counts to names"
|
||||
msgstr "Lägg till ej sedda räkningar till namn"
|
||||
|
||||
msgctxt "#30279"
|
||||
msgid "TV Shows - Unwatched"
|
||||
msgstr "TV Serier- Ej sedd"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30286"
|
||||
msgid "Movies - Unwatched"
|
||||
msgstr "Film - Ej sedd"
|
||||
|
||||
msgctxt "#30285"
|
||||
msgid " - Unwatched"
|
||||
msgstr "- Ej sedd"
|
||||
|
||||
msgctxt "#30282"
|
||||
msgid "No Jellyfin servers detected on your local network."
|
||||
msgstr "Inga Jellyfin-servrar upptäcktes på ditt lokala nätverk."
|
||||
|
||||
msgctxt "#30281"
|
||||
msgid "Refresh Cached Images"
|
||||
msgstr "Uppdatera cachelagrade bilder"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30278"
|
||||
msgid " - Next Up"
|
||||
msgstr "- Nästa upp"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30277"
|
||||
msgid "JellyCon needs to prompt for resume on partily played items, Kodi can also prompt, this can cause a double prompt. Do you want to remove the double prompt?"
|
||||
msgstr ""
|
||||
"JellyCon måste fråga om återuppta på partiellt spelade objekt, Kodi kan "
|
||||
"också fråga, detta kan orsaka en dubbel prompt. Vill du ta bort den dubbla "
|
||||
"prompten?"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30276"
|
||||
msgid "Extra Resume Prompt Detected"
|
||||
msgstr "Extra uppmaning om återuppta upptäckt"
|
||||
|
||||
msgctxt "#30247"
|
||||
msgid "Custom Widget Content"
|
||||
msgstr "Anpassat widgetinnehåll"
|
||||
|
||||
msgctxt "#30238"
|
||||
msgid "Playback stream options"
|
||||
msgstr "Alternativ för uppspelning av stream"
|
||||
|
||||
msgctxt "#30220"
|
||||
msgid "Prompt to delete movie after %"
|
||||
msgstr "Uppmaning att radera film efter %"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30219"
|
||||
msgid " - Prompt before play"
|
||||
msgstr "- Fråga före uppspelning"
|
||||
|
||||
msgctxt "#30217"
|
||||
msgid "Prompt to delete episode after %"
|
||||
msgstr "Uppmaning att radera avsnitt efter %"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30215"
|
||||
msgid "On playback stop (100% = disabled)"
|
||||
msgstr "Vid uppspelningsstopp (100 % = avaktiverad)"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30210"
|
||||
msgid "HTTP direct stream"
|
||||
msgstr "HTTP direktström"
|
||||
|
||||
msgctxt "#30209"
|
||||
msgid "File direct path"
|
||||
msgstr "Direkt sökväg till filen"
|
||||
|
||||
msgctxt "#30208"
|
||||
msgid "Max stream bitrate (Kbits)"
|
||||
msgstr "Högsta strömbithastighet (kbps)"
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Processing Item : "
|
||||
msgstr "Bearbetning av objektet:"
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Show load progress"
|
||||
msgstr "Visa laddningsförloppet"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "Ej tillgängligt"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30053"
|
||||
msgid "Waiting for server to delete"
|
||||
msgstr "Väntar på att servern ska radera"
|
||||
|
||||
msgctxt "#30044"
|
||||
msgid "Incorrect Username/Password"
|
||||
msgstr "Fel användarnamn/lösenord"
|
||||
|
||||
msgctxt "#30027"
|
||||
msgid "Enable debug logging"
|
||||
msgstr "Aktivera felsökningsloggning"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30026"
|
||||
msgid "Widget item select action"
|
||||
msgstr "Välj åtgärd för widgetobjekt"
|
||||
|
||||
msgctxt "#30023"
|
||||
msgid "Hide unwatched episode details"
|
||||
msgstr "Dölj detaljer om avsnittet som inte har setts"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30020"
|
||||
msgid "Flatten single season"
|
||||
msgstr "Platta enstaka säsong"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30019"
|
||||
msgid "Filtered episode name format"
|
||||
msgstr "Filtrerat avsnittsnamnformat"
|
||||
|
||||
msgctxt "#30018"
|
||||
msgid "Number of items to show in filtered lists"
|
||||
msgstr "Antal objekt som ska visas i filtrerade listor"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Device display name"
|
||||
msgstr "Enhetens visningsnamn"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30015"
|
||||
msgid "Log timing data"
|
||||
msgstr "Logga tidsdata"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "#30010"
|
||||
msgid "Number of performance profiles to capture"
|
||||
msgstr "Antal prestandaprofiler som ska fångas"
|
||||
2
resources/language/resource.language.tr/strings.po
Normal file
@@ -0,0 +1,2 @@
|
||||
msgid ""
|
||||
msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit"
|
||||
2
resources/language/resource.language.uk/strings.po
Normal file
@@ -0,0 +1,2 @@
|
||||
msgid ""
|
||||
msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit"
|
||||
1084
resources/language/resource.language.zh_Hans/strings.po
Normal file
104
resources/language/resource.language.zh_Hant/strings.po
Normal file
@@ -0,0 +1,104 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2022-04-14 06:13+0000\n"
|
||||
"Last-Translator: rayanamukami <rayanamukami@gmail.com>\n"
|
||||
"Language-Team: Chinese (Traditional) <https://translate.jellyfin.org/"
|
||||
"projects/jellycon/jellycon/zh_Hant/>\n"
|
||||
"Language: zh_Hant\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
|
||||
msgctxt "#30207"
|
||||
msgid "Playback"
|
||||
msgstr "撥放"
|
||||
|
||||
msgctxt "#30206"
|
||||
msgid "Playback type"
|
||||
msgstr "撥放類型"
|
||||
|
||||
msgctxt "#30201"
|
||||
msgid "Unable to connect to server"
|
||||
msgstr "無法連接到伺服器"
|
||||
|
||||
msgctxt "#30200"
|
||||
msgid "URL error"
|
||||
msgstr "網址錯誤"
|
||||
|
||||
msgctxt "#30180"
|
||||
msgid "Select User"
|
||||
msgstr "選擇使用者"
|
||||
|
||||
msgctxt "#30167"
|
||||
msgid "Selected Server Address"
|
||||
msgstr "選擇伺服器位置"
|
||||
|
||||
msgctxt "#30166"
|
||||
msgid "Select Server"
|
||||
msgstr "選擇伺服器"
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Error"
|
||||
msgstr "錯誤"
|
||||
|
||||
msgctxt "#30063"
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
msgctxt "#30091"
|
||||
msgid "Confirm delete?"
|
||||
msgstr "是否確認刪除?"
|
||||
|
||||
msgctxt "#30025"
|
||||
msgid "Password:"
|
||||
msgstr "密碼:"
|
||||
|
||||
msgctxt "#30024"
|
||||
msgid "Username:"
|
||||
msgstr "使用者名稱:"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Interface"
|
||||
msgstr "介面"
|
||||
|
||||
msgctxt "#30022"
|
||||
msgid "Advanced"
|
||||
msgstr "進階"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Jellyfin"
|
||||
msgstr "Jellyfin"
|
||||
|
||||
msgctxt "#30012"
|
||||
msgid "[Change user]"
|
||||
msgstr "[變更使用者]"
|
||||
|
||||
msgctxt "#30008"
|
||||
msgid "Samba password"
|
||||
msgstr "Samba 密碼"
|
||||
|
||||
msgctxt "#30007"
|
||||
msgid "Samba username"
|
||||
msgstr "Samba 使用者名稱"
|
||||
|
||||
msgctxt "#30003"
|
||||
msgid "Verify HTTPS certificate"
|
||||
msgstr "驗證 HTTPS 憑證"
|
||||
|
||||
msgctxt "#30006"
|
||||
msgid "Password"
|
||||
msgstr "密碼"
|
||||
|
||||
msgctxt "#30005"
|
||||
msgid "Username"
|
||||
msgstr "使用者名稱"
|
||||
|
||||
msgctxt "#30001"
|
||||
msgid "Port"
|
||||
msgstr "埠"
|
||||
|
||||
msgctxt "#30000"
|
||||
msgid "Host"
|
||||
msgstr "主機"
|
||||
@@ -7,7 +7,7 @@ import threading
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class BitrateDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
slider_control = None
|
||||
bitrate_label = None
|
||||
initial_bitrate_value = 0
|
||||
selected_transcode_value = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("BitrateDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("ActionMenu: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
self.slider_control = self.getControl(3000)
|
||||
self.slider_control.setInt(self.initial_bitrate_value, 400, 100, 15000)
|
||||
|
||||
self.bitrate_label = self.getControl(3030)
|
||||
bitrate_label_string = str(self.slider_control.getInt()) + " Kbs"
|
||||
self.bitrate_label.setLabel(bitrate_label_string)
|
||||
|
||||
def onFocus(self, control_id):
|
||||
pass
|
||||
|
||||
def doAction(self, action_id):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("ActionMenu: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
bitrate_label_string = str(self.slider_control.getInt()) + " Kbs"
|
||||
self.bitrate_label.setLabel(bitrate_label_string)
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
elif action.getId() == 7: # ENTER
|
||||
self.selected_transcode_value = self.slider_control.getInt()
|
||||
self.close()
|
||||
|
||||
def onClick(self, control_id):
|
||||
if control_id == 3000:
|
||||
log.debug("ActionMenu: Selected Item: {0}".format(control_id))
|
||||
@@ -14,15 +14,13 @@ import xbmcplugin
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
|
||||
from .downloadutils import DownloadUtils
|
||||
from .loghandler import LazyLogger
|
||||
from .jellyfin import api
|
||||
from .lazylogger import LazyLogger
|
||||
from .jsonrpc import JsonRpc, get_value
|
||||
from .translation import string_load
|
||||
from .datamanager import DataManager
|
||||
from .utils import get_art, double_urlencode
|
||||
from .utils import translate_string, load_user_details
|
||||
from .kodi_utils import HomeWindow
|
||||
from .item_functions import get_art
|
||||
|
||||
downloadUtils = DownloadUtils()
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
@@ -70,8 +68,8 @@ class CacheArtwork(threading.Thread):
|
||||
log.debug("cache_delete_for_links")
|
||||
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(string_load(30281))
|
||||
progress.update(30, string_load(30347))
|
||||
progress.create(translate_string(30281))
|
||||
progress.update(30, translate_string(30347))
|
||||
|
||||
item_image_url_part = "Items/%s/Images/" % item_id
|
||||
item_image_url_part = item_image_url_part.replace("/", "%2f")
|
||||
@@ -82,7 +80,7 @@ class CacheArtwork(threading.Thread):
|
||||
result = JsonRpc('Settings.GetSettingValue').execute(web_query)
|
||||
xbmc_webserver_enabled = result['result']['value']
|
||||
if not xbmc_webserver_enabled:
|
||||
xbmcgui.Dialog().ok(string_load(30294), string_load(30295))
|
||||
xbmcgui.Dialog().ok(translate_string(30294), translate_string(30295))
|
||||
return
|
||||
|
||||
params = {"properties": ["url"]}
|
||||
@@ -90,7 +88,7 @@ class CacheArtwork(threading.Thread):
|
||||
textures = json_result.get("result", {}).get("textures", [])
|
||||
log.debug("texture ids: {0}".format(textures))
|
||||
|
||||
progress.update(70, string_load(30346))
|
||||
progress.update(70, translate_string(30346))
|
||||
|
||||
delete_count = 0
|
||||
for texture in textures:
|
||||
@@ -104,10 +102,10 @@ class CacheArtwork(threading.Thread):
|
||||
|
||||
del textures
|
||||
|
||||
progress.update(100, string_load(30125))
|
||||
progress.update(100, translate_string(30125))
|
||||
progress.close()
|
||||
|
||||
xbmcgui.Dialog().ok(string_load(30281), '{}: {}'.format(string_load(30344), delete_count))
|
||||
xbmcgui.Dialog().ok(translate_string(30281), '{}: {}'.format(translate_string(30344), delete_count))
|
||||
|
||||
def cache_artwork_interactive(self):
|
||||
log.debug("cache_artwork_interactive")
|
||||
@@ -119,21 +117,21 @@ class CacheArtwork(threading.Thread):
|
||||
result = JsonRpc('Settings.GetSettingValue').execute(web_query)
|
||||
xbmc_webserver_enabled = result['result']['value']
|
||||
if not xbmc_webserver_enabled:
|
||||
xbmcgui.Dialog().ok(string_load(30294), '{} - {}'.format(string_load(30295), string_load(30355)))
|
||||
xbmcgui.Dialog().ok(translate_string(30294), '{} - {}'.format(translate_string(30295), translate_string(30355)))
|
||||
xbmc.executebuiltin('ActivateWindow(servicesettings)')
|
||||
return
|
||||
|
||||
result_report = []
|
||||
|
||||
# ask questions
|
||||
question_delete_unused = xbmcgui.Dialog().yesno(string_load(30296), string_load(30297))
|
||||
question_cache_images = xbmcgui.Dialog().yesno(string_load(30299), string_load(30300))
|
||||
question_delete_unused = xbmcgui.Dialog().yesno(translate_string(30296), translate_string(30297))
|
||||
question_cache_images = xbmcgui.Dialog().yesno(translate_string(30299), translate_string(30300))
|
||||
|
||||
delete_canceled = False
|
||||
# now do work - delete unused
|
||||
if question_delete_unused:
|
||||
delete_pdialog = xbmcgui.DialogProgress()
|
||||
delete_pdialog.create(string_load(30298), "")
|
||||
delete_pdialog.create(translate_string(30298), "")
|
||||
index = 0
|
||||
|
||||
params = {"properties": ["url"]}
|
||||
@@ -171,9 +169,9 @@ class CacheArtwork(threading.Thread):
|
||||
delete_canceled = True
|
||||
break
|
||||
|
||||
result_report.append(string_load(30385) + str(len(textures)))
|
||||
result_report.append(string_load(30386) + str(len(unused_texture_ids)))
|
||||
result_report.append(string_load(30387) + str(index))
|
||||
result_report.append(translate_string(30385) + str(len(textures)))
|
||||
result_report.append(translate_string(30386) + str(len(unused_texture_ids)))
|
||||
result_report.append(translate_string(30387) + str(index))
|
||||
|
||||
del textures
|
||||
del jellyfin_texture_urls
|
||||
@@ -187,7 +185,7 @@ class CacheArtwork(threading.Thread):
|
||||
# now do work - cache images
|
||||
if question_cache_images:
|
||||
cache_pdialog = xbmcgui.DialogProgress()
|
||||
cache_pdialog.create(string_load(30301), "")
|
||||
cache_pdialog.create(translate_string(30301), "")
|
||||
cache_report = self.cache_artwork(cache_pdialog)
|
||||
cache_pdialog.close()
|
||||
del cache_pdialog
|
||||
@@ -196,12 +194,12 @@ class CacheArtwork(threading.Thread):
|
||||
|
||||
if len(result_report) > 0:
|
||||
msg = "\r\n".join(result_report)
|
||||
xbmcgui.Dialog().textviewer(string_load(30125), msg, usemono=True)
|
||||
xbmcgui.Dialog().textviewer(translate_string(30125), msg, usemono=True)
|
||||
|
||||
def cache_artwork_background(self):
|
||||
log.debug("cache_artwork_background")
|
||||
dp = xbmcgui.DialogProgressBG()
|
||||
dp.create(string_load(30301), "")
|
||||
dp.create(translate_string(30301), "")
|
||||
result_text = None
|
||||
try:
|
||||
result_text = self.cache_artwork(dp)
|
||||
@@ -214,9 +212,11 @@ class CacheArtwork(threading.Thread):
|
||||
|
||||
def get_jellyfin_artwork(self, progress):
|
||||
log.debug("get_jellyfin_artwork")
|
||||
user_details = load_user_details()
|
||||
user_id = user_details.get('user_id')
|
||||
|
||||
url = ""
|
||||
url += "{server}/Users/{userid}/Items"
|
||||
url += "/Users/{}/Items".format(user_id)
|
||||
url += "?Recursive=true"
|
||||
url += "&EnableUserData=False"
|
||||
url += "&Fields=BasicSyncInfo"
|
||||
@@ -224,21 +224,21 @@ class CacheArtwork(threading.Thread):
|
||||
url += "&ImageTypeLimit=1"
|
||||
url += "&format=json"
|
||||
|
||||
data_manager = DataManager()
|
||||
results = data_manager.get_content(url)
|
||||
results = api.get(url)
|
||||
if results is None:
|
||||
results = []
|
||||
|
||||
if isinstance(results, dict):
|
||||
results = results.get("Items")
|
||||
|
||||
server = downloadUtils.get_server()
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
log.debug("Jellyfin Item Count Count: {0}".format(len(results)))
|
||||
|
||||
if self.stop_all_activity:
|
||||
return None
|
||||
|
||||
progress.update(0, string_load(30359))
|
||||
progress.update(0, translate_string(30359))
|
||||
|
||||
texture_urls = set()
|
||||
|
||||
@@ -268,7 +268,7 @@ class CacheArtwork(threading.Thread):
|
||||
# get the password
|
||||
xbmc_password = get_value("services.webserverpassword")
|
||||
|
||||
progress.update(0, string_load(30356))
|
||||
progress.update(0, translate_string(30356))
|
||||
|
||||
params = {"properties": ["url"]}
|
||||
json_result = JsonRpc('Textures.GetTextures').execute(params)
|
||||
@@ -278,7 +278,7 @@ class CacheArtwork(threading.Thread):
|
||||
if self.stop_all_activity:
|
||||
return
|
||||
|
||||
progress.update(0, string_load(30357))
|
||||
progress.update(0, translate_string(30357))
|
||||
|
||||
texture_urls = set()
|
||||
for texture in textures:
|
||||
@@ -296,7 +296,7 @@ class CacheArtwork(threading.Thread):
|
||||
if self.stop_all_activity:
|
||||
return
|
||||
|
||||
progress.update(0, string_load(30358))
|
||||
progress.update(0, translate_string(30358))
|
||||
|
||||
jellyfin_texture_urls = self.get_jellyfin_artwork(progress)
|
||||
if jellyfin_texture_urls is None:
|
||||
@@ -326,8 +326,7 @@ class CacheArtwork(threading.Thread):
|
||||
|
||||
count_done = 0
|
||||
for index, get_url in enumerate(missing_texture_urls, 1):
|
||||
url = double_urlencode(get_url)
|
||||
kodi_texture_url = ("/image/image://%s" % url)
|
||||
kodi_texture_url = "/image/image://{0}".format(get_url)
|
||||
log.debug("kodi_texture_url: {0}".format(kodi_texture_url))
|
||||
|
||||
percentage = int((float(index) / float(total)) * 100)
|
||||
@@ -348,7 +347,7 @@ class CacheArtwork(threading.Thread):
|
||||
break
|
||||
|
||||
result_report = []
|
||||
result_report.append(string_load(30302) + str(len(texture_urls)))
|
||||
result_report.append(string_load(30303) + str(len(missing_texture_urls)))
|
||||
result_report.append(string_load(30304) + str(count_done))
|
||||
result_report.append(translate_string(30302) + str(len(texture_urls)))
|
||||
result_report.append(translate_string(30303) + str(len(missing_texture_urls)))
|
||||
result_report.append(translate_string(30304) + str(count_done))
|
||||
return result_report
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
from uuid import uuid4
|
||||
from kodi_six.utils import py2_decode
|
||||
import xbmcaddon
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
|
||||
from .kodi_utils import HomeWindow
|
||||
from .loghandler import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class ClientInformation:
|
||||
|
||||
@staticmethod
|
||||
def get_device_id():
|
||||
|
||||
window = HomeWindow()
|
||||
client_id = window.get_property("client_id")
|
||||
|
||||
if client_id:
|
||||
return client_id
|
||||
|
||||
jellyfin_guid_path = py2_decode(xbmc.translatePath("special://temp/jellycon_guid"))
|
||||
log.debug("jellyfin_guid_path: {0}".format(jellyfin_guid_path))
|
||||
guid = xbmcvfs.File(jellyfin_guid_path)
|
||||
client_id = guid.read()
|
||||
guid.close()
|
||||
|
||||
if not client_id:
|
||||
# Needs to be captilized for backwards compat
|
||||
client_id = uuid4().hex.upper()
|
||||
log.debug("Generating a new guid: {0}".format(client_id))
|
||||
guid = xbmcvfs.File(jellyfin_guid_path, 'w')
|
||||
guid.write(client_id)
|
||||
guid.close()
|
||||
log.debug("jellyfin_client_id (NEW): {0}".format(client_id))
|
||||
else:
|
||||
log.debug("jellyfin_client_id: {0}".format(client_id))
|
||||
|
||||
window.set_property("client_id", client_id)
|
||||
return client_id
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
addon = xbmcaddon.Addon()
|
||||
version = addon.getAddonInfo("version")
|
||||
return version
|
||||
|
||||
@staticmethod
|
||||
def get_client():
|
||||
return 'Kodi JellyCon'
|
||||
@@ -1,41 +0,0 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
import threading
|
||||
import xbmc
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from resources.lib.functions import show_menu
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class ContextMonitor(threading.Thread):
|
||||
|
||||
stop_thread = False
|
||||
|
||||
def run(self):
|
||||
|
||||
item_id = None
|
||||
log.debug("ContextMonitor Thread Started")
|
||||
|
||||
while not xbmc.Monitor().abortRequested() and not self.stop_thread:
|
||||
|
||||
if xbmc.getCondVisibility("Window.IsActive(fullscreenvideo) | Window.IsActive(visualisation)"):
|
||||
xbmc.sleep(1000)
|
||||
else:
|
||||
if xbmc.getCondVisibility("Window.IsVisible(contextmenu)"):
|
||||
if item_id:
|
||||
xbmc.executebuiltin("Dialog.Close(contextmenu,true)")
|
||||
params = {}
|
||||
params["item_id"] = item_id
|
||||
show_menu(params)
|
||||
|
||||
container_id = xbmc.getInfoLabel("System.CurrentControlID")
|
||||
item_id = xbmc.getInfoLabel("Container(" + str(container_id) + ").ListItem.Property(id)")
|
||||
|
||||
xbmc.sleep(100)
|
||||
|
||||
log.debug("ContextMonitor Thread Exited")
|
||||
|
||||
def stop_monitor(self):
|
||||
log.debug("ContextMonitor Stop Called")
|
||||
self.stop_thread = True
|
||||
@@ -1,20 +1,19 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
from six.moves import cPickle
|
||||
|
||||
from .downloadutils import DownloadUtils
|
||||
from .loghandler import LazyLogger
|
||||
from .jellyfin import api
|
||||
from .lazylogger import LazyLogger
|
||||
from .item_functions import extract_item_info
|
||||
from .kodi_utils import HomeWindow
|
||||
from .translation import string_load
|
||||
from .tracking import timer
|
||||
from .filelock import FileLock
|
||||
from .utils import translate_string, load_user_details, translate_path
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
@@ -40,15 +39,12 @@ class CacheItem:
|
||||
|
||||
class DataManager:
|
||||
|
||||
addon_dir = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
addon_dir = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
|
||||
def __init__(self, *args):
|
||||
# log.debug("DataManager __init__")
|
||||
pass
|
||||
self.user_details = load_user_details()
|
||||
|
||||
@timer
|
||||
def get_content(self, url):
|
||||
return DownloadUtils().download_url(url)
|
||||
self.api = api
|
||||
|
||||
@timer
|
||||
def get_items(self, url, gui_options, use_cache=False):
|
||||
@@ -57,19 +53,14 @@ class DataManager:
|
||||
log.debug("last_content_url : use_cache={0} url={1}".format(use_cache, url))
|
||||
home_window.set_property("last_content_url", url)
|
||||
|
||||
download_utils = DownloadUtils()
|
||||
user_id = download_utils.get_user_id()
|
||||
server = download_utils.get_server()
|
||||
user_id = self.user_details.get('user_id')
|
||||
server = self.api.server
|
||||
|
||||
m = hashlib.md5()
|
||||
m.update('{}|{}|{}'.format(user_id, server, url).encode())
|
||||
url_hash = m.hexdigest()
|
||||
cache_file = os.path.join(self.addon_dir, "cache_" + url_hash + ".pickle")
|
||||
|
||||
# changed_url = url + "&MinDateLastSavedForUser=" + urllib.unquote("2019-09-16T13:45:30")
|
||||
# results = self.GetContent(changed_url)
|
||||
# log.debug("DataManager Changes Since Date : {0}", results)
|
||||
|
||||
item_list = None
|
||||
total_records = 0
|
||||
baseline_name = None
|
||||
@@ -103,7 +94,7 @@ class DataManager:
|
||||
if item_list is None or len(item_list) == 0:
|
||||
log.debug("Loading url data from server")
|
||||
|
||||
results = self.get_content(url)
|
||||
results = self.api.get(url)
|
||||
|
||||
if results is None:
|
||||
results = []
|
||||
@@ -135,7 +126,6 @@ class DataManager:
|
||||
cache_item.total_records = total_records
|
||||
|
||||
cache_thread.cached_item = cache_item
|
||||
# copy.deepcopy(item_list)
|
||||
|
||||
if not use_cache:
|
||||
cache_thread = None
|
||||
@@ -171,7 +161,6 @@ class CacheManagerThread(threading.Thread):
|
||||
def run(self):
|
||||
|
||||
log.debug("CacheManagerThread : Started")
|
||||
# log.debug("CacheManagerThread : Cache Item : {0}", self.cached_item.__dict__)
|
||||
|
||||
home_window = HomeWindow()
|
||||
is_fresh = False
|
||||
@@ -203,7 +192,7 @@ class CacheManagerThread(threading.Thread):
|
||||
log.debug("CacheManagerThread : Cache Hash : {0}".format(cached_hash))
|
||||
|
||||
data_manager = DataManager()
|
||||
results = data_manager.get_content(self.cached_item.items_url)
|
||||
results = data_manager.api.get(self.cached_item.items_url)
|
||||
if results is None:
|
||||
results = []
|
||||
|
||||
@@ -264,7 +253,7 @@ class CacheManagerThread(threading.Thread):
|
||||
def clear_cached_server_data():
|
||||
log.debug("clear_cached_server_data() called")
|
||||
|
||||
addon_dir = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
addon_dir = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
dirs, files = xbmcvfs.listdir(addon_dir)
|
||||
|
||||
del_count = 0
|
||||
@@ -275,14 +264,14 @@ def clear_cached_server_data():
|
||||
del_count += 1
|
||||
|
||||
log.debug('Deleted {} files'.format(del_count))
|
||||
msg = string_load(30394)
|
||||
xbmcgui.Dialog().ok(string_load(30393), msg)
|
||||
msg = translate_string(30394)
|
||||
xbmcgui.Dialog().ok(translate_string(30393), msg)
|
||||
|
||||
|
||||
def clear_old_cache_data():
|
||||
log.debug("clear_old_cache_data() : called")
|
||||
|
||||
addon_dir = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
addon_dir = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
dirs, files = xbmcvfs.listdir(addon_dir)
|
||||
|
||||
del_count = 0
|
||||
|
||||
246
resources/lib/dialogs.py
Normal file
@@ -0,0 +1,246 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmcgui
|
||||
|
||||
from .lazylogger import LazyLogger
|
||||
from .utils import translate_string, send_event_notification
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class BitrateDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
slider_control = None
|
||||
bitrate_label = None
|
||||
initial_bitrate_value = 0
|
||||
selected_transcode_value = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("BitrateDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("ActionMenu: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
self.slider_control = self.getControl(3000)
|
||||
self.slider_control.setInt(self.initial_bitrate_value, 400, 100, 15000)
|
||||
|
||||
self.bitrate_label = self.getControl(3030)
|
||||
bitrate_label_string = str(self.slider_control.getInt()) + " Kbs"
|
||||
self.bitrate_label.setLabel(bitrate_label_string)
|
||||
self.getControl(3011).setLabel(translate_string(30314))
|
||||
|
||||
def onFocus(self, control_id):
|
||||
pass
|
||||
|
||||
def doAction(self, action_id):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("ActionMenu: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
bitrate_label_string = str(self.slider_control.getInt()) + " Kbs"
|
||||
self.bitrate_label.setLabel(bitrate_label_string)
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
elif action.getId() == 7: # ENTER
|
||||
self.selected_transcode_value = self.slider_control.getInt()
|
||||
self.close()
|
||||
|
||||
def onClick(self, control_id):
|
||||
if control_id == 3000:
|
||||
log.debug("ActionMenu: Selected Item: {0}".format(control_id))
|
||||
|
||||
|
||||
class ResumeDialog(xbmcgui.WindowXMLDialog):
|
||||
resumePlay = -1
|
||||
resumeTimeStamp = ""
|
||||
action_exitkeys_id = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
log.debug("ResumeDialog INITIALISED")
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
self.getControl(3010).setLabel(self.resumeTimeStamp)
|
||||
self.getControl(3011).setLabel(translate_string(30237))
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self, actionID):
|
||||
pass
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if controlID == 3010:
|
||||
self.resumePlay = 0
|
||||
self.close()
|
||||
if controlID == 3011:
|
||||
self.resumePlay = 1
|
||||
self.close()
|
||||
|
||||
def setResumeTime(self, timeStamp):
|
||||
self.resumeTimeStamp = timeStamp
|
||||
|
||||
def getResumeAction(self):
|
||||
return self.resumePlay
|
||||
|
||||
|
||||
class SafeDeleteDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
confirm = False
|
||||
message = "Demo Message"
|
||||
heading = "Demo Heading"
|
||||
action_exitkeys_id = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("SafeDeleteDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("SafeDeleteDialog: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
message_control = self.getControl(3)
|
||||
message_control.setText(self.message)
|
||||
|
||||
message_control = self.getControl(4)
|
||||
message_control.setLabel(self.heading)
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self, actionID):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("SafeDeleteDialog: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
else:
|
||||
log.debug("SafeDeleteDialog: onAction: {0}".format(action.getId()))
|
||||
|
||||
def onClick(self, controlID):
|
||||
if controlID == 1:
|
||||
self.confirm = True
|
||||
self.close()
|
||||
elif controlID == 2:
|
||||
self.confirm = False
|
||||
self.close()
|
||||
|
||||
|
||||
class PlayNextDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
action_exitkeys_id = None
|
||||
episode_info = None
|
||||
play_called = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("PlayNextDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("PlayNextDialog: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
index = self.episode_info.get("IndexNumber", -1)
|
||||
series_name = self.episode_info.get("SeriesName")
|
||||
next_epp_name = "Episode %02d - (%s)" % (index, self.episode_info.get("Name", "n/a"))
|
||||
|
||||
series_label = self.getControl(3011)
|
||||
series_label.setLabel(series_name)
|
||||
|
||||
series_label = self.getControl(3012)
|
||||
series_label.setLabel(next_epp_name)
|
||||
|
||||
def onFocus(self, control_id):
|
||||
pass
|
||||
|
||||
def doAction(self, action_id):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("PlayNextDialog: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
else:
|
||||
log.debug("PlayNextDialog: onAction: {0}".format(action.getId()))
|
||||
|
||||
def onClick(self, control_id):
|
||||
if control_id == 3013:
|
||||
log.debug("PlayNextDialog: Play Next Episode")
|
||||
self.play_called
|
||||
self.close()
|
||||
next_item_id = self.episode_info.get("Id")
|
||||
log.debug("Playing Next Episode: {0}".format(next_item_id))
|
||||
play_info = {}
|
||||
play_info["item_id"] = next_item_id
|
||||
play_info["auto_resume"] = "-1"
|
||||
play_info["force_transcode"] = False
|
||||
send_event_notification("jellycon_play_action", play_info)
|
||||
elif control_id == 3014:
|
||||
self.close()
|
||||
|
||||
def set_episode_info(self, info):
|
||||
self.episode_info = info
|
||||
|
||||
def get_play_called(self):
|
||||
return self.play_called
|
||||
|
||||
|
||||
class QuickConnectDialog(xbmcgui.WindowXMLDialog):
|
||||
connect_method = -1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
log.debug("QuickConnectDialog INITIALISED")
|
||||
|
||||
self.code = ''
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
message_control = self.getControl(3)
|
||||
message_control.setText(self.code)
|
||||
|
||||
message_control = self.getControl(4)
|
||||
message_control.setLabel(translate_string(30443))
|
||||
|
||||
self.getControl(3010).setLabel(translate_string(30444))
|
||||
self.getControl(3011).setLabel(translate_string(30365))
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self, actionID):
|
||||
pass
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if controlID == 3010:
|
||||
self.connect_method = 1
|
||||
self.close()
|
||||
if controlID == 3011:
|
||||
self.connect_method = 0
|
||||
self.close()
|
||||
|
||||
def getConnectMethod(self):
|
||||
return self.connect_method
|
||||
@@ -10,12 +10,9 @@ import sys
|
||||
import re
|
||||
|
||||
from .datamanager import DataManager
|
||||
from .kodi_utils import HomeWindow
|
||||
from .downloadutils import DownloadUtils
|
||||
from .translation import string_load
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
from .item_functions import add_gui_item, ItemDetails
|
||||
from .utils import send_event_notification
|
||||
from .utils import send_event_notification, translate_string, load_user_details, get_default_filters
|
||||
from .tracking import timer
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
@@ -28,7 +25,7 @@ def get_content(url, params):
|
||||
default_sort = params.get("sort")
|
||||
media_type = params.get("media_type", None)
|
||||
if not media_type:
|
||||
xbmcgui.Dialog().ok(string_load(30135), string_load(30139))
|
||||
xbmcgui.Dialog().ok(translate_string(30135), translate_string(30139))
|
||||
|
||||
log.debug("URL: {0}".format(url))
|
||||
log.debug("MediaType: {0}".format(media_type))
|
||||
@@ -78,8 +75,8 @@ def get_content(url, params):
|
||||
progress = None
|
||||
if settings.getSetting('showLoadProgress') == "true":
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(string_load(30112))
|
||||
progress.update(0, string_load(30113))
|
||||
progress.create(translate_string(30112))
|
||||
progress.update(0, translate_string(30113))
|
||||
|
||||
# update url for paging
|
||||
start_index = 0
|
||||
@@ -106,13 +103,6 @@ def get_content(url, params):
|
||||
url = url + "&StartIndex=" + str(start_index) + "&Limit=" + str(page_limit)
|
||||
log.debug("ADDING NEXT URL: {0}".format(url_next))
|
||||
|
||||
# use the data manager to get the data
|
||||
# result = dataManager.GetContent(url)
|
||||
|
||||
# total_records = 0
|
||||
# if result is not None and isinstance(result, dict):
|
||||
# total_records = result.get("TotalRecordCount", 0)
|
||||
|
||||
use_cache = params.get("use_cache", "true") == "true"
|
||||
|
||||
dir_items, detected_type, total_records = process_directory(url, progress, params, use_cache)
|
||||
@@ -174,7 +164,7 @@ def get_content(url, params):
|
||||
log.debug("No view id for view type:{0}".format(view_key))
|
||||
|
||||
if progress is not None:
|
||||
progress.update(100, string_load(30125))
|
||||
progress.update(100, translate_string(30125))
|
||||
progress.close()
|
||||
|
||||
return
|
||||
@@ -227,8 +217,9 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
|
||||
data_manager = DataManager()
|
||||
settings = xbmcaddon.Addon()
|
||||
download_utils = DownloadUtils()
|
||||
server = download_utils.get_server()
|
||||
server = settings.getSetting('server_address')
|
||||
user_details = load_user_details()
|
||||
user_id = user_details.get('user_id')
|
||||
|
||||
name_format = params.get("name_format", None)
|
||||
name_format_type = None
|
||||
@@ -248,6 +239,15 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
gui_options["name_format_type"] = name_format_type
|
||||
|
||||
use_cache = settings.getSetting("use_cache") == "true" and use_cache_data
|
||||
default_filters = get_default_filters()
|
||||
|
||||
# Fix skin shortcuts from pre-0.5.0
|
||||
item_limit = int(settings.getSetting("show_x_filtered_items"))
|
||||
url = url.replace('{server}', '')
|
||||
url = url.replace('{userid}', user_id)
|
||||
url = url.replace('{field_filters}', default_filters)
|
||||
url = url.replace('{ItemLimit}', str(item_limit))
|
||||
|
||||
cache_file, item_list, total_records, cache_thread = data_manager.get_items(url, gui_options, use_cache)
|
||||
|
||||
# flatten single season
|
||||
@@ -257,13 +257,13 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
if flatten_single_season and len(item_list) == 1 and item_list[0].item_type == "Season":
|
||||
season_id = item_list[0].id
|
||||
series_id = item_list[0].series_id
|
||||
season_url = ('{server}/Shows/' + series_id +
|
||||
season_url = ('/Shows/' + series_id +
|
||||
'/Episodes'
|
||||
'?userId={userid}' +
|
||||
'?userId={}'.format(user_id) +
|
||||
'&seasonId=' + season_id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=SpecialEpisodeNumbers,{field_filters}' +
|
||||
'&Fields=SpecialEpisodeNumbers,{}'.format(default_filters) +
|
||||
'&format=json')
|
||||
if progress is not None:
|
||||
progress.close()
|
||||
@@ -297,7 +297,7 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
|
||||
if progress is not None:
|
||||
percent_done = (float(current_item) / float(item_count)) * 100
|
||||
progress.update(int(percent_done), string_load(30126) + str(current_item))
|
||||
progress.update(int(percent_done), translate_string(30126) + str(current_item))
|
||||
current_item = current_item + 1
|
||||
|
||||
if detected_type is not None:
|
||||
@@ -322,28 +322,28 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
|
||||
if item_details.is_folder is True:
|
||||
if item_details.item_type == "Series":
|
||||
u = ('{server}/Shows/' + item_details.id +
|
||||
u = ('/Shows/' + item_details.id +
|
||||
'/Seasons'
|
||||
'?userId={userid}' +
|
||||
'&Fields={field_filters}' +
|
||||
'?userId={}'.format(user_id) +
|
||||
'&Fields={}'.format(default_filters) +
|
||||
'&format=json')
|
||||
|
||||
elif item_details.item_type == "Season":
|
||||
u = ('{server}/Shows/' + item_details.series_id +
|
||||
u = ('/Shows/' + item_details.series_id +
|
||||
'/Episodes'
|
||||
'?userId={userid}' +
|
||||
'?userId={}'.format(user_id) +
|
||||
'&seasonId=' + item_details.id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=SpecialEpisodeNumbers,{field_filters}' +
|
||||
'&Fields=SpecialEpisodeNumbers,{}'.format(default_filters) +
|
||||
'&format=json')
|
||||
|
||||
else:
|
||||
u = ('{server}/Users/{userid}/items' +
|
||||
u = ('/Users/{}/items'.format(user_id) +
|
||||
'?ParentId=' + item_details.id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields={field_filters}' +
|
||||
'&Fields={}'.format(default_filters) +
|
||||
'&format=json')
|
||||
|
||||
default_sort = item_details.item_type == "Playlist"
|
||||
@@ -356,7 +356,7 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
log.debug("Dropping empty folder item : {0}".format(item_details.__dict__))
|
||||
|
||||
elif item_details.item_type == "MusicArtist":
|
||||
u = ('{server}/Users/{userid}/items' +
|
||||
u = ('/Users/{}/items'.format(user_id) +
|
||||
'?ArtistIds=' + item_details.id +
|
||||
'&IncludeItemTypes=MusicAlbum' +
|
||||
'&CollapseBoxSetItems=false' +
|
||||
@@ -378,13 +378,12 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
and first_season_item is not None
|
||||
and len(dir_items) > 1
|
||||
and first_season_item.series_id is not None):
|
||||
series_url = ('{server}/Shows/' + first_season_item.series_id +
|
||||
series_url = ('/Shows/' + first_season_item.series_id +
|
||||
'/Episodes'
|
||||
'?userId={userid}' +
|
||||
# '&seasonId=' + season_id +
|
||||
'?userId={}'.format(user_id) +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=SpecialEpisodeNumbers,{field_filters}' +
|
||||
'&Fields=SpecialEpisodeNumbers,{}'.format(default_filters) +
|
||||
'&format=json')
|
||||
played = 0
|
||||
overlay = "7"
|
||||
@@ -395,7 +394,7 @@ def process_directory(url, progress, params, use_cache_data=False):
|
||||
item_details = ItemDetails()
|
||||
|
||||
item_details.id = first_season_item.id
|
||||
item_details.name = string_load(30290)
|
||||
item_details.name = translate_string(30290)
|
||||
item_details.art = first_season_item.art
|
||||
item_details.play_count = played
|
||||
item_details.overlay = overlay
|
||||
|
||||
@@ -1,724 +0,0 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import requests
|
||||
import hashlib
|
||||
import ssl
|
||||
import gzip
|
||||
import json
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from base64 import b64encode
|
||||
from collections import defaultdict
|
||||
from traceback import format_exc
|
||||
from kodi_six.utils import py2_decode
|
||||
from six import ensure_text
|
||||
|
||||
from .kodi_utils import HomeWindow
|
||||
from .clientinfo import ClientInformation
|
||||
from .loghandler import LazyLogger
|
||||
from .translation import string_load
|
||||
from .tracking import timer
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
def save_user_details(settings, user_name, user_password):
|
||||
save_user_to_settings = settings.getSetting("save_user_to_settings") == "true"
|
||||
if save_user_to_settings:
|
||||
settings.setSetting("username", user_name)
|
||||
settings.setSetting("password", user_password)
|
||||
else:
|
||||
settings.setSetting("username", "")
|
||||
settings.setSetting("password", "")
|
||||
home_window = HomeWindow()
|
||||
home_window.set_property("username", user_name)
|
||||
home_window.set_property("password", user_password)
|
||||
|
||||
|
||||
def load_user_details(settings):
|
||||
save_user_to_settings = settings.getSetting("save_user_to_settings") == "true"
|
||||
if save_user_to_settings:
|
||||
user_name = settings.getSetting("username")
|
||||
user_password = settings.getSetting("password")
|
||||
else:
|
||||
home_window = HomeWindow()
|
||||
user_name = home_window.get_property("username")
|
||||
user_password = home_window.get_property("password")
|
||||
|
||||
user_details = {}
|
||||
user_details["username"] = user_name
|
||||
user_details["password"] = user_password
|
||||
return user_details
|
||||
|
||||
|
||||
def get_details_string():
|
||||
|
||||
addon_settings = xbmcaddon.Addon()
|
||||
include_media = addon_settings.getSetting("include_media") == "true"
|
||||
include_people = addon_settings.getSetting("include_people") == "true"
|
||||
include_overview = addon_settings.getSetting("include_overview") == "true"
|
||||
|
||||
filer_list = [
|
||||
"DateCreated",
|
||||
"EpisodeCount",
|
||||
"SeasonCount",
|
||||
"Path",
|
||||
"Genres",
|
||||
"Studios",
|
||||
"Etag",
|
||||
"Taglines",
|
||||
"SortName",
|
||||
"RecursiveItemCount",
|
||||
"ChildCount",
|
||||
"ProductionLocations",
|
||||
"CriticRating",
|
||||
"OfficialRating",
|
||||
"CommunityRating",
|
||||
"PremiereDate",
|
||||
"ProductionYear",
|
||||
"AirTime",
|
||||
"Status",
|
||||
"Tags"
|
||||
]
|
||||
|
||||
if include_media:
|
||||
filer_list.append("MediaStreams")
|
||||
|
||||
if include_people:
|
||||
filer_list.append("People")
|
||||
|
||||
if include_overview:
|
||||
filer_list.append("Overview")
|
||||
|
||||
return ",".join(filer_list)
|
||||
|
||||
|
||||
class DownloadUtils:
|
||||
use_https = False
|
||||
verify_cert = False
|
||||
|
||||
def __init__(self, *args):
|
||||
settings = xbmcaddon.Addon()
|
||||
|
||||
self.use_https = False
|
||||
if settings.getSetting('protocol') == "1":
|
||||
self.use_https = True
|
||||
log.debug("use_https: {0}".format(self.use_https))
|
||||
|
||||
self.verify_cert = settings.getSetting('verify_cert') == 'true'
|
||||
log.debug("verify_cert: {0}".format(self.verify_cert))
|
||||
|
||||
def post_capabilities(self):
|
||||
|
||||
url = "{server}/Sessions/Capabilities/Full?format=json"
|
||||
data = {
|
||||
'SupportsMediaControl': True,
|
||||
'PlayableMediaTypes': ["Video", "Audio"],
|
||||
'SupportedCommands': ["MoveUp",
|
||||
"MoveDown",
|
||||
"MoveLeft",
|
||||
"MoveRight",
|
||||
"Select",
|
||||
"Back",
|
||||
"ToggleContextMenu",
|
||||
"ToggleFullscreen",
|
||||
"ToggleOsdMenu",
|
||||
"GoHome",
|
||||
"PageUp",
|
||||
"NextLetter",
|
||||
"GoToSearch",
|
||||
"GoToSettings",
|
||||
"PageDown",
|
||||
"PreviousLetter",
|
||||
"TakeScreenshot",
|
||||
"VolumeUp",
|
||||
"VolumeDown",
|
||||
"ToggleMute",
|
||||
"SendString",
|
||||
"DisplayMessage",
|
||||
"SetAudioStreamIndex",
|
||||
"SetSubtitleStreamIndex",
|
||||
"SetRepeatMode",
|
||||
"Mute",
|
||||
"Unmute",
|
||||
"SetVolume",
|
||||
"PlayNext",
|
||||
"Play",
|
||||
"Playstate",
|
||||
"PlayMediaSource"]
|
||||
}
|
||||
|
||||
self.download_url(url, post_body=data, method="POST")
|
||||
log.debug("Posted Capabilities: {0}".format(data))
|
||||
|
||||
def get_item_playback_info(self, item_id, force_transcode):
|
||||
|
||||
addon_settings = xbmcaddon.Addon()
|
||||
|
||||
filtered_codecs = []
|
||||
if addon_settings.getSetting("force_transcode_h265") == "true":
|
||||
filtered_codecs.append("hevc")
|
||||
filtered_codecs.append("h265")
|
||||
if addon_settings.getSetting("force_transcode_mpeg2") == "true":
|
||||
filtered_codecs.append("mpeg2video")
|
||||
if addon_settings.getSetting("force_transcode_msmpeg4v3") == "true":
|
||||
filtered_codecs.append("msmpeg4v3")
|
||||
if addon_settings.getSetting("force_transcode_mpeg4") == "true":
|
||||
filtered_codecs.append("mpeg4")
|
||||
|
||||
playback_bitrate = addon_settings.getSetting("max_stream_bitrate")
|
||||
force_playback_bitrate = addon_settings.getSetting("force_max_stream_bitrate")
|
||||
if force_transcode:
|
||||
playback_bitrate = force_playback_bitrate
|
||||
|
||||
audio_codec = addon_settings.getSetting("audio_codec")
|
||||
audio_playback_bitrate = addon_settings.getSetting("audio_playback_bitrate")
|
||||
audio_max_channels = addon_settings.getSetting("audio_max_channels")
|
||||
|
||||
audio_bitrate = int(audio_playback_bitrate) * 1000
|
||||
bitrate = int(playback_bitrate) * 1000
|
||||
|
||||
profile = {
|
||||
"Name": "Kodi",
|
||||
"MaxStaticBitrate": bitrate,
|
||||
"MaxStreamingBitrate": bitrate,
|
||||
"MusicStreamingTranscodingBitrate": audio_bitrate,
|
||||
"TimelineOffsetSeconds": 5,
|
||||
"TranscodingProfiles": [
|
||||
{
|
||||
"Type": "Audio"
|
||||
},
|
||||
{
|
||||
"Container": "ts",
|
||||
"Protocol": "hls",
|
||||
"Type": "Video",
|
||||
"AudioCodec": audio_codec,
|
||||
"VideoCodec": "h264",
|
||||
"MaxAudioChannels": audio_max_channels
|
||||
},
|
||||
{
|
||||
"Container": "jpeg",
|
||||
"Type": "Photo"
|
||||
}
|
||||
],
|
||||
"DirectPlayProfiles": [
|
||||
{
|
||||
"Type": "Video"
|
||||
},
|
||||
{
|
||||
"Type": "Audio"
|
||||
},
|
||||
{
|
||||
"Type": "Photo"
|
||||
}
|
||||
],
|
||||
"ResponseProfiles": [],
|
||||
"ContainerProfiles": [],
|
||||
"CodecProfiles": [],
|
||||
"SubtitleProfiles": [
|
||||
{
|
||||
"Format": "srt",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "srt",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "ass",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "ass",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "sub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "sub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "ssa",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "ssa",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "smi",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "smi",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "pgssub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "pgssub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "dvdsub",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "dvdsub",
|
||||
"Method": "External"
|
||||
},
|
||||
{
|
||||
"Format": "pgs",
|
||||
"Method": "Embed"
|
||||
},
|
||||
{
|
||||
"Format": "pgs",
|
||||
"Method": "External"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if len(filtered_codecs) > 0:
|
||||
profile['DirectPlayProfiles'][0]['VideoCodec'] = "-%s" % ",".join(filtered_codecs)
|
||||
|
||||
if force_transcode:
|
||||
profile['DirectPlayProfiles'] = []
|
||||
|
||||
if addon_settings.getSetting("playback_video_force_8") == "true":
|
||||
profile['CodecProfiles'].append(
|
||||
{
|
||||
"Type": "Video",
|
||||
"Codec": "h264",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "VideoBitDepth",
|
||||
"Value": "8",
|
||||
"IsRequired": False
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
profile['CodecProfiles'].append(
|
||||
{
|
||||
"Type": "Video",
|
||||
"Codec": "h265,hevc",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoProfile",
|
||||
"Value": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
playback_info = {
|
||||
'UserId': self.get_user_id(),
|
||||
'DeviceProfile': profile,
|
||||
'AutoOpenLiveStream': True
|
||||
}
|
||||
|
||||
if force_transcode:
|
||||
url = "{server}/Items/%s/PlaybackInfo?MaxStreamingBitrate=%s&EnableDirectPlay=false&EnableDirectStream=false" % (item_id, bitrate)
|
||||
else:
|
||||
url = "{server}/Items/%s/PlaybackInfo?MaxStreamingBitrate=%s" % (item_id, bitrate)
|
||||
|
||||
log.debug("PlaybackInfo : {0}".format(url))
|
||||
log.debug("PlaybackInfo : {0}".format(profile))
|
||||
play_info_result = self.download_url(url, post_body=playback_info, method="POST")
|
||||
log.debug("PlaybackInfo : {0}".format(play_info_result))
|
||||
|
||||
return play_info_result
|
||||
|
||||
def get_server(self):
|
||||
settings = xbmcaddon.Addon()
|
||||
|
||||
#For migration from storing URL parts to just one URL
|
||||
if settings.getSetting('ipaddress') != "" and settings.getSetting('ipaddress') != "<none>":
|
||||
log.info("Migrating to new server url storage")
|
||||
url = ("http://" if settings.getSetting('protocol') == "0" else "https://") + settings.getSetting('ipaddress') + ":" + settings.getSetting('port')
|
||||
settings.setSetting('server_address', url)
|
||||
settings.setSetting('ipaddress', "")
|
||||
|
||||
return settings.getSetting('server_address')
|
||||
|
||||
@staticmethod
|
||||
def get_all_artwork(item, server):
|
||||
all_art = defaultdict(lambda: "")
|
||||
|
||||
item_id = item["Id"]
|
||||
item_type = item["Type"]
|
||||
image_tags = item["ImageTags"]
|
||||
|
||||
# All the image tags
|
||||
for tag_name in image_tags:
|
||||
tag = image_tags[tag_name]
|
||||
art_url = "%s/Items/%s/Images/%s/0?Format=original&Tag=%s" % (server, item_id, tag_name, tag)
|
||||
all_art[tag_name] = art_url
|
||||
|
||||
# Series images
|
||||
if item_type in ["Episode", "Season"]:
|
||||
image_tag = item["SeriesPrimaryImageTag"]
|
||||
series_id = item["SeriesId"]
|
||||
if image_tag and series_id:
|
||||
art_url = "%s/Items/%s/Images/Primary/0?Format=original&Tag=%s" % (server, series_id, image_tag)
|
||||
all_art["Primary.Series"] = art_url
|
||||
|
||||
return all_art
|
||||
|
||||
def get_artwork(self, data, art_type, parent=False, index=0, server=None):
|
||||
|
||||
item_id = data["Id"]
|
||||
item_type = data["Type"]
|
||||
|
||||
if item_type in ["Episode", "Season"]:
|
||||
if art_type != "Primary" or parent is True:
|
||||
item_id = data["SeriesId"]
|
||||
|
||||
image_tag = ""
|
||||
|
||||
# for episodes always use the parent BG
|
||||
if item_type == "Episode" and art_type == "Backdrop":
|
||||
item_id = data.get("ParentBackdropItemId")
|
||||
bg_item_tags = data.get("ParentBackdropImageTags", [])
|
||||
if bg_item_tags:
|
||||
image_tag = bg_item_tags[0]
|
||||
elif art_type == "Backdrop" and parent is True:
|
||||
item_id = data.get("ParentBackdropItemId")
|
||||
bg_item_tags = data.get("ParentBackdropImageTags", [])
|
||||
if bg_item_tags:
|
||||
image_tag = bg_item_tags[0]
|
||||
elif art_type == "Backdrop":
|
||||
bg_tags = data.get("BackdropImageTags", [])
|
||||
if bg_tags:
|
||||
image_tag = bg_tags[index]
|
||||
elif parent is False:
|
||||
image_tags = data.get("ImageTags", [])
|
||||
if image_tags:
|
||||
image_tag_type = image_tags.get(art_type)
|
||||
if image_tag_type:
|
||||
image_tag = image_tag_type
|
||||
elif parent is True:
|
||||
if (item_type == "Episode" or item_type == "Season") and art_type == 'Primary':
|
||||
tag_name = 'SeriesPrimaryImageTag'
|
||||
id_name = 'SeriesId'
|
||||
else:
|
||||
tag_name = 'Parent%sImageTag' % art_type
|
||||
id_name = 'Parent%sItemId' % art_type
|
||||
parent_image_id = data.get(id_name)
|
||||
parent_image_tag = data.get(tag_name)
|
||||
if parent_image_id is not None and parent_image_tag is not None:
|
||||
item_id = parent_image_id
|
||||
image_tag = parent_image_tag
|
||||
|
||||
# ParentTag not passed for Banner and Art
|
||||
if not image_tag and not ((art_type == 'Banner' or art_type == 'Art') and parent is True):
|
||||
return ""
|
||||
|
||||
artwork = "%s/Items/%s/Images/%s/%s?Format=original&Tag=%s" % (server, item_id, art_type, index, image_tag)
|
||||
|
||||
if self.use_https and not self.verify_cert:
|
||||
artwork += "|verifypeer=false"
|
||||
|
||||
return artwork
|
||||
|
||||
|
||||
def image_url(self, item_id, art_type, index, width, height, image_tag, server):
|
||||
|
||||
# test imageTag e3ab56fe27d389446754d0fb04910a34
|
||||
artwork = "%s/Items/%s/Images/%s/%s?Format=original&Tag=%s" % (server, item_id, art_type, index, image_tag)
|
||||
if int(width) > 0:
|
||||
artwork += '&MaxWidth=%s' % width
|
||||
if int(height) > 0:
|
||||
artwork += '&MaxHeight=%s' % height
|
||||
|
||||
if self.use_https and not self.verify_cert:
|
||||
artwork += "|verifypeer=false"
|
||||
|
||||
return artwork
|
||||
|
||||
def get_user_artwork(self, user, item_type):
|
||||
|
||||
if "PrimaryImageTag" not in user:
|
||||
return ""
|
||||
user_id = user.get("Id")
|
||||
tag = user.get("PrimaryImageTag")
|
||||
server = self.get_server()
|
||||
|
||||
artwork = "%s/Users/%s/Images/%s?Format=original&tag=%s" % (server, user_id, item_type, tag)
|
||||
|
||||
if self.use_https and not self.verify_cert:
|
||||
artwork += "|verifypeer=false"
|
||||
|
||||
return artwork
|
||||
|
||||
def get_user_id(self):
|
||||
|
||||
window = HomeWindow()
|
||||
userid = window.get_property("userid")
|
||||
user_image = window.get_property("userimage")
|
||||
|
||||
if userid:
|
||||
log.debug("JellyCon DownloadUtils -> Returning saved UserID: {0}".format(userid))
|
||||
return userid
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
user_details = load_user_details(settings)
|
||||
user_name = user_details.get("username", "")
|
||||
|
||||
if not user_name:
|
||||
return ""
|
||||
log.debug("Looking for user name: {0}".format(user_name))
|
||||
|
||||
try:
|
||||
result = self.download_url("{server}/Users/Public?format=json", suppress=True, authenticate=False)
|
||||
except Exception as msg:
|
||||
log.error("Get User unable to connect: {0}".format(msg))
|
||||
return ""
|
||||
|
||||
log.debug("GETUSER_JSONDATA_01: {0}".format(py2_decode(result)))
|
||||
|
||||
if not result:
|
||||
return ""
|
||||
|
||||
log.debug("GETUSER_JSONDATA_02: {0}".format(result))
|
||||
|
||||
secure = False
|
||||
for user in result:
|
||||
if user.get("Name") == ensure_text(user_name, "utf-8"):
|
||||
userid = user.get("Id")
|
||||
user_image = self.get_user_artwork(user, 'Primary')
|
||||
log.debug("Username Found: {0}".format(user.get("Name")))
|
||||
if user.get("HasPassword", False):
|
||||
secure = True
|
||||
log.debug("Username Is Secure (HasPassword=True)")
|
||||
break
|
||||
|
||||
if secure or not userid:
|
||||
auth_ok = self.authenticate()
|
||||
if auth_ok == "":
|
||||
xbmcgui.Dialog().notification(string_load(30316),
|
||||
string_load(30044),
|
||||
icon="special://home/addons/plugin.video.jellycon/icon.png")
|
||||
return ""
|
||||
if not userid:
|
||||
userid = window.get_property("userid")
|
||||
|
||||
if userid and not user_image:
|
||||
user_image = 'DefaultUser.png'
|
||||
|
||||
if userid == "":
|
||||
xbmcgui.Dialog().notification(string_load(30316),
|
||||
string_load(30045),
|
||||
icon="special://home/addons/plugin.video.jellycon/icon.png")
|
||||
|
||||
log.debug("userid: {0}".format(userid))
|
||||
|
||||
window.set_property("userid", userid)
|
||||
window.set_property("userimage", user_image)
|
||||
|
||||
return userid
|
||||
|
||||
def authenticate(self):
|
||||
|
||||
window = HomeWindow()
|
||||
|
||||
token = window.get_property("AccessToken")
|
||||
if token is not None and token != "":
|
||||
log.debug("JellyCon DownloadUtils -> Returning saved AccessToken: {0}".format(token))
|
||||
return token
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
server_address = settings.getSetting("server_address")
|
||||
|
||||
url = "{server}/Users/AuthenticateByName?format=json"
|
||||
|
||||
user_details = load_user_details(settings)
|
||||
user_name = user_details.get("username", "")
|
||||
pwd_text = user_details.get("password", "")
|
||||
|
||||
message_data = {'username': user_name, 'pw': pwd_text}
|
||||
|
||||
result = self.download_url(url, post_body=message_data, method="POST", suppress=True, authenticate=False)
|
||||
log.debug("AuthenticateByName: {0}".format(result))
|
||||
|
||||
access_token = None
|
||||
userid = None
|
||||
access_token = result.get("AccessToken")
|
||||
userid = result["User"].get("Id")
|
||||
|
||||
if access_token is not None:
|
||||
log.debug("User Authenticated: {0}".format(access_token))
|
||||
log.debug("User Id: {0}".format(userid))
|
||||
window.set_property("AccessToken", access_token)
|
||||
window.set_property("userid", userid)
|
||||
|
||||
self.post_capabilities()
|
||||
|
||||
return access_token
|
||||
else:
|
||||
log.debug("User NOT Authenticated")
|
||||
window.set_property("AccessToken", "")
|
||||
window.set_property("userid", "")
|
||||
window.set_property("userimage", "")
|
||||
return ""
|
||||
|
||||
def get_auth_header(self, authenticate=True):
|
||||
client_info = ClientInformation()
|
||||
txt_mac = client_info.get_device_id()
|
||||
version = client_info.get_version()
|
||||
client = client_info.get_client()
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
device_name = settings.getSetting('deviceName')
|
||||
# remove none ascii chars
|
||||
device_name = py2_decode(device_name)
|
||||
# remove some chars not valid for names
|
||||
device_name = device_name.replace("\"", "_")
|
||||
if len(device_name) == 0:
|
||||
device_name = "JellyCon"
|
||||
|
||||
headers = {}
|
||||
headers["Accept-encoding"] = "gzip"
|
||||
headers["Accept-Charset"] = "UTF-8,*"
|
||||
|
||||
if authenticate is False:
|
||||
auth_string = "MediaBrowser Client=\"" + client + "\",Device=\"" + device_name + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers['X-Emby-Authorization'] = auth_string
|
||||
return headers
|
||||
else:
|
||||
userid = self.get_user_id()
|
||||
auth_string = "MediaBrowser UserId=\"" + userid + "\",Client=\"" + client + "\",Device=\"" + device_name + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers['X-Emby-Authorization'] = auth_string
|
||||
|
||||
auth_token = self.authenticate()
|
||||
if auth_token != "":
|
||||
headers["X-MediaBrowser-Token"] = auth_token
|
||||
|
||||
log.debug("JellyCon Authentication Header: {0}".format(headers))
|
||||
return headers
|
||||
|
||||
@timer
|
||||
def download_url(self, url, suppress=False, post_body=None, method="GET", authenticate=True, headers=None):
|
||||
log.debug("downloadUrl")
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
user_details = load_user_details(settings)
|
||||
username = user_details.get("username", "")
|
||||
server = None
|
||||
|
||||
http_timeout = int(settings.getSetting("http_timeout"))
|
||||
|
||||
if authenticate and username == "":
|
||||
return {}
|
||||
|
||||
if settings.getSetting("suppressErrors") == "true":
|
||||
suppress = True
|
||||
|
||||
log.debug("Before: {0}".format(url))
|
||||
|
||||
if url.find("{server}") != -1:
|
||||
server = self.get_server()
|
||||
if server is None:
|
||||
return {}
|
||||
url = url.replace("{server}", server)
|
||||
|
||||
if url.find("{userid}") != -1:
|
||||
userid = self.get_user_id()
|
||||
if not userid:
|
||||
return {}
|
||||
url = url.replace("{userid}", userid)
|
||||
|
||||
if url.find("{ItemLimit}") != -1:
|
||||
show_x_filtered_items = settings.getSetting("show_x_filtered_items")
|
||||
url = url.replace("{ItemLimit}", show_x_filtered_items)
|
||||
|
||||
if url.find("{field_filters}") != -1:
|
||||
filter_string = get_details_string()
|
||||
url = url.replace("{field_filters}", filter_string)
|
||||
|
||||
if url.find("{random_movies}") != -1:
|
||||
home_window = HomeWindow()
|
||||
random_movies = home_window.get_property("random-movies")
|
||||
if not random_movies:
|
||||
return {}
|
||||
url = url.replace("{random_movies}", random_movies)
|
||||
|
||||
log.debug("After: {0}".format(url))
|
||||
|
||||
try:
|
||||
url_bits = urlparse(url.strip())
|
||||
user_name = url_bits.username
|
||||
user_password = url_bits.password
|
||||
|
||||
head = self.get_auth_header(authenticate)
|
||||
|
||||
if user_name and user_password:
|
||||
log.info("Replacing username & Password info")
|
||||
# add basic auth headers
|
||||
user_and_pass = b64encode(b"%s:%s" % (user_name, user_password)).decode("ascii")
|
||||
head["Authorization"] = 'Basic %s' % user_and_pass
|
||||
|
||||
head["User-Agent"] = "JellyCon-" + ClientInformation().get_version()
|
||||
|
||||
http_request = getattr(requests, method.lower())
|
||||
|
||||
if post_body:
|
||||
|
||||
if isinstance(post_body, dict):
|
||||
head["Content-Type"] = "application/json"
|
||||
post_body = json.dumps(post_body)
|
||||
else:
|
||||
head["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
log.debug("Content-Type: {0}".format(head["Content-Type"]))
|
||||
log.debug("POST DATA: {0}".format(post_body))
|
||||
|
||||
data = http_request(url, data=post_body, headers=head)
|
||||
else:
|
||||
data = http_request(url, headers=head)
|
||||
|
||||
|
||||
if data.status_code == 200:
|
||||
|
||||
if headers is not None and isinstance(headers, dict):
|
||||
headers.update(data.headers)
|
||||
log.debug("{0}".format(data.json()))
|
||||
|
||||
elif data.status_code >= 400:
|
||||
|
||||
if data.status_code == 401:
|
||||
# remove any saved password
|
||||
m = hashlib.md5()
|
||||
m.update(username)
|
||||
hashed_username = m.hexdigest()
|
||||
log.error("HTTP response error 401 auth error, removing any saved passwords for user: {0}".format(hashed_username))
|
||||
settings.setSetting("saved_user_password_" + hashed_username, "")
|
||||
save_user_details(settings, "", "")
|
||||
|
||||
log.error("HTTP response error for {0}: {1} {2}".format(url, data.status_code, data.content))
|
||||
if suppress is False:
|
||||
xbmcgui.Dialog().notification(string_load(30316),
|
||||
'{}: {}'.format(string_load(30200), data.content),
|
||||
icon="special://home/addons/plugin.video.jellycon/icon.png")
|
||||
try:
|
||||
result = data.json()
|
||||
except:
|
||||
result = {}
|
||||
return result
|
||||
except Exception as msg:
|
||||
log.error("{0}".format(format_exc()))
|
||||
log.error("Unable to connect to {0} : {1}".format(server, msg))
|
||||
if not suppress:
|
||||
xbmcgui.Dialog().notification(string_load(30316),
|
||||
str(msg),
|
||||
icon="special://home/addons/plugin.video.jellycon/icon.png")
|
||||
@@ -90,6 +90,7 @@ import sys
|
||||
import time
|
||||
import errno
|
||||
|
||||
|
||||
class FileLock(object):
|
||||
""" A file locking mechanism that has context-manager support so
|
||||
you can use it in a ``with`` statement. This should be relatively cross
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
from six.moves.urllib.parse import quote, unquote, parse_qsl
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import cProfile
|
||||
import pstats
|
||||
import json
|
||||
from six import StringIO
|
||||
|
||||
import xbmcplugin
|
||||
@@ -15,21 +14,17 @@ import xbmcgui
|
||||
import xbmcaddon
|
||||
import xbmc
|
||||
|
||||
from .downloadutils import DownloadUtils, load_user_details
|
||||
from .utils import get_art, send_event_notification, convert_size
|
||||
from .jellyfin import api
|
||||
from .utils import translate_string, get_version, load_user_details, get_art_url, get_default_filters, translate_path, kodi_version
|
||||
from .kodi_utils import HomeWindow
|
||||
from .clientinfo import ClientInformation
|
||||
from .datamanager import DataManager, clear_cached_server_data
|
||||
from .datamanager import clear_cached_server_data
|
||||
from .server_detect import check_server, check_connection_speed
|
||||
from .loghandler import LazyLogger
|
||||
from .menu_functions import display_main_menu, display_menu, show_movie_alpha_list, show_tvshow_alpha_list, show_genre_list, show_search, show_movie_pages
|
||||
from .translation import string_load
|
||||
from .lazylogger import LazyLogger
|
||||
from .menu_functions import display_main_menu, display_menu, show_movie_alpha_list, show_tvshow_alpha_list, show_genre_list, show_search, show_movie_pages, show_artist_alpha_list
|
||||
from .server_sessions import show_server_sessions
|
||||
from .action_menu import ActionMenu
|
||||
from .bitrate_dialog import BitrateDialog
|
||||
from .safe_delete_dialog import SafeDeleteDialog
|
||||
from .dialogs import BitrateDialog
|
||||
from .widgets import get_widget_content, get_widget_content_cast, check_for_new_content
|
||||
from . import trakttokodi
|
||||
from .cache_images import CacheArtwork
|
||||
from .dir_functions import get_content, process_directory
|
||||
from .tracking import timer
|
||||
@@ -37,16 +32,13 @@ from .skin_cloner import clone_default_skin
|
||||
from .play_utils import play_file
|
||||
|
||||
__addon__ = xbmcaddon.Addon()
|
||||
__addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile'))
|
||||
__addondir__ = translate_path(__addon__.getAddonInfo('profile'))
|
||||
__cwd__ = __addon__.getAddonInfo('path')
|
||||
PLUGINPATH = xbmc.translatePath(os.path.join(__cwd__))
|
||||
PLUGINPATH = translate_path(os.path.join(__cwd__))
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
downloadUtils = DownloadUtils()
|
||||
dataManager = DataManager()
|
||||
user_details = load_user_details()
|
||||
|
||||
|
||||
@timer
|
||||
@@ -63,9 +55,9 @@ def main_entry_point():
|
||||
pr.enable()
|
||||
|
||||
log.debug("Running Python: {0}".format(sys.version_info))
|
||||
log.debug("Running JellyCon: {0}".format(ClientInformation().get_version()))
|
||||
log.debug("Running JellyCon: {0}".format(get_version()))
|
||||
log.debug("Kodi BuildVersion: {0}".format(xbmc.getInfoLabel("System.BuildVersion")))
|
||||
log.debug("Kodi Version: {0}".format(kodi_version))
|
||||
log.debug("Kodi Version: {0}".format(kodi_version()))
|
||||
log.debug("Script argument data: {0}".format(sys.argv))
|
||||
|
||||
params = get_params()
|
||||
@@ -74,9 +66,6 @@ def main_entry_point():
|
||||
request_path = params.get("request_path", None)
|
||||
param_url = params.get('url', None)
|
||||
|
||||
if param_url:
|
||||
param_url = unquote(param_url)
|
||||
|
||||
mode = params.get("mode", None)
|
||||
|
||||
if len(params) == 1 and request_path and request_path.find("/library/movies") > -1:
|
||||
@@ -102,6 +91,8 @@ def main_entry_point():
|
||||
show_movie_alpha_list(params)
|
||||
elif mode == "TVSHOW_ALPHA":
|
||||
show_tvshow_alpha_list(params)
|
||||
elif mode == "ARTIST_ALPHA":
|
||||
show_artist_alpha_list(params)
|
||||
elif mode == "GENRES":
|
||||
show_genre_list(params)
|
||||
elif mode == "MOVIE_PAGES":
|
||||
@@ -125,11 +116,9 @@ def main_entry_point():
|
||||
elif mode == "WIDGET_CONTENT_CAST":
|
||||
get_widget_content_cast(int(sys.argv[1]), params)
|
||||
elif mode == "SHOW_CONTENT":
|
||||
# plugin://plugin.video.jellycon?mode=SHOW_CONTENT&item_type=Movie|Series
|
||||
check_server()
|
||||
show_content(params)
|
||||
elif mode == "SEARCH":
|
||||
# plugin://plugin.video.jellycon?mode=SEARCH
|
||||
xbmcplugin.setContent(int(sys.argv[1]), 'files')
|
||||
show_search()
|
||||
elif mode == "NEW_SEARCH":
|
||||
@@ -138,8 +127,6 @@ def main_entry_point():
|
||||
search_results_person(params)
|
||||
elif mode == "SHOW_SERVER_SESSIONS":
|
||||
show_server_sessions()
|
||||
elif mode == "TRAKTTOKODI":
|
||||
trakttokodi.entry_point(params)
|
||||
elif mode == "SHOW_ADDON_MENU":
|
||||
display_menu(params)
|
||||
elif mode == "GET_CONTENT_BY_TV_SHOW":
|
||||
@@ -192,10 +179,10 @@ def __get_parent_id_from(params):
|
||||
show_provider_ids = params.get("show_ids")
|
||||
if show_provider_ids is not None:
|
||||
log.debug("TV show providers IDs: {}".format(show_provider_ids))
|
||||
get_show_url = "{server}/Users/{userid}/Items?fields=MediaStreams&Recursive=true" \
|
||||
get_show_url = "/Users/{}/Items?fields=MediaStreams&Recursive=true" \
|
||||
"&IncludeItemTypes=series&IncludeMedia=true&ImageTypeLimit=1&Limit=16" \
|
||||
"&AnyProviderIdEquals=" + show_provider_ids
|
||||
content = dataManager.get_content(get_show_url)
|
||||
"&AnyProviderIdEquals={}".format(api.user_id, show_provider_ids)
|
||||
content = api.get(get_show_url)
|
||||
show = content.get("Items")
|
||||
if len(show) == 1:
|
||||
result = content.get("Items")[0].get("Id")
|
||||
@@ -211,9 +198,8 @@ def toggle_watched(params):
|
||||
item_id = params.get("item_id", None)
|
||||
if item_id is None:
|
||||
return
|
||||
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
|
||||
data_manager = DataManager()
|
||||
result = data_manager.get_content(url)
|
||||
url = "/Users/{}/Items/{}?format=json".format(api.user_id, item_id)
|
||||
result = api.get(url)
|
||||
log.debug("toggle_watched item info: {0}".format(result))
|
||||
|
||||
user_data = result.get("UserData", None)
|
||||
@@ -228,8 +214,8 @@ def toggle_watched(params):
|
||||
|
||||
def mark_item_watched(item_id):
|
||||
log.debug("Mark Item Watched: {0}".format(item_id))
|
||||
url = "{server}/Users/{userid}/PlayedItems/" + item_id
|
||||
downloadUtils.download_url(url, post_body="", method="POST")
|
||||
url = "/Users/{}/PlayedItems/{}".format(user_details.get('user_id'), item_id)
|
||||
api.post(url)
|
||||
check_for_new_content()
|
||||
home_window = HomeWindow()
|
||||
last_url = home_window.get_property("last_content_url")
|
||||
@@ -242,8 +228,8 @@ def mark_item_watched(item_id):
|
||||
|
||||
def mark_item_unwatched(item_id):
|
||||
log.debug("Mark Item UnWatched: {0}".format(item_id))
|
||||
url = "{server}/Users/{userid}/PlayedItems/" + item_id
|
||||
downloadUtils.download_url(url, method="DELETE")
|
||||
url = "/Users/{}/PlayedItems/{}".format(user_details.get('user_id'), item_id)
|
||||
api.delete(url)
|
||||
check_for_new_content()
|
||||
home_window = HomeWindow()
|
||||
last_url = home_window.get_property("last_content_url")
|
||||
@@ -256,8 +242,8 @@ def mark_item_unwatched(item_id):
|
||||
|
||||
def mark_item_favorite(item_id):
|
||||
log.debug("Add item to favourites: {0}".format(item_id))
|
||||
url = "{server}/Users/{userid}/FavoriteItems/" + item_id
|
||||
downloadUtils.download_url(url, post_body="", method="POST")
|
||||
url = "/Users/{}/FavoriteItems/{}".format(user_details.get('user_id'), item_id)
|
||||
api.post(url)
|
||||
check_for_new_content()
|
||||
home_window = HomeWindow()
|
||||
last_url = home_window.get_property("last_content_url")
|
||||
@@ -269,8 +255,8 @@ def mark_item_favorite(item_id):
|
||||
|
||||
def unmark_item_favorite(item_id):
|
||||
log.debug("Remove item from favourites: {0}".format(item_id))
|
||||
url = "{server}/Users/{userid}/FavoriteItems/" + item_id
|
||||
downloadUtils.download_url(url, method="DELETE")
|
||||
url = "/Users/{}/FavoriteItems/{}".format(user_details.get('user_id'), item_id)
|
||||
api.delete(url)
|
||||
check_for_new_content()
|
||||
home_window = HomeWindow()
|
||||
last_url = home_window.get_property("last_content_url")
|
||||
@@ -282,7 +268,7 @@ def unmark_item_favorite(item_id):
|
||||
|
||||
def delete(item_id):
|
||||
|
||||
item = downloadUtils.download_url("{server}/Users/{userid}/Items/" + item_id + "?format=json")
|
||||
item = api.delete("/Users/{}/Items/{}".format(user_details.get('user_id'), item_id))
|
||||
|
||||
item_id = item.get("Id")
|
||||
item_name = item.get("Name", "")
|
||||
@@ -300,16 +286,16 @@ def delete(item_id):
|
||||
final_name += item_name
|
||||
|
||||
if not item.get("CanDelete", False):
|
||||
xbmcgui.Dialog().ok(string_load(30135), string_load(30417), final_name)
|
||||
xbmcgui.Dialog().ok(translate_string(30135), translate_string(30417), final_name)
|
||||
return
|
||||
|
||||
return_value = xbmcgui.Dialog().yesno(string_load(30091), '{}\n{}'.format(final_name, string_load(30092)))
|
||||
return_value = xbmcgui.Dialog().yesno(translate_string(30091), '{}\n{}'.format(final_name, translate_string(30092)))
|
||||
if return_value:
|
||||
log.debug('Deleting Item: {0}'.format(item_id))
|
||||
url = '{server}/Items/' + item_id
|
||||
url = '/Items/{}'.format(item_id)
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(string_load(30052), string_load(30053))
|
||||
downloadUtils.download_url(url, method="DELETE")
|
||||
progress.create(translate_string(30052), translate_string(30053))
|
||||
api.delete(url)
|
||||
progress.close()
|
||||
check_for_new_content()
|
||||
home_window = HomeWindow()
|
||||
@@ -321,6 +307,9 @@ def delete(item_id):
|
||||
|
||||
|
||||
def get_params():
|
||||
'''
|
||||
Retrieve the request data from Kodi
|
||||
'''
|
||||
|
||||
plugin_path = sys.argv[0]
|
||||
paramstring = sys.argv[2]
|
||||
@@ -328,27 +317,12 @@ def get_params():
|
||||
log.debug("Parameter string: {0}".format(paramstring))
|
||||
log.debug("Plugin Path string: {0}".format(plugin_path))
|
||||
|
||||
param = {}
|
||||
param = dict(parse_qsl(paramstring[1:]))
|
||||
|
||||
# add plugin path
|
||||
request_path = plugin_path.replace("plugin://plugin.video.jellycon", "")
|
||||
param["request_path"] = request_path
|
||||
|
||||
if len(paramstring) >= 2:
|
||||
if paramstring[0] == "?":
|
||||
paramstring = paramstring[1:]
|
||||
|
||||
if paramstring[len(paramstring) - 1] == '/':
|
||||
paramstring = paramstring[0:len(paramstring) - 2]
|
||||
|
||||
pairsofparams = paramstring.split('&')
|
||||
for i in range(len(pairsofparams)):
|
||||
splitparams = pairsofparams[i].split('=')
|
||||
if (len(splitparams)) == 2:
|
||||
param[splitparams[0]] = splitparams[1]
|
||||
elif (len(splitparams)) == 3:
|
||||
param[splitparams[0]] = splitparams[1] + "=" + splitparams[2]
|
||||
|
||||
log.debug("JellyCon -> Detected parameters: {0}".format(param))
|
||||
return param
|
||||
|
||||
@@ -360,9 +334,8 @@ def show_menu(params):
|
||||
settings = xbmcaddon.Addon()
|
||||
item_id = params["item_id"]
|
||||
|
||||
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
|
||||
data_manager = DataManager()
|
||||
result = data_manager.get_content(url)
|
||||
url = "/Users/{}/Items/{}?format=json".format(api.user_id, item_id)
|
||||
result = api.get(url)
|
||||
log.debug("Menu item info: {0}".format(result))
|
||||
|
||||
if result is None:
|
||||
@@ -371,42 +344,42 @@ def show_menu(params):
|
||||
action_items = []
|
||||
|
||||
if result["Type"] in ["Episode", "Movie", "Music", "Video", "Audio", "TvChannel", "Program"]:
|
||||
li = xbmcgui.ListItem(string_load(30314))
|
||||
li = xbmcgui.ListItem(translate_string(30314), offscreen=True)
|
||||
li.setProperty('menu_id', 'play')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ["Season", "MusicAlbum", "Playlist"]:
|
||||
li = xbmcgui.ListItem(string_load(30317))
|
||||
li = xbmcgui.ListItem(translate_string(30317), offscreen=True)
|
||||
li.setProperty('menu_id', 'play_all')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ["Episode", "Movie", "Video", "TvChannel", "Program"]:
|
||||
li = xbmcgui.ListItem(string_load(30275))
|
||||
li = xbmcgui.ListItem(translate_string(30275), offscreen=True)
|
||||
li.setProperty('menu_id', 'transcode')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ["Episode", "Movie", "Music", "Video", "Audio"]:
|
||||
li = xbmcgui.ListItem(string_load(30402))
|
||||
li = xbmcgui.ListItem(translate_string(30402), offscreen=True)
|
||||
li.setProperty('menu_id', 'add_to_playlist')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ("Movie", "Series"):
|
||||
li = xbmcgui.ListItem(string_load(30307))
|
||||
li = xbmcgui.ListItem(translate_string(30307), offscreen=True)
|
||||
li.setProperty('menu_id', 'play_trailer')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] == "Episode" and result["ParentId"] is not None:
|
||||
li = xbmcgui.ListItem(string_load(30327))
|
||||
li = xbmcgui.ListItem(translate_string(30327), offscreen=True)
|
||||
li.setProperty('menu_id', 'view_season')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ("Series", "Season", "Episode"):
|
||||
li = xbmcgui.ListItem(string_load(30354))
|
||||
li = xbmcgui.ListItem(translate_string(30354), offscreen=True)
|
||||
li.setProperty('menu_id', 'view_series')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] == "Movie":
|
||||
li = xbmcgui.ListItem("Show Extras")
|
||||
li = xbmcgui.ListItem("Show Extras", offscreen=True)
|
||||
li.setProperty('menu_id', 'show_extras')
|
||||
action_items.append(li)
|
||||
|
||||
@@ -415,49 +388,43 @@ def show_menu(params):
|
||||
progress = user_data.get("PlaybackPositionTicks", 0) != 0
|
||||
played = user_data.get("Played", False)
|
||||
if not played or progress:
|
||||
li = xbmcgui.ListItem(string_load(30270))
|
||||
li = xbmcgui.ListItem(translate_string(30270), offscreen=True)
|
||||
li.setProperty('menu_id', 'mark_watched')
|
||||
action_items.append(li)
|
||||
if played or progress:
|
||||
li = xbmcgui.ListItem(string_load(30271))
|
||||
li = xbmcgui.ListItem(translate_string(30271), offscreen=True)
|
||||
li.setProperty('menu_id', 'mark_unwatched')
|
||||
action_items.append(li)
|
||||
|
||||
if user_data.get("IsFavorite", False) is False:
|
||||
li = xbmcgui.ListItem(string_load(30272))
|
||||
li = xbmcgui.ListItem(translate_string(30272), offscreen=True)
|
||||
li.setProperty('menu_id', 'jellyfin_set_favorite')
|
||||
action_items.append(li)
|
||||
else:
|
||||
li = xbmcgui.ListItem(string_load(30273))
|
||||
li = xbmcgui.ListItem(translate_string(30273), offscreen=True)
|
||||
li.setProperty('menu_id', 'jellyfin_unset_favorite')
|
||||
action_items.append(li)
|
||||
|
||||
can_delete = result.get("CanDelete", False)
|
||||
if can_delete:
|
||||
li = xbmcgui.ListItem(string_load(30274))
|
||||
li = xbmcgui.ListItem(translate_string(30274), offscreen=True)
|
||||
li.setProperty('menu_id', 'delete')
|
||||
action_items.append(li)
|
||||
|
||||
safe_delete = home_window.get_property("safe_delete_plugin_available") == "true"
|
||||
if safe_delete:
|
||||
li = xbmcgui.ListItem("Safe Delete")
|
||||
li.setProperty('menu_id', 'safe_delete')
|
||||
action_items.append(li)
|
||||
|
||||
li = xbmcgui.ListItem(string_load(30398))
|
||||
li = xbmcgui.ListItem(translate_string(30398), offscreen=True)
|
||||
li.setProperty('menu_id', 'refresh_server')
|
||||
action_items.append(li)
|
||||
|
||||
li = xbmcgui.ListItem(string_load(30281))
|
||||
li = xbmcgui.ListItem(translate_string(30281), offscreen=True)
|
||||
li.setProperty('menu_id', 'refresh_images')
|
||||
action_items.append(li)
|
||||
|
||||
if result["Type"] in ["Movie", "Series"]:
|
||||
li = xbmcgui.ListItem(string_load(30399))
|
||||
li = xbmcgui.ListItem(translate_string(30399), offscreen=True)
|
||||
li.setProperty('menu_id', 'hide')
|
||||
action_items.append(li)
|
||||
|
||||
li = xbmcgui.ListItem(string_load(30401))
|
||||
li = xbmcgui.ListItem(translate_string(30401), offscreen=True)
|
||||
li.setProperty('menu_id', 'info')
|
||||
action_items.append(li)
|
||||
|
||||
@@ -471,11 +438,11 @@ def show_menu(params):
|
||||
|
||||
if container_content_type in ["movies", "tvshows", "seasons", "episodes", "sets"]:
|
||||
if view_match:
|
||||
li = xbmcgui.ListItem("Unset as default view")
|
||||
li = xbmcgui.ListItem("Unset as default view", offscreen=True)
|
||||
li.setProperty('menu_id', 'unset_view')
|
||||
action_items.append(li)
|
||||
else:
|
||||
li = xbmcgui.ListItem("Set as default view")
|
||||
li = xbmcgui.ListItem("Set as default view", offscreen=True)
|
||||
li.setProperty('menu_id', 'set_view')
|
||||
action_items.append(li)
|
||||
|
||||
@@ -502,22 +469,22 @@ def show_menu(params):
|
||||
settings.setSetting(view_key, "")
|
||||
|
||||
elif selected_action == "refresh_server":
|
||||
url = ("{server}/Items/" + item_id + "/Refresh" +
|
||||
url = ("/Items/" + item_id + "/Refresh" +
|
||||
"?Recursive=true" +
|
||||
"&ImageRefreshMode=FullRefresh" +
|
||||
"&MetadataRefreshMode=FullRefresh" +
|
||||
"&ReplaceAllImages=true" +
|
||||
"&ReplaceAllMetadata=true")
|
||||
res = downloadUtils.download_url(url, post_body="", method="POST")
|
||||
res = api.post(url)
|
||||
log.debug("Refresh Server Responce: {0}".format(res))
|
||||
|
||||
elif selected_action == "hide":
|
||||
user_details = load_user_details(settings)
|
||||
user_name = user_details["username"]
|
||||
user_details = load_user_details()
|
||||
user_name = user_details["user_name"]
|
||||
hide_tag_string = "hide-" + user_name
|
||||
url = "{server}/Items/" + item_id + "/Tags/Add"
|
||||
url = "/Items/{}/Tags/Add".format(item_id)
|
||||
post_tag_data = {"Tags": [{"Name": hide_tag_string}]}
|
||||
res = downloadUtils.download_url(url, post_body=post_tag_data, method="POST")
|
||||
res = api.post(url, post_tag_data)
|
||||
log.debug("Add Tag Responce: {0}".format(res))
|
||||
|
||||
check_for_new_content()
|
||||
@@ -571,63 +538,8 @@ def show_menu(params):
|
||||
elif selected_action == "delete":
|
||||
delete(item_id)
|
||||
|
||||
elif selected_action == "safe_delete":
|
||||
url = "{server}/jellyfin_safe_delete/delete_item/" + item_id
|
||||
result = downloadUtils.download_url(url)
|
||||
dialog = xbmcgui.Dialog()
|
||||
if result:
|
||||
log.debug("Safe_Delete_Action: {0}".format(result))
|
||||
action_token = result["action_token"]
|
||||
|
||||
message = "You are about to delete the following item[CR][CR]"
|
||||
|
||||
message += "Type: " + result["item_info"]["Item_type"] + "[CR]"
|
||||
|
||||
if result["item_info"]["Item_type"] == "Series":
|
||||
message += "Name: " + result["item_info"]["item_name"] + "[CR]"
|
||||
elif result["item_info"]["Item_type"] == "Season":
|
||||
message += "Season: " + str(result["item_info"]["season_number"]) + "[CR]"
|
||||
message += "Name: " + result["item_info"]["season_name"] + "[CR]"
|
||||
elif result["item_info"]["Item_type"] == "Episode":
|
||||
message += "Series: " + result["item_info"]["series_name"] + "[CR]"
|
||||
message += "Season: " + result["item_info"]["season_name"] + "[CR]"
|
||||
message += "Episode: " + str(result["item_info"]["episode_number"]) + "[CR]"
|
||||
message += "Name: " + result["item_info"]["item_name"] + "[CR]"
|
||||
else:
|
||||
message += "Name: " + result["item_info"]["item_name"] + "[CR]"
|
||||
|
||||
message += "[CR]File List[CR][CR]"
|
||||
|
||||
for file_info in result["file_list"]:
|
||||
message += " - " + file_info["Key"] + " (" + convert_size(file_info["Value"]) + ")[CR]"
|
||||
message += "[CR][CR]Are you sure?[CR][CR]"
|
||||
|
||||
confirm_dialog = SafeDeleteDialog("SafeDeleteDialog.xml", PLUGINPATH, "default", "720p")
|
||||
confirm_dialog.message = message
|
||||
confirm_dialog.heading = "Confirm delete files?"
|
||||
confirm_dialog.doModal()
|
||||
log.debug("safe_delete_confirm_dialog: {0}".format(confirm_dialog.confirm))
|
||||
|
||||
if confirm_dialog.confirm:
|
||||
url = "{server}/jellyfin_safe_delete/delete_item_action"
|
||||
playback_info = {
|
||||
'item_id': item_id,
|
||||
'action_token': action_token
|
||||
}
|
||||
delete_action = downloadUtils.download_url(url, method="POST", post_body=playback_info)
|
||||
log.debug("Delete result action: {0}".format(delete_action))
|
||||
if not delete_action:
|
||||
dialog.ok("Error", "Error deleting files", "Error in responce from server")
|
||||
elif not delete_action.get("result"):
|
||||
dialog.ok("Error", "Error deleting files", delete_action["message"])
|
||||
else:
|
||||
dialog.ok("Deleted", "Files deleted")
|
||||
else:
|
||||
dialog.ok("Error", "Error getting safe delete confirmation")
|
||||
|
||||
elif selected_action == "show_extras":
|
||||
# "http://localhost:8096/Users/3138bed521e5465b9be26d2c63be94af/Items/78/SpecialFeatures"
|
||||
u = "{server}/Users/{userid}/Items/" + item_id + "/SpecialFeatures"
|
||||
u = "/Users/{}/Items/{}/SpecialFeatures".format(api.user_id, item_id)
|
||||
action_url = ("plugin://plugin.video.jellycon/?url=" + quote(u) + "&mode=GET_CONTENT&media_type=Videos")
|
||||
built_in_command = 'ActivateWindow(Videos, ' + action_url + ', return)'
|
||||
xbmc.executebuiltin(built_in_command)
|
||||
@@ -636,13 +548,13 @@ def show_menu(params):
|
||||
xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
parent_id = result["ParentId"]
|
||||
series_id = result["SeriesId"]
|
||||
u = ('{server}/Shows/' + series_id +
|
||||
u = ('/Shows/' + series_id +
|
||||
'/Episodes'
|
||||
'?userId={userid}' +
|
||||
'?userId={}'.format(api.user_id) +
|
||||
'&seasonId=' + parent_id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=SpecialEpisodeNumbers,{field_filters}' +
|
||||
'&Fields=SpecialEpisodeNumbers,{}'.format(get_default_filters()) +
|
||||
'&format=json')
|
||||
action_url = ("plugin://plugin.video.jellycon/?url=" + quote(u) + "&mode=GET_CONTENT&media_type=Season")
|
||||
built_in_command = 'ActivateWindow(Videos, ' + action_url + ', return)'
|
||||
@@ -655,10 +567,10 @@ def show_menu(params):
|
||||
if not series_id:
|
||||
series_id = item_id
|
||||
|
||||
u = ('{server}/Shows/' + series_id +
|
||||
u = ('/Shows/' + series_id +
|
||||
'/Seasons'
|
||||
'?userId={userid}' +
|
||||
'&Fields={field_filters}' +
|
||||
'?userId={}'.format(api.user_id) +
|
||||
'&Fields={}'.format(get_default_filters()) +
|
||||
'&format=json')
|
||||
|
||||
action_url = ("plugin://plugin.video.jellycon/?url=" + quote(u) + "&mode=GET_CONTENT&media_type=Series")
|
||||
@@ -678,40 +590,6 @@ def show_menu(params):
|
||||
xbmc.executebuiltin("Action(info)")
|
||||
|
||||
|
||||
def populate_listitem(item_id):
|
||||
log.debug("populate_listitem: {0}".format(item_id))
|
||||
|
||||
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
|
||||
result = downloadUtils.download_url(url)
|
||||
log.debug("populate_listitem item info: {0}".format(result))
|
||||
|
||||
item_title = result.get("Name", string_load(30280))
|
||||
|
||||
list_item = xbmcgui.ListItem(label=item_title)
|
||||
|
||||
server = downloadUtils.get_server()
|
||||
|
||||
art = get_art(result, server=server)
|
||||
list_item.setIconImage(art['thumb']) # back compat
|
||||
list_item.setProperty('fanart_image', art['fanart']) # back compat
|
||||
list_item.setProperty('discart', art['discart']) # not avail to setArt
|
||||
list_item.setArt(art)
|
||||
|
||||
list_item.setProperty('IsPlayable', 'false')
|
||||
list_item.setProperty('IsFolder', 'false')
|
||||
list_item.setProperty('id', result.get("Id"))
|
||||
|
||||
# play info
|
||||
details = {
|
||||
'title': item_title,
|
||||
'plot': result.get("Overview")
|
||||
}
|
||||
|
||||
list_item.setInfo("Video", infoLabels=details)
|
||||
|
||||
return list_item
|
||||
|
||||
|
||||
def show_content(params):
|
||||
log.debug("showContent Called: {0}".format(params))
|
||||
|
||||
@@ -722,11 +600,11 @@ def show_content(params):
|
||||
if item_type.lower().find("movie") == -1:
|
||||
group_movies = False
|
||||
|
||||
content_url = ("{server}/Users/{userid}/Items" +
|
||||
content_url = ("/Users/{}/Items".format(api.user_id) +
|
||||
"?format=json" +
|
||||
"&ImageTypeLimit=1" +
|
||||
"&IsMissing=False" +
|
||||
"&Fields={field_filters}" +
|
||||
"&Fields={}".format(get_default_filters()) +
|
||||
'&CollapseBoxSetItems=' + str(group_movies) +
|
||||
'&GroupItemsIntoCollections=' + str(group_movies) +
|
||||
"&Recursive=true" +
|
||||
@@ -744,10 +622,10 @@ def search_results_person(params):
|
||||
handle = int(sys.argv[1])
|
||||
|
||||
person_id = params.get("person_id")
|
||||
details_url = ('{server}/Users/{userid}/items' +
|
||||
details_url = ('/Users/{}/Items'.format(api.user_id) +
|
||||
'?PersonIds=' + person_id +
|
||||
'&Recursive=true' +
|
||||
'&Fields={field_filters}' +
|
||||
'&Fields={}'.format(get_default_filters()) +
|
||||
'&format=json')
|
||||
|
||||
params["name_format"] = "Episode|episode_name_format"
|
||||
@@ -792,13 +670,13 @@ def search_results(params):
|
||||
item_type = item_type.lower()
|
||||
|
||||
if item_type == 'movie':
|
||||
heading_type = string_load(30231)
|
||||
heading_type = translate_string(30231)
|
||||
content_type = 'movies'
|
||||
elif item_type == 'series':
|
||||
heading_type = string_load(30229)
|
||||
heading_type = translate_string(30229)
|
||||
content_type = 'tvshows'
|
||||
elif item_type == 'episode':
|
||||
heading_type = string_load(30235)
|
||||
heading_type = translate_string(30235)
|
||||
content_type = 'episodes'
|
||||
params["name_format"] = "Episode|episode_name_format"
|
||||
elif item_type == "music" or item_type == "audio" or item_type == "musicalbum":
|
||||
@@ -817,7 +695,7 @@ def search_results(params):
|
||||
home_window = HomeWindow()
|
||||
last_search = home_window.get_property("last_search")
|
||||
kb = xbmc.Keyboard()
|
||||
kb.setHeading(heading_type.capitalize() + ' ' + string_load(30246).lower())
|
||||
kb.setHeading(heading_type.capitalize() + ' ' + translate_string(30246).lower())
|
||||
kb.setDefault(last_search)
|
||||
kb.doModal()
|
||||
|
||||
@@ -844,12 +722,12 @@ def search_results(params):
|
||||
progress = None
|
||||
if settings.getSetting('showLoadProgress') == "true":
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(string_load(30112))
|
||||
progress.update(0, string_load(30113))
|
||||
progress.create(translate_string(30112))
|
||||
progress.update(0, translate_string(30113))
|
||||
|
||||
# what type of search
|
||||
if item_type == "person":
|
||||
search_url = ("{server}/Persons" +
|
||||
search_url = ("/Persons" +
|
||||
"?searchTerm=" + query +
|
||||
"&IncludePeople=true" +
|
||||
"&IncludeMedia=false" +
|
||||
@@ -861,25 +739,25 @@ def search_results(params):
|
||||
"&Recursive=true" +
|
||||
"&EnableTotalRecordCount=false" +
|
||||
"&ImageTypeLimit=1" +
|
||||
"&userId={userid}")
|
||||
"&userId={}".format(api.user_id))
|
||||
|
||||
person_search_results = dataManager.get_content(search_url)
|
||||
person_search_results = api.get(search_url)
|
||||
log.debug("Person Search Result : {0}".format(person_search_results))
|
||||
if person_search_results is None:
|
||||
return
|
||||
|
||||
person_items = person_search_results.get("Items", [])
|
||||
|
||||
server = downloadUtils.get_server()
|
||||
server = settings.getSetting('server_address')
|
||||
list_items = []
|
||||
for item in person_items:
|
||||
person_id = item.get('Id')
|
||||
person_name = item.get('Name')
|
||||
person_thumbnail = downloadUtils.get_artwork(item, "Primary", server=server)
|
||||
person_thumbnail = get_art_url(item, "Primary", server=server)
|
||||
|
||||
action_url = sys.argv[0] + "?mode=NEW_SEARCH_PERSON&person_id=" + person_id
|
||||
|
||||
list_item = xbmcgui.ListItem(label=person_name)
|
||||
list_item = xbmcgui.ListItem(label=person_name, offscreen=True)
|
||||
list_item.setProperty("id", person_id)
|
||||
|
||||
art_links = {}
|
||||
@@ -897,7 +775,7 @@ def search_results(params):
|
||||
xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
|
||||
|
||||
else:
|
||||
search_url = ("{server}/Users/{userid}/Items" +
|
||||
search_url = ("/Users/{}/Items".format(api.user_id) +
|
||||
"?searchTerm=" + query +
|
||||
"&IncludePeople=false" +
|
||||
"&IncludeMedia=true" +
|
||||
@@ -906,7 +784,7 @@ def search_results(params):
|
||||
"&IncludeArtists=false" +
|
||||
"&IncludeItemTypes=" + item_type +
|
||||
"&Limit=16" +
|
||||
"&Fields={field_filters}" +
|
||||
"&Fields={}".format(get_default_filters()) +
|
||||
"&Recursive=true" +
|
||||
"&EnableTotalRecordCount=false" +
|
||||
"&ImageTypeLimit=1")
|
||||
@@ -918,7 +796,7 @@ def search_results(params):
|
||||
xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
|
||||
|
||||
if progress is not None:
|
||||
progress.update(100, string_load(30125))
|
||||
progress.update(100, translate_string(30125))
|
||||
progress.close()
|
||||
|
||||
|
||||
@@ -973,9 +851,9 @@ def play_action(params):
|
||||
def play_item_trailer(item_id):
|
||||
log.debug("== ENTER: playTrailer ==")
|
||||
|
||||
url = ("{server}/Users/{userid}/Items/%s/LocalTrailers?format=json" % item_id)
|
||||
url = "/Users/{}/Items/{}/LocalTrailers?format=json".format(user_details.get('user_id'), item_id)
|
||||
|
||||
result = downloadUtils.download_url(url)
|
||||
result = api.get(url)
|
||||
|
||||
if result is None:
|
||||
return
|
||||
@@ -998,8 +876,8 @@ def play_item_trailer(item_id):
|
||||
trailer_names.append(name)
|
||||
trailer_list.append(info)
|
||||
|
||||
url = ("{server}/Users/{userid}/Items/%s?format=json&Fields=RemoteTrailers" % item_id)
|
||||
result = downloadUtils.download_url(url)
|
||||
url = "/Users/{}/Items/{}?format=json&Fields=RemoteTrailers".format(user_details.get('user_id'), item_id)
|
||||
result = api.get(url)
|
||||
log.debug("RemoteTrailers: {0}".format(result))
|
||||
count = 1
|
||||
|
||||
@@ -1029,7 +907,7 @@ def play_item_trailer(item_id):
|
||||
trailer_text.append(name)
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
resp = dialog.select(string_load(30308), trailer_text)
|
||||
resp = dialog.select(translate_string(30308), trailer_text)
|
||||
if resp > -1:
|
||||
trailer = trailer_list[resp]
|
||||
log.debug("SelectedTrailer: {0}".format(trailer))
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
|
||||
import xbmcvfs
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import base64
|
||||
import re
|
||||
from random import shuffle
|
||||
@@ -13,16 +14,16 @@ import threading
|
||||
import requests
|
||||
import io
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .datamanager import DataManager
|
||||
from .downloadutils import DownloadUtils
|
||||
from .utils import get_art
|
||||
from .jellyfin import api
|
||||
from .lazylogger import LazyLogger
|
||||
from .item_functions import get_art
|
||||
from .utils import translate_path
|
||||
|
||||
pil_loaded = False
|
||||
try:
|
||||
from PIL import ImageFilter, Image, ImageOps
|
||||
from PIL import Image, ImageOps
|
||||
pil_loaded = True
|
||||
except Exception as err:
|
||||
except ImportError:
|
||||
pil_loaded = False
|
||||
|
||||
PORT_NUMBER = 24276
|
||||
@@ -31,8 +32,8 @@ log = LazyLogger(__name__)
|
||||
|
||||
def get_image_links(url):
|
||||
|
||||
download_utils = DownloadUtils()
|
||||
server = download_utils.get_server()
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
if server is None:
|
||||
return []
|
||||
|
||||
@@ -50,8 +51,7 @@ def get_image_links(url):
|
||||
if not re.search('EnableUserData=', url, re.IGNORECASE):
|
||||
url += "&EnableUserData=False"
|
||||
|
||||
data_manager = DataManager()
|
||||
result = data_manager.get_content(url)
|
||||
result = api.get(url)
|
||||
|
||||
items = result.get("Items")
|
||||
if not items:
|
||||
@@ -171,7 +171,7 @@ class HttpImageHandler(BaseHTTPRequestHandler):
|
||||
|
||||
else:
|
||||
|
||||
image_path = xbmc.translatePath("special://home/addons/plugin.video.jellycon/icon.png").decode('utf-8')
|
||||
image_path = translate_path("special://home/addons/plugin.video.jellycon/icon.png").decode('utf-8')
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
modified = xbmcvfs.Stat(image_path).st_mtime()
|
||||
@@ -187,7 +187,6 @@ class HttpImageHandler(BaseHTTPRequestHandler):
|
||||
|
||||
class HttpImageServerThread(threading.Thread):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
self.keep_running = True
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import sys
|
||||
import os
|
||||
from six.moves.urllib.parse import quote
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
from .utils import get_art, datetime_from_string
|
||||
from .loghandler import LazyLogger
|
||||
from .downloadutils import DownloadUtils
|
||||
from .kodi_utils import HomeWindow
|
||||
from .utils import datetime_from_string, get_art_url, image_url, kodi_version
|
||||
from .lazylogger import LazyLogger
|
||||
from six import ensure_text
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
addon_instance = xbmcaddon.Addon()
|
||||
addon_path = addon_instance.getAddonInfo('path')
|
||||
PLUGINPATH = xbmc.translatePath(os.path.join(addon_path))
|
||||
|
||||
download_utils = DownloadUtils()
|
||||
home_window = HomeWindow()
|
||||
|
||||
|
||||
class ItemDetails:
|
||||
@@ -262,10 +247,9 @@ def extract_item_info(item, gui_options):
|
||||
person_id = person.get("Id")
|
||||
person_tag = person.get("PrimaryImageTag")
|
||||
if person_tag:
|
||||
person_thumbnail = download_utils.image_url(person_id,
|
||||
"Primary", 0, 400, 400,
|
||||
person_tag,
|
||||
server=gui_options["server"])
|
||||
person_thumbnail = image_url(person_id, "Primary", 0, 400,
|
||||
400, person_tag,
|
||||
server=gui_options["server"])
|
||||
else:
|
||||
person_thumbnail = ""
|
||||
person = {"name": person_name, "role": person_role, "thumbnail": person_thumbnail}
|
||||
@@ -418,7 +402,7 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
end_time = datetime_from_string(item_details.program_end_date)
|
||||
|
||||
duration = (end_time - start_time).total_seconds()
|
||||
time_done = (datetime.now() - start_time).total_seconds()
|
||||
time_done = (datetime.now().astimezone() - start_time).total_seconds()
|
||||
percentage_done = (float(time_done) / float(duration)) * 100.0
|
||||
capped_percentage = int(percentage_done)
|
||||
|
||||
@@ -428,10 +412,14 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
item_details.duration = int(duration)
|
||||
item_details.resume_time = int(time_done)
|
||||
|
||||
list_item_name = (item_details.program_channel_name +
|
||||
" - " + list_item_name +
|
||||
" - " + start_time_string + " to " + end_time_string +
|
||||
" (" + str(int(percentage_done)) + "%)")
|
||||
if item_details.program_channel_name:
|
||||
list_item_name = '{} - {} - {} to {} ({}%)'.format(
|
||||
item_details.program_channel_name, list_item_name,
|
||||
start_time_string, end_time_string, capped_percentage)
|
||||
else:
|
||||
list_item_name = '{} - {} to {} ({}%)'.format(
|
||||
list_item_name, start_time_string, end_time_string,
|
||||
capped_percentage)
|
||||
|
||||
time_info = "Start : " + start_time_string + "\n"
|
||||
time_info += "End : " + end_time_string + "\n"
|
||||
@@ -441,10 +429,7 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
else:
|
||||
item_details.plot = time_info
|
||||
|
||||
if kodi_version > 17:
|
||||
list_item = xbmcgui.ListItem(list_item_name, offscreen=True)
|
||||
else:
|
||||
list_item = xbmcgui.ListItem(list_item_name, iconImage=thumb_path, thumbnailImage=thumb_path)
|
||||
list_item = xbmcgui.ListItem(list_item_name, offscreen=True)
|
||||
|
||||
item_properties = {}
|
||||
|
||||
@@ -471,11 +456,8 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
info_labels = {}
|
||||
|
||||
# add cast
|
||||
if item_details.cast is not None:
|
||||
if kodi_version >= 17:
|
||||
list_item.setCast(item_details.cast)
|
||||
else:
|
||||
info_labels['cast'] = info_labels['castandrole'] = [(cast_member['name'], cast_member['role']) for cast_member in item_details.cast]
|
||||
if item_details.cast:
|
||||
list_item.setCast(item_details.cast)
|
||||
|
||||
info_labels["title"] = list_item_name
|
||||
if item_details.sort_name:
|
||||
@@ -608,10 +590,89 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
if item_details.baseline_itemname is not None:
|
||||
item_properties["suggested_from_watching"] = item_details.baseline_itemname
|
||||
|
||||
if kodi_version > 17:
|
||||
list_item.setProperties(item_properties)
|
||||
else:
|
||||
for key, value in item_properties.iteritems():
|
||||
list_item.setProperty(key, value)
|
||||
list_item.setProperties(item_properties)
|
||||
|
||||
return u, list_item, folder
|
||||
|
||||
|
||||
def get_art(item, server):
|
||||
|
||||
art = {
|
||||
'thumb': '',
|
||||
'fanart': '',
|
||||
'poster': '',
|
||||
'banner': '',
|
||||
'clearlogo': '',
|
||||
'clearart': '',
|
||||
'discart': '',
|
||||
'landscape': '',
|
||||
'tvshow.fanart': '',
|
||||
'tvshow.poster': '',
|
||||
'tvshow.clearart': '',
|
||||
'tvshow.clearlogo': '',
|
||||
'tvshow.banner': '',
|
||||
'tvshow.landscape': ''
|
||||
}
|
||||
|
||||
image_tags = item.get("ImageTags", {})
|
||||
if image_tags and image_tags.get("Primary"):
|
||||
art['thumb'] = get_art_url(item, "Primary", server=server)
|
||||
|
||||
item_type = item["Type"]
|
||||
|
||||
if item_type == "Genre":
|
||||
art['poster'] = get_art_url(item, "Primary", server=server)
|
||||
elif item_type == "Episode":
|
||||
art['tvshow.poster'] = get_art_url(item, "Primary", parent=True, server=server)
|
||||
art['tvshow.clearart'] = get_art_url(item, "Art", parent=True, server=server)
|
||||
art['clearart'] = get_art_url(item, "Art", parent=True, server=server)
|
||||
art['tvshow.clearlogo'] = get_art_url(item, "Logo", parent=True, server=server)
|
||||
art['clearlogo'] = get_art_url(item, "Logo", parent=True, server=server)
|
||||
art['tvshow.banner'] = get_art_url(item, "Banner", parent=True, server=server)
|
||||
art['banner'] = get_art_url(item, "Banner", parent=True, server=server)
|
||||
art['tvshow.landscape'] = get_art_url(item, "Thumb", parent=True, server=server)
|
||||
art['landscape'] = get_art_url(item, "Thumb", parent=True, server=server)
|
||||
art['tvshow.fanart'] = get_art_url(item, "Backdrop", parent=True, server=server)
|
||||
art['fanart'] = get_art_url(item, "Backdrop", parent=True, server=server)
|
||||
elif item_type == "Season":
|
||||
art['tvshow.poster'] = get_art_url(item, "Primary", parent=True, server=server)
|
||||
art['season.poster'] = get_art_url(item, "Primary", parent=False, server=server)
|
||||
art['poster'] = get_art_url(item, "Primary", parent=False, server=server)
|
||||
art['tvshow.clearart'] = get_art_url(item, "Art", parent=True, server=server)
|
||||
art['clearart'] = get_art_url(item, "Art", parent=True, server=server)
|
||||
art['tvshow.clearlogo'] = get_art_url(item, "Logo", parent=True, server=server)
|
||||
art['clearlogo'] = get_art_url(item, "Logo", parent=True, server=server)
|
||||
art['tvshow.banner'] = get_art_url(item, "Banner", parent=True, server=server)
|
||||
art['season.banner'] = get_art_url(item, "Banner", parent=False, server=server)
|
||||
art['banner'] = get_art_url(item, "Banner", parent=False, server=server)
|
||||
art['tvshow.landscape'] = get_art_url(item, "Thumb", parent=True, server=server)
|
||||
art['season.landscape'] = get_art_url(item, "Thumb", parent=False, server=server)
|
||||
art['landscape'] = get_art_url(item, "Thumb", parent=False, server=server)
|
||||
art['tvshow.fanart'] = get_art_url(item, "Backdrop", parent=True, server=server)
|
||||
art['fanart'] = get_art_url(item, "Backdrop", parent=True, server=server)
|
||||
elif item_type == "Series":
|
||||
art['tvshow.poster'] = get_art_url(item, "Primary", parent=False, server=server)
|
||||
art['poster'] = get_art_url(item, "Primary", parent=False, server=server)
|
||||
art['tvshow.clearart'] = get_art_url(item, "Art", parent=False, server=server)
|
||||
art['clearart'] = get_art_url(item, "Art", parent=False, server=server)
|
||||
art['tvshow.clearlogo'] = get_art_url(item, "Logo", parent=False, server=server)
|
||||
art['clearlogo'] = get_art_url(item, "Logo", parent=False, server=server)
|
||||
art['tvshow.banner'] = get_art_url(item, "Banner", parent=False, server=server)
|
||||
art['banner'] = get_art_url(item, "Banner", parent=False, server=server)
|
||||
art['tvshow.landscape'] = get_art_url(item, "Thumb", parent=False, server=server)
|
||||
art['landscape'] = get_art_url(item, "Thumb", parent=False, server=server)
|
||||
art['tvshow.fanart'] = get_art_url(item, "Backdrop", parent=False, server=server)
|
||||
art['fanart'] = get_art_url(item, "Backdrop", parent=False, server=server)
|
||||
elif item_type == "Movie" or item_type == "BoxSet":
|
||||
art['poster'] = get_art_url(item, "Primary", server=server)
|
||||
art['landscape'] = get_art_url(item, "Thumb", server=server)
|
||||
art['banner'] = get_art_url(item, "Banner", server=server)
|
||||
art['clearlogo'] = get_art_url(item, "Logo", server=server)
|
||||
art['clearart'] = get_art_url(item, "Art", server=server)
|
||||
art['discart'] = get_art_url(item, "Disc", server=server)
|
||||
|
||||
art['fanart'] = get_art_url(item, "Backdrop", server=server)
|
||||
if not art['fanart']:
|
||||
art['fanart'] = get_art_url(item, "Backdrop", parent=True, server=server)
|
||||
|
||||
return art
|
||||
|
||||
176
resources/lib/jellyfin.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmcaddon
|
||||
from kodi_six.utils import py2_decode
|
||||
|
||||
import requests
|
||||
|
||||
from .utils import get_device_id, get_version, load_user_details
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class API:
|
||||
def __init__(self, server=None, user_id=None, token=None):
|
||||
self.server = server
|
||||
self.user_id = user_id
|
||||
self.token = token
|
||||
|
||||
self.settings = xbmcaddon.Addon()
|
||||
|
||||
self.headers = {}
|
||||
self.create_headers()
|
||||
self.verify_cert = settings.getSetting('verify_cert') == 'true'
|
||||
|
||||
def get(self, path):
|
||||
if 'x-mediabrowser-token' not in self.headers:
|
||||
self.create_headers()
|
||||
|
||||
# Fixes initial login where class is initialized before wizard completes
|
||||
if not self.server:
|
||||
self.settings = xbmcaddon.Addon()
|
||||
self.server = self.settings.getSetting('server_address')
|
||||
|
||||
url = '{}{}'.format(self.server, path)
|
||||
|
||||
r = requests.get(url, headers=self.headers, verify=self.verify_cert)
|
||||
try:
|
||||
response_data = r.json()
|
||||
except:
|
||||
response_data = {}
|
||||
return response_data
|
||||
|
||||
def post(self, url, payload={}):
|
||||
if 'x-mediabrowser-token' not in self.headers:
|
||||
self.create_headers()
|
||||
|
||||
url = '{}{}'.format(self.server, url)
|
||||
|
||||
r = requests.post(url, json=payload, headers=self.headers, verify=self.verify_cert)
|
||||
try:
|
||||
response_data = r.json()
|
||||
except:
|
||||
response_data = {}
|
||||
return response_data
|
||||
|
||||
def delete(self, url):
|
||||
if 'x-mediabrowser-token' not in self.headers:
|
||||
self.create_headers()
|
||||
|
||||
url = '{}{}'.format(self.server, url)
|
||||
|
||||
requests.delete(url, headers=self.headers, verify=self.verify_cert)
|
||||
|
||||
def authenticate(self, auth_data):
|
||||
response = self.post('/Users/AuthenticateByName', auth_data)
|
||||
token = response.get('AccessToken')
|
||||
if token:
|
||||
self.token = token
|
||||
self.user_id = response.get('User').get('Id')
|
||||
# Create headers again to include auth token
|
||||
self.create_headers()
|
||||
return response
|
||||
else:
|
||||
log.error('Unable to authenticate to Jellyfin server')
|
||||
return {}
|
||||
|
||||
def create_headers(self):
|
||||
|
||||
# If the headers already exist with an auth token, return
|
||||
if self.headers and 'x-mediabrowser-token' in self.headers:
|
||||
return
|
||||
|
||||
headers = {}
|
||||
device_name = self.settings.getSetting('deviceName')
|
||||
if len(device_name) == 0:
|
||||
device_name = "JellyCon"
|
||||
# Ensure ascii and remove invalid characters
|
||||
device_name = py2_decode(device_name).replace('"', '_').replace(',', '_')
|
||||
device_id = get_device_id()
|
||||
version = get_version()
|
||||
|
||||
authorization = (
|
||||
'MediaBrowser Client="Kodi JellyCon", Device="{device}", '
|
||||
'DeviceId="{device_id}", Version="{version}"'
|
||||
).format(
|
||||
device=device_name,
|
||||
device_id=device_id,
|
||||
version=version
|
||||
)
|
||||
|
||||
headers['x-emby-authorization'] = authorization
|
||||
|
||||
# If we have a valid token, ensure it's included in the headers
|
||||
if self.token:
|
||||
headers['x-mediabrowser-token'] = self.token
|
||||
else:
|
||||
# Check for updated credentials since initialization
|
||||
user_details = load_user_details()
|
||||
token = user_details.get('token')
|
||||
if token:
|
||||
self.token = token
|
||||
headers['x-mediabrowser-token'] = self.token
|
||||
|
||||
# Make headers available to api calls
|
||||
self.headers = headers
|
||||
|
||||
def post_capabilities(self):
|
||||
url = '/Sessions/Capabilities/Full'
|
||||
|
||||
data = {
|
||||
'SupportsMediaControl': True,
|
||||
'PlayableMediaTypes': ["Video", "Audio"],
|
||||
'SupportedCommands': ["MoveUp",
|
||||
"MoveDown",
|
||||
"MoveLeft",
|
||||
"MoveRight",
|
||||
"Select",
|
||||
"Back",
|
||||
"ToggleContextMenu",
|
||||
"ToggleFullscreen",
|
||||
"ToggleOsdMenu",
|
||||
"GoHome",
|
||||
"PageUp",
|
||||
"NextLetter",
|
||||
"GoToSearch",
|
||||
"GoToSettings",
|
||||
"PageDown",
|
||||
"PreviousLetter",
|
||||
"TakeScreenshot",
|
||||
"VolumeUp",
|
||||
"VolumeDown",
|
||||
"ToggleMute",
|
||||
"SendString",
|
||||
"DisplayMessage",
|
||||
"SetAudioStreamIndex",
|
||||
"SetSubtitleStreamIndex",
|
||||
"SetRepeatMode",
|
||||
"Mute",
|
||||
"Unmute",
|
||||
"SetVolume",
|
||||
"PlayNext",
|
||||
"Play",
|
||||
"Playstate",
|
||||
"PlayMediaSource"]
|
||||
}
|
||||
|
||||
self.post(url, data)
|
||||
|
||||
def speedtest(self, test_data_size):
|
||||
self.create_headers()
|
||||
|
||||
url = '{}/playback/bitratetest?size={}'.format(self.server, test_data_size)
|
||||
# Because this needs the stream argument, this doesn't go through self.get()
|
||||
response = requests.get(url, stream=True, headers=self.headers, verify=self.verify_cert)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
user_details = load_user_details()
|
||||
api = API(
|
||||
settings.getSetting('server_address'),
|
||||
user_details.get('user_id'),
|
||||
user_details.get('token')
|
||||
)
|
||||
@@ -1,14 +1,12 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
import xbmcaddon
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
addon = xbmcaddon.Addon()
|
||||
@@ -38,29 +36,10 @@ class HomeWindow:
|
||||
|
||||
|
||||
def add_menu_directory_item(label, path, folder=True, art=None):
|
||||
li = xbmcgui.ListItem(label, path=path)
|
||||
li = xbmcgui.ListItem(label, path=path, offscreen=True)
|
||||
if art is None:
|
||||
art = {}
|
||||
art["thumb"] = addon.getAddonInfo('icon')
|
||||
li.setArt(art)
|
||||
|
||||
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
|
||||
|
||||
|
||||
def get_kodi_version():
|
||||
|
||||
json_data = xbmc.executeJSONRPC(
|
||||
'{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["version", "name"]}, "id": 1 }')
|
||||
|
||||
result = json.loads(json_data)
|
||||
|
||||
try:
|
||||
result = result.get("result")
|
||||
version_data = result.get("version")
|
||||
version = float(str(version_data.get("major")) + "." + str(version_data.get("minor")))
|
||||
log.debug("Version: {0} - {1}".format(version, version_data))
|
||||
except:
|
||||
version = 0.0
|
||||
log.error("Version Error : RAW Version Data: {0}".format(result))
|
||||
|
||||
return version
|
||||
|
||||
19
resources/lib/lazylogger.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
|
||||
class LazyLogger(object):
|
||||
"""`helper.loghandler.getLogger()` is used everywhere.
|
||||
This class helps avoiding import errors.
|
||||
"""
|
||||
__logger = None
|
||||
__logger_name = None
|
||||
|
||||
def __init__(self, logger_name=None):
|
||||
self.__logger_name = logger_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.__logger is None:
|
||||
from .loghandler import getLogger
|
||||
self.__logger = getLogger(self.__logger_name)
|
||||
return getattr(self.__logger, name)
|
||||
@@ -10,12 +10,13 @@ import traceback
|
||||
|
||||
from six import ensure_text
|
||||
from kodi_six import xbmc, xbmcaddon
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from .utils import translate_path
|
||||
|
||||
##################################################################################################
|
||||
|
||||
__addon__ = xbmcaddon.Addon(id='plugin.video.jellycon')
|
||||
__pluginpath__ = xbmc.translatePath(__addon__.getAddonInfo('path'))
|
||||
__pluginpath__ = translate_path(__addon__.getAddonInfo('path'))
|
||||
|
||||
##################################################################################################
|
||||
|
||||
@@ -111,22 +112,6 @@ class MyFormatter(logging.Formatter):
|
||||
record.relpath = os.path.relpath(record.pathname, __pluginpath__)
|
||||
|
||||
|
||||
class LazyLogger(object):
|
||||
"""`helper.loghandler.getLogger()` is used everywhere.
|
||||
This class helps avoiding import errors.
|
||||
"""
|
||||
__logger = None
|
||||
__logger_name = None
|
||||
|
||||
def __init__(self, logger_name=None):
|
||||
self.__logger_name = logger_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.__logger is None:
|
||||
self.__logger = getLogger(self.__logger_name)
|
||||
return getattr(self.__logger, name)
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
enc = sys.getfilesystemencoding()
|
||||
|
||||
|
||||
@@ -4,14 +4,47 @@ import threading
|
||||
import time
|
||||
|
||||
import xbmc
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .functions import show_menu
|
||||
from .lazylogger import LazyLogger
|
||||
from .widgets import check_for_new_content
|
||||
from .tracking import timer
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class ContextMonitor(threading.Thread):
|
||||
|
||||
stop_thread = False
|
||||
|
||||
def run(self):
|
||||
|
||||
item_id = None
|
||||
log.debug("ContextMonitor Thread Started")
|
||||
|
||||
while not xbmc.Monitor().abortRequested() and not self.stop_thread:
|
||||
|
||||
if xbmc.getCondVisibility("Window.IsActive(fullscreenvideo) | Window.IsActive(visualisation)"):
|
||||
xbmc.sleep(1000)
|
||||
else:
|
||||
if xbmc.getCondVisibility("Window.IsVisible(contextmenu)"):
|
||||
if item_id:
|
||||
xbmc.executebuiltin("Dialog.Close(contextmenu,true)")
|
||||
params = {}
|
||||
params["item_id"] = item_id
|
||||
show_menu(params)
|
||||
|
||||
container_id = xbmc.getInfoLabel("System.CurrentControlID")
|
||||
item_id = xbmc.getInfoLabel("Container(" + str(container_id) + ").ListItem.Property(id)")
|
||||
|
||||
xbmc.sleep(100)
|
||||
|
||||
log.debug("ContextMonitor Thread Exited")
|
||||
|
||||
def stop_monitor(self):
|
||||
log.debug("ContextMonitor Stop Called")
|
||||
self.stop_thread = True
|
||||
|
||||
|
||||
class LibraryChangeMonitor(threading.Thread):
|
||||
|
||||
last_library_change_check = 0
|
||||
@@ -1,13 +1,12 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class PictureViewer(xbmcgui.WindowXMLDialog):
|
||||
picture_url = None
|
||||
action_exitkeys_id = None
|
||||
|
||||
@@ -4,11 +4,11 @@ import os
|
||||
import threading
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .play_utils import send_event_notification
|
||||
from .lazylogger import LazyLogger
|
||||
from .dialogs import PlayNextDialog
|
||||
from .utils import translate_path
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
@@ -32,6 +32,8 @@ class PlayNextService(threading.Thread):
|
||||
play_next_triggered = False
|
||||
is_playing = False
|
||||
|
||||
now_playing = None
|
||||
|
||||
while not xbmc.Monitor().abortRequested() and not self.stop_thread:
|
||||
|
||||
player = xbmc.Player()
|
||||
@@ -42,6 +44,13 @@ class PlayNextService(threading.Thread):
|
||||
play_next_trigger_time = int(settings.getSetting('play_next_trigger_time'))
|
||||
log.debug("New play_next_trigger_time value: {0}".format(play_next_trigger_time))
|
||||
|
||||
now_playing_file = player.getPlayingFile()
|
||||
if now_playing_file != now_playing:
|
||||
# If the playing file has changed, reset the play next values
|
||||
play_next_dialog = None
|
||||
play_next_triggered = False
|
||||
now_playing = now_playing_file
|
||||
|
||||
duration = player.getTotalTime()
|
||||
position = player.getTime()
|
||||
trigger_time = play_next_trigger_time # 300
|
||||
@@ -51,7 +60,7 @@ class PlayNextService(threading.Thread):
|
||||
play_next_triggered = True
|
||||
log.debug("play_next_triggered hit at {0} seconds from end".format(time_to_end))
|
||||
|
||||
play_data = get_playing_data(self.monitor.played_information)
|
||||
play_data = get_playing_data()
|
||||
log.debug("play_next_triggered play_data : {0}".format(play_data))
|
||||
|
||||
next_episode = play_data.get("next_episode")
|
||||
@@ -61,7 +70,7 @@ class PlayNextService(threading.Thread):
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
plugin_path = settings.getAddonInfo('path')
|
||||
plugin_path_real = xbmc.translatePath(os.path.join(plugin_path))
|
||||
plugin_path_real = translate_path(os.path.join(plugin_path))
|
||||
|
||||
play_next_dialog = PlayNextDialog("PlayNextDialog.xml", plugin_path_real, "default", "720p")
|
||||
play_next_dialog.set_episode_info(next_episode)
|
||||
@@ -78,6 +87,7 @@ class PlayNextService(threading.Thread):
|
||||
play_next_dialog = None
|
||||
|
||||
is_playing = False
|
||||
now_playing = None
|
||||
|
||||
if xbmc.Monitor().waitForAbort(1):
|
||||
break
|
||||
@@ -85,67 +95,3 @@ class PlayNextService(threading.Thread):
|
||||
def stop_servcie(self):
|
||||
log.debug("PlayNextService Stop Called")
|
||||
self.stop_thread = True
|
||||
|
||||
|
||||
class PlayNextDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
action_exitkeys_id = None
|
||||
episode_info = None
|
||||
play_called = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("PlayNextDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("PlayNextDialog: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
index = self.episode_info.get("IndexNumber", -1)
|
||||
series_name = self.episode_info.get("SeriesName")
|
||||
next_epp_name = "Episode %02d - (%s)" % (index, self.episode_info.get("Name", "n/a"))
|
||||
|
||||
series_label = self.getControl(3011)
|
||||
series_label.setLabel(series_name)
|
||||
|
||||
series_label = self.getControl(3012)
|
||||
series_label.setLabel(next_epp_name)
|
||||
|
||||
def onFocus(self, control_id):
|
||||
pass
|
||||
|
||||
def doAction(self, action_id):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("PlayNextDialog: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
else:
|
||||
log.debug("PlayNextDialog: onAction: {0}".format(action.getId()))
|
||||
|
||||
def onClick(self, control_id):
|
||||
if control_id == 3013:
|
||||
log.debug("PlayNextDialog: Play Next Episode")
|
||||
self.play_called
|
||||
self.close()
|
||||
next_item_id = self.episode_info.get("Id")
|
||||
log.debug("Playing Next Episode: {0}".format(next_item_id))
|
||||
play_info = {}
|
||||
play_info["item_id"] = next_item_id
|
||||
play_info["auto_resume"] = "-1"
|
||||
play_info["force_transcode"] = False
|
||||
send_event_notification("jellycon_play_action", play_info)
|
||||
elif control_id == 3014:
|
||||
self.close()
|
||||
|
||||
def set_episode_info(self, info):
|
||||
self.episode_info = info
|
||||
|
||||
def get_play_called(self):
|
||||
return self.play_called
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .translation import string_load
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class ResumeDialog(xbmcgui.WindowXMLDialog):
|
||||
resumePlay = -1
|
||||
resumeTimeStamp = ""
|
||||
action_exitkeys_id = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
log.debug("ResumeDialog INITIALISED")
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
self.getControl(3010).setLabel(self.resumeTimeStamp)
|
||||
self.getControl(3011).setLabel(string_load(30237))
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self, actionID):
|
||||
pass
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if controlID == 3010:
|
||||
self.resumePlay = 0
|
||||
self.close()
|
||||
if controlID == 3011:
|
||||
self.resumePlay = 1
|
||||
self.close()
|
||||
|
||||
def setResumeTime(self, timeStamp):
|
||||
self.resumeTimeStamp = timeStamp
|
||||
|
||||
def getResumeAction(self):
|
||||
return self.resumePlay
|
||||
@@ -1,57 +0,0 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
class SafeDeleteDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
confirm = False
|
||||
message = "Demo Message"
|
||||
heading = "Demo Heading"
|
||||
action_exitkeys_id = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug("SafeDeleteDialog: __init__")
|
||||
xbmcgui.WindowXML.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
log.debug("SafeDeleteDialog: onInit")
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
message_control = self.getControl(3)
|
||||
message_control.setText(self.message)
|
||||
|
||||
message_control = self.getControl(4)
|
||||
message_control.setLabel(self.heading)
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self, actionID):
|
||||
pass
|
||||
|
||||
def onMessage(self, message):
|
||||
log.debug("SafeDeleteDialog: onMessage: {0}".format(message))
|
||||
|
||||
def onAction(self, action):
|
||||
|
||||
if action.getId() == 10: # ACTION_PREVIOUS_MENU
|
||||
self.close()
|
||||
elif action.getId() == 92: # ACTION_NAV_BACK
|
||||
self.close()
|
||||
else:
|
||||
log.debug("SafeDeleteDialog: onAction: {0}".format(action.getId()))
|
||||
|
||||
def onClick(self, controlID):
|
||||
if controlID == 1:
|
||||
self.confirm = True
|
||||
self.close()
|
||||
elif controlID == 2:
|
||||
self.confirm = False
|
||||
self.close()
|
||||
@@ -3,25 +3,18 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
|
||||
import socket
|
||||
import json
|
||||
from six.moves.urllib.parse import urlparse
|
||||
import requests
|
||||
import ssl
|
||||
import time
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
import xbmc
|
||||
from six import ensure_binary
|
||||
from kodi_six.utils import py2_decode
|
||||
|
||||
from .kodi_utils import HomeWindow
|
||||
from .downloadutils import DownloadUtils, save_user_details, load_user_details
|
||||
from .loghandler import LazyLogger
|
||||
from .translation import string_load
|
||||
from .utils import datetime_from_string
|
||||
from .clientinfo import ClientInformation
|
||||
from .jellyfin import API
|
||||
from .lazylogger import LazyLogger
|
||||
from .utils import datetime_from_string, translate_string, save_user_details, load_user_details
|
||||
from .dialogs import QuickConnectDialog
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
@@ -33,26 +26,15 @@ def check_connection_speed():
|
||||
log.debug("check_connection_speed")
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
verify_cert = settings.getSetting('verify_cert') == 'true'
|
||||
http_timeout = int(settings.getSetting("http_timeout"))
|
||||
speed_test_data_size = int(settings.getSetting("speed_test_data_size"))
|
||||
test_data_size = speed_test_data_size * 1000000
|
||||
user_details = load_user_details()
|
||||
|
||||
du = DownloadUtils()
|
||||
server = du.get_server()
|
||||
|
||||
url = server + "/playback/bitratetest?size=%s" % test_data_size
|
||||
|
||||
head = du.get_auth_header(True)
|
||||
head["User-Agent"] = "JellyCon-" + ClientInformation().get_version()
|
||||
|
||||
request_details = {
|
||||
"stream": True,
|
||||
"headers": head
|
||||
}
|
||||
|
||||
if not verify_cert:
|
||||
request_details["verify"] = False
|
||||
api = API(
|
||||
settings.getSetting('server_address'),
|
||||
user_details.get('user_id'),
|
||||
user_details.get('token')
|
||||
)
|
||||
|
||||
progress_dialog = xbmcgui.DialogProgress()
|
||||
message = 'Testing with {0} MB of data'.format(speed_test_data_size)
|
||||
@@ -60,8 +42,7 @@ def check_connection_speed():
|
||||
start_time = time.time()
|
||||
|
||||
log.debug("Starting Connection Speed Test")
|
||||
|
||||
response = requests.get(url, **request_details)
|
||||
response = api.speedtest(test_data_size)
|
||||
|
||||
last_percentage_done = 0
|
||||
total_data_read = 0
|
||||
@@ -97,38 +78,12 @@ def check_connection_speed():
|
||||
return speed
|
||||
|
||||
|
||||
def check_safe_delete_available():
|
||||
log.debug("check_safe_delete_available")
|
||||
|
||||
du = DownloadUtils()
|
||||
result = du.download_url("{server}/Plugins")
|
||||
if result:
|
||||
log.debug("Server Plugin List: {0}".format(result))
|
||||
|
||||
safe_delete_found = False
|
||||
for plugin in result:
|
||||
if plugin["Name"] == "Safe Delete":
|
||||
safe_delete_found = True
|
||||
break
|
||||
|
||||
log.debug("Safe Delete Plugin Available: {0}".format(safe_delete_found))
|
||||
home_window = HomeWindow()
|
||||
if safe_delete_found:
|
||||
home_window.set_property("safe_delete_plugin_available", "true")
|
||||
else:
|
||||
home_window.clear_property("safe_delete_plugin_available")
|
||||
|
||||
else:
|
||||
log.debug("Error getting server plugin list")
|
||||
|
||||
|
||||
def get_server_details():
|
||||
log.debug("Getting Server Details from Network")
|
||||
servers = []
|
||||
|
||||
message = b"who is JellyfinServer?"
|
||||
multi_group = ("<broadcast>", 7359)
|
||||
# multi_group = ("127.0.0.1", 7359)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.settimeout(4.0)
|
||||
@@ -139,18 +94,17 @@ def get_server_details():
|
||||
log.debug("Sending UDP Data: {0}".format(message))
|
||||
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create('{} : {}'.format(__addon_name__, string_load(30373)))
|
||||
progress.update(0, string_load(30374))
|
||||
progress.create('{} : {}'.format(__addon_name__, translate_string(30373)))
|
||||
progress.update(0, translate_string(30374))
|
||||
xbmc.sleep(1000)
|
||||
server_count = 0
|
||||
|
||||
# while True:
|
||||
try:
|
||||
sock.sendto(message, multi_group)
|
||||
while True:
|
||||
try:
|
||||
server_count += 1
|
||||
progress.update(server_count * 10, '{}: {}'.format(string_load(30375), server_count))
|
||||
progress.update(server_count * 10, '{}: {}'.format(translate_string(30375), server_count))
|
||||
xbmc.sleep(1000)
|
||||
data, addr = sock.recvfrom(1024)
|
||||
servers.append(json.loads(data))
|
||||
@@ -169,18 +123,17 @@ def check_server(force=False, change_user=False, notify=False):
|
||||
log.debug("checkServer Called")
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
server_url = ""
|
||||
something_changed = False
|
||||
du = DownloadUtils()
|
||||
|
||||
# Initialize api object
|
||||
api = API()
|
||||
|
||||
if force is False:
|
||||
# if not forcing use server details from settings
|
||||
svr = du.get_server()
|
||||
if svr is not None:
|
||||
server_url = svr
|
||||
api.server = settings.getSetting('server_address')
|
||||
|
||||
# if the server is not set then try to detect it
|
||||
if server_url == "":
|
||||
if not api.server:
|
||||
|
||||
# scan for local server
|
||||
server_info = get_server_details()
|
||||
@@ -190,7 +143,7 @@ def check_server(force=False, change_user=False, notify=False):
|
||||
|
||||
server_list = []
|
||||
for server in server_info:
|
||||
server_item = xbmcgui.ListItem(server.get("Name", string_load(30063)))
|
||||
server_item = xbmcgui.ListItem(server.get("Name", translate_string(30063)))
|
||||
sub_line = server.get("Address")
|
||||
server_item.setLabel2(sub_line)
|
||||
server_item.setProperty("address", server.get("Address"))
|
||||
@@ -199,177 +152,88 @@ def check_server(force=False, change_user=False, notify=False):
|
||||
server_list.append(server_item)
|
||||
|
||||
if len(server_list) > 0:
|
||||
return_index = xbmcgui.Dialog().select('{} : {}'.format(__addon_name__, string_load(30166)),
|
||||
return_index = xbmcgui.Dialog().select('{} : {}'.format(__addon_name__, translate_string(30166)),
|
||||
server_list,
|
||||
useDetails=True)
|
||||
if return_index != -1:
|
||||
server_url = server_info[return_index]["Address"]
|
||||
api.server = server_info[return_index]["Address"]
|
||||
|
||||
if not server_url:
|
||||
return_index = xbmcgui.Dialog().yesno(__addon_name__, '{}\n{}'.format(string_load(30282), string_load(30370)))
|
||||
if not api.server:
|
||||
return_index = xbmcgui.Dialog().yesno(__addon_name__, '{}\n{}'.format(translate_string(30282), translate_string(30370)))
|
||||
if not return_index:
|
||||
xbmc.executebuiltin("ActivateWindow(Home)")
|
||||
return
|
||||
|
||||
while True:
|
||||
kb = xbmc.Keyboard()
|
||||
kb.setHeading(string_load(30372))
|
||||
if server_url:
|
||||
kb.setDefault(server_url)
|
||||
kb.setHeading(translate_string(30372))
|
||||
if api.server:
|
||||
kb.setDefault(api.server)
|
||||
else:
|
||||
kb.setDefault("http://<server address>:8096")
|
||||
kb.setDefault("http://")
|
||||
kb.doModal()
|
||||
if kb.isConfirmed():
|
||||
server_url = kb.getText()
|
||||
api.server = kb.getText()
|
||||
else:
|
||||
xbmc.executebuiltin("ActivateWindow(Home)")
|
||||
return
|
||||
|
||||
public_lookup_url = "%s/System/Info/Public?format=json" % (server_url)
|
||||
|
||||
log.debug("Testing_Url: {0}".format(public_lookup_url))
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create('{} : {}'.format(__addon_name__, string_load(30376)))
|
||||
progress.update(0, string_load(30377))
|
||||
result = du.download_url(public_lookup_url, authenticate=False)
|
||||
progress.create('{} : {}'.format(__addon_name__, translate_string(30376)))
|
||||
progress.update(0, translate_string(30377))
|
||||
result = api.get('/System/Info/Public')
|
||||
progress.close()
|
||||
|
||||
if result:
|
||||
xbmcgui.Dialog().ok('{} : {}'.format(__addon_name__, string_load(30167)),
|
||||
server_url)
|
||||
xbmcgui.Dialog().ok('{} : {}'.format(__addon_name__, translate_string(30167)),
|
||||
api.server)
|
||||
break
|
||||
else:
|
||||
return_index = xbmcgui.Dialog().yesno('{} : {}'.format(__addon_name__, string_load(30135)),
|
||||
server_url,
|
||||
string_load(30371))
|
||||
return_index = xbmcgui.Dialog().yesno('{} : {}'.format(__addon_name__, translate_string(30135)),
|
||||
api.server,
|
||||
translate_string(30371))
|
||||
if not return_index:
|
||||
xbmc.executebuiltin("ActivateWindow(Home)")
|
||||
return
|
||||
|
||||
log.debug("Selected server: {0}".format(server_url))
|
||||
settings.setSetting("server_address", server_url)
|
||||
log.debug("Selected server: {0}".format(api.server))
|
||||
settings.setSetting("server_address", api.server)
|
||||
something_changed = True
|
||||
|
||||
# do we need to change the user
|
||||
user_details = load_user_details(settings)
|
||||
current_username = user_details.get("username", "")
|
||||
current_username = py2_decode(current_username)
|
||||
current_username = settings.getSetting('username')
|
||||
user_details = load_user_details()
|
||||
|
||||
# if asked or we have no current user then show user selection screen
|
||||
if something_changed or change_user or len(current_username) == 0:
|
||||
if something_changed or change_user or len(current_username) == 0 or not user_details:
|
||||
|
||||
# stop playback when switching users
|
||||
xbmc.Player().stop()
|
||||
du = DownloadUtils()
|
||||
|
||||
# get a list of users
|
||||
log.debug("Getting user list")
|
||||
result = du.download_url(server_url + "/Users/Public?format=json", authenticate=False)
|
||||
auth = quick_connect(api)
|
||||
|
||||
log.debug("jsonData: {0}".format(py2_decode(result)))
|
||||
|
||||
selected_id = -1
|
||||
users = []
|
||||
for user in result:
|
||||
config = user.get("Configuration")
|
||||
if config is not None:
|
||||
if config.get("IsHidden", False) is False:
|
||||
name = user.get("Name")
|
||||
admin = user.get("Policy", {}).get("IsAdministrator", False)
|
||||
|
||||
time_ago = ""
|
||||
last_active = user.get("LastActivityDate")
|
||||
if last_active:
|
||||
last_active_date = datetime_from_string(last_active)
|
||||
log.debug("LastActivityDate: {0}".format(last_active_date))
|
||||
ago = datetime.now() - last_active_date
|
||||
log.debug("LastActivityDate: {0}".format(ago))
|
||||
days = divmod(ago.seconds, 86400)
|
||||
hours = divmod(days[1], 3600)
|
||||
minutes = divmod(hours[1], 60)
|
||||
log.debug("LastActivityDate: {0} {1} {2}".format(days[0], hours[0], minutes[0]))
|
||||
if days[0]:
|
||||
time_ago += " %sd" % days[0]
|
||||
if hours[0]:
|
||||
time_ago += " %sh" % hours[0]
|
||||
if minutes[0]:
|
||||
time_ago += " %sm" % minutes[0]
|
||||
time_ago = time_ago.strip()
|
||||
if not time_ago:
|
||||
time_ago = "Active: now"
|
||||
else:
|
||||
time_ago = "Active: %s ago" % time_ago
|
||||
log.debug("LastActivityDate: {0}".format(time_ago))
|
||||
|
||||
user_item = xbmcgui.ListItem(name)
|
||||
user_image = du.get_user_artwork(user, 'Primary')
|
||||
if not user_image:
|
||||
user_image = "DefaultUser.png"
|
||||
art = {"Thumb": user_image}
|
||||
user_item.setArt(art)
|
||||
user_item.setLabel2("TEST")
|
||||
|
||||
sub_line = time_ago
|
||||
|
||||
if user.get("HasPassword", False) is True:
|
||||
sub_line += ", Password"
|
||||
user_item.setProperty("secure", "true")
|
||||
|
||||
m = hashlib.md5()
|
||||
m.update(ensure_binary(name))
|
||||
hashed_username = m.hexdigest()
|
||||
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
|
||||
if saved_password:
|
||||
sub_line += ": Saved"
|
||||
|
||||
else:
|
||||
user_item.setProperty("secure", "false")
|
||||
|
||||
if admin:
|
||||
sub_line += ", Admin"
|
||||
else:
|
||||
sub_line += ", User"
|
||||
|
||||
user_item.setProperty("manual", "false")
|
||||
user_item.setLabel2(sub_line)
|
||||
users.append(user_item)
|
||||
|
||||
if current_username == name:
|
||||
selected_id = len(users) - 1
|
||||
|
||||
if current_username:
|
||||
selection_title = string_load(30180) + " (" + current_username + ")"
|
||||
if auth:
|
||||
users = []
|
||||
user_selection = -1
|
||||
selected_user_name = auth.get('User', {}).get('Name')
|
||||
something_changed = True
|
||||
else:
|
||||
selection_title = string_load(30180)
|
||||
users, user_selection = user_select(api, current_username)
|
||||
|
||||
# add manual login
|
||||
user_item = xbmcgui.ListItem(string_load(30365))
|
||||
art = {"Thumb": "DefaultUser.png"}
|
||||
user_item.setArt(art)
|
||||
user_item.setLabel2(string_load(30366))
|
||||
user_item.setProperty("secure", "true")
|
||||
user_item.setProperty("manual", "true")
|
||||
users.append(user_item)
|
||||
|
||||
return_value = xbmcgui.Dialog().select(selection_title,
|
||||
users,
|
||||
preselect=selected_id,
|
||||
autoclose=20000,
|
||||
useDetails=True)
|
||||
|
||||
if return_value > -1 and return_value != selected_id:
|
||||
if not auth and user_selection > -1:
|
||||
|
||||
something_changed = True
|
||||
selected_user = users[return_value]
|
||||
selected_user = users[user_selection]
|
||||
selected_user_name = selected_user.getLabel()
|
||||
secured = selected_user.getProperty("secure") == "true"
|
||||
manual = selected_user.getProperty("manual") == "true"
|
||||
selected_user_name = selected_user.getLabel()
|
||||
|
||||
log.debug("Selected User Name: {0} : {1}".format(return_value, selected_user_name))
|
||||
home_window = HomeWindow()
|
||||
|
||||
# If using a manual login, ask for username
|
||||
if manual:
|
||||
kb = xbmc.Keyboard()
|
||||
kb.setHeading(string_load(30005))
|
||||
kb.setHeading(translate_string(30005))
|
||||
if current_username:
|
||||
kb.setDefault(current_username)
|
||||
kb.doModal()
|
||||
@@ -379,54 +243,181 @@ def check_server(force=False, change_user=False, notify=False):
|
||||
else:
|
||||
return
|
||||
|
||||
if secured:
|
||||
# we need a password, check the settings first
|
||||
m = hashlib.md5()
|
||||
m.update(selected_user_name.encode())
|
||||
hashed_username = m.hexdigest()
|
||||
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
|
||||
allow_password_saving = settings.getSetting("allow_password_saving") == "true"
|
||||
home_window.set_property('user_name', selected_user_name)
|
||||
user_details = load_user_details()
|
||||
|
||||
# if not saving passwords but have a saved ask to clear it
|
||||
if not allow_password_saving and saved_password:
|
||||
clear_password = xbmcgui.Dialog().yesno(string_load(30368), string_load(30369))
|
||||
if clear_password:
|
||||
settings.setSetting("saved_user_password_" + hashed_username, "")
|
||||
# Ask for password if user has one
|
||||
password = ''
|
||||
if secured and not user_details.get('token'):
|
||||
kb = xbmc.Keyboard()
|
||||
kb.setHeading(translate_string(30006))
|
||||
kb.setHiddenInput(True)
|
||||
kb.doModal()
|
||||
if kb.isConfirmed():
|
||||
password = kb.getText()
|
||||
|
||||
if saved_password:
|
||||
log.debug("Saving username and password: {0}".format(selected_user_name))
|
||||
log.debug("Using stored password for user: {0}".format(hashed_username))
|
||||
save_user_details(settings, selected_user_name, saved_password)
|
||||
|
||||
else:
|
||||
kb = xbmc.Keyboard()
|
||||
kb.setHeading(string_load(30006))
|
||||
kb.setHiddenInput(True)
|
||||
kb.doModal()
|
||||
if kb.isConfirmed():
|
||||
log.debug("Saving username and password: {0}".format(selected_user_name))
|
||||
save_user_details(settings, selected_user_name, kb.getText())
|
||||
|
||||
# should we save the password
|
||||
if allow_password_saving:
|
||||
save_password = xbmcgui.Dialog().yesno(string_load(30363), string_load(30364))
|
||||
if save_password:
|
||||
log.debug("Saving password for fast user switching: {0}".format(hashed_username))
|
||||
settings.setSetting("saved_user_password_" + hashed_username, kb.getText())
|
||||
else:
|
||||
log.debug("Saving username with no password: {0}".format(selected_user_name))
|
||||
save_user_details(settings, selected_user_name, "")
|
||||
auth_payload = {'username': selected_user_name, 'pw': password}
|
||||
auth = api.authenticate(auth_payload)
|
||||
|
||||
if something_changed:
|
||||
home_window = HomeWindow()
|
||||
home_window.clear_property("userid")
|
||||
home_window.clear_property("AccessToken")
|
||||
home_window.clear_property("userimage")
|
||||
home_window.clear_property("jellycon_widget_reload")
|
||||
du = DownloadUtils()
|
||||
du.authenticate()
|
||||
du.get_user_id()
|
||||
token = auth.get('AccessToken')
|
||||
user_id = auth.get('User').get('Id')
|
||||
save_user_details(selected_user_name, user_id, token)
|
||||
xbmc.executebuiltin("ActivateWindow(Home)")
|
||||
if "estuary_jellycon" in xbmc.getSkinDir():
|
||||
xbmc.executebuiltin("SetFocus(9000, 0, absolute)")
|
||||
xbmc.executebuiltin("ReloadSkin()")
|
||||
|
||||
|
||||
def quick_connect(api):
|
||||
'''
|
||||
Log in using quick connect funcion
|
||||
'''
|
||||
settings = xbmcaddon.Addon()
|
||||
addon_path = settings.getAddonInfo('path')
|
||||
|
||||
result = api.get('/QuickConnect/Initiate')
|
||||
|
||||
if not isinstance(result, dict) or not result:
|
||||
log.debug('Quick connect is disabled on the server')
|
||||
return {}
|
||||
|
||||
code = result.get('Code')
|
||||
secret = result.get('Secret')
|
||||
|
||||
# Open Quick Connect dialog, ask to proceed
|
||||
qc_dialog = QuickConnectDialog("QuickConnectDialog.xml", addon_path, "default", "720p")
|
||||
qc_dialog.code = code
|
||||
qc_dialog.doModal()
|
||||
connect_method = qc_dialog.getConnectMethod()
|
||||
del qc_dialog
|
||||
|
||||
if connect_method < 1:
|
||||
# User backed out or selected manual login
|
||||
return {}
|
||||
|
||||
count = 0
|
||||
while count < 15:
|
||||
# Check the server to see if the auth request has been completed
|
||||
log.debug('Checking for quick connect auth: attempt {}'.format(count))
|
||||
check = api.get('/QuickConnect/Connect?secret={}'.format(secret))
|
||||
if check.get('Authenticated'):
|
||||
break
|
||||
count += 1
|
||||
xbmc.sleep(1000)
|
||||
|
||||
if not check.get('Authenticated'):
|
||||
log.info('Quick connect not authorized in 15 seconds, defaulting to manual authentication')
|
||||
return {}
|
||||
|
||||
# Retrieve authentication information
|
||||
auth = api.post('/Users/AuthenticateWithQuickConnect',
|
||||
{'secret': secret})
|
||||
|
||||
return auth
|
||||
|
||||
|
||||
def user_select(api, current_username):
|
||||
'''
|
||||
Display user selection screen
|
||||
'''
|
||||
# Retrieve list of public users from server
|
||||
result = api.get('/Users/Public')
|
||||
|
||||
# Build user display
|
||||
selected_id = -1
|
||||
users = []
|
||||
for user in result:
|
||||
user_item = create_user_listitem(api.server, user)
|
||||
if user_item:
|
||||
users.append(user_item)
|
||||
name = user.get("Name")
|
||||
|
||||
# Highlight currently logged in user
|
||||
if current_username == name:
|
||||
selected_id = len(users) - 1
|
||||
|
||||
if current_username:
|
||||
selection_title = translate_string(30180) + " (" + current_username + ")"
|
||||
else:
|
||||
selection_title = translate_string(30180)
|
||||
|
||||
# Add manual login item
|
||||
user_item = xbmcgui.ListItem(translate_string(30365))
|
||||
art = {"Thumb": "DefaultUser.png"}
|
||||
user_item.setArt(art)
|
||||
user_item.setLabel2(translate_string(30366))
|
||||
user_item.setProperty("secure", "true")
|
||||
user_item.setProperty("manual", "true")
|
||||
users.append(user_item)
|
||||
|
||||
user_selection = xbmcgui.Dialog().select(
|
||||
selection_title,
|
||||
users,
|
||||
preselect=selected_id,
|
||||
autoclose=20000,
|
||||
useDetails=True)
|
||||
|
||||
return (users, user_selection)
|
||||
|
||||
|
||||
def create_user_listitem(server, user):
|
||||
'''
|
||||
Create a user listitem for the user selection screen
|
||||
'''
|
||||
config = user.get("Configuration")
|
||||
if config is not None:
|
||||
name = user.get("Name")
|
||||
time_ago = ""
|
||||
last_active = user.get("LastActivityDate")
|
||||
# Calculate how long it's been since the user was last active
|
||||
if last_active:
|
||||
last_active_date = datetime_from_string(last_active)
|
||||
ago = datetime.now().astimezone() - last_active_date
|
||||
# Check days
|
||||
if ago.days > 0:
|
||||
time_ago += ' {}d'.format(ago.days)
|
||||
# Check minutes
|
||||
if ago.seconds > 60:
|
||||
hours = 0
|
||||
# Check hours
|
||||
if ago.seconds > 3600:
|
||||
hours = int(ago.seconds/3600)
|
||||
time_ago += ' {}h'.format(hours)
|
||||
minutes = int((ago.seconds - (hours * 3600)) / 60)
|
||||
time_ago += ' {}m'.format(minutes)
|
||||
time_ago = time_ago.strip()
|
||||
if not time_ago:
|
||||
time_ago = "Active: now"
|
||||
else:
|
||||
time_ago = "Active: {} ago".format(time_ago)
|
||||
|
||||
user_item = xbmcgui.ListItem(name)
|
||||
|
||||
# If the user doesn't have a profile image, user the default
|
||||
if 'PrimaryImageTag' not in user:
|
||||
user_image = "DefaultUser.png"
|
||||
else:
|
||||
user_id = user.get('Id')
|
||||
tag = user.get('PrimaryImageTag')
|
||||
user_image = '{}/Users/{}/Images/Primary?Format=original&tag={}'.format(
|
||||
server, user_id, tag
|
||||
)
|
||||
|
||||
art = {"Thumb": user_image}
|
||||
user_item.setArt(art)
|
||||
|
||||
sub_line = time_ago
|
||||
|
||||
if user.get("HasPassword", False) is True:
|
||||
user_item.setProperty("secure", "true")
|
||||
else:
|
||||
user_item.setProperty("secure", "false")
|
||||
|
||||
user_item.setProperty("manual", "false")
|
||||
user_item.setLabel2(sub_line)
|
||||
|
||||
return user_item
|
||||
return None
|
||||
|
||||
@@ -3,11 +3,12 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
import sys
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
import xbmcaddon
|
||||
|
||||
from .downloadutils import DownloadUtils
|
||||
from .loghandler import LazyLogger
|
||||
from .utils import get_art
|
||||
from .datamanager import DataManager
|
||||
from .jellyfin import api
|
||||
from .lazylogger import LazyLogger
|
||||
from .item_functions import get_art
|
||||
from .utils import load_user_details
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
@@ -16,25 +17,26 @@ def show_server_sessions():
|
||||
log.debug("showServerSessions Called")
|
||||
|
||||
handle = int(sys.argv[1])
|
||||
download_utils = DownloadUtils()
|
||||
data_manager = DataManager()
|
||||
|
||||
url = "{server}/Users/{userid}"
|
||||
results = data_manager.get_content(url)
|
||||
user_details = load_user_details()
|
||||
url = "/Users/{}".format(user_details.get('user_id'))
|
||||
results = api.get(url)
|
||||
|
||||
is_admin = results.get("Policy", {}).get("IsAdministrator", False)
|
||||
if not is_admin:
|
||||
xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
|
||||
return
|
||||
|
||||
url = "{server}/Sessions"
|
||||
results = data_manager.get_content(url)
|
||||
url = "/Sessions"
|
||||
results = api.get(url)
|
||||
log.debug("session_info: {0}".format(results))
|
||||
|
||||
if results is None:
|
||||
return
|
||||
|
||||
list_items = []
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
for session in results:
|
||||
device_name = session.get("DeviceName", "na")
|
||||
user_name = session.get("UserName", "na")
|
||||
@@ -59,7 +61,6 @@ def show_server_sessions():
|
||||
|
||||
art = {}
|
||||
if now_playing:
|
||||
server = download_utils.get_server()
|
||||
art = get_art(now_playing, server)
|
||||
|
||||
runtime = now_playing.get("RunTimeTicks", 0)
|
||||
|
||||
@@ -9,10 +9,10 @@ import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
from .jsonrpc import JsonRpc, get_value, set_value
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
from .utils import translate_path, kodi_version
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
ver = xbmc.getInfoLabel('System.BuildVersion')[:2]
|
||||
|
||||
|
||||
def clone_default_skin():
|
||||
@@ -49,7 +49,7 @@ def walk_path(root_path, relative_path, all_files):
|
||||
def clone_skin():
|
||||
log.debug("Cloning Estuary Skin")
|
||||
|
||||
kodi_path = xbmc.translatePath("special://xbmc")
|
||||
kodi_path = translate_path("special://xbmc")
|
||||
kodi_skin_source = os.path.join(kodi_path, "addons", "skin.estuary")
|
||||
log.debug("Kodi Skin Source: {0}".format(kodi_skin_source))
|
||||
|
||||
@@ -61,7 +61,7 @@ def clone_skin():
|
||||
for found in all_files:
|
||||
log.debug("Found Path: {0}".format(found))
|
||||
|
||||
kodi_home_path = xbmc.translatePath("special://home")
|
||||
kodi_home_path = translate_path("special://home")
|
||||
kodi_skin_destination = os.path.join(kodi_home_path, "addons", "skin.estuary_jellycon")
|
||||
log.debug("Kodi Skin Destination: {0}".format(kodi_skin_destination))
|
||||
|
||||
@@ -91,7 +91,7 @@ def clone_skin():
|
||||
# get jellycon path
|
||||
jellycon_path = os.path.join(kodi_home_path, "addons", "plugin.video.jellycon")
|
||||
|
||||
log.debug("Major Version: {0}".format(ver))
|
||||
log.debug("Major Version: {0}".format(kodi_version()))
|
||||
|
||||
file_list = ["Home.xml",
|
||||
"Includes_Home.xml",
|
||||
@@ -101,7 +101,7 @@ def clone_skin():
|
||||
|
||||
# Copy customized skin files from our addon into cloned skin
|
||||
for file_name in file_list:
|
||||
source = os.path.join(jellycon_path, "resources", "skins", "skin.estuary", ver, "xml", file_name)
|
||||
source = os.path.join(jellycon_path, "resources", "skins", "skin.estuary", str(kodi_version), "xml", file_name)
|
||||
destination = os.path.join(kodi_skin_destination, "xml", file_name)
|
||||
xbmcvfs.copy(source, destination)
|
||||
|
||||
@@ -130,7 +130,6 @@ def clone_skin():
|
||||
def update_kodi_settings():
|
||||
log.debug("Settings Kodi Settings")
|
||||
|
||||
# set_value("screensaver.mode", "script.screensaver.logoff")
|
||||
set_value("videoplayer.seekdelay", 0)
|
||||
set_value("filelists.showparentdiritems", False)
|
||||
set_value("filelists.showaddsourcebuttons", False)
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
import sys
|
||||
import functools
|
||||
import time
|
||||
from .loghandler import LazyLogger
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
# Gnu General Public License - see LICENSE.TXT
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
import encodings
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .loghandler import LazyLogger
|
||||
from .datamanager import DataManager
|
||||
|
||||
from .translation import string_load
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
dataManager = DataManager()
|
||||
|
||||
details_string = 'EpisodeCount,SeasonCount,Path,Etag,MediaStreams'
|
||||
icon = xbmc.translatePath('special://home/addons/plugin.video.jellycon/icon.png')
|
||||
|
||||
|
||||
def not_found(content_string):
|
||||
xbmcgui.Dialog().notification('JellyCon', '{}: {}'.format(string_load(30305), content_string), icon=icon, sound=False)
|
||||
|
||||
|
||||
def playback_starting(content_string):
|
||||
xbmcgui.Dialog().notification('JellyCon', '{}: {}'.format(string_load(30306), content_string), icon=icon, sound=False)
|
||||
|
||||
|
||||
def search(item_type, query):
|
||||
content_url = ('{server}/Search/Hints?searchTerm=' + query +
|
||||
'&IncludeItemTypes=' + item_type +
|
||||
'&UserId={userid}'
|
||||
'&StartIndex=0' +
|
||||
'&Limit=25' +
|
||||
'&IncludePeople=false&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false')
|
||||
|
||||
result = dataManager.get_content(content_url)
|
||||
return result
|
||||
|
||||
|
||||
def get_items(video_type, item_id=None, parent_id=None):
|
||||
content_url = None
|
||||
result = dict()
|
||||
|
||||
if video_type == 'season':
|
||||
content_url = ('{server}/Shows/' + item_id +
|
||||
'/Seasons'
|
||||
'?userId={userid}' +
|
||||
'&Fields=' + details_string +
|
||||
'&format=json')
|
||||
|
||||
elif video_type == 'movie' or video_type == 'episode':
|
||||
content_url = ('{server}/Users/{userid}/items' +
|
||||
'?ParentId=' + parent_id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=' + details_string +
|
||||
'&format=json')
|
||||
|
||||
if content_url:
|
||||
result = dataManager.get_content(content_url)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_item(item_id):
|
||||
result = dataManager.get_content('{server}/Users/{userid}/Items/' + item_id + '?Fields=ProviderIds&format=json')
|
||||
return result
|
||||
|
||||
|
||||
def get_imdb_id(item_id):
|
||||
item = get_item(item_id)
|
||||
imdb = item.get('ProviderIds', {}).get('Imdb')
|
||||
return imdb
|
||||
|
||||
|
||||
def get_season_id(parent_id, season):
|
||||
season_items = get_items('season', parent_id)
|
||||
season_items = season_items.get('Items')
|
||||
|
||||
if season_items is None:
|
||||
season_items = []
|
||||
|
||||
for season_item in season_items:
|
||||
if season_item.get('IndexNumber') == int(season):
|
||||
season_id = season_item.get('Id')
|
||||
return season_id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_episode_id(parent_id, episode):
|
||||
episode_items = get_items('episode', parent_id=parent_id)
|
||||
episode_items = episode_items.get('Items')
|
||||
|
||||
if episode_items is None:
|
||||
episode_items = []
|
||||
|
||||
for episode_item in episode_items:
|
||||
if episode_item.get('IndexNumber') == int(episode):
|
||||
episode_id = episode_item.get('Id')
|
||||
return episode_id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_match(item_type, title, year, imdb_id):
|
||||
query = quote(title)
|
||||
|
||||
results = search(item_type, query=query)
|
||||
results = results.get('SearchHints')
|
||||
if results is None:
|
||||
results = []
|
||||
log.debug('SearchHints jsonData: {0}'.format(results))
|
||||
|
||||
potential_matches = []
|
||||
|
||||
for item in results:
|
||||
name = item.get('Name')
|
||||
production_year = item.get('ProductionYear')
|
||||
if (name == title and int(year) == production_year) or (int(year) == production_year):
|
||||
potential_matches.append(item)
|
||||
|
||||
log.debug('Potential matches: {0}'.format(potential_matches))
|
||||
|
||||
for item in potential_matches:
|
||||
item_imdb_id = get_imdb_id(item.get('ItemId'))
|
||||
if item_imdb_id == imdb_id:
|
||||
log.debug('Found match: {0}'.format(item))
|
||||
return item
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def entry_point(parameters):
|
||||
item_type = None
|
||||
action = parameters.get('action', None)
|
||||
video_type = parameters.get('video_type', None)
|
||||
|
||||
title = unquote(parameters.get('title', ''))
|
||||
|
||||
year = parameters.get('year', '')
|
||||
episode = parameters.get('episode', '')
|
||||
season = parameters.get('season', '')
|
||||
imdb_id = parameters.get('imdb_id', '')
|
||||
|
||||
if video_type == 'show' or video_type == 'season' or video_type == 'episode':
|
||||
item_type = 'Series'
|
||||
elif video_type == 'movie':
|
||||
item_type = 'Movie'
|
||||
|
||||
if not item_type:
|
||||
return
|
||||
|
||||
match = get_match(item_type, title, year, imdb_id)
|
||||
|
||||
if not match:
|
||||
title_search_word = ''
|
||||
title_words = title.split(' ')
|
||||
|
||||
for word in title_words:
|
||||
if len(word) > len(title_search_word):
|
||||
title_search_word = word
|
||||
|
||||
title_search_word = title_search_word.replace(':', '')
|
||||
|
||||
if title_search_word:
|
||||
match = get_match(item_type, title_search_word, year, imdb_id)
|
||||
|
||||
str_season = str(season)
|
||||
if len(str_season) == 1:
|
||||
str_season = '0' + str_season
|
||||
str_episode = str(episode)
|
||||
if len(str_episode) == 1:
|
||||
str_episode = '0' + str_episode
|
||||
|
||||
if action == 'play':
|
||||
play_item_id = None
|
||||
|
||||
if video_type == 'movie':
|
||||
if match:
|
||||
play_item_id = match.get('ItemId')
|
||||
|
||||
if not play_item_id:
|
||||
not_found('{title} ({year})'.format(title=title, year=year))
|
||||
|
||||
elif video_type == 'episode':
|
||||
if not season or not episode:
|
||||
return
|
||||
|
||||
if match:
|
||||
item_id = match.get('ItemId')
|
||||
season_id = get_season_id(item_id, season)
|
||||
|
||||
if season_id:
|
||||
episode_id = get_episode_id(season_id, episode)
|
||||
if episode_id:
|
||||
play_item_id = episode_id
|
||||
|
||||
if not play_item_id:
|
||||
not_found('{title} ({year}) - S{season}E{episode}'.format(title=title, year=year, season=str_season, episode=str_episode))
|
||||
|
||||
if play_item_id:
|
||||
if video_type == 'episode':
|
||||
playback_starting('{title} ({year}) - S{season}E{episode}'.format(title=title, year=year, season=str_season, episode=str_episode))
|
||||
else:
|
||||
playback_starting('{title} ({year})'.format(title=title, year=year))
|
||||
xbmc.executebuiltin('RunPlugin(plugin://plugin.video.jellycon/?mode=PLAY&item_id={item_id})'.format(item_id=play_item_id))
|
||||
|
||||
elif action == 'open':
|
||||
url = media_type = None
|
||||
|
||||
if video_type == 'show':
|
||||
if match:
|
||||
item_id = match.get('ItemId')
|
||||
media_type = 'series'
|
||||
url = ('{server}/Shows/' + item_id +
|
||||
'/Seasons'
|
||||
'?userId={userid}' +
|
||||
'&Fields=' + details_string +
|
||||
'&format=json')
|
||||
|
||||
if not url:
|
||||
not_found('{title} ({year})'.format(title=title, year=year))
|
||||
|
||||
elif video_type == 'season':
|
||||
if not season:
|
||||
return
|
||||
|
||||
if match:
|
||||
item_id = match.get('ItemId')
|
||||
season_id = get_season_id(item_id, season)
|
||||
|
||||
if season_id:
|
||||
media_type = 'episodes'
|
||||
|
||||
url = ('{server}/Users/{userid}/items' +
|
||||
'?ParentId=' + season_id +
|
||||
'&IsVirtualUnAired=false' +
|
||||
'&IsMissing=false' +
|
||||
'&Fields=' + details_string +
|
||||
'&format=json')
|
||||
|
||||
if not url:
|
||||
not_found('{title} ({year}) - S{season}'.format(title=title, year=year, season=str_season))
|
||||
|
||||
if url and media_type:
|
||||
xbmc.executebuiltin('ActivateWindow(Videos, plugin://plugin.video.jellycon/?mode=GET_CONTENT&url={url}&media_type={media_type})'.format(url=quote(url), media_type=media_type))
|
||||
@@ -1,16 +0,0 @@
|
||||
from __future__ import division, absolute_import, print_function, unicode_literals
|
||||
|
||||
import xbmcaddon
|
||||
from .loghandler import LazyLogger
|
||||
from kodi_six.utils import py2_encode
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
addon = xbmcaddon.Addon()
|
||||
|
||||
|
||||
def string_load(string_id):
|
||||
try:
|
||||
return py2_encode(addon.getLocalizedString(string_id))
|
||||
except Exception as e:
|
||||
log.error('Failed String Load: {0} ({1})', string_id, e)
|
||||
return str(string_id)
|
||||
@@ -4,181 +4,49 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
import xbmcaddon
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
from kodi_six.utils import py2_encode, py2_decode
|
||||
import sys
|
||||
|
||||
import binascii
|
||||
import string
|
||||
import random
|
||||
import json
|
||||
import base64
|
||||
import time
|
||||
import math
|
||||
import os
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
import calendar
|
||||
from dateutil import tz
|
||||
import re
|
||||
from uuid import uuid4
|
||||
from six import ensure_text, ensure_binary, text_type
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
from .downloadutils import DownloadUtils
|
||||
from .loghandler import LazyLogger
|
||||
from .clientinfo import ClientInformation
|
||||
from .lazylogger import LazyLogger
|
||||
from .kodi_utils import HomeWindow
|
||||
|
||||
# hack to get datetime strptime loaded
|
||||
throwaway = time.strptime('20110101', '%Y%m%d')
|
||||
|
||||
# define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
|
||||
def get_jellyfin_url(base_url, params):
|
||||
def kodi_version():
|
||||
# Kodistubs returns empty string, causing Python 3 tests to choke on int()
|
||||
# TODO: Make Kodistubs version configurable for testing purposes
|
||||
if sys.version_info.major == 2:
|
||||
default_versionstring = "18"
|
||||
else:
|
||||
default_versionstring = "19.1 (19.1.0) Git:20210509-85e05228b4"
|
||||
|
||||
version_string = xbmc.getInfoLabel('System.BuildVersion') or default_versionstring
|
||||
return int(version_string.split(' ', 1)[0].split('.', 1)[0])
|
||||
|
||||
|
||||
def get_jellyfin_url(path, params):
|
||||
params["format"] = "json"
|
||||
url_params = urlencode(params)
|
||||
# Filthy hack until I get around to reworking the network flow
|
||||
# It relies on {thing} strings in downloadutils.py
|
||||
url_params = url_params.replace('%7B', '{').replace('%7D', '}')
|
||||
return base_url + "?" + url_params
|
||||
|
||||
|
||||
###########################################################################
|
||||
class PlayUtils:
|
||||
|
||||
@staticmethod
|
||||
def get_play_url(media_source, play_session_id):
|
||||
log.debug("get_play_url - media_source: {0}", media_source)
|
||||
|
||||
# check if strm file Container
|
||||
if media_source.get('Container') == 'strm':
|
||||
log.debug("Detected STRM Container")
|
||||
playurl, listitem_props = PlayUtils().get_strm_details(media_source)
|
||||
if playurl is None:
|
||||
log.debug("Error, no strm content")
|
||||
return None, None, None
|
||||
else:
|
||||
return playurl, "0", listitem_props
|
||||
|
||||
# get all the options
|
||||
addon_settings = xbmcaddon.Addon()
|
||||
server = downloadUtils.get_server()
|
||||
use_https = addon_settings.getSetting('protocol') == "1"
|
||||
verify_cert = addon_settings.getSetting('verify_cert') == 'true'
|
||||
allow_direct_file_play = addon_settings.getSetting('allow_direct_file_play') == 'true'
|
||||
|
||||
can_direct_play = media_source["SupportsDirectPlay"]
|
||||
can_direct_stream = media_source["SupportsDirectStream"]
|
||||
can_transcode = media_source["SupportsTranscoding"]
|
||||
container = media_source["Container"]
|
||||
|
||||
playurl = None
|
||||
playback_type = None
|
||||
|
||||
# check if file can be directly played
|
||||
if allow_direct_file_play and can_direct_play:
|
||||
direct_path = media_source["Path"]
|
||||
direct_path = direct_path.replace("\\", "/")
|
||||
direct_path = direct_path.strip()
|
||||
|
||||
# handle DVD structure
|
||||
if container == "dvd":
|
||||
direct_path = direct_path + "/VIDEO_TS/VIDEO_TS.IFO"
|
||||
elif container == "bluray":
|
||||
direct_path = direct_path + "/BDMV/index.bdmv"
|
||||
|
||||
if direct_path.startswith("//"):
|
||||
direct_path = "smb://" + direct_path[2:]
|
||||
|
||||
log.debug("playback_direct_path: {0}".format(direct_path))
|
||||
|
||||
if xbmcvfs.exists(direct_path):
|
||||
playurl = direct_path
|
||||
playback_type = "0"
|
||||
|
||||
# check if file can be direct streamed
|
||||
if can_direct_stream and playurl is None:
|
||||
item_id = media_source.get('Id')
|
||||
playurl = ("%s/Videos/%s/stream" +
|
||||
"?static=true" +
|
||||
"&PlaySessionId=%s" +
|
||||
"&MediaSourceId=%s")
|
||||
playurl = playurl % (server, item_id, play_session_id, item_id)
|
||||
if use_https and not verify_cert:
|
||||
playurl += "|verifypeer=false"
|
||||
playback_type = "1"
|
||||
|
||||
# check is file can be transcoded
|
||||
if can_transcode and playurl is None:
|
||||
item_id = media_source.get('Id')
|
||||
client_info = ClientInformation()
|
||||
device_id = client_info.get_device_id()
|
||||
user_token = downloadUtils.authenticate()
|
||||
playback_bitrate = addon_settings.getSetting("force_max_stream_bitrate")
|
||||
bitrate = int(playback_bitrate) * 1000
|
||||
playback_max_width = addon_settings.getSetting("playback_max_width")
|
||||
audio_codec = addon_settings.getSetting("audio_codec")
|
||||
audio_playback_bitrate = addon_settings.getSetting("audio_playback_bitrate")
|
||||
audio_bitrate = int(audio_playback_bitrate) * 1000
|
||||
audio_max_channels = addon_settings.getSetting("audio_max_channels")
|
||||
playback_video_force_8 = addon_settings.getSetting("playback_video_force_8") == "true"
|
||||
|
||||
transcode_params = {
|
||||
"MediaSourceId": item_id,
|
||||
"DeviceId": device_id,
|
||||
"PlaySessionId": play_session_id,
|
||||
"api_key": user_token,
|
||||
"SegmentContainer": "ts",
|
||||
"VideoCodec": "h264",
|
||||
"VideoBitrate": bitrate,
|
||||
"MaxWidth": playback_max_width,
|
||||
"AudioCodec": audio_codec,
|
||||
"TranscodingMaxAudioChannels": audio_max_channels,
|
||||
"AudioBitrate": audio_bitrate
|
||||
}
|
||||
if playback_video_force_8:
|
||||
transcode_params.update({"MaxVideoBitDepth": "8"})
|
||||
|
||||
transcode_path = urlencode(transcode_params)
|
||||
|
||||
playurl = "%s/Videos/%s/master.m3u8?%s" % (server, item_id, transcode_path)
|
||||
|
||||
if use_https and not verify_cert:
|
||||
playurl += "|verifypeer=false"
|
||||
|
||||
playback_type = "2"
|
||||
|
||||
return playurl, playback_type, []
|
||||
|
||||
@staticmethod
|
||||
def get_strm_details(media_source):
|
||||
playurl = None
|
||||
listitem_props = []
|
||||
|
||||
contents = media_source.get('Path').encode('utf-8') # contains contents of strm file with linebreaks
|
||||
|
||||
line_break = '\r'
|
||||
if '\r\n' in contents:
|
||||
line_break = '\r\n'
|
||||
elif '\n' in contents:
|
||||
line_break = '\n'
|
||||
|
||||
lines = contents.split(line_break)
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
log.debug("STRM Line: {0}".format(line))
|
||||
if line.startswith('#KODIPROP:'):
|
||||
match = re.search('#KODIPROP:(?P<item_property>[^=]+?)=(?P<property_value>.+)', line)
|
||||
if match:
|
||||
item_property = match.group('item_property')
|
||||
property_value = match.group('property_value')
|
||||
log.debug("STRM property found: {0} value: {1}".format(item_property, property_value))
|
||||
listitem_props.append((item_property, property_value))
|
||||
else:
|
||||
log.debug("STRM #KODIPROP incorrect format")
|
||||
elif line.startswith('#'):
|
||||
# unrecognized, treat as comment
|
||||
log.debug("STRM unrecognized line identifier, ignored")
|
||||
elif line != '':
|
||||
playurl = line
|
||||
log.debug("STRM playback url found")
|
||||
|
||||
log.debug("Playback URL: {0} ListItem Properties: {1}".format(playurl, listitem_props))
|
||||
return playurl, listitem_props
|
||||
return '{}?{}'.format(path, url_params)
|
||||
|
||||
|
||||
def get_checksum(item):
|
||||
@@ -196,98 +64,10 @@ def get_checksum(item):
|
||||
return checksum
|
||||
|
||||
|
||||
def get_art(item, server):
|
||||
art = {
|
||||
'thumb': '',
|
||||
'fanart': '',
|
||||
'poster': '',
|
||||
'banner': '',
|
||||
'clearlogo': '',
|
||||
'clearart': '',
|
||||
'discart': '',
|
||||
'landscape': '',
|
||||
'tvshow.fanart': '',
|
||||
'tvshow.poster': '',
|
||||
'tvshow.clearart': '',
|
||||
'tvshow.clearlogo': '',
|
||||
'tvshow.banner': '',
|
||||
'tvshow.landscape': ''
|
||||
}
|
||||
|
||||
image_tags = item.get("ImageTags", {})
|
||||
if image_tags and image_tags.get("Primary"):
|
||||
art['thumb'] = downloadUtils.get_artwork(item, "Primary", server=server)
|
||||
|
||||
item_type = item["Type"]
|
||||
|
||||
if item_type == "Genre":
|
||||
art['poster'] = downloadUtils.get_artwork(item, "Primary", server=server)
|
||||
elif item_type == "Episode":
|
||||
art['tvshow.poster'] = downloadUtils.get_artwork(item, "Primary", parent=True, server=server)
|
||||
art['tvshow.clearart'] = downloadUtils.get_artwork(item, "Art", parent=True, server=server)
|
||||
art['clearart'] = downloadUtils.get_artwork(item, "Art", parent=True, server=server)
|
||||
art['tvshow.clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=True, server=server)
|
||||
art['clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=True, server=server)
|
||||
art['tvshow.banner'] = downloadUtils.get_artwork(item, "Banner", parent=True, server=server)
|
||||
art['banner'] = downloadUtils.get_artwork(item, "Banner", parent=True, server=server)
|
||||
art['tvshow.landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=True, server=server)
|
||||
art['landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=True, server=server)
|
||||
art['tvshow.fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=True, server=server)
|
||||
art['fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=True, server=server)
|
||||
elif item_type == "Season":
|
||||
art['tvshow.poster'] = downloadUtils.get_artwork(item, "Primary", parent=True, server=server)
|
||||
art['season.poster'] = downloadUtils.get_artwork(item, "Primary", parent=False, server=server)
|
||||
art['poster'] = downloadUtils.get_artwork(item, "Primary", parent=False, server=server)
|
||||
art['tvshow.clearart'] = downloadUtils.get_artwork(item, "Art", parent=True, server=server)
|
||||
art['clearart'] = downloadUtils.get_artwork(item, "Art", parent=True, server=server)
|
||||
art['tvshow.clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=True, server=server)
|
||||
art['clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=True, server=server)
|
||||
art['tvshow.banner'] = downloadUtils.get_artwork(item, "Banner", parent=True, server=server)
|
||||
art['season.banner'] = downloadUtils.get_artwork(item, "Banner", parent=False, server=server)
|
||||
art['banner'] = downloadUtils.get_artwork(item, "Banner", parent=False, server=server)
|
||||
art['tvshow.landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=True, server=server)
|
||||
art['season.landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=False, server=server)
|
||||
art['landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=False, server=server)
|
||||
art['tvshow.fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=True, server=server)
|
||||
art['fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=True, server=server)
|
||||
elif item_type == "Series":
|
||||
art['tvshow.poster'] = downloadUtils.get_artwork(item, "Primary", parent=False, server=server)
|
||||
art['poster'] = downloadUtils.get_artwork(item, "Primary", parent=False, server=server)
|
||||
art['tvshow.clearart'] = downloadUtils.get_artwork(item, "Art", parent=False, server=server)
|
||||
art['clearart'] = downloadUtils.get_artwork(item, "Art", parent=False, server=server)
|
||||
art['tvshow.clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=False, server=server)
|
||||
art['clearlogo'] = downloadUtils.get_artwork(item, "Logo", parent=False, server=server)
|
||||
art['tvshow.banner'] = downloadUtils.get_artwork(item, "Banner", parent=False, server=server)
|
||||
art['banner'] = downloadUtils.get_artwork(item, "Banner", parent=False, server=server)
|
||||
art['tvshow.landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=False, server=server)
|
||||
art['landscape'] = downloadUtils.get_artwork(item, "Thumb", parent=False, server=server)
|
||||
art['tvshow.fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=False, server=server)
|
||||
art['fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=False, server=server)
|
||||
elif item_type == "Movie" or item_type == "BoxSet":
|
||||
art['poster'] = downloadUtils.get_artwork(item, "Primary", server=server)
|
||||
art['landscape'] = downloadUtils.get_artwork(item, "Thumb", server=server)
|
||||
art['banner'] = downloadUtils.get_artwork(item, "Banner", server=server)
|
||||
art['clearlogo'] = downloadUtils.get_artwork(item, "Logo", server=server)
|
||||
art['clearart'] = downloadUtils.get_artwork(item, "Art", server=server)
|
||||
art['discart'] = downloadUtils.get_artwork(item, "Disc", server=server)
|
||||
|
||||
art['fanart'] = downloadUtils.get_artwork(item, "Backdrop", server=server)
|
||||
if not art['fanart']:
|
||||
art['fanart'] = downloadUtils.get_artwork(item, "Backdrop", parent=True, server=server)
|
||||
|
||||
return art
|
||||
|
||||
|
||||
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for _ in range(size))
|
||||
|
||||
|
||||
def double_urlencode(text):
|
||||
text = single_urlencode(text)
|
||||
text = single_urlencode(text)
|
||||
return text
|
||||
|
||||
|
||||
def single_urlencode(text):
|
||||
# urlencode needs a utf- string
|
||||
text = urlencode({'blahblahblah': text.encode('utf-8')})
|
||||
@@ -295,29 +75,42 @@ def single_urlencode(text):
|
||||
return text.decode('utf-8') # return the result again as unicode
|
||||
|
||||
|
||||
def send_event_notification(method, data):
|
||||
message_data = json.dumps(data)
|
||||
source_id = "jellycon"
|
||||
base64_data = base64.b64encode(message_data.encode())
|
||||
escaped_data = '\\"[\\"{0}\\"]\\"'.format(base64_data)
|
||||
command = 'XBMC.NotifyAll({0}.SIGNAL,{1},{2})'.format(source_id, method, escaped_data)
|
||||
log.debug("Sending notification event data: {0}".format(command))
|
||||
xbmc.executebuiltin(command)
|
||||
def send_event_notification(method, data=None, hexlify=False):
|
||||
'''
|
||||
Send events through Kodi's notification system
|
||||
'''
|
||||
data = data or {}
|
||||
|
||||
if hexlify:
|
||||
# Used exclusively for the upnext plugin
|
||||
data = ensure_text(binascii.hexlify(ensure_binary(json.dumps(data))))
|
||||
sender = 'plugin.video.jellycon'
|
||||
data = '"[%s]"' % json.dumps(data).replace('"', '\\"')
|
||||
|
||||
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
|
||||
|
||||
|
||||
def datetime_from_string(time_string):
|
||||
|
||||
# Builtin python library can't handle ISO-8601 well. Make it compatible
|
||||
if time_string[-1:] == "Z":
|
||||
time_string = re.sub("[0-9]{1}Z", " UTC", time_string)
|
||||
elif time_string[-6:] == "+00:00":
|
||||
time_string = re.sub("[0-9]{1}\+00:00", " UTC", time_string)
|
||||
log.debug("New Time String : {0}".format(time_string))
|
||||
|
||||
start_time = time.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")
|
||||
dt = datetime(*(start_time[0:6]))
|
||||
timestamp = calendar.timegm(dt.timetuple())
|
||||
local_dt = datetime.fromtimestamp(timestamp)
|
||||
local_dt.replace(microsecond=dt.microsecond)
|
||||
try:
|
||||
dt = datetime.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")
|
||||
except TypeError:
|
||||
# https://bugs.python.org/issue27400
|
||||
dt = datetime(*(time.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")[0:6]))
|
||||
|
||||
# Convert server dates from UTC to local time
|
||||
utc = tz.tzutc()
|
||||
local = tz.tzlocal()
|
||||
|
||||
utc_dt = dt.replace(tzinfo=utc)
|
||||
local_dt = utc_dt.astimezone(local)
|
||||
|
||||
return local_dt
|
||||
|
||||
|
||||
@@ -329,3 +122,238 @@ def convert_size(size_bytes):
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return "%s %s" % (s, size_name[i])
|
||||
|
||||
|
||||
def translate_string(string_id):
|
||||
try:
|
||||
addon = xbmcaddon.Addon()
|
||||
return py2_encode(addon.getLocalizedString(string_id))
|
||||
except Exception as e:
|
||||
log.error('Failed String Load: {0} ({1})', string_id, e)
|
||||
return str(string_id)
|
||||
|
||||
|
||||
def get_device_id():
|
||||
|
||||
window = HomeWindow()
|
||||
username = window.get_property('username')
|
||||
client_id = window.get_property("client_id")
|
||||
hashed_name = hashlib.md5(username.encode()).hexdigest()
|
||||
|
||||
if client_id:
|
||||
return '{}-{}'.format(client_id, hashed_name)
|
||||
|
||||
jellyfin_guid_path = py2_decode(translate_path("special://temp/jellycon_guid"))
|
||||
log.debug("jellyfin_guid_path: {0}".format(jellyfin_guid_path))
|
||||
guid = xbmcvfs.File(jellyfin_guid_path)
|
||||
client_id = guid.read()
|
||||
guid.close()
|
||||
|
||||
if not client_id:
|
||||
client_id = uuid4().hex
|
||||
log.debug("Generating a new guid: {0}".format(client_id))
|
||||
guid = xbmcvfs.File(jellyfin_guid_path, 'w')
|
||||
guid.write(client_id)
|
||||
guid.close()
|
||||
log.debug("jellyfin_client_id (NEW): {0}".format(client_id))
|
||||
else:
|
||||
log.debug("jellyfin_client_id: {0}".format(client_id))
|
||||
|
||||
window.set_property("client_id", client_id)
|
||||
return '{}-{}'.format(client_id, hashed_name)
|
||||
|
||||
|
||||
def get_version():
|
||||
addon = xbmcaddon.Addon()
|
||||
version = addon.getAddonInfo("version")
|
||||
return version
|
||||
|
||||
|
||||
def save_user_details(user_name, user_id, token):
|
||||
settings = xbmcaddon.Addon()
|
||||
save_user_to_settings = settings.getSetting('save_user_to_settings') == 'true'
|
||||
addon_data = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
|
||||
# Save to a config file for reference later if desired
|
||||
if save_user_to_settings:
|
||||
try:
|
||||
with open(os.path.join(addon_data, 'auth.json'), 'rb') as infile:
|
||||
auth_data = json.load(infile)
|
||||
except:
|
||||
# File doesn't exist or is empty
|
||||
auth_data = {}
|
||||
|
||||
auth_data[user_name] = {
|
||||
'user_id': user_id,
|
||||
'token': token
|
||||
}
|
||||
|
||||
with open(os.path.join(addon_data, 'auth.json'), 'wb') as outfile:
|
||||
data = json.dumps(auth_data, sort_keys=True, indent=4, ensure_ascii=False)
|
||||
if isinstance(data, text_type):
|
||||
data = data.encode('utf-8')
|
||||
outfile.write(data)
|
||||
|
||||
# Make the username available for easy lookup
|
||||
window = HomeWindow()
|
||||
settings.setSetting('username', user_name)
|
||||
window.set_property('user_name', user_name)
|
||||
|
||||
|
||||
def load_user_details():
|
||||
settings = xbmcaddon.Addon()
|
||||
window = HomeWindow()
|
||||
# Check current variables first, then check settings
|
||||
user_name = window.get_property('user_name')
|
||||
if not user_name:
|
||||
user_name = settings.getSetting('username')
|
||||
save_user_to_settings = settings.getSetting('save_user_to_settings') == 'true'
|
||||
addon_data = translate_path(xbmcaddon.Addon().getAddonInfo('profile'))
|
||||
|
||||
if save_user_to_settings:
|
||||
try:
|
||||
with open(os.path.join(addon_data, 'auth.json'), 'rb') as infile:
|
||||
auth_data = json.load(infile)
|
||||
except:
|
||||
# File doesn't exist yet
|
||||
return {}
|
||||
|
||||
user_data = auth_data.get(user_name, {})
|
||||
user_id = user_data.get('user_id')
|
||||
auth_token = user_data.get('token')
|
||||
|
||||
# Payload to return to calling function
|
||||
user_details = {}
|
||||
user_details['user_name'] = user_name
|
||||
user_details['user_id'] = user_id
|
||||
user_details['token'] = auth_token
|
||||
return user_details
|
||||
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def get_current_user_id():
|
||||
user_details = load_user_details()
|
||||
user_id = user_details.get('user_id')
|
||||
return user_id
|
||||
|
||||
|
||||
def get_art_url(data, art_type, parent=False, index=0, server=None):
|
||||
|
||||
item_id = data["Id"]
|
||||
item_type = data["Type"]
|
||||
|
||||
if item_type in ["Episode", "Season"]:
|
||||
if art_type != "Primary" or parent is True:
|
||||
item_id = data["SeriesId"]
|
||||
|
||||
image_tag = ""
|
||||
|
||||
# for episodes always use the parent BG
|
||||
if item_type == "Episode" and art_type == "Backdrop":
|
||||
item_id = data.get("ParentBackdropItemId")
|
||||
bg_item_tags = data.get("ParentBackdropImageTags", [])
|
||||
if bg_item_tags:
|
||||
image_tag = bg_item_tags[0]
|
||||
elif art_type == "Backdrop" and parent is True:
|
||||
item_id = data.get("ParentBackdropItemId")
|
||||
bg_item_tags = data.get("ParentBackdropImageTags", [])
|
||||
if bg_item_tags:
|
||||
image_tag = bg_item_tags[0]
|
||||
elif art_type == "Backdrop":
|
||||
bg_tags = data.get("BackdropImageTags", [])
|
||||
if bg_tags:
|
||||
image_tag = bg_tags[index]
|
||||
elif parent is False:
|
||||
image_tags = data.get("ImageTags", [])
|
||||
if image_tags:
|
||||
image_tag_type = image_tags.get(art_type)
|
||||
if image_tag_type:
|
||||
image_tag = image_tag_type
|
||||
elif parent is True:
|
||||
if (item_type == "Episode" or item_type == "Season") and art_type == 'Primary':
|
||||
tag_name = 'SeriesPrimaryImageTag'
|
||||
id_name = 'SeriesId'
|
||||
else:
|
||||
tag_name = 'Parent%sImageTag' % art_type
|
||||
id_name = 'Parent%sItemId' % art_type
|
||||
parent_image_id = data.get(id_name)
|
||||
parent_image_tag = data.get(tag_name)
|
||||
if parent_image_id is not None and parent_image_tag is not None:
|
||||
item_id = parent_image_id
|
||||
image_tag = parent_image_tag
|
||||
|
||||
# ParentTag not passed for Banner and Art
|
||||
if not image_tag and not ((art_type == 'Banner' or art_type == 'Art') and parent is True):
|
||||
return ""
|
||||
|
||||
artwork = "{}/Items/{}/Images/{}/{}?Format=original&Tag={}".format(
|
||||
server, item_id, art_type, index, image_tag)
|
||||
return artwork
|
||||
|
||||
|
||||
def image_url(item_id, art_type, index, width, height, image_tag, server):
|
||||
|
||||
# test imageTag e3ab56fe27d389446754d0fb04910a34
|
||||
artwork = "{}/Items/{}/Images/{}/{}?Format=original&Tag={}".format(server, item_id, art_type, index, image_tag)
|
||||
if int(width) > 0:
|
||||
artwork += '&MaxWidth={}'.format(width)
|
||||
if int(height) > 0:
|
||||
artwork += '&MaxHeight={}'.format(height)
|
||||
|
||||
return artwork
|
||||
|
||||
|
||||
def get_default_filters():
|
||||
|
||||
addon_settings = xbmcaddon.Addon()
|
||||
include_media = addon_settings.getSetting("include_media") == "true"
|
||||
include_people = addon_settings.getSetting("include_people") == "true"
|
||||
include_overview = addon_settings.getSetting("include_overview") == "true"
|
||||
|
||||
filer_list = [
|
||||
"DateCreated",
|
||||
"EpisodeCount",
|
||||
"SeasonCount",
|
||||
"Path",
|
||||
"Genres",
|
||||
"Studios",
|
||||
"Etag",
|
||||
"Taglines",
|
||||
"SortName",
|
||||
"RecursiveItemCount",
|
||||
"ChildCount",
|
||||
"ProductionLocations",
|
||||
"CriticRating",
|
||||
"OfficialRating",
|
||||
"CommunityRating",
|
||||
"PremiereDate",
|
||||
"ProductionYear",
|
||||
"AirTime",
|
||||
"Status",
|
||||
"Tags"
|
||||
]
|
||||
|
||||
if include_media:
|
||||
filer_list.append("MediaStreams")
|
||||
|
||||
if include_people:
|
||||
filer_list.append("People")
|
||||
|
||||
if include_overview:
|
||||
filer_list.append("Overview")
|
||||
|
||||
return ','.join(filer_list)
|
||||
|
||||
|
||||
def translate_path(path):
|
||||
'''
|
||||
Use new library location for translate path starting in Kodi 19
|
||||
'''
|
||||
version = kodi_version()
|
||||
|
||||
if version > 18:
|
||||
return xbmcvfs.translatePath(path)
|
||||
else:
|
||||
return xbmc.translatePath(path)
|
||||
|
||||
@@ -6,16 +6,18 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
import json
|
||||
import threading
|
||||
import websocket
|
||||
import time
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
from .jellyfin import API
|
||||
from .functions import play_action
|
||||
from .loghandler import LazyLogger
|
||||
from . import clientinfo
|
||||
from . import downloadutils
|
||||
from .lazylogger import LazyLogger
|
||||
from .jsonrpc import JsonRpc
|
||||
from .kodi_utils import HomeWindow
|
||||
from .utils import get_device_id, load_user_details
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
@@ -32,9 +34,9 @@ class WebSocketClient(threading.Thread):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
self.monitor = xbmc.Monitor()
|
||||
self.retry_count = 0
|
||||
|
||||
self.client_info = clientinfo.ClientInformation()
|
||||
self.device_id = self.client_info.get_device_id()
|
||||
self.device_id = get_device_id()
|
||||
|
||||
self._library_monitor = library_change_monitor
|
||||
|
||||
@@ -104,7 +106,6 @@ class WebSocketClient(threading.Thread):
|
||||
params["audio_stream_index"] = audio_stream_index
|
||||
play_action(params)
|
||||
|
||||
|
||||
def _playstate(self, data):
|
||||
|
||||
command = data['Command']
|
||||
@@ -227,11 +228,9 @@ class WebSocketClient(threading.Thread):
|
||||
if command in builtin:
|
||||
xbmc.executebuiltin(builtin[command])
|
||||
|
||||
def on_close(self, ws):
|
||||
log.debug("Closed")
|
||||
|
||||
def on_open(self, ws):
|
||||
log.debug("Connected")
|
||||
self.retry_count = 0
|
||||
self.post_capabilities()
|
||||
|
||||
def on_error(self, ws, error):
|
||||
@@ -239,36 +238,35 @@ class WebSocketClient(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
|
||||
# websocket.enableTrace(True)
|
||||
download_utils = downloadutils.DownloadUtils()
|
||||
|
||||
token = None
|
||||
while token is None or token == "":
|
||||
token = download_utils.authenticate()
|
||||
user_details = load_user_details()
|
||||
token = user_details.get('token')
|
||||
if self.monitor.waitForAbort(10):
|
||||
return
|
||||
|
||||
# Get the appropriate prefix for the websocket
|
||||
server = download_utils.get_server()
|
||||
if "https" in server:
|
||||
server = server.replace('https', "wss")
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
if "https://" in server:
|
||||
server = server.replace('https://', 'wss://')
|
||||
else:
|
||||
server = server.replace('http', "ws")
|
||||
server = server.replace('http://', 'ws://')
|
||||
|
||||
websocket_url = "%s/socket?api_key=%s&deviceId=%s" % (server, token, self.device_id)
|
||||
log.debug("websocket url: {0}".format(websocket_url))
|
||||
|
||||
self._client = websocket.WebSocketApp(
|
||||
websocket_url,
|
||||
on_open=lambda ws, message: self.on_open(ws),
|
||||
on_open=lambda ws: self.on_open(ws),
|
||||
on_message=lambda ws, message: self.on_message(ws, message),
|
||||
on_error=lambda ws, error: self.on_error(ws, error),
|
||||
on_close=lambda ws, error: self.on_close(ws))
|
||||
on_error=lambda ws, error: self.on_error(ws, error))
|
||||
|
||||
log.debug("Starting WebSocketClient")
|
||||
|
||||
while not self.monitor.abortRequested():
|
||||
|
||||
time.sleep(self.retry_count * 5)
|
||||
self._client.run_forever(ping_interval=10)
|
||||
|
||||
if self._stop_websocket:
|
||||
@@ -278,6 +276,8 @@ class WebSocketClient(threading.Thread):
|
||||
# Abort was requested, exit
|
||||
break
|
||||
|
||||
if self.retry_count < 12:
|
||||
self.retry_count += 1
|
||||
log.debug("Reconnecting WebSocket")
|
||||
|
||||
log.debug("WebSocketClient Stopped")
|
||||
@@ -291,5 +291,13 @@ class WebSocketClient(threading.Thread):
|
||||
|
||||
def post_capabilities(self):
|
||||
|
||||
download_utils = downloadutils.DownloadUtils()
|
||||
download_utils.post_capabilities()
|
||||
settings = xbmcaddon.Addon()
|
||||
user_details = load_user_details()
|
||||
|
||||
api = API(
|
||||
settings.getSetting('server_address'),
|
||||
user_details.get('user_id'),
|
||||
user_details.get('token')
|
||||
)
|
||||
|
||||
api.post_capabilities()
|
||||
|
||||
@@ -3,23 +3,18 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
||||
import xbmcaddon
|
||||
import xbmcplugin
|
||||
import xbmcgui
|
||||
import xbmc
|
||||
import hashlib
|
||||
import random
|
||||
import time
|
||||
|
||||
from .downloadutils import DownloadUtils
|
||||
from .utils import get_jellyfin_url
|
||||
from .datamanager import DataManager
|
||||
from .loghandler import LazyLogger
|
||||
from .jellyfin import api
|
||||
from .utils import get_jellyfin_url, image_url, get_current_user_id, get_art_url, get_default_filters, kodi_version
|
||||
from .lazylogger import LazyLogger
|
||||
from .kodi_utils import HomeWindow
|
||||
from .dir_functions import process_directory
|
||||
from .tracking import timer
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
downloadUtils = DownloadUtils()
|
||||
dataManager = DataManager()
|
||||
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
background_items = []
|
||||
background_current_item = 0
|
||||
@@ -30,20 +25,22 @@ def set_random_movies():
|
||||
log.debug("set_random_movies Called")
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
item_limit = settings.getSetting("show_x_filtered_items")
|
||||
hide_watched = settings.getSetting("hide_watched") == "true"
|
||||
user_id = get_current_user_id()
|
||||
|
||||
url_params = {}
|
||||
url_params["Recursive"] = True
|
||||
url_params["limit"] = 20
|
||||
url_params["limit"] = item_limit
|
||||
if hide_watched:
|
||||
url_params["IsPlayed"] = False
|
||||
url_params["SortBy"] = "Random"
|
||||
url_params["IncludeItemTypes"] = "Movie"
|
||||
url_params["ImageTypeLimit"] = 0
|
||||
|
||||
url = get_jellyfin_url("{server}/Users/{userid}/Items", url_params)
|
||||
url = get_jellyfin_url("/Users/{}/Items".format(user_id), url_params)
|
||||
|
||||
results = downloadUtils.download_url(url, suppress=True)
|
||||
results = api.get(url)
|
||||
|
||||
randon_movies_list = []
|
||||
if results is not None:
|
||||
@@ -70,6 +67,10 @@ def set_background_image(force=False):
|
||||
global background_current_item
|
||||
global background_items
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
user_id = get_current_user_id()
|
||||
|
||||
if force:
|
||||
background_current_item = 0
|
||||
del background_items
|
||||
@@ -77,7 +78,7 @@ def set_background_image(force=False):
|
||||
|
||||
if len(background_items) == 0:
|
||||
log.debug("set_background_image: Need to load more backgrounds {0} - {1}".format(
|
||||
len(background_items), background_current_item))
|
||||
len(background_items), background_current_item))
|
||||
|
||||
url_params = {}
|
||||
url_params["Recursive"] = True
|
||||
@@ -86,17 +87,17 @@ def set_background_image(force=False):
|
||||
url_params["IncludeItemTypes"] = "Movie,Series"
|
||||
url_params["ImageTypeLimit"] = 1
|
||||
|
||||
url = get_jellyfin_url('{server}/Users/{userid}/Items', url_params)
|
||||
url = get_jellyfin_url('/Users/{}/Items'.format(user_id), url_params)
|
||||
|
||||
server = downloadUtils.get_server()
|
||||
results = downloadUtils.download_url(url, suppress=True)
|
||||
results = api.get(url)
|
||||
|
||||
if results is not None:
|
||||
items = results.get("Items", [])
|
||||
background_current_item = 0
|
||||
background_items = []
|
||||
for item in items:
|
||||
bg_image = downloadUtils.get_artwork(item, "Backdrop", server=server)
|
||||
bg_image = get_art_url(
|
||||
item, "Backdrop", server=server)
|
||||
if bg_image:
|
||||
label = item.get("Name")
|
||||
item_background = {}
|
||||
@@ -104,12 +105,14 @@ def set_background_image(force=False):
|
||||
item_background["name"] = label
|
||||
background_items.append(item_background)
|
||||
|
||||
log.debug("set_background_image: Loaded {0} more backgrounds".format(len(background_items)))
|
||||
log.debug("set_background_image: Loaded {0} more backgrounds".format(
|
||||
len(background_items)))
|
||||
|
||||
if len(background_items) > 0:
|
||||
bg_image = background_items[background_current_item].get("image")
|
||||
label = background_items[background_current_item].get("name")
|
||||
log.debug("set_background_image: {0} - {1} - {2}".format(background_current_item, label, bg_image))
|
||||
log.debug(
|
||||
"set_background_image: {0} - {1} - {2}".format(background_current_item, label, bg_image))
|
||||
|
||||
background_current_item += 1
|
||||
if background_current_item >= len(background_items):
|
||||
@@ -126,7 +129,8 @@ def check_for_new_content():
|
||||
|
||||
home_window = HomeWindow()
|
||||
settings = xbmcaddon.Addon()
|
||||
simple_new_content_check = settings.getSetting("simple_new_content_check") == "true"
|
||||
simple_new_content_check = settings.getSetting(
|
||||
"simple_new_content_check") == "true"
|
||||
|
||||
if simple_new_content_check:
|
||||
log.debug("Using simple new content check")
|
||||
@@ -134,6 +138,7 @@ def check_for_new_content():
|
||||
home_window.set_property("jellycon_widget_reload", current_time_stamp)
|
||||
log.debug("Setting New Widget Hash: {0}".format(current_time_stamp))
|
||||
return
|
||||
user_id = get_current_user_id()
|
||||
|
||||
url_params = {}
|
||||
url_params["Recursive"] = True
|
||||
@@ -144,9 +149,9 @@ def check_for_new_content():
|
||||
url_params["IncludeItemTypes"] = "Movie,Episode"
|
||||
url_params["ImageTypeLimit"] = 0
|
||||
|
||||
added_url = get_jellyfin_url('{server}/Users/{userid}/Items', url_params)
|
||||
added_url = get_jellyfin_url('/Users/{}/Items'.format(user_id), url_params)
|
||||
|
||||
result = downloadUtils.download_url(added_url, suppress=True)
|
||||
result = api.get(added_url)
|
||||
log.debug("LATEST_ADDED_ITEM: {0}".format(result))
|
||||
|
||||
last_added_date = ""
|
||||
@@ -166,9 +171,9 @@ def check_for_new_content():
|
||||
url_params["IncludeItemTypes"] = "Movie,Episode"
|
||||
url_params["ImageTypeLimit"] = 0
|
||||
|
||||
played_url = get_jellyfin_url('{server}/Users/{userid}/Items', url_params)
|
||||
played_url = get_jellyfin_url('/Users/{}/Items'.format(user_id), url_params)
|
||||
|
||||
result = downloadUtils.download_url(played_url, suppress=True)
|
||||
result = api.get(played_url)
|
||||
log.debug("LATEST_PLAYED_ITEM: {0}".format(result))
|
||||
|
||||
last_played_date = ""
|
||||
@@ -186,7 +191,7 @@ def check_for_new_content():
|
||||
log.debug("Current Widget Hash: {0}".format(current_widget_hash))
|
||||
|
||||
m = hashlib.md5()
|
||||
m.update(last_played_date + last_added_date)
|
||||
m.update((last_played_date + last_added_date).encode())
|
||||
new_widget_hash = m.hexdigest()
|
||||
log.debug("New Widget Hash: {0}".format(new_widget_hash))
|
||||
|
||||
@@ -198,11 +203,13 @@ def check_for_new_content():
|
||||
@timer
|
||||
def get_widget_content_cast(handle, params):
|
||||
log.debug("getWigetContentCast Called: {0}".format(params))
|
||||
server = downloadUtils.get_server()
|
||||
settings = xbmcaddon.Addon()
|
||||
server = settings.getSetting('server_address')
|
||||
user_id = get_current_user_id()
|
||||
|
||||
item_id = params["id"]
|
||||
data_manager = DataManager()
|
||||
result = data_manager.get_content("{server}/Users/{userid}/Items/" + item_id)
|
||||
result = api.get(
|
||||
"/Users/{}/Items/{}".format(user_id, item_id))
|
||||
log.debug("ItemInfo: {0}".format(result))
|
||||
|
||||
if not result:
|
||||
@@ -228,12 +235,10 @@ def get_widget_content_cast(handle, params):
|
||||
person_tag = person.get("PrimaryImageTag")
|
||||
person_thumbnail = None
|
||||
if person_tag:
|
||||
person_thumbnail = downloadUtils.image_url(person_id, "Primary", 0, 400, 400, person_tag, server=server)
|
||||
person_thumbnail = image_url(
|
||||
person_id, "Primary", 0, 400, 400, person_tag, server=server)
|
||||
|
||||
if kodi_version > 17:
|
||||
list_item = xbmcgui.ListItem(label=person_name, offscreen=True)
|
||||
else:
|
||||
list_item = xbmcgui.ListItem(label=person_name)
|
||||
list_item = xbmcgui.ListItem(label=person_name, offscreen=True)
|
||||
|
||||
list_item.setProperty("id", person_id)
|
||||
|
||||
@@ -263,20 +268,23 @@ def get_widget_content(handle, params):
|
||||
log.debug("getWigetContent Called: {0}".format(params))
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
item_limit = int(settings.getSetting("show_x_filtered_items"))
|
||||
hide_watched = settings.getSetting("hide_watched") == "true"
|
||||
use_cached_widget_data = settings.getSetting("use_cached_widget_data") == "true"
|
||||
use_cached_widget_data = settings.getSetting(
|
||||
"use_cached_widget_data") == "true"
|
||||
|
||||
widget_type = params.get("type")
|
||||
if widget_type is None:
|
||||
log.error("getWigetContent type not set")
|
||||
return
|
||||
user_id = get_current_user_id()
|
||||
|
||||
log.debug("widget_type: {0}".format(widget_type))
|
||||
|
||||
url_verb = "{server}/Users/{userid}/Items"
|
||||
url_verb = "/Users/{}/Items".format(user_id)
|
||||
url_params = {}
|
||||
url_params["Limit"] = "{ItemLimit}"
|
||||
url_params["Fields"] = "{field_filters}"
|
||||
url_params["Limit"] = item_limit
|
||||
url_params["Fields"] = get_default_filters()
|
||||
url_params["ImageTypeLimit"] = 1
|
||||
url_params["IsMissing"] = False
|
||||
|
||||
@@ -290,6 +298,7 @@ def get_widget_content(handle, params):
|
||||
url_params["IsPlayed"] = False
|
||||
url_params["IsVirtualUnaired"] = False
|
||||
url_params["IncludeItemTypes"] = "Movie"
|
||||
url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "inprogress_movies":
|
||||
xbmcplugin.setContent(handle, 'movies')
|
||||
@@ -299,25 +308,28 @@ def get_widget_content(handle, params):
|
||||
url_params["Filters"] = "IsResumable"
|
||||
url_params["IsVirtualUnaired"] = False
|
||||
url_params["IncludeItemTypes"] = "Movie"
|
||||
url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "random_movies":
|
||||
home_window = HomeWindow()
|
||||
xbmcplugin.setContent(handle, 'movies')
|
||||
url_params["Ids"] = "{random_movies}"
|
||||
url_params["Ids"] = home_window.get_property("random-movies")
|
||||
|
||||
elif widget_type == "recent_tvshows":
|
||||
xbmcplugin.setContent(handle, 'episodes')
|
||||
url_verb = '{server}/Users/{userid}/Items/Latest'
|
||||
url_verb = '/Users/{}/Items/Latest'.format(user_id)
|
||||
url_params["GroupItems"] = True
|
||||
url_params["Limit"] = 45
|
||||
url_params["Recursive"] = True
|
||||
url_params["SortBy"] = "DateCreated"
|
||||
url_params["SortOrder"] = "Descending"
|
||||
url_params["Fields"] = "{field_filters}"
|
||||
url_params["Fields"] = get_default_filters()
|
||||
if hide_watched:
|
||||
url_params["IsPlayed"] = False
|
||||
url_params["IsVirtualUnaired"] = False
|
||||
url_params["IncludeItemTypes"] = "Episode"
|
||||
url_params["ImageTypeLimit"] = 1
|
||||
url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "recent_episodes":
|
||||
xbmcplugin.setContent(handle, 'episodes')
|
||||
@@ -329,6 +341,7 @@ def get_widget_content(handle, params):
|
||||
url_params["IsPlayed"] = False
|
||||
url_params["IsVirtualUnaired"] = False
|
||||
url_params["IncludeItemTypes"] = "Episode"
|
||||
url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "inprogress_episodes":
|
||||
xbmcplugin.setContent(handle, 'episodes')
|
||||
@@ -338,31 +351,43 @@ def get_widget_content(handle, params):
|
||||
url_params["Filters"] = "IsResumable"
|
||||
url_params["IsVirtualUnaired"] = False
|
||||
url_params["IncludeItemTypes"] = "Episode"
|
||||
url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "nextup_episodes":
|
||||
xbmcplugin.setContent(handle, 'episodes')
|
||||
url_verb = "{server}/Shows/NextUp"
|
||||
url_params["Limit"] = "{ItemLimit}"
|
||||
url_params["userid"] = "{userid}"
|
||||
url_verb = "/Shows/NextUp"
|
||||
url_params = url_params.copy()
|
||||
url_params["Limit"] = item_limit
|
||||
url_params["userid"] = user_id
|
||||
url_params["Recursive"] = True
|
||||
url_params["Fields"] = "{field_filters}"
|
||||
url_params["ImageTypeLimit"] = 1
|
||||
# Collect InProgress items to be combined with NextUp
|
||||
inprogress_url_verb = "/Users/{}/Items".format(user_id)
|
||||
inprogress_url_params = url_params.copy()
|
||||
inprogress_url_params["Recursive"] = True
|
||||
inprogress_url_params["SortBy"] = "DatePlayed"
|
||||
inprogress_url_params["SortOrder"] = "Descending"
|
||||
inprogress_url_params["Filters"] = "IsResumable"
|
||||
inprogress_url_params["IsVirtualUnaired"] = False
|
||||
inprogress_url_params["IncludeItemTypes"] = "Episode"
|
||||
inprogress_url_params["Limit"] = item_limit
|
||||
|
||||
elif widget_type == "movie_recommendations":
|
||||
suggested_items_url_params = {}
|
||||
suggested_items_url_params["userId"] = "{userid}"
|
||||
suggested_items_url_params["userId"] = user_id
|
||||
suggested_items_url_params["categoryLimit"] = 15
|
||||
suggested_items_url_params["ItemLimit"] = 20
|
||||
suggested_items_url_params["ItemLimit"] = item_limit
|
||||
suggested_items_url_params["ImageTypeLimit"] = 0
|
||||
suggested_items_url = get_jellyfin_url("{server}/Movies/Recommendations", suggested_items_url_params)
|
||||
suggested_items_url = get_jellyfin_url(
|
||||
"/Movies/Recommendations", suggested_items_url_params)
|
||||
|
||||
data_manager = DataManager()
|
||||
suggested_items = data_manager.get_content(suggested_items_url)
|
||||
suggested_items = api.get(suggested_items_url)
|
||||
ids = []
|
||||
set_id = 0
|
||||
while len(ids) < 20 and suggested_items:
|
||||
while len(ids) < item_limit and suggested_items:
|
||||
items = suggested_items[set_id]
|
||||
log.debug("BaselineItemName : {0} - {1}".format(set_id, items.get("BaselineItemName")))
|
||||
log.debug(
|
||||
"BaselineItemName : {0} - {1}".format(set_id, items.get("BaselineItemName")))
|
||||
items = items["Items"]
|
||||
rand = random.randint(0, len(items) - 1)
|
||||
item = items[rand]
|
||||
@@ -376,21 +401,27 @@ def get_widget_content(handle, params):
|
||||
set_id = 0
|
||||
|
||||
id_list = ",".join(ids)
|
||||
log.debug("Recommended Items : {0}".format(len(ids), id_list))
|
||||
log.debug("Recommended Items : {0}".format(len(ids)))
|
||||
url_params["Ids"] = id_list
|
||||
|
||||
items_url = get_jellyfin_url(url_verb, url_params)
|
||||
|
||||
list_items, detected_type, total_records = process_directory(items_url, None, params, use_cached_widget_data)
|
||||
if url_params.get('IncludeItemTypes', '') == 'Episode' or params.get('type', '') == 'nextup_episodes':
|
||||
params["name_format"] = "Episode|episode_name_format"
|
||||
|
||||
# remove resumable items from next up
|
||||
list_items, detected_type, total_records = process_directory(
|
||||
items_url, None, params, use_cached_widget_data)
|
||||
|
||||
# Combine In Progress and Next Up Episodes, append next up after In Progress
|
||||
if widget_type == "nextup_episodes":
|
||||
filtered_list = []
|
||||
for item in list_items:
|
||||
resume_time = item[1].getProperty("ResumeTime")
|
||||
if resume_time is None or float(resume_time) == 0.0:
|
||||
filtered_list.append(item)
|
||||
list_items = filtered_list
|
||||
inprogress_url = get_jellyfin_url(
|
||||
inprogress_url_verb, inprogress_url_params)
|
||||
|
||||
params["name_format"] = "Episode|episode_name_format"
|
||||
list_items_inprogress, detected_type, total_records = process_directory(
|
||||
inprogress_url, None, params, use_cached_widget_data)
|
||||
|
||||
list_items = list_items_inprogress + list_items
|
||||
|
||||
if detected_type is not None:
|
||||
# if the media type is not set then try to use the detected type
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
<setting id="protocol" type="select" label="30390" lvalues="30391|30392" default="0" visible="false"/>
|
||||
<setting id="port" type="text" label="30001" default="8096" visible="false" enable="false" />
|
||||
<setting id="server_address" type="text" label="30000" default="" visible="true" enable="true" />
|
||||
<setting id="verify_cert" type="bool" label="30003" default="false" visible="true" enable="true" />
|
||||
<setting id="verify_cert" type="bool" label="30003" default="true" visible="true" enable="true" />
|
||||
|
||||
<setting label="30389" type="lsep"/>
|
||||
<setting type="sep" />
|
||||
<setting label="30012" type="action" action="RunScript(plugin.video.jellycon,0,?mode=CHANGE_USER)" option="close"/>
|
||||
<setting id="username" type="text" label="30024" />
|
||||
<setting id="password" type="text" option="hidden" label="30025" />
|
||||
<setting id="save_user_to_settings" type="bool" label="30378" default="true" visible="true" enable="true" />
|
||||
<setting id="allow_password_saving" type="bool" label="30367" default="true" visible="true" enable="true" />
|
||||
|
||||
@@ -46,13 +45,6 @@
|
||||
<setting id="audio_playback_bitrate" type="select" label="30418" values="128|160|192|256|320|384|448|640" default="256" visible="true"/>
|
||||
<setting id="audio_max_channels" type="slider" label="30420" default="8" range="2,1,8" option="int" visible="true"/>
|
||||
|
||||
<!--
|
||||
<setting label="30209" type="lsep"/>
|
||||
<setting type="sep" />
|
||||
<setting id="smbusername" type="text" label="30007" default="" enable="true" visible="true"/>
|
||||
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" enable="true" visible="true"/>
|
||||
-->
|
||||
|
||||
</category>
|
||||
<category label="30214">
|
||||
|
||||
@@ -74,8 +66,8 @@
|
||||
|
||||
<setting label="30329" type="lsep"/>
|
||||
<setting type="sep" />
|
||||
<setting id="stopPlaybackOnScreensaver" type="bool" label="30332" default="true" visible="true" enable="true" />
|
||||
<setting id="changeUserOnScreenSaver" type="bool" label="30330" default="true" visible="true" enable="true" />
|
||||
<setting id="stopPlaybackOnScreensaver" type="bool" label="30332" default="false" visible="true" enable="true" />
|
||||
<setting id="changeUserOnScreenSaver" type="bool" label="30330" default="false" visible="true" enable="true" />
|
||||
<setting id="cacheImagesOnScreenSaver" type="bool" label="30333" default="true" visible="true" enable="true" />
|
||||
<setting id="cacheImagesOnScreenSaver_interval" type="slider" label="30400" default="0" range="0,1,60" option="int" visible="true"/>
|
||||
|
||||
@@ -92,7 +84,7 @@
|
||||
<setting id="add_user_ratings" type="bool" label="30348" default="true" visible="true" enable="true" />
|
||||
<setting id="include_people" type="bool" label="30183" default="false" visible="true" enable="true" />
|
||||
<setting id="hide_unwatched_details" type="bool" label="30023" default="false" visible="true" enable="true" />
|
||||
<setting id="episode_name_format" type="select" label="30019" default="{SeriesName} - {ItemName}" values="{SeriesName} - {ItemName}|{ItemName}|s{SeasonIndex}e{EpisodeIndex} - {ItemName}|{SeriesName} - s{SeasonIndex}e{EpisodeIndex} - {ItemName}" />
|
||||
<setting id="episode_name_format" type="select" label="30019" default="{SeriesName} - {ItemName}" values="{SeriesName} - {ItemName}|{ItemName}|S{SeasonIndex}E{EpisodeIndex} - {ItemName}|{SeriesName} - S{SeasonIndex}E{EpisodeIndex} - {ItemName}" />
|
||||
|
||||
<setting label="30222" type="lsep"/>
|
||||
<setting type="sep" />
|
||||
@@ -155,4 +147,4 @@
|
||||
<setting id="sort-Episodes" type="select" label="30235" lvalues="30423|30424|30426|30425|30427|30429|30430|30428" default="0" visible="true"/>
|
||||
|
||||
</category>
|
||||
</settings>
|
||||
</settings>
|
||||
|
||||
@@ -13,39 +13,53 @@
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>380</width>
|
||||
<height>100</height>
|
||||
<height>150</height>
|
||||
<texture border="40">bg.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="label" id="3020">
|
||||
<width>120</width>
|
||||
<left>20</left>
|
||||
<top>5</top>
|
||||
<height>45</height>
|
||||
<label>Bitrate : </label>
|
||||
<textcolor>99FFFFFF</textcolor>
|
||||
<font>font14</font>
|
||||
<align>left</align>
|
||||
</control>
|
||||
<control type="label" id="3020">
|
||||
<width>120</width>
|
||||
<left>20</left>
|
||||
<top>5</top>
|
||||
<height>45</height>
|
||||
<label>Bitrate : </label>
|
||||
<textcolor>99FFFFFF</textcolor>
|
||||
<font>font14</font>
|
||||
<align>left</align>
|
||||
</control>
|
||||
|
||||
<control type="label" id="3030">
|
||||
<width>150</width>
|
||||
<left>120</left>
|
||||
<top>5</top>
|
||||
<height>45</height>
|
||||
<label>100 Mbs</label>
|
||||
<textcolor>99FFFFFF</textcolor>
|
||||
<font>font14</font>
|
||||
<align>left</align>
|
||||
</control>
|
||||
<control type="label" id="3030">
|
||||
<width>150</width>
|
||||
<left>120</left>
|
||||
<top>5</top>
|
||||
<height>45</height>
|
||||
<label>100 Mbs</label>
|
||||
<textcolor>99FFFFFF</textcolor>
|
||||
<font>font14</font>
|
||||
<align>left</align>
|
||||
</control>
|
||||
|
||||
<control type="slider" id="3000">
|
||||
<left>20</left>
|
||||
<top>55</top>
|
||||
<width>340</width>
|
||||
<height>30</height>
|
||||
<visible>true</visible>
|
||||
</control>
|
||||
<control type="slider" id="3000">
|
||||
<left>20</left>
|
||||
<top>55</top>
|
||||
<width>340</width>
|
||||
<height>30</height>
|
||||
<ondown>3011</ondown>
|
||||
<visible>true</visible>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3011">
|
||||
<texturenofocus border="1" colordiffuse="ff161616">white.png</texturenofocus>
|
||||
<texturefocus border="1" colordiffuse="ff525252">white.png</texturefocus>
|
||||
<left>20</left>
|
||||
<top>100</top>
|
||||
<width>340</width>
|
||||
<height>40</height>
|
||||
<label></label>
|
||||
<onup>3000</onup>
|
||||
<font>font14</font>
|
||||
<align>center</align>
|
||||
</control>
|
||||
|
||||
</controls>
|
||||
</window>
|
||||
</window>
|
||||
|
||||
68
resources/skins/default/720p/QuickConnectDialog.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window id="3301" type="dialog">
|
||||
<defaultcontrol always="true">3010</defaultcontrol>
|
||||
<zorder>2</zorder>
|
||||
<coordinates>
|
||||
<system>1</system>
|
||||
<left>450</left>
|
||||
<top>200</top>
|
||||
</coordinates>
|
||||
<controls>
|
||||
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>400</width>
|
||||
<height>270</height>
|
||||
<texture border="40">bg.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="label" id="4">
|
||||
<left>20</left>
|
||||
<top>5</top>
|
||||
<width>360</width>
|
||||
<height>50</height>
|
||||
<label>Heading</label>
|
||||
<font>font45_title</font>
|
||||
<align>center</align>
|
||||
</control>
|
||||
|
||||
<control type="textbox" id="3">
|
||||
<left>20</left>
|
||||
<top>65</top>
|
||||
<width>360</width>
|
||||
<height>50</height>
|
||||
<font>font45</font>
|
||||
<align>center</align>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3010">
|
||||
<texturenofocus border="1" colordiffuse="ff161616">white.png</texturenofocus>
|
||||
<texturefocus border="1" colordiffuse="ff525252">white.png</texturefocus>
|
||||
<left>20</left>
|
||||
<top>135</top>
|
||||
<width>360</width>
|
||||
<height>40</height>
|
||||
<label></label>
|
||||
<onup></onup>
|
||||
<ondown>3011</ondown>
|
||||
<font>font14</font>
|
||||
<align>center</align>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3011">
|
||||
<texturenofocus border="1" colordiffuse="ff161616">white.png</texturenofocus>
|
||||
<texturefocus border="1" colordiffuse="ff525252">white.png</texturefocus>
|
||||
<left>20</left>
|
||||
<top>195</top>
|
||||
<width>360</width>
|
||||
<height>40</height>
|
||||
<label></label>
|
||||
<onup>3010</onup>
|
||||
<ondown>3012</ondown>
|
||||
<font>font14</font>
|
||||
<align>center</align>
|
||||
</control>
|
||||
|
||||
</controls>
|
||||
</window>
|
||||
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 137 B |
|
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 109 B |
@@ -1,15 +0,0 @@
|
||||
|
||||
$string_ids = @()
|
||||
|
||||
Select-String -path resources\language\resource.language.en_gb\strings.po -pattern "msgctxt " | select Line | ForEach {
|
||||
$id = [regex]::match($_.Line.ToString(), '\"#([0-9]+)\"').Groups[1].Value
|
||||
if($string_ids -contains $id)
|
||||
{
|
||||
Write-Host "ERROR: String ID Already Exists : " $id
|
||||
}
|
||||
else
|
||||
{
|
||||
$string_ids += $id
|
||||
Get-ChildItem *.py,settings.xml,resources\language\resource.language.en_gb\strings.po -recurse | Select-String -pattern $id | group Pattern | where {$_.Count -eq 1} | select Name, Count
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
del /F /Q /S %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon
|
||||
rmdir /Q /S %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon
|
||||
|
||||
xcopy /Y addon.xml %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
xcopy /Y default.py %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
xcopy /Y fanart.jpg %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
xcopy /Y icon.png %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
xcopy /Y kodi.png %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
xcopy /Y service.py %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\
|
||||
|
||||
xcopy /E /Y resources %HOMEPATH%\AppData\Roaming\Kodi\addons\plugin.video.jellycon\resources\
|
||||
|
||||
cd "%programfiles%\Kodi"
|
||||
kodi.exe
|
||||
@@ -1,45 +0,0 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import subprocess
|
||||
from shutil import copy2, copytree, rmtree
|
||||
import os
|
||||
import sys
|
||||
|
||||
package_path = "package"
|
||||
|
||||
def ignore_files(path, item_list):
|
||||
return [".idea", ".git", ".gitignore", "scripts", package_path]
|
||||
|
||||
zip_path = "c:\\Program Files\\7-Zip\\7z.exe"
|
||||
addon_path = sys.argv[1]
|
||||
|
||||
tree = ET.parse(addon_path + "\\addon.xml")
|
||||
root = tree.getroot()
|
||||
|
||||
|
||||
id = root.attrib["id"]
|
||||
version = root.attrib["version"]
|
||||
|
||||
print (package_path + " - " + version)
|
||||
|
||||
try:
|
||||
rmtree(package_path + "\\" + id)
|
||||
except FileNotFoundError as err:
|
||||
pass
|
||||
|
||||
copytree(addon_path, package_path + "\\" + id, ignore=ignore_files)
|
||||
|
||||
zip_name = id + "-" + version + ".zip"
|
||||
|
||||
os.chdir(package_path)
|
||||
cmd_7zip = [zip_path, "a", zip_name, id]
|
||||
sp = subprocess.Popen(cmd_7zip, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
|
||||
sp.wait()
|
||||
os.chdir("..")
|
||||
|
||||
copy2(package_path + "\\" + id + "\\addon.xml", package_path + "\\addon.xml")
|
||||
|
||||
try:
|
||||
rmtree(package_path + "\\" + id)
|
||||
except FileNotFoundError as err:
|
||||
pass
|
||||
|
||||
27
service.py
@@ -8,16 +8,14 @@ import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
from resources.lib.downloadutils import DownloadUtils, save_user_details
|
||||
from resources.lib.loghandler import LazyLogger
|
||||
from resources.lib.lazylogger import LazyLogger
|
||||
from resources.lib.play_utils import Service, PlaybackService, send_progress
|
||||
from resources.lib.kodi_utils import HomeWindow
|
||||
from resources.lib.widgets import set_background_image, set_random_movies
|
||||
from resources.lib.websocket_client import WebSocketClient
|
||||
from resources.lib.menu_functions import set_library_window_values
|
||||
from resources.lib.context_monitor import ContextMonitor
|
||||
from resources.lib.server_detect import check_server, check_safe_delete_available, check_connection_speed
|
||||
from resources.lib.library_change_monitor import LibraryChangeMonitor
|
||||
from resources.lib.server_detect import check_server, check_connection_speed
|
||||
from resources.lib.monitors import LibraryChangeMonitor, ContextMonitor
|
||||
from resources.lib.datamanager import clear_old_cache_data
|
||||
from resources.lib.tracking import set_timing_enabled
|
||||
from resources.lib.image_server import HttpImageServerThread
|
||||
@@ -53,20 +51,11 @@ while not monitor.abortRequested():
|
||||
|
||||
check_server()
|
||||
|
||||
download_utils = DownloadUtils()
|
||||
|
||||
# auth the service
|
||||
try:
|
||||
download_utils.authenticate()
|
||||
download_utils.get_user_id()
|
||||
except Exception as error:
|
||||
log.error("Error with initial service auth: {0}".format(error))
|
||||
|
||||
|
||||
image_server = HttpImageServerThread()
|
||||
image_server.start()
|
||||
|
||||
# set up all the services
|
||||
monitor = Service()
|
||||
playback_service = PlaybackService(monitor)
|
||||
|
||||
home_window = HomeWindow()
|
||||
@@ -74,7 +63,6 @@ last_progress_update = time.time()
|
||||
last_content_check = time.time()
|
||||
last_background_update = 0
|
||||
last_random_movie_update = 0
|
||||
safe_delete_check = False
|
||||
|
||||
# start the library update monitor
|
||||
library_change_monitor = LibraryChangeMonitor()
|
||||
@@ -138,7 +126,8 @@ while home_window.get_property('exit') == 'False':
|
||||
if user_changed or first_run:
|
||||
settings = xbmcaddon.Addon()
|
||||
server_speed_check_data = settings.getSetting("server_speed_check_data")
|
||||
server_host = download_utils.get_server()
|
||||
server_speed_check_data = settings.getSetting("server_speed_check_data")
|
||||
server_host = settings.getSetting('server_address')
|
||||
if server_host is not None and server_host != "" and server_host != "<none>" and server_host not in server_speed_check_data:
|
||||
message = "This is the first time you have connected to this server.\nDo you want to run a connection speed test?"
|
||||
response = xbmcgui.Dialog().yesno("First Connection", message)
|
||||
@@ -167,10 +156,6 @@ while home_window.get_property('exit') == 'False':
|
||||
websocket_client = WebSocketClient(library_change_monitor)
|
||||
websocket_client.start()
|
||||
|
||||
if user_changed or not safe_delete_check:
|
||||
check_safe_delete_available()
|
||||
safe_delete_check = True
|
||||
|
||||
elif screen_saver_active:
|
||||
last_random_movie_update = time.time() - (random_movie_list_interval - 15)
|
||||
if background_interval != 0 and ((time.time() - last_background_update) > background_interval):
|
||||
|
||||