AniSkip
An Elixir client for AniSkip, the community database of where an anime episode's opening and ending are — what "Skip intro" is made of.
def deps do
[{:aniskip, "~> 0.1"}]
end
Where the opening is
Entries are keyed by MyAnimeList id and the episode's number within that MAL entry (a second season's first episode is episode 1 of the season's own entry).
{:ok, times} = AniSkip.times(5114, 1)
#=> [%{kind: :op, from: 58.6, to: 148.6, length: 1469.6, id: "067c…"},
# %{kind: :ed, from: 1349.9, to: 1474.0, length: 1474.7, id: "473e…"}]
An episode AniSkip does not know is {:ok, []}.
Fitting them to your file
Each time says how long the file it was measured on was. A different cut of the same episode (a Blu-ray with a preview, a TV rip without the recap) has the opening somewhere else, so match the times to the file you are playing:
skips = AniSkip.fit(times, video_duration_seconds)
#=> [%{kind: :intro, from: 58.6, to: 148.6, tail: false},
# %{kind: :credits, from: 1349.9, to: 1474.0, tail: true}]
AniSkip.at(skips, current_time) #=> the skip to offer now, or nil
tail is credits that run to the end of the file — a player can offer
those as "Next episode". A file still being converted (its length short of
the measured one) keeps the times until it is long enough to judge.
Giving back
AniSkip.vote(skip_id, :up)
AniSkip.submit(mal_id, episode, :op, 58.6, 148.6, file_length, submitter_uuid)
Be gentle
Nothing here is cached. AniSkip is a small community service; keep each
episode's answer for a day in your own cache ({:ok, []} too), and put a
budget on lookups.
Errors and testing
Every function returns values and never raises: {:error, :rate_limited},
{:error, {:http, status}} or {:error, {:transport, reason}}. Every
request merges req_options, so point it at Req.Test:
# config/test.exs
config :aniskip, req_options: [plug: {Req.Test, AniSkip}]
License
MIT © Alexander Don