adbb
Object Oriented UDP Client for AniDB, originally forked from adba.
I created this mainly to be able to add new files to my "mylist" on anidb when I add them to my local collection.
As I tend to rip my own files and have no intention of spreading them to a wide audience, I needed to add these as
"generic" files to anidb. And manual work is always less fun then automating said work...
As the anidb UDP API enforces a very slow rate of requests, this implementation caches all information requested from
anidb and uses the cache whenever possible. The cache is stored in mysql (or any other sqlalchemy-compatible
database). For how long does it cache? It depends! Shortest caching period is one day, after that some not very
inteligent algorithm will add some probability score which is used to decide if the cache should be updated or not.
It's untuned and will probably be difficult to get right for all use cases... I'm listening to any ideas about how to
make this better.
Also, you can always force an update of the cache by using the objects update() method.
The Anime title search is implemented using the animetitles.xml file hosted at anidb. It is automatically downloaded
and stored localy (for now hardcoded to /var/tmp/adbb/animetitles.xml.gz). This animetitles file is also cached for 7
days (using mtime to calculate age) and then is automatically updated. You can of course "update" it manually by removing the cached file.
Since version 1 adbb also supports tvdb/tmdb/imdb-mapping via Anime-Lists.
Requirements
- recent python
- pycryptodome (required hash methods has been removed from the standard python library)
- sqlalchemy
- sqlalchemy-compatible database:
- mysql
- sqlite
- postgresql (the one most recently used and tested)
Usage
import adbb
user="<anidb-username>"
pwd="<anidb-password>"
sql="sqlite:///adbb.db"
adbb.init(user, pwd, sql, debug=True)
anime = adbb.Anime("Kemono no Souja Erin")
print("{} has {} episodes and is a {}".format(anime.title, anime.nr_of_episodes, anime.type))
episode = adbb.Episode(anime=anime, epno=5)
print("'{}' episode {} has title '{}'".format(episode.anime.title, episode.episode_number, episode.title_eng))
file = File(path="/media/Anime/Series/Kemono no Souja Erin/[winterbird] Kemono no Souja Erin - 05 [8EEAA040].mkv")
print("'{}' contains episode {} of '{}'. Mylist state is '{}'".format(file.path, file.episode.episode_number, file.anime.title, file.mylist_state))
with open('poster.jpg', 'wb') as f:
adbb.download_image(f, anime)
adbb.close()
Reference
Anime object
Anime(init)
'init' can be either a title or aid. Titles are searched in the animetitles.xml file using fuzzy text matching
(implemented using difflib). Only a single Anime is created, using the best title match. Note that some titles are
ambigious. A search for 'Ranma', for example, can return either the series 'Ranma 1/2' (which has "Ranma" as a
synonym) or 'Ranma 1/2 Nettou Hen' which has "Ranma" as an official title).
Attributes
aid
- AniDB anime IDtitles
- A list of all titles for this Animetitle
- main title of this Animeupdated
- datetime when information about this Anime was last fetched from AniDBtvdbid
- TVDB ID for this anime or None if not available.tmdbid
- TMDB ID for this anime or None if not available. Can be a list if this Anime maps to multiple movies, use the tmdbid attribute on the Episode object to get the tmdbid for a specific episode.imdbid
- IMDB ID for this anime or None if not available. Can be a list if thie Anime maps to multiple movies, use the imdbid attribute on the Episode object to get the imdbid for a specific episode.relations
- A list of tuples containing relations to this anime. The first entry in the tuple is a string describing the relation type and the second is an Anime-object for the related anime.fanart
- if enabled it will return a list of dicts directly translated from the json returned from the fanart.tv API. Returns an empty list if not enabled.
The following attributes as returned from the AniDB API
year
type
nr_of_episodes
highest_episode_number
special_ep_count
air_date
end_date
url
picname
rating
vote_count
temp_rating
temp_vote_count
average_review_rating
review_count
is_18_restricted
ann_id
allcinema_id
animenfo_id
anidb_updated
special_count
credit_count
other_count
trailer_count
parody_count
Episode
Episode(anime=None, epno=None, eid=None)
Episode object can be created by specifying both anime and epno, or using just eid. anime can be either a title, aid
or an Anime object. epno should be either a string or int representing the episode number. eid should be an int.
Attributes
eid
- AniDB episode IDanime
- Anime object for the anime series that this episode belongs toepisode_number
- The episode number (note that this is a string)updated
- datetime when information about this episode was last fetched from AniDBtvdb_episode
- A tuple containing (season, episode)
if this episode can be mapped to a TVDB episode. Note that episode
is usualy a string containing the episode number, but can also be a tuple with (episode_number, partnumber) or a string containing episode numbers separated by '+' if the anidb episode is mapped to part of a TVDB episode or vice versa.tmdbid
- TMDB ID for this episode or None if not available.imdbid
- IMDB ID for this episode or None if not available.
The following attributes as returned from the AniDB API
length
rating
votes
title_eng
title_romaji
title_kanji
aired
type
File
File(path=None, fid=None, anime=None, episode=None)
File object requires either path, fid, or anime and episode to be set. When setting anime and episode this file will
either be a generic file, or the file you have in your mylist for this anime and episode. fid is obviously the AniDB
file ID. Path is the fun one.
When a path is specified the library will first check the size and ed2k-hash to the AniDB database. If the file exists
there this will obviously represent that file. If the file doesn't exist in the AniDB databse the library will try
to figure out which anime and episode this file represents. The episode number is guessed from the filename by using
some regex. If no episode number is found, adbb will check if the Anime only has a single episode; and if that is the
case it will assume that the file has episode number '1'. The Anime title is guessed from the parent directory if there
is a good-enough match in the animetitles- file, otherwise it's guessed from the filename. For details, check
_guess_anime_ep_from_file() and _guess_epno_from_filename() in the File class in animeobjs.py, and get_titles() in
anames.py.
Functions
The File object has some functions for managing the file in mylist.
update_mylist(state=None, watched=None, source=None, other=None)
remove_from_mylist()
The update_mylist() function can be used both to add and to edit a file in mylist. state can be one of 'unknown',
'on hdd', 'on cd' or 'deleted'. watched can be either True, False or an datetime object describing when it was watched.
Attributes
anime
- Anime object for which anime this file containsepisode
- Episode object for which episode this file containsgroup
- Group object for file authorsmultiep
- List of episode numbers this file contains. The episode number parsing supports multiple episodes, but
fetching from AniDB does not so this is not reliable and I'm not really sure what to do with it...fid
- File ID from AniDBpath
- Full Path to this file (if created with a path)size
- file size in bytesed2khash
- ed2k-hash, because AniDB still uses it...updated
- datetime when information about this file was last fetched from AniDB
The following attributes as returned from the AniDB API
lid
gid
is_deprecated
is_generic
crc_ok
file_version
censored
length_in_seconds
description
aired_date
mylist_state
mylist_filestate
mylist_viewed
mylist_viewdate
mylist_storage
mylist_source
mylist_other
Group
Group(name=None, gid=None)
Group object requires either a name (can be either short or long name) or gid.
A group created with a name is always considered valid, and will be saved to the database even if the name does not
represent a group in AniDB. In that case both the name and the short name will be set to the given name, and all
other atributes will be empty.
Attributes
updated
- datetime when information about this file was last fetched from AniDB
The following attributes as returned from the AniDB API
gid
rating
votes
acount
fcount
name
short
irc_channel
irc_server
url
picname
founded
disbanded
dateflag
last_release
last_activity
Fanart
The Anime object contains an attribute called fanart
that can be used to fetch available fanart for that series/movie from fanart.tv if two conditions are met:
- you must provide an API key either in the
init()
-call using the keyword fanart_api_key
or by providing it in an .netrc-file. - The series/movie must be properly mapped to a tvdb/tmdb/imdb-ID in Anime-Lists
The fanart
attribute just returns a list of metadata from the fanart.tv api; but the adbb.download_fanart()
-method can be used to download the actual fanart. This example downloads the first background fanart the api returned.
The attribute is directly translated from the json API, so for structure description you should check the fanart.tv API reference. Note that it differs slightly between series and movies.
import adbb
api_key='secret'
adbb.init('sqlite:///.adbb.db', netrc_file='.netrc', fanart_api_key=api_key)
anime = adbb.Anime("Kemono no Souja Erin")
fanart = anime.fanart
background_url = fanart[0]["showbackground"][0]["url"]
with open("background.jpg", "wb") as f:
adbb.download_fanart(f, background_url, preview=False)
adbb.close()
netrc
Although you can provide usernames and passwords directly to the init()
call it can be useful to have them stored elsewhere. init()
supports the netrc_file
keyword argument to fetch authentication information from a .netrc-file. The library checks the .netrc
-file for the following credentials:
- anidb username, password and encryption key. The
account
option is used to set the encryption key (machinename must be one of 'api.anidb.net', 'api.anidb.info', 'anidb.net') - database credentials (machinename must match your mysql/postgres hostname)
- fanart API key (machinename must be one of 'fanart.tv', 'assets.fanart.tv', 'webservice.fanart.tv', 'api.fanart.tv'
machine api.anidb.net
username winterbird
password supersecretpassword
account supersecretencryptionkey
machine sql.localdomain
username adbb
password supersecretpassword
machine fanart.tv
account supersecretapikey
Encryption
As per the UDP API specification encrypted network traffic is not enabled by default but must be manually activated by the user.
In the case of adbb, you activate encryption by providing your encryption key when initializeing the library. Either with the api_key
-keyword argument to init()
or by using a .netrc.
You specify the encryption key yourself in your AniDB Profile. It's way past 1990, you really shouldn't send usernames and password unencrypted over the internet.
Utilities
The library contains two command line utilities for mylist management. These are purely implemented after
personal need, but is probably useful for other people as well. The source code could also be consulted for
inspiration to make other tools. For usage, run the command with --help.
adbb_cache
Tool to manage the cache database. At the moment it can only clean the database of unwanted/uneeded stuff, but perhaps importing data to the cache could be supported at some point..
run adbb_cache --help
and adbb_cache <subcommand> --help
for usage. The most useful subcommands ar probably old
to remove stuff that hasn't been touched in a while (90 days by default), and file
which can be used to remove files from the database as well as (with the proper flags) from filesystem and mylist.
This tool does not use the UDP API, except if it's asked to remove files from mylist.
arrange_anime
Tool to identify episode files and move/rename them for easy identification by for media centers.
You should probably run it with --dry-run first to make sure it behaves as expected.
jellyfin_anime_sync
Glueware for AniDB<->jellyfin integration. Requires jellyfin-apiclient-python.
For more information, and usage, for this tool, see JELLYFIN.md.
Upgrading
Object API
I'll do my best to keep the API stable, so if you just use the Objects the code should continue to work with new releases.
Datbase
You should recreate the databse after every release. I haven't figure out how to make sane database migrations on schema changes, so for now you should repopulate the cache when upgrading (just remove the sqlite databasefile or drop and recreate the postgres/mysql database).
Utilities
I'll be restrictive about behavioural changes, and try to document them when they occur, but no promises as of now.
TODO:
In no particular order:
- importing cache from anidb mylist exports.
- add support for descriptions (The only(?) feature missing to create a full-featured media-center scraper).
Unfortunately, episode-descriptions are not supported by the UDP API.
- any other feature request?