examples: anime library sorter + pure-Lua Anitomy parser
Add a worked nautd scripting example that files each anime episode into a
media-server-friendly library the moment its file finishes verifying:
<SORTED_ROOT>/<Title>/Season NN/<Title> - SNNENN.<ext>
- examples/anitomy.lua: a compact, dependency-free reimplementation of Anitomy
(title/season/episode/release-group/resolution/year) in pure Lua — no
require/io/os, so it embeds in the sandbox.
- examples/anime_sort.lua: on_file_complete hook that parses the filename and
calls naut.move_file(); the embedded parser is a verbatim copy of anitomy.lua.
- examples/test_anitomy.lua, test_anime_sort.lua: parser battery + end-to-end
path-building test with an embedded-vs-module drift guard. Wired into ctest as
example_anitomy / example_anime_sort when a lua interpreter is present.
- docs: examples/README.md plus pointers from README and docs/scripting.md.
Verified end to end against a live nautd: file_complete -> move_file -> on-disk
relocate into the sorted tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2178d6a70c
commit
50a357968a
8 changed files with 837 additions and 1 deletions
88
examples/test_anime_sort.lua
Normal file
88
examples/test_anime_sort.lua
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- Tests anime_sort.lua end to end (stubbing the `naut` host API) and verifies
|
||||
-- its embedded parser still matches examples/anitomy.lua.
|
||||
-- Run: lua examples/test_anime_sort.lua
|
||||
local dir = arg[0]:match("^(.*/)") or "./"
|
||||
package.path = dir .. "?.lua;" .. package.path
|
||||
local ref = require("anitomy")
|
||||
|
||||
-- stub the host API the script calls, and silence its prints
|
||||
local captured
|
||||
_G.naut = { move_file = function(tid, idx, dest) captured = dest end }
|
||||
local realprint = print
|
||||
_G.print = function() end
|
||||
|
||||
dofile(dir .. "anime_sort.lua") -- defines on_file_complete + _G.anime_sort
|
||||
|
||||
_G.print = realprint
|
||||
|
||||
local fails = 0
|
||||
local function fire(path)
|
||||
captured = nil
|
||||
on_file_complete({ torrent_id = 1, index = 0, path = path })
|
||||
return captured
|
||||
end
|
||||
local function expect(path, want_dest)
|
||||
local got = fire(path)
|
||||
if got ~= want_dest then
|
||||
fails = fails + 1
|
||||
realprint("FAIL " .. path)
|
||||
realprint(" got: " .. tostring(got))
|
||||
realprint(" want: " .. tostring(want_dest))
|
||||
else
|
||||
realprint("ok " .. path)
|
||||
end
|
||||
end
|
||||
|
||||
-- episodes
|
||||
expect("/downloads/1/[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv",
|
||||
"/sorted/Boku no Hero Academia/Season 01/Boku no Hero Academia - S01E12.mkv")
|
||||
expect("/downloads/2/[Group] Attack on Titan S04E28 [BD 1080p HEVC].mkv",
|
||||
"/sorted/Attack on Titan/Season 04/Attack on Titan - S04E28.mkv")
|
||||
expect("/dl/[Commie] Steins;Gate 0 - 12 [BD 1080p].mkv",
|
||||
"/sorted/Steins;Gate 0/Season 01/Steins;Gate 0 - S01E12.mkv")
|
||||
expect("Demon.Slayer.Kimetsu.no.Yaiba.S03E11.1080p.mkv",
|
||||
"/sorted/Demon Slayer Kimetsu no Yaiba/Season 03/Demon Slayer Kimetsu no Yaiba - S03E11.mkv")
|
||||
expect("/x/[Erai-raws] Jujutsu Kaisen 2nd Season - 17 [1080p].mkv",
|
||||
"/sorted/Jujutsu Kaisen/Season 02/Jujutsu Kaisen - S02E17.mkv")
|
||||
|
||||
-- movies (no episode)
|
||||
expect("/m/[Group] A Silent Voice [BD 1080p FLAC].mkv",
|
||||
"/sorted/A Silent Voice/A Silent Voice.mkv")
|
||||
expect("/m/Spirited.Away.2001.1080p.BluRay.x264.mkv",
|
||||
"/sorted/Spirited Away/Spirited Away (2001).mkv")
|
||||
|
||||
-- non-video file must be skipped (no move queued)
|
||||
do
|
||||
local got = fire("/d/[Group] Some Show - 01 [1080p].ass")
|
||||
if got ~= nil then
|
||||
fails = fails + 1
|
||||
realprint("FAIL non-video should be skipped, got: " .. tostring(got))
|
||||
else
|
||||
realprint("ok non-video skipped")
|
||||
end
|
||||
end
|
||||
|
||||
-- drift guard: embedded parser must agree with anitomy.lua
|
||||
local embedded = _G.anime_sort.anitomy
|
||||
local drift = 0
|
||||
for _, name in ipairs({
|
||||
"[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv",
|
||||
"[Group] Attack on Titan S04E28 [BD 1080p HEVC].mkv",
|
||||
"One.Piece.S01E1071.1080p.WEB.x264.mkv",
|
||||
"Spirited.Away.2001.1080p.BluRay.x264.mkv",
|
||||
"[Commie] Steins;Gate 0 - 12 [BD 1080p].mkv",
|
||||
}) do
|
||||
local a, b = ref.parse(name), embedded.parse(name)
|
||||
for _, k in ipairs({ "title", "season", "episode", "year", "episode_end" }) do
|
||||
if a[k] ~= b[k] then
|
||||
drift = drift + 1
|
||||
realprint(("DRIFT %s field %s: anitomy=%s embedded=%s")
|
||||
:format(name, k, tostring(a[k]), tostring(b[k])))
|
||||
end
|
||||
end
|
||||
end
|
||||
if drift == 0 then realprint("ok embedded parser matches anitomy.lua")
|
||||
else fails = fails + drift end
|
||||
|
||||
if fails == 0 then realprint("\nALL PASS"); os.exit(0)
|
||||
else realprint("\n" .. fails .. " FAILED"); os.exit(1) end
|
||||
Loading…
Add table
Add a link
Reference in a new issue