A client for AniSkip, the community database of where an anime episode's opening and ending are. 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, not episode 13 of the show).
{: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…"}]Each time says how long the file it was measured on was (length), because
a different cut of the same episode (a BD release with a preview, a TV rip
without the recap) has the opening somewhere else. fit/2 keeps the times
that match the file you are playing and gives you one :intro and one
:credits to offer, and at/3 says which to show at the current position.
skips = AniSkip.fit(times, video_duration)
AniSkip.at(skips, current_time) #=> %{kind: :intro, from: 58.6, to: 148.6, tail: false} | nilNothing here is cached: AniSkip asks to be used gently, so keep each
episode's answer for a day in your own cache (an episode it does not know
comes back as {:ok, []}, and is worth remembering too). Every request
merges :req_options (per call or config :aniskip, :req_options), so the
client can be pointed at Req.Test.
Summary
Types
An opening (:op) or ending (:ed); AniSkip's mixed-op/mixed-ed are read as the same.
A skip to offer: tail is true when it runs to the end of the file (credits).
Functions
The skip to offer at now seconds into the file: the first one now is
inside of, at least :settle seconds in (default 1.5, so a button does not
flash at a boundary) and more than two seconds before its end. nil when
there is none.
The skips to offer for a file total seconds long: the first opening as
:intro and the first ending as :credits, in the order given. A time
measured on a file of another length (more than tolerance seconds apart,
default 5) belongs to another cut and is dropped — unless total is still
short of that length, as it is while a conversion is under way and the file
is growing, when it is kept. With no total (nil or 0) everything fits.
Reads AniSkip's answer (%{"results" => [...]}) into times. A result whose
end is not after its start, or of a type not asked about (a recap), is left
out.
Submit a time for episode episode of MAL entry mal_id: kind is :op
or :ed, from/to in seconds, length the file's length in seconds
(what later readers match against), and submitter an id for the person
submitting — AniSkip wants a UUID, kept stable per user so their
submissions can be rate-limited and weighed together. :provider names
the source (default "custom"). {:ok, skip_id} on success.
The times AniSkip knows for episode episode of MAL entry mal_id:
{:ok, [time]}, {:ok, []} when it knows none (its 404), {:error, _}
when it could not be asked.
Vote a time up or down by its id (from times/3). AniSkip hides times
that are voted down enough. :ok on success.
Types
@type kind() :: :op | :ed
An opening (:op) or ending (:ed); AniSkip's mixed-op/mixed-ed are read as the same.
A skip to offer: tail is true when it runs to the end of the file (credits).
@type time() :: %{ kind: kind(), from: float(), to: float(), length: float() | nil, id: String.t() | nil }
One time AniSkip knows, in seconds: length is the episode length it was
measured on (nil when not recorded), id its skip id (for vote/3).
Functions
The skip to offer at now seconds into the file: the first one now is
inside of, at least :settle seconds in (default 1.5, so a button does not
flash at a boundary) and more than two seconds before its end. nil when
there is none.
The skips to offer for a file total seconds long: the first opening as
:intro and the first ending as :credits, in the order given. A time
measured on a file of another length (more than tolerance seconds apart,
default 5) belongs to another cut and is dropped — unless total is still
short of that length, as it is while a conversion is under way and the file
is growing, when it is kept. With no total (nil or 0) everything fits.
tail marks a skip that runs to within three seconds of the end: credits
with nothing after them, which a player can offer as "next episode".
Reads AniSkip's answer (%{"results" => [...]}) into times. A result whose
end is not after its start, or of a type not asked about (a recap), is left
out.
@spec submit( pos_integer(), pos_integer(), kind(), number(), number(), number(), String.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Submit a time for episode episode of MAL entry mal_id: kind is :op
or :ed, from/to in seconds, length the file's length in seconds
(what later readers match against), and submitter an id for the person
submitting — AniSkip wants a UUID, kept stable per user so their
submissions can be rate-limited and weighed together. :provider names
the source (default "custom"). {:ok, skip_id} on success.
@spec times(pos_integer(), pos_integer(), keyword()) :: {:ok, [time()]} | {:error, term()}
The times AniSkip knows for episode episode of MAL entry mal_id:
{:ok, [time]}, {:ok, []} when it knows none (its 404), {:error, _}
when it could not be asked.
Options:
:types— which to ask for, from:op,:ed,:mixed_op,:mixed_ed(default all four).:episode_length— your file's length in seconds; AniSkip then returns only times measured on a file of about that length. The default0asks for every time, which withfit/2is the better way: you see what is known before deciding.:req_options— merged into the request (Req.Test, timeouts).
Vote a time up or down by its id (from times/3). AniSkip hides times
that are voted down enough. :ok on success.