Just an automation utility/hobby project for fun, potentially useless? to download missing shows from
your AniList collection not present in a plex media server.
How does it work?
This little app consists of 5 modules:
- anilist
Handles requests to anilist to fetch your list/s
- app
Central point of the application, handles calls to other modules, database operations, utilities, e.t.c.
- nyaa
Queries nyaa.si for torrents matching shows missing in your plex media server but available in one or more of your lists and any shows in a given list name at run time. (see
How do I use it?
) - plex
Handles searching and matching anilist meida agains plex library shows. This module will use
romaji
,english
andsynonyms
from anilist when searching in plex until a match is found, and each result is tested for a 98% match (this avoids false negatives given shows in plex may slightly differ from anilist names). - transmission
Handles dispatching torrents to
Transmission
for downloading
Q: What versin of python is required?
A: Python 3.7+ because the application uses @dataclass
annotations
Q: What about python 2?
A: π that's funny
Q: Where are the unit tests?
A: I didn't have time π©
Q: What if I don't have transmission?
A: The torrent files will be downloaded in a local directory labled torrents ./app/torrents
Q: Are there any limitations?
A: Yes!! ofcourse π π
- Sometimes
plexapi
returns 0 results for a search query, desipite the search term being 100% exact (not sure why this happens) and because of this the application will assume this item doesn't exist in your plex library and add it to the list of missing shows - Due to how Plex presents it's shows as seasons and AniList represents media as individual items and nyaa has a lot of variation, I can't guarantee that shows will be found or matched correctly.
- More??? You tell me_
How do I use it?
Before we get started I will assume that you have some basic knowledge regarding
python
andpip
Tip: Google is your friend π
- Create your configuration files in
.app/config/
- Create your authentication files in
.app/auth/
- Install Python 3.7 and Python virtual environment and set it up
- Install the dependencies
pip install -r requirements.txt
You can find configuration instructions here and authentication instructions here
The application is CLI based and you can expose the available commands through:
python manage.py --help
N.B if you're not running python
keyword | String | Keyword for the search query category | Integer | See Categories subcategory | Integer | See Categories filters | Integer | See Filters page | Integer | Number between 0 and 1000
Nyaa.search(keyword="Shoukoku no Altair", category=1)
Returns a list of dictionaries like this
{
"category": "Anime - English-translated",
"url": "https://nyaa.si/view/968600",
"name": "[HorribleSubs] Shoukoku no Altair - 14 [720p].mkv",
"download_url": "https://nyaa.si/download/968600.torrent",
"magnet": "<magnet torrent URI>",
"size": "317.2 MiB",
"date": "2017-10-13 20:16",
"seeders": "538",
"leechers": "286",
"completed_downloads": "852"
}
TinyDB is a lightweight document oriented database optimized for your happiness :) It's written in pure Python and has no external dependencies. The target are small apps that would be blown away by a SQL-DB or an external database server.
from tinydb import TinyDB
db = TinyDB('/path/to/db.json')
db.insert({'int': 1, 'char': 'a'})
db.insert({'int': 1, 'char': 'b'})
Query Language
from tinydb import TinyDB, Query
User = Query()
db = TinyDB('/path/to/db.json')
# Search for a field value
db.search(User.name == 'John')
[{'name': 'John', 'age': 22}, {'name': 'John', 'age': 37}]
# Combine two queries with logical and
db.search((User.name == 'John') & (User.age <= 30))
[{'name': 'John', 'age': 22}]
# Combine two queries with logical or
db.search((User.name == 'John') | (User.name == 'Bob'))
[{'name': 'John', 'age': 22}, {'name': 'John', 'age': 37}, {'name': 'Bob', 'age': 42}]
# More possible comparisons: != < > <= >=
# More possible checks: where(...).matches(regex), where(...).test(your_test_func)
Tables
table = db.table('name')
table.insert({'value': True})
table.all()
[{'value': True}]
There are two types of authentication. If you are running on a separate network or using Plex Users you can log into MyPlex to get a PlexServer instance. An example of this is below. NOTE: Servername below is the name of the server (not the hostname and port). If logged into Plex Web you can see the server name in the top left above your available libraries.
from plexapi.myplex import MyPlexAccount
account = MyPlexAccount('<USERNAME>', '<PASSWORD>')
# returns a PlexServer instance
plex = account.resource('<SERVERNAME>').connect()
If you want to avoid logging into MyPlex and you already know your auth token string, you can use the PlexServer object directly as above, but passing in the baseurl and auth token directly.
from plexapi.server import PlexServer
baseurl = 'http://plexserver:32400'
token = '2ffLuB84dqLswk9skLos'
plex = PlexServer(baseurl, token)
The example below shows how you can execute queries against a local schema.
from gql import gql, Client
client = Client(schema=schema)
query = gql('''
{
hello
}
''')
client.execute(query)
This module simplifies creation of data classes ([PEP 557][pep-557]) from dictionaries.
from dataclasses import dataclass
from dacite import from_dict
@dataclass
class User:
name: str
age: int
is_active: bool
data = {
'name': 'john',
'age': 30,
'is_active': True,
}
user = from_dict(data_class=User, data=data)
assert user == User(name='john', age=30, is_active=True)
Anitopy is a Python library for parsing anime video filenames. It's simple to use and it's based on the C++ library
Anitomy <https://github.com/erengy/anitomy>
_.
import anitopy
anitopy.parse('[TaigaSubs]_Toradora!_(2008)_-_01v2_-_Tiger_and_Dragon_[1280x720_H.264_FLAC][1234ABCD].mkv')
Will result in the following:
{
"anime_title": "Toradora!",
"anime_year": "2008",
"audio_term": "FLAC",
"episode_number": "01",
"episode_title": "Tiger and Dragon",
"file_checksum": "1234ABCD",
"file_extension": "mkv",
"file_name": "[TaigaSubs]_Toradora!_(2008)_-_01v2_-_Tiger_and_Dragon_[1280x720_H.264_FLAC][1234ABCD].mkv",
"release_group": "TaigaSubs",
"release_version": "2",
"video_resolution": "1280x720",
"video_term": "H.264"
}
clutch
was designed to be a more lightweight and consistent Transmission RPC library than what was currently available for Python. Instead of simply using the keys/fields in the Transmission RPC spec which have a mix of dashed separated words and mixed case words,clutch
tries to convert all keys to be more Pythonic: underscore separated words. This conversion is done so that it is still possible to specify the fields/argument specified in the Transmission RPC spec, but if you do so your mileage may vary (probably want to avoid it).
To install:
pip install transmission-clutch
To use:
>>> from clutch.core import Client
The module exports a function that takes an Unicode object (Python 2.x) or string (Python 3.x) and returns a string (that can be encoded to ASCII bytes in Python 3.x):
from unidecode import unidecode
unidecode(u'ko\u017eu\u0161\u010dek')
# outputs > 'kozuscek'
unidecode(u'30 \U0001d5c4\U0001d5c6/\U0001d5c1')
# outputs > '30 km/h'
unidecode(u"\u5317\u4EB0")
# outputs > 'Bei Jing '