108 Commits
Author SHA1 Message Date
foxyz bcc6d4bac8 Release v1.2.0
ci / core (push) Successful in 31s
ci / server (push) Successful in 1m4s
release / linux (push) Successful in 3m44s
ci / updater-windows (push) Successful in 11m38s
release / windows (push) Successful in 5m24s
2026-10-13 01:29:32 +03:00
foxyzandClaude Opus 5.5 4526fe86d9 release.sh: notes via git log -n (head broke the pipe under pipefail past 20 commits)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:29:32 +03:00
foxyzandClaude Opus 5.5 8e82a1f7be server: lock file catches up with core's version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:29:14 +03:00
foxyzandClaude Opus 5.5 4d74cee67d leaf_widgets from Gitea (v0.1.0) instead of a local path, so CI can build
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:29:08 +03:00
foxyzandClaude Opus 5.5 d4952db497 dev_release.sh: --minor / --major start a new dev line, later runs follow it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:21:46 +03:00
foxyzandClaude Opus 5.5 50b2b000a4 Links: yt-dlp's "No video formats found" reads as a sign-in/cookies hint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:18:00 +03:00
foxyzandClaude Opus 5.5 197fe113a1 Jellyfin: a watched episode leaves Continue (resume point cleared); watched at 70 %, or reaching the ending or last 15 % after 40 %
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:11:35 +03:00
foxyzandClaude Opus 5.5 7487b4deb1 Protocol 15: one shared session log on the server, chat never logged, Mark a problem
Every app streams its redacted diagnostic lines (server time, batched
every 10 s, gzip, buffered while offline) to POST /l/; the server merges
them with its own into session-YYYY-MM-DD.log in time order, tagged by
who. Opt-out in Settings > About; Mark a problem (Ctrl+Shift+B, the
player menus, About) writes a marker with an optional note. Copy
diagnostics can include the server's session log. Chat text never
reaches any log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:41:06 +03:00
foxyzandClaude Opus 5.5 4ab37c55cf Sessions leave redacted logs; viewers wait for a member relay's source before opening it
Logs: one redaction pass (query strings to names, member tokens,
passwords, cookies, auth headers) for every line; daily files with size
and age caps in the app (hosted server included) and the dedicated
server (--log-dir); swaps, relay channels, per-member /r /m /j outcomes,
server fetches, mpv load errors, yt-dlp runs. Settings > About: Copy
diagnostics, Open logs folder.

Bilibili through someone else: the viewer opened /r/ before the source
dialled in; the server holds that request and mpv gives up after 5 s
("could not be opened"). Viewers now open a member relay once it is
live, the putter starts its source before its own player, and track
sizes are read in parallel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:05:32 +03:00
foxyzandClaude Opus 5.5 572104f49e Schedule: "waiting · searched N min ago" and "release rejected" (with AnimeFlow's reason) from its new episode fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:55:56 +03:00
foxyzandClaude Opus 5.5 a407d3cbb0 Schedule: watched marks and progress on episodes, "waiting for release" instead of a Request for missing ones, air dates matched as Japan days
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:46:41 +03:00
foxyzandClaude Opus 5.5 1ec4fc8b13 Side-app use: copy/open links and titles everywhere, version on the Settings tab, known links preview instantly, Ctrl+V adds to Up next
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:50:49 +03:00
foxyzandClaude Opus 5.5 9572e8457d uosc timeline ported from uosc (hatching on the uncached part, line marker, remaining time, buffered label, diamonds); mouse wheel sets the volume anywhere on the video, with a volume readout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:50:49 +03:00
foxyzandClaude Opus 5.5 d8585fb78e Protocol 14: a warm relay preloads relayed Up next items for everyone
The server keeps one small relay (32 MiB ahead, 8 behind) for Up next's
first relayed item, keyed by its queue id at a URL that stays the same
after the swap: fetched by the server for server-fetch links, fed by
the adder's app for through-me links and files, by a member with an
account for member-passed Jellyfin items. Every app preloads from it;
the swap promotes it with its buffer. Dropped when the item stops
being first or its adder leaves. Measured switch: ~1.6 s cold, 20-40 ms
preloaded. ROADMAP gains the research item on server-side fetching of
member-resolved URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:53:29 +03:00
foxyzandClaude Opus 5.5 e52a7633a0 A fresh player for every media change (the frozen frame after a swap needed Ctrl+R); promotion and fresh players share one hand-over
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:08:34 +03:00
foxyzandClaude Opus 5.5 c3fe8b682d dev_release.sh: retry the upload up to 3 times
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:30:00 +03:00
foxyzandClaude Opus 5.5 1508d2f804 Jellyfin rows reload after watching: the Stopped report clears the server's cached lists
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:24:38 +03:00
foxyzandClaude Opus 5.5 86eb30f498 Dev publish: new archive before deleting the old, manifest last (a failed upload no longer empties the dev release)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:21:05 +03:00
foxyzandClaude Opus 5.5 ddd7a21195 Protocol 13: Up next preloads, so switching to it is instant
Every app gets the first Up next item ready itself (own Jellyfin plan,
direct links, guests via /j when state.relayNext says the server's
login can pass it on) and opens it in a hidden, muted, paused second
player; the swap promotes that player instead of opening cold, so its
buffer counts at once. Reports go out on open and when the buffer
reaches the gate's need. SwitchTrace times each phase. Measured in the
e2e harness: 1.5-3 s before, 35-103 ms with a preload. Relayed items
still open cold for everyone but their source (ROADMAP).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:15:16 +03:00
foxyzandClaude Opus 5.5 4275abe470 Up next control: the next item's picture and title in the bar, a preview card on hover
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:42:28 +03:00
foxyzandClaude Opus 5.5 cf2805d3bd Menus: one weight, balanced padding, focus shown only for keys, picker text lines up with its button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:36:51 +03:00
foxyzandClaude Opus 5.5 52f1c481a6 Window comes back at its last size, position and maximized state
Saved on resize, move, maximize and close; on start (and hot restart)
the normal size and maximized state come back everywhere, the position
on Windows and X11 when it still lands on a display (Wayland places
windows itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:29:23 +03:00
foxyzandClaude Opus 5.5 771b961b32 Player overlay as one piece, Up next always warm and on every source, Jellyfin never unmarks a watched episode
- Overlay: one layout for both designs (top bar with Up next as one of
  its buttons; one action band above the controls for the status pill,
  skips, next episode and end cards), one glass recipe (AppGlass) and
  one pill (FloatingPill); hovering any of them, or the open queue,
  holds the controls up.
- Up next: the first item (or the first continuation episode) is kept
  resolved on the app that puts it up, refreshed before it expires;
  Schedule and drop/paste can queue too.
- Jellyfin: WatchSession (core) counts real watched coverage; an
  episode is watched at 80 % or at its ending after half; a played one
  is re-marked after Playing (10.11 unmarks on open) and never gets a
  position; a peek leaves the old resume point; part-way resume points
  stay under 85 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:53:42 +03:00
foxyzandClaude Opus 5.5 6b10cae4e6 Protocol 12: Up next (a shared queue) and where the video comes from
Up next: a server-side queue anyone edits by item id (add at end or
next, move, remove, play now, skip); each item says who added it and
how it plays. At the real end the first item swaps in after the end
card's countdown, put up by whoever can (its adder for through-me links
and files, someone with access for Jellyfin, else anyone); items that
can't play are skipped with the reason. The next item's link is
resolved ahead. Greyed "Continues with" episodes below. A chip with the
controls opens it as a popover (leaf_widgets, local path dependency for
now); "Add to queue" next to Put it up everywhere.

Delivery: the server measures the source's rate, the media's bitrate
and each member's read rate; the members card shows a source line
(who passes it on, through what, keeping up or not, source left) and
each member's route and buffer. A slow source is told once per media.
A failed server fetch is announced to everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:40:26 +03:00
foxyzandClaude Opus 5.5 95cd668cfb One app menu style for every picker; links go Through me by default
- AppSelect / AppMenuButton (widgets/app_menu.dart): glass menu anchored
  to its button, check on the current item, keyboard and Esc; replaces
  the Material dropdowns in Settings, player settings, Jellyfin and the
  member menu. Material menus elsewhere get the same surface by theme.
- Link page: Through me is the main choice (you can't know which sites
  need cookies); Everyone / Server fetches it sit under "Other ways".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:58:18 +03:00
foxyzandClaude Opus 5.5 2a7b553597 Pills only with the controls, web links in their final layout at once, chat pop-ups when paused by default
- Next episode (known ending, else the last two minutes) and Skip ED
  show only while the controls are up, like Skip 1:30 (PillGate).
- A web link's name and wide cover slot show from the first state, so
  nothing flips when its thumbnail arrives.
- Chat pop-ups default to Only when paused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:13:50 +03:00
foxyzandClaude Opus 5.5 27b4ec2c96 Swap shows only the new media, no guessed endings, quieter screen
- A swap stops the previous media at once; buffer counts only for the
  media the player holds (client) and the current mediaId (server), so
  the old video can no longer start the new one.
- The automatic frozen-picture reload is gone (false alarms on still
  scenes); Reload video stays.
- The "Next episode" pill shows only from a known ending.
- A web link's thumbnail is its cover, under the video's own name.
- While playing, the unread chip stays off the picture; chat pop-ups
  can be All / Only when paused / Off.
- Server fetch failures say when the server has no cookies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 04:31:14 +03:00
foxyzandClaude Opus 5.5 ab133dd82f Test builds: tool/dev_release.sh builds Linux here and publishes to a dev prerelease that dev builds update from
X.Y.Z-dev.N builds read the dev prerelease's manifest as well as the
stable one and take the newer, so a tester gets each test build through
the normal update and lands back on stable at the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:29:40 +03:00
foxyzandClaude Opus 5.5 1b0a3db439 Player: leave Bilibili's danmaku out of yt-dlp subtitles (it stalled playback)
The danmaku XML is served raw-deflate; ffmpeg fails to inflate it on
every read, logging a warning each time (millions, drained on the UI
thread) while the video waits on the track: a frame every few seconds.
mpv's hook now asks yt-dlp for sub-langs all,-danmaku.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:53:36 +03:00
foxyzandClaude Opus 5.5 6d388483ef CI: run on branch pushes only, not on release tags (release.yml builds those)
ci / core (push) Successful in 30s
ci / server (push) Successful in 57s
ci / updater-windows (push) Successful in 4m46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:00:41 +03:00
foxyz bbecdcc4d8 Release v1.1.2
ci / core (push) Successful in 33s
release / linux (push) Successful in 4m28s
ci / updater-windows (push) Successful in 4m49s
ci / server (push) Successful in 58s
release / windows (push) Successful in 4m30s
2026-10-10 21:45:57 +03:00
foxyzandClaude Opus 5.5 f1ccf88b06 Roadmap: drop the link-token item (what a user pastes is theirs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:45:57 +03:00
foxyzandClaude Opus 5.5 d3be13ecda Errors: refuse links the probe can't play (with Put it up anyway), plain error texts, and others see who can't play
report.error / state.members[].error (optional, no protocol bump): an
errored member is out of the ready gate until its player opens the media
or the media changes; others see "Bob can't play this: ..." and a mark
on the avatar; your own pill offers Retry (reload).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:29:41 +03:00
foxyzandClaude Opus 5.5 1b04e002c2 Player: mpv logs at warn (v only while probing the render mode), 4xx fails at once, load errors show
The session player asked libmpv for every v-level message all session and
media_kit drains them on the UI thread: a retry storm could freeze the
window. ffmpeg reconnects only on 5xx, bounded. A load that fails before
a duration is known reports once ('can't play this: HTTP error 403 ...').

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:12:00 +03:00
foxyzandClaude Opus 5.5 f23cc7b311 Schedule: remove the per-user follow (bookmark, Following filter, AnimeFlow follow calls)
Personal tracking will come from the user's AniList lists; AnimeFlow keeps
downloads, availability and requests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:51:42 +03:00
foxyzandClaude Opus 5.5 473c82ec0a Schedule: AnimeFlow's AniList episode number wins outright (JoJo S6E4 no longer claims next week's ep 4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:42:28 +03:00
foxyzandClaude Opus 5.5 e621b392af Dependencies: Flutter upgrade, file_picker 13, flutter_lints 6
ci / core (push) Successful in 28s
ci / server (push) Successful in 57s
ci / updater-windows (push) Successful in 6m20s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:16:45 +03:00
foxyzandClaude Opus 5.5 884f208330 Schedule: On Jellyfin only when the episode itself is there; Aired · not on Jellyfin yet otherwise
For shows AnimeFlow doesn't track, the app now looks the episode up in the
linked Jellyfin (one /Shows/{id}/Episodes per series, 3 min memo; TVDB
number via anilist_map offsets). No linked account: a neutral Aired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:15:18 +03:00
foxyz cc45b86271 Release v1.1.1
ci / core (push) Successful in 28s
ci / server (push) Successful in 56s
release / windows (push) Successful in 4m33s
release / linux (push) Successful in 3m44s
ci / updater-windows (push) Successful in 4m25s
2026-10-10 01:01:28 +03:00
foxyzandClaude Opus 5.5 84c5cda668 CI (Windows): fetch git dependencies fresh; the host's cached fox_utils clone was stale
ci / core (push) Successful in 42s
ci / server (push) Successful in 1m17s
ci / updater-windows (push) Successful in 4m26s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:41:17 +03:00
foxyzandClaude Opus 5.5 bc96cb13d9 fox_utils 0.1.9: fixes the Windows build (broken since 1.0.1)
ci / core (push) Successful in 28s
ci / updater-windows (push) Failing after 43s
ci / server (push) Successful in 1m10s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:32:29 +03:00
foxyzandClaude Opus 5.5 f7f2f0cddd CI: show the verbose Windows build's error lines and tail
ci / core (push) Successful in 28s
ci / server (push) Successful in 55s
ci / updater-windows (push) Failing after 3m55s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:23:11 +03:00
foxyzandClaude Opus 5.5 05b06b1fc1 CI: capture the verbose Windows build through cmd (PowerShell 'Stop' aborted on stderr)
ci / core (push) Successful in 28s
ci / server (push) Successful in 58s
ci / updater-windows (push) Failing after 4m5s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:13:23 +03:00
foxyzandClaude Opus 5.5 748d58f036 CI: build Windows on every push; a failed Windows build prints the compiler errors
ci / core (push) Successful in 28s
ci / server (push) Successful in 57s
ci / updater-windows (push) Failing after 4m50s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:01:02 +03:00
foxyz 1fd4081461 Release v1.1.0
ci / updater-windows (push) Successful in 32s
ci / core (push) Successful in 27s
release / windows (push) Failing after 20s
ci / server (push) Successful in 54s
release / linux (push) Successful in 3m46s
2026-10-09 23:53:53 +03:00
foxyzandClaude Opus 5.5 2d39018a73 One updater for app and server: updater in core; watch-together-server --upgrade / --check-update; update.sh uses the server
The server's version is stamped like the app's (APP_VERSION) when packaged,
so it can compare against the manifest; --upgrade swaps the whole install
folder (app and server) and asks for a restart. update.sh/.bat no longer
need GTK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:53:52 +03:00
foxyzandClaude Opus 5.5 059b7dd306 Solo: no idle unready, no party corner in fullscreen; a cancelled Invite returns to solo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:39:48 +03:00
foxyzandClaude Opus 5.5 0115d54ab8 Strip shows the joined server's name (its --name via /health), the address until known
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:36:30 +03:00
foxyzandClaude Opus 5.5 fe5d6c3b3f Relay HLS/DASH-only pages as pieces tracks (protocol 11)
A track can be served as pieces: the server writes a VOD playlist whose
entries are its own piece URLs (segments, init maps, AES-128 keys; byte
ranges folded in; yt-dlp's DASH fragments become the same list). A piece
buffer per track fills like the byte buffer. Picks a single file, then a
split byte pair, then pieces. DRM, live, master playlists are refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:35:31 +03:00
foxyzandClaude Opus 5.5 05f9e794a9 Cookies: keep every site of an imported file (no site picker); only the cookies box takes drops on Settings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:26:55 +03:00
foxyzandClaude Opus 5.5 75065f90cd Welcome band only for newcomers, with Don't show again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:13:34 +03:00
foxyzandClaude Opus 5.5 e0db5cc655 Solo watching and a clearer offline start
Putting anything up offline starts a solo session: an in-app server bound
to 127.0.0.1 only, joined like hosting, so tracking, next episode and the
player all work. Invite friends re-hosts on the Host card's port and
password with the same media and position; Stop needs no confirmation.
Offline, the Library opens with a welcome band (Join a friend, Host, Watch
solo); without a Jellyfin account it leads with Files and Link, Jellyfin
is an optional card. Play buttons work offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:08:11 +03:00
foxyzandClaude Opus 5.5 8186fbb5d3 yt-dlp cookies: from a browser or a file (cookies.txt or extension JSON), per site, private; server --yt-dlp-cookies
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:59:10 +03:00
foxyzandClaude Opus 5.5 8b81da4069 Protocol 11: sit out replaces kicking; nudge
Anyone can sit anyone out (host included); only you bring yourself back.
Out members stay in chat and the party, never block or count, and load no
media; a card with the live position and Join back (Space) replaces the
video. The mini player's x sits you out; optionally idle does too. Nudge:
the server forwards it to the target only (3/s, 20/min per sender); the
target hears the nudge sound, gets a toast and a taskbar attention
request. Kick, its messages, errors and dialog are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:05:35 +03:00
foxyzandClaude Opus 5.5 dbfd351ee4 Relay picks the most compact video codec (HEVC, AV1, VP9, then h264)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:44:14 +03:00
foxyzandClaude Opus 5.5 a4be24a2a5 Sounds: three presets (Cosmic, Abyss, Wood & glass), chosen overall or per sound; nudge sound
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:21:43 +03:00
foxyzandClaude Opus 5.5 b038706dff Protocol 11: split-track relay (YouTube video+audio), chunked upstream reads
Relayed media can carry 2-4 byte tracks (video, audio): yt-dlp picks a
split pair when no single-file format exists (h264 first), the source
channel serves each track, the server keeps a buffer per track (budget
split by size, 8 MiB floor) and viewers open an mpv edl:// of the track
URLs. Expired URLs re-resolve once for all tracks. Upstream GETs are cut
into <= http_chunk_size (10 MiB) ranges: YouTube throttles larger ones.
Live streams and DRM are refused; HLS/DASH pieces are a follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:59:41 +03:00
foxyzandClaude Opus 5.5 b2b77a6b3f Hot-restart guard comes from fox_utils 0.1.8 (shared with other media_kit apps)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:58:08 +03:00
foxyzandClaude Opus 5.5 d002ff6941 Linux debug: hot restart no longer crashes with live mpv players
Hot restart frees the old isolate's NativeCallables while mpv handles stay
alive; media_kit then quits them on the next start and mpv calls the dead
wakeup callback (SIGSEGV on the mpv 'core' thread). A debug-only runner
hook clears every live handle's wakeup callback on the engine's
on-pre-engine-restart signal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:53:07 +03:00
foxyzandClaude Opus 5.5 151ec95289 Show the app as Starsync (window titles, launcher entry, About, README); internals unchanged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:47:35 +03:00
foxyzandClaude Opus 5.5 745717a4a0 yt-dlp for page links in every Link mode; app icon and Linux launcher entry
yt-dlp: a managed, checksum-verified, daily-updated yt-dlp in the app and
the dedicated server (--[no-]yt-dlp, --yt-dlp-dir). Pasted links are
probed: direct files stay links, pages are resolved for title, uploader,
duration and thumbnail. Everyone fetches it: mpv's ytdl hook on the
managed copy. I pass it on / the server fetches it: the best single-file
format, re-resolved once when its URL expires. Relay names never split
the /r/ path; viewers of server-fetched pages wait for the relay to go
live. No wire change.

Icon: two stars orbiting a play button (assets/icon/icon.svg, PNGs, the
Windows .ico). GTK app id moe.senkochan.watch_together; release builds
install their .desktop file and icons into ~/.local/share so Wayland
shows the icon; the window icon is set for X11. README shows the icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:21:22 +03:00
foxyzandClaude Opus 5.5 9ddd85de90 Esc steps out one level: chat, fullscreen, big player to mini, then ask to leave
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:41:38 +03:00
foxyzandClaude Opus 5.5 24fc137e4f No buffering rings at the end of short videos; links never guess an AniList show
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:23:43 +03:00
foxyzandClaude Opus 5.5 9a4b04672b Schedule: AniList entries split within a season (anilist_map, per-episode AniList id) count as on Jellyfin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:12:43 +03:00
foxyz ff51d52fbb Release v1.0.1
ci / updater-windows (push) Successful in 47s
ci / core (push) Successful in 27s
ci / server (push) Successful in 45s
release / linux (push) Successful in 3m49s
release / windows (push) Failing after 5m53s
2026-10-09 08:18:29 +03:00
foxyzandClaude Opus 5.5 f2272d7980 Space never presses a focused button; chat toasts open the chat; plain wheel always scrolls the page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:52:48 +03:00
foxyzandClaude Opus 5.5 cf80bb2167 End of video plays out, no false 'loading' near/after the end; bigger status pill avatars; start card fits the mini
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:31:03 +03:00
foxyzandClaude Opus 5.5 8effa573a0 Protocol 10 presence gate, drop/paste anywhere, quick UI fixes
Ready gate: whoever is in the server while it is not playing counts (joining
while playing does not pause anyone); a swap counts everyone present and
keeps ready flags; the quorum rule is gone. Optional 'Unready when the
video changes'. No 'Skip 1:30' guess when the media has chapters.

Drag & drop and Ctrl+V anywhere (fox_utils): files, folders and links get a
small card asking how to open them. Files series start collapsed.

UI: Tab never moves focus and Space is always ready outside text fields;
chat focuses its field; unread badge on uosc and a chip in fullscreen;
people-only chat with an Activity toggle; skip pills show 5 s then with
the controls; volume up to 150%; own avatar same size as the party.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:06:48 +03:00
foxyzandClaude Opus 5.5 5f22be6c20 Roadmap: wishlist placed (ready gate, quick UI, drop/paste, Files, yt-dlp, AniList, ops)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:54:08 +03:00
foxyzandClaude Opus 5.5 740273745b Desktop: depend on fox_utils v0.1.7 (git) for drag & drop and clipboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:32:22 +03:00
foxyzandClaude Opus 5.5 27b4e1dc0c Sounds: full volume by default; everyone ready is the bell alone
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:28:53 +03:00
foxyzandClaude Opus 5.5 fce392e425 Sounds: Japanese-house sound set in the app, layout demo removed
Eleven sounds for events that carry news (ready, unready, everyone ready,
join, leave, kick, new video, others' chat, idle warning, connection
lost/back); none for what the video already says. New voices: koto
(Karplus-Strong, bendable), furin chime, rin bowl, bamboo, hyoshigi,
shoji slide, in the Yo scale on D. Played by SynthSfx at the Settings
volume with per-sound switches and previews. The debug layout mock is
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:23:22 +03:00
foxyzandClaude Opus 5.5 d8fd464e33 Sounds: synthesized sound set and a debug sound board
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:45:14 +03:00
foxyz 0c38528520 Release v1.0.0
ci / core (push) Successful in 25s
ci / updater-windows (push) Successful in 48s
ci / server (push) Successful in 49s
release / linux (push) Successful in 3m34s
release / windows (push) Successful in 4m38s
2026-10-09 01:39:35 +03:00
foxyzandClaude Opus 5.5 b534c8c7ea Player: now-playing title with cover, start banner, one status pill; media art for everyone
The swapper sends the media's poster and banner once (`art`, Jellyfin
images or AniList by parsed name); state carries their hashes and every
member fetches them from /i/ like avatars. state.media.swapBy names who
put the media up. uosc and the standard bar show cover, title and
episode; a banner card shows before playback starts. The top-left health
pill and the waiting pill become one frosted status pill in plain words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:19:34 +03:00
foxyzandClaude Opus 5.5 d092ea371a 1.0 library + player settings: Jellyfin/Link/Files library with details panel, player gear, settings cards, host flag
Library: source switch, selectable cards with a details panel, one merged
Continue row, Latest grouped per series without re-releases, Link page with
previews and recent links, Files embedded. Player: gear popover in both
designs, progress line while controls hide, live hwdec. Settings rebuilt as
cards. State marks the host member (proto stays 9, unreleased); hosting
shows LAN invite addresses; window buttons in fullscreen. fadeThrough keeps
the outgoing view's state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:58:12 +03:00
foxyzandClaude Opus 5.5 99cda7ed04 Schedule: mouse drag scrolls the board sideways only; wheel stays vertical
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:31:58 +03:00
foxyzandClaude Opus 5.5 d29c12e4f0 1.0 shell: one window with a party strip; protocol 9 profile + avatars by hash
The shell (lib/src/shell/) replaces the Home and session screens:
- Party strip: profile, Library / Schedule / Settings tabs, Join and
  Host cards (hosting no longer takes media), status-ring avatar stack
  with the member list, chat & activity panel, Leave.
- The real session player is mounted once and shown full, as a
  draggable corner-snapping mini player, or fullscreen (window_manager,
  party and toasts overlaid); the Video is never mounted twice.
- Profile popover with inline rename, colour and a crop picker (256 px
  PNG), pushed live to the session; avatar toasts; waiting pill.
- Motion kit (fastEaseInToSlowEaseOut, fade-through, popovers,
  mouse-drag/wheel horizontal scrolling).
- Library = the existing picker for now (restyled next round); Schedule
  = the new SchedulePage; Settings = left nav over the existing panels.

Protocol 9 (packages/core, packages/server, sync client):
- {"t":"profile"} renames / changes the picture mid-session ("rename",
  "avatar" feed actions); names are cleaned the same way at hello.
- state.members carry avatarHash; pictures are served once from
  /a/<sid>/<token>/<hash> (PNG/JPEG/WebP/GIF by magic bytes only) and
  cached by hash in the app (AvatarCache).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:23:46 +03:00
foxyzandClaude Opus 5.5 f790470935 Files page: folders, video-only listing grouped by series, lazy mpv thumbnails
Standalone FilesPage (plugged into the shell later): watched folders
(sidebar, chips with remove when narrow), a header saying where you are,
series sections and a 16:9 grid for loose videos, thumbnails requested
only for tiles that stay visible and cancelled when scrolled away,
hidden-files count with examples, rescan, Recent with missing-file
state, play errors in a banner, offline browsing with play disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:13:28 +03:00
foxyzandClaude Opus 5.5 2eb6840ec4 Schedule: "on Jellyfin" for shows AnimeFlow doesn't track, AniList episode numbers
From real use: a show on Jellyfin read "not in AnimeFlow". AnimeFlow's
new GET /library lists every Jellyfin series with AniList ids per TVDB
season; matchSchedule uses it for untracked shows (status inLibrary,
Put it up, no Request) and to fill jellyfinSeriesId. A failing or
missing /library never fails the schedule. Episodes may carry
anilist_episode (split cours, absolute numbering), matched first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:01:28 +03:00
foxyzandClaude Opus 5.5 677ece81c4 Schedule page: day columns on AniList + AnimeFlow, real requests and owner inbox
Standalone SchedulePage (plugged into the shell later): cover-first
cards in day columns from last week's Monday, filters (On Jellyfin /
Following / All airing, niche toggle, compact), requests that really
call AnimeFlow (episode with AnimeFlow's own numbering, series via
AniDB search by AniList id), follow, Put it up resolving the Jellyfin
episode, the requests sheet with the owner's Retry/Decline, refresh
every 60 s while visible, stale-AniList and AnimeFlow-error states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:59:22 +03:00
foxyzandClaude Opus 5.5 caa0765890 Schedule backend: AniList client, AnimeFlow client, schedule matching
lib/src/schedule/ for the 1.0 Schedule tab (UI and docs come with the
schedule round):
- AniListClient: airingSchedules in a window, 50 per page, adult/music
  dropped; memory 10 min + disk 1 h cache, one Retry-After wait on 429,
  stale cache on failure; real User-Agent (AniList 403s some defaults).
- AnimeFlowClient: the full app API (me, schedule, series, follow,
  search, requests incl. approve/reject, users/scopes) with the user's
  Jellyfin token, only for a Jellyfin on AnimeFlow's parent domain;
  ETag/304, typed error codes, backoff on nginx's HTML 429, /me cached
  at most 60 s so scope changes show.
- matchSchedule + ScheduleRepository: AniList airings with AnimeFlow
  status by (episode anilist_id, episode), then absolute number, then
  air day; degrades to AniList-only when AnimeFlow fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:47:33 +03:00
foxyzandClaude Opus 5.5 c76fee70dc Files backend: release-name parser, video-only scanner, folder store, mpv thumbnails
lib/src/files/ for the 1.0 Files source (UI comes with the library
round; ARCHITECTURE gets its section then):
- parseReleaseName: series/season/episode/group/year/extras from release
  names (fansub, scene, batch, NCOP/OVA markers); seriesKey for grouping.
- scanFolder (isolate): videos only, samples/trailers and other files
  counted as hidden, sidecar subtitles paired by name (+ language/flag
  suffixes), 20 000-file cap; groupBySeries -> series + loose videos.
- FolderStore: watched folders and recent files in Hive, as signals.
- thumbnailFor: a 480 px JPEG at 25% via a hidden mpv player
  (screenshot-to-file, vo=null), cached by path+size+mtime, 2 at a time,
  cancellable, never throws. No ffmpeg needed (Windows ships only libmpv).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:46:01 +03:00
foxyzandClaude Opus 5.5 6f136bfc4d Debug-only layout demo of the 1.0 shell (reference for the redesign)
Behind a bug button in Home's top bar, debug builds only: single window
with a party strip, Library (Jellyfin / Link / Files), Schedule on real
AniList data with AnimeFlow statuses (read-only), Settings, profile with
live rename and avatar crop, floating mini player, full and fullscreen
player with a real media_kit player, chat panel and avatar toasts. Not
wired to sessions; the real implementation replaces it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:34:02 +03:00
foxyzandClaude Opus 5.5 e7f33b5dc3 Relay: a member's media through the server's buffer (proto 8)
ci / core (push) Successful in 28s
ci / updater-windows (push) Successful in 49s
ci / server (push) Successful in 49s
Viewers who cannot reach the media get it through the server: a file on
any member's computer, a link passed on by a member ("Through me") or
fetched by the server ("Server fetches it", password-protected servers
only), and Jellyfin items for guests without an account, sourced by
whoever put them up with their own sign-in (or a pasted link's key),
which never leaves their machine. Fixes guests on a dedicated server
without a Jellyfin login being blocked.

The source dials /r/.../source (works behind NAT) and answers pulls with
an adaptive in-flight window (2 x rate x min RTT, 256 KiB..8 MiB). The
server keeps a memory-only buffer of 1 MiB slices sized like a viewer's
mpv buffer (pinned head/tail, 64 MiB back, 256 MiB ahead), fills it with
one sequential pull at a time (blocked viewers first, newest request
only), fetches nothing while nobody watches through it, and drops it on
the next swap or stop. Measured with mpv through shaped links
(tool/relay_e2e.dart): 400 Mbit/s seeks ~0.03 s; 40 Mbit/s no stalls,
3 viewers cost the source ~1x the bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:41:01 +03:00
foxyz 9c5733719b Release v0.2.2
ci / core (push) Successful in 24s
ci / updater-windows (push) Successful in 45s
ci / server (push) Successful in 36s
release / linux (push) Successful in 3m31s
release / windows (push) Successful in 4m49s
2026-10-08 05:30:59 +03:00
foxyzandClaude Opus 5.5 50bcc2e1a2 CI: Windows build + release on yuuka-windows
ci / core (push) Successful in 32s
ci / server (push) Successful in 44s
ci / updater-windows (push) Successful in 1m25s
tool/package_windows.ps1 builds watch-together-<ver>-windows-x64.zip
(same layout as the Linux tarball, libmpv bundled, update.bat and
watch-together-server.exe). The release workflow gets a windows job;
ci runs the updater tests on Windows. Publishing moves to
tool/release_publish.dart (dart: only, no python/bash on Windows), which
tolerates both jobs racing to create the release and replaces an asset
on re-runs. release.sh writes both asset URLs into the manifest.
Windows jobs check out with plain git (the host's cached
actions/checkout clone was corrupt).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:27:38 +03:00
foxyzandClaude Opus 5.5 2c21cfde11 Updater: per-platform manifest assets, Windows apply tested on NTFS
The manifest gains `assets` (linux-x64 / windows-x64 → archive URL);
top-level `url` stays the Linux tarball for 0.2.1 installs. A Windows-
only test applies a bsdtar zip over a running exe (PING.EXE copy) and
checks it is parked as .old and swept next start; Linux-layout tests
(exec bits, symlinks) skip on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:27:38 +03:00
foxyz e6f243d6a6 Release v0.2.1
ci / core (push) Successful in 24s
ci / server (push) Successful in 36s
release / linux (push) Successful in 3m35s
2026-10-07 03:39:00 +03:00
foxyzandClaude Opus 5.5 8845007cbf CI: create build/ before compiling the server; drop artifact upload
upload-artifact@v4 is unsupported on Gitea (GHES error) and failed the
release job after a successful build; the release asset is the
artifact. dart compile exe doesn't create the output directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 03:38:59 +03:00
foxyz 8c71ee21c1 Release v0.2.0
ci / core (push) Successful in 24s
ci / server (push) Failing after 38s
release / linux (push) Failing after 3m27s
2026-10-07 03:15:36 +03:00
foxyzandClaude Opus 5.5 75d8651f17 Release CI: libmpv/epoxy deps, stable Flutter, multipart asset upload
The v0.1.0 release job failed: media_kit_video needs libmpv-dev and
libepoxy-dev at build time. Build on stable (what's verified locally),
work on root-only runner images, upload the asset as Gitea's multipart
attachment, and reuse an existing release on re-runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 03:15:35 +03:00
foxyzandClaude Opus 5.5 165cd2a7f9 Experimental mpv-style (uosc) controls behind a Settings toggle
Thick timeline with hatched cache range, chapter diamonds and
chapter/time hover boxes; collapses to a thin progress line when
hidden. Flat icon row with track count badges, shader preset badge,
flat dark menus. Own show/hide state, live switch in window and
fullscreen. New buffered signal from demuxer-cache-time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 03:10:17 +03:00
foxyzandClaude Opus 5.5 bc98c5c665 Chapters on the scrubber, Skip OP/ED, audio/subtitle menus
- PlaybackController exposes chapters, segments (Jellyfin
  MediaSegments for account holders), tracks, position/duration.
- Own seek bar with chapter diamonds and chapter title on hover,
  seeking through the sync user-seek path.
- Skip OP / Skip ED pills from Intro/Outro segments or chapter names
  (ED skips to its end only); Skip 1:30 fallback for >20 min videos
  without OP info; next-episode pill now starts at the ED.
- PgUp/PgDn chapters, Shift+Right +1:30; Audio and Subtitles menus
  with count badges via the existing track path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:56:41 +03:00
foxyzandClaude Opus 5.5 ae4edc29e8 Jellyfin end to end: per-viewer playback, progress, episodes, swap browser
- Host/swap send refs only; pasted Jellyfin links become refs; the host
  hands the relay its own app-obtained credential.
- Each viewer resolves with their own account (token in a header) or
  via the relay, else a clear blocked card with Link account.
- Per-account progress reporting (Playing/Progress/Stopped), 401 marks
  the account expired.
- Previous/next episode in the controls (< > / Shift+P/N), floating
  Next episode pill near the end, auto-advance countdown (one
  designated swapper), track carry-over per series.
- Swap panel uses the shared MediaPicker; browser location remembered
  per server and opens on the current series.
- Fix: states are applied one at a time (a stale pause could land
  after a newer play).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:38:24 +03:00
foxyzandClaude Opus 5.5 773a04369e Jellyfin in the app: account linking and library browser
Integrations links several Jellyfin servers (Quick Connect or
password, app-obtained tokens only, status/reconnect/sign out). The
Host tab's Jellyfin source is a full browser: continue watching, next
up, latest per library, library grids with sort/filter/infinite
scroll, series pages with smart resume/next-up, details, grouped
search, inspector pane on wide screens, keyboard navigation.
MediaPicker selection is now a sealed PickedMedia. Hosting a Jellyfin
pick is interim (host's stream URL) until per-viewer playback lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:05:46 +03:00
foxyzandClaude Opus 5.5 45f4c1bc84 Jellyfin refs on the wire + guest relay (proto 7)
- Media can be a Jellyfin ref {serverId, itemId, mediaSourceId, title,
  subtitle, runtimeSecs}; the server never stores or broadcasts a URL
  or token for it.
- Optional server-side Jellyfin credential (serve map / --jellyfin
  file) enables GET /j/<sid>/<token>/<itemId>: range-faithful proxy of
  the active item only, exact client header fields, upstream aborted on
  disconnect, bytes counted. welcome.relayUrl / state.relay advertise it.
- Members report jf access (own account vs relay).
- /m now sends the real Content-Type (Relic overrode it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:57:04 +03:00
foxyzandClaude Opus 5.5 fb2927c094 Shaders: Anime4K presets in-session and in Settings
Vendored Anime4K v4.0.1 GLSL (MIT), extracted to app support once per
version. Presets A/B/C/A+A/B+B/C+A in HQ and Fast per Anime4K's mpv
templates, plus custom files. Shader button in the video controls
(works in fullscreen), Ctrl+1..6 / Ctrl+0, live apply with read-back,
failures fall back to Off with a toast. Render mode detected from
mpv's GL log; warns when on the software path. Docs include the
Jellyfin client section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:45:54 +03:00
foxyzandClaude Opus 5.5 4814bfb561 Core: pure-Dart Jellyfin client
Auth (Quick Connect incl. the Initiate 400 fix: Content-Length + full
client header; password), server identity and link parsing, browsing
(views, resume, next up, latest, paged items, search, seasons,
episodes, next episode), images, PlaybackInfo and stream URLs,
progress reporting and user data, and a track matcher for carrying
audio/subtitle choices across episodes. Fixture tests plus env-gated
live tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:42:03 +03:00
foxyzandClaude Opus 5.5 d8d487c189 Sync fixes: clean seeks, clock offset, end of file, 2.5 min read-ahead (proto 6)
- User seeks are intercepted at the player (SyncPlayer), previewed
  locally, sent once 300 ms after the drag ends; own echoes are matched
  by seekSeq and never re-applied; the landed check can't reapply a
  stale target. 1 s position poll removed.
- ping/pong clock offset (min-RTT ClockSync); positions use server time.
- Reports carry duration; buffer gate is min(4 s, remaining - 2 s);
  new finished transport instead of a HELD stall at the end.
- cache/readahead 150 s, 256 MiB demuxer cache, pins read back.
- Resumed reports put a stale ready member back in the set; hello.resume
  takes over the old member after an unclean drop (no ghost).
- Docs: roadmap rendering findings (HW path works; shaders verified).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:20:18 +03:00
foxyzandClaude Opus 5.5 0669ec0fc2 Home v2: top bar with Join/Host tabs, content-first layout
Top bar merges window chrome, profile, tabs and page icons. Join: one
compact row plus recent-server tiles with live /health status and a
needs-update badge. Host: reusable MediaPicker (Jellyfin placeholder,
Link, File) filling the width, server settings in a narrow column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:04:45 +03:00
foxyzandClaude Opus 5.5 81cac1214d Server on an isolate, dedicated server binary, room -> server (proto 5)
- serve(Map config, events) is the one server entrypoint: the app
  hosts it via Isolate.spawn (crash surfaced on the Host card, logs
  forwarded as [server]); bin/server.dart is the dedicated server,
  compiled into the bundle as watch-together-server. --headless gone.
- Host identity via a per-hosting hostKey in hello, replacing the
  loopback heuristic.
- Rename room -> server across code, files and wire (t:"state",
  /health name); protocol bumped to 5.
- CI compiles the server binary; isolate smoke tests in server and
  desktop suites. Docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:55:14 +03:00
foxyzandClaude Opus 5.5 d8a0729029 Docs rewrite: current-state docs, HANDOFF retired
README for the repo page; HOW-IT-WORKS overview; ARCHITECTURE,
PROTOCOL, DEVELOPMENT checked against the code; ROADMAP is backlog
only (isolate/dedicated server, sync fixes, rendering+shaders,
Windows build, Jellyfin epic). HANDOFF.md and mpv-http-contract.md
folded in and deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:42:28 +03:00
foxyzandClaude Opus 5.5 aeff087c7d Desktop: video freeze recovery, session fixes, Home redesign
- Player + VideoController per session; Reload video (Ctrl+R) and a
  frozen-picture watchdog that reloads automatically.
- Shortcuts no longer fire while typing; empty swap disabled;
  hwdec setting/env actually applied.
- Idle unready measured from real inactivity; cancellable reconnects
  and setup; ready restored from server state; kicked/failed host
  stops its server; graceful leave on window close; no URLs with
  keys in display names.
- Welcome + connect screens replaced by one Home screen with Join and
  Host cards, recent servers, Settings, Integrations and About.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:41:02 +03:00
foxyzandClaude Opus 5.5 beae87b0cc Updater: real-layout apply with rollback, in-app update + restart
- Strip the tarball's top-level folder; stage inside the install root
  (same filesystem) and swap with full rollback; keep file modes.
- Semver prerelease ordering (1.2.3-rc1 < 1.2.3).
- UpdateController: background check, download with progress, apply,
  restart; required-update wording for newer protocols; Check now.
- update.sh/update.bat in the bundle; Windows apply path (untested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:41:02 +03:00
foxyzandClaude Opus 5.5 e2301aa4ec Server hardening: finite numbers, open servers, unkickable host, no URL leaks
- Reject non-finite numerics at protocol parse and in the engine
  (a 1e400 seek became Infinity and broke every broadcast).
- Reject empty swaps; sanitize URL-looking media names.
- Blank password = open server; defaults no longer 'change-me'.
- Loopback peers (the in-process host) can't be kicked.
- /health no longer exposes media/transport; file URLs use the
  host the client dialled instead of 127.0.0.1.
- Count served bytes as streamed, not as requested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:30:12 +03:00
foxyz 32848aaf71 Simplify release DX: local manifest, optional hash, slimmer publish
release.sh writes updates/manifest.json at release time (predictable
asset URL) — no CI commit-back, no push-token problem, no detached
checkout dance. release_publish.sh is create-release + upload only.
Manifest sha256 now optional; updater skips verification with a note
when absent (HTTPS + gzip CRC cover the realistic cases). Fixed the
repo-URL sed keeping a .git suffix (caught by a worktree dry run).
Updater tests: 8 (sha-less manifest flow added).
2026-09-13 03:03:48 +03:00
foxyz afb4a9e051 release.sh: annotated tags so --follow-tags actually sends them
Lightweight tags are silently skipped by --follow-tags; annotated
ones ride along. The v0.1.0 tag predates this and still needs an
explicit push.
2026-09-12 21:36:22 +03:00
328 changed files with 78526 additions and 4623 deletions

No files matched your search

+37
View File
@@ -1,6 +1,9 @@
name: ci name: ci
on: on:
# Branches only: a release tag points at a commit CI already checked on
# main, and release.yml builds it.
push: push:
branches: ['**']
pull_request: pull_request:
jobs: jobs:
@@ -31,3 +34,37 @@ jobs:
working-directory: packages/server working-directory: packages/server
- run: dart test - run: dart test
working-directory: packages/server working-directory: packages/server
# Compiling the dedicated server proves it stays Flutter-free.
- run: mkdir -p build && dart compile exe bin/server.dart -o build/watch-together-server
working-directory: packages/server
- run: build/watch-together-server --help
working-directory: packages/server
# The updater's Windows apply path (running exe parked as .old) only
# means something on real NTFS. Runner notes: see release.yml.
updater-windows:
runs-on: windows-latest
steps:
- name: Checkout
shell: powershell
run: |
git init -q .
git remote add origin "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.git"
git fetch -q origin "$env:GITHUB_SHA"
if ($LASTEXITCODE) { exit 1 }
git checkout -q --force FETCH_HEAD
- name: Setup Flutter
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tool/setup_flutter_windows.ps1
- name: Updater tests
shell: powershell
working-directory: packages/core
run: |
dart pub get
if ($LASTEXITCODE) { exit 1 }
dart test test/updater_test.dart
# The release build, without publishing: a broken Windows build shows
# here, not only on a tag.
- name: Windows build
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tool/package_windows.ps1
+48 -14
View File
@@ -14,30 +14,64 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Install Linux build deps - name: Install Linux build deps
# media_kit_video links libmpv + epoxy via pkg-config. Runner images
# differ: some run as root without sudo.
run: | run: |
if command -v apt-get >/dev/null; then SUDO=sudo; [ "$(id -u)" = 0 ] && SUDO=
sudo apt-get update $SUDO apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev $SUDO apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev \
fi liblzma-dev g++ libmpv-dev libepoxy-dev xz-utils unzip curl python3
- name: Setup Flutter (beta) - name: Setup Flutter (stable)
run: | run: |
git clone https://github.com/flutter/flutter.git -b beta --depth 1 ~/flutter git clone https://github.com/flutter/flutter.git -b stable --depth 1 ~/flutter
echo "$HOME/flutter/bin" >> $GITHUB_PATH echo "$HOME/flutter/bin" >> $GITHUB_PATH
- name: Build tarball - name: Build tarball
run: tool/package_linux.sh run: tool/package_linux.sh
- name: Upload tarball artifact - name: Publish release
uses: actions/upload-artifact@v4
with:
name: linux-tarball
path: dist/*.tar.gz
- name: Publish release + manifest
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: tool/release_publish.sh "${{ github.ref_name }}" run: |
VER="${GITHUB_REF_NAME#v}"
dart tool/release_publish.dart "$GITHUB_REF_NAME" "dist/watch-together-$VER-linux-x64.tar.gz"
windows:
# yuuka-windows: host-mode runner (no containers), runs as SYSTEM, has
# git + VS 2022 Build Tools but no bash/python — PowerShell and Dart
# only. Checkout is plain git: that host's cached actions/checkout
# clone was corrupt, and no `uses:` means no action cache to rot.
runs-on: windows-latest
steps:
- name: Checkout
shell: powershell
run: |
git init -q .
git remote add origin "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.git"
git fetch -q --tags origin "$env:GITHUB_SHA"
if ($LASTEXITCODE) { exit 1 }
git checkout -q --force FETCH_HEAD
- name: Setup Flutter
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tool/setup_flutter_windows.ps1
- name: Build zip
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tool/package_windows.ps1
- name: Publish release
if: startsWith(github.ref, 'refs/tags/v')
shell: powershell
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
$Ver = $env:GITHUB_REF_NAME.TrimStart('v')
dart tool/release_publish.dart $env:GITHUB_REF_NAME "dist/watch-together-$Ver-windows-x64.zip"
if ($LASTEXITCODE) { exit 1 }
+1
View File
@@ -17,6 +17,7 @@ coverage/
# Build artifacts that should never be committed # Build artifacts that should never be committed
*.tar.gz *.tar.gz
*.zip *.zip
/dist/
app.*.symbols app.*.symbols
app.*.map.json app.*.map.json
+31 -26
View File
@@ -1,35 +1,40 @@
# AGENTS.md — sync_relay_project # AGENTS.md
Project conventions for agents. Global interaction rules live in the Repo-specific rules. Global interaction rules live in the user-level
user-level AGENTS.md; this file holds repo-specific facts only. AGENTS.md. Source of truth: the code, then the docs below.
Source of truth order: codebase > `HANDOFF.md` + `docs/ROADMAP.md` >
chat history. ## Docs
- [README.md](README.md): users. [docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md):
plain tour. [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): packages, sync,
media, updater, decisions. [docs/PROTOCOL.md](docs/PROTOCOL.md): wire format.
[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): build, test, release, gotchas.
[docs/ROADMAP.md](docs/ROADMAP.md): backlog and epics.
- Docs describe current truth only: no dates, no "fixed" notes, no phase
logs (git has history, plans go in ROADMAP).
- Each fact lives in exactly one file; others link to it.
- Code and its docs change in the same commit. A wire change also bumps
`RelayConstants.protoVersion` and edits PROTOCOL.md.
- Say "server" (there is only one), never "room", in UI, code and docs.
## Wishlist drafts ## Wishlist drafts
The user drops raw idea/bug lists as drafts (gitignored, never The user drops raw idea/bug lists as drafts (gitignored, never committed:
committed: `docs/Wishlist.md`, `docs/drafts/`). Agent duty on sight: `docs/Wishlist.md`, `docs/drafts/`). On sight, translate each entry into its
translate each entry into its proper place — `docs/ROADMAP.md` place in `docs/ROADMAP.md` (ordered backlog item or epic; keep repro detail
backlog (ordered, one sitting-sized item each) or Epics, real bugs for real bugs), confirm placement with the user, then delete the draft.
with repro detail preserved — then confirm placement with the user Drafts must never be committed, even partially.
and delete the draft. Drafts must never be committed, even partially.
## Commits ## Commits
Few commits, one per topic/session — never per fix. Rewriting Few commits, one per topic/session, never per fix. Rewriting unpushed
unpushed history (amend, squash) is expected, not exceptional. history (amend, squash) is normal. Commit code + its docs together. No
Commit code + its docs together. No `flutter analyze` unless asked `flutter analyze` unless asked or diagnosing; scoped `dart analyze` on touched
or diagnosing; scoped `dart analyze` on touched files is the files is the pre-commit check. Run tests only when the code under test changed.
pre-commit check. Tests run only when the code under test changed.
## Releases (user pushes tags, CI does the rest) ## Releases
Release flow: `tool/release.sh [patch|minor|major]` derives the `tool/release.sh [patch|minor|major]` then `git push --follow-tags`; CI
version (no typing), bumps all pubspecs, commits, tags → review → publishes. Details: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). Never commit
`git push --follow-tags` → `.gitea/workflows/release.yml` secrets: tokens come from env (`DISCORD_WEBHOOK_URL`, `RELEASE_TOKEN`) or
builds, publishes the Forgejo release, commits Actions (`GITHUB_TOKEN`/`GITEA_TOKEN`).
`updates/manifest.json` back. `tool/release_publish.sh` is the same
flow by hand. Workflows live in `.gitea/workflows` (this forge reads
`.gitea` first, `.github` is ignored when it exists).
Never commit secrets: tokens arrive via env (`DISCORD_WEBHOOK_URL`,
`RELEASE_TOKEN`) or Actions-provided (`GITHUB_TOKEN/GITEA_TOKEN`).
-321
View File
@@ -1,321 +0,0 @@
# HANDOFF — sync_relay_project (watch-together app)
Date: 2026-09-12 (updated late session). Phases 0–4 + presence hardening
+ UI redesigns done, all green (core 42, server 41, desktop 24).
Phases retired as a tracker 09-12 — `docs/ROADMAP.md` is now a flat
backlog (Built / Backlog / Epics / Deferred); phase text lives in
git history.
Black screen (§8) DIAGNOSED + FIXED (avatar-texture teardown race;
Visibility keep-mounted) + VERIFIED, incl. over internet. Bisect
screens (legacy/minimal/nosheet) REMOVED 09-12 — app.dart routes
session straight to SessionScreen. Internet test PASSED (WAN port
6969, 09-12); user reports 2-client LAN soak + 1.4 GB real-file
test also done 09-12 (outcomes not yet recorded below). Tonight's
fixes: server logging wired into app (`initLogging` + hello logs),
Cancel aborts pending connect, first-join failure no longer
retry-loops. Git repo INITIALIZED (commit 427d851, root
.gitignore). Prior chat may be unreliable; this file plus `docs/`
is the full state (`docs/ROADMAP.md` is now the phase tracker).
## 1. What this is
Private watch-together desktop app (Windows + Linux). Host serves a file
or shared link from their own machine; 4–8 friends watch in sync.
One binary, three modes: Client (Join), Server+Client (Host, opens
ports, self-plays via loopback), `--headless` (server only, no window).
North star: <1.5 s drift, seeks settle ~2 s, cache rides short drops,
kick kills sync+video, no relay memory growth.
## 2. Locked architecture (do not reopen without the user)
- `packages/core`: protocol, sync engine, media abstractions. ZERO Flutter
imports (enforced by test). `packages/server`: Relic-based room + media
server, also zero Flutter imports. `apps/desktop`: the ONE Flutter
binary embedding both. Direct-serve model: host opens ports, clients
open nothing. NO host-upload tunnel (the brief's WSS chunk protocol is
obsolete unless a VPS tier ever needs remote-host upload).
- Server HTTP+WS: **Relic ^1.2.0**. Client WS: **web_socket_channel**.
Server lib also uses **web_socket** for event types.
- PACKAGES ARE NOT LOCKED (user decision 09-11, overrides the old brief
list): pick on merit, no churn where the stack works. Current desktop
additions: media_kit_video + media_kit_libs_video (required),
file_picker (avatar/media picking), hive_ce + hive_ce_flutter
(connect-form persistence), image (avatar shrink). signals + media_kit
+ Relic stay (load-bearing).
- Sync: heavily Syncplay-inspired + original readiness design, NOT 1:1.
Reference clone (scratch, may be gone — re-clone shallow):
`git clone --depth 1 https://github.com/Syncplay/syncplay.git /tmp/opencode/syncplay-ref`
Audit already done; conscious deviations: clock never moves from
reports (theirs follows slowest watcher), no pause-on-leave,
force-for-all instead of operator-only setOthersReadiness, and
QUORUM gate (09-12, user found solo-play meaningless): HELD→PLAYING
needs in-set size ≥ 2 unless ≤1 member is fresh — lone watcher plays
alone, second watcher present ⇒ one Space waits. Late joiners outside
the set never block.
- Single room password (one secret, persisted in Hive on the user's own
box — accepted). No op roles (deferred: op flag + second hello
secret). Kick = transient nudge: revoke + close + `kicked` reason,
instant rejoin with fresh token.
- Deferred, do not build: TLS/Caddy, Jellyfin native, updater
expansion (v1 `--upgrade` SHIPPED 09-12 — see §9.12; in-app
prompt + Windows helper still deferred), playlist (extension shape reserved in PROTOCOL.md), op roles, sounds
(researched, wired, no assets — see §7).
- Standing rules: health reporting (every failure displays, nothing
stalls silently); proto bump + PROTOCOL.md same-commit on ANY
wire change (still v4 — quorum/timeouts are server policy, no bump).
## 3. Repo map (root: Flutter/sync_relay_project/)
- `packages/core/lib/src/`: constants.dart (tuning: seek 300 ms,
drift 5 s, slow 1.5 s @0.95, min_buffer 4 s, heartbeat 1 s, ping 5 s,
avatar 256 KiB (base64 chars; wide headroom over any 128x128 PNG
— raised 09-12 from 64 KiB, which rejected normal PNGs), name 32, chat 500; dropAfter lives as RoomEngine ctor
default, now 30 s), errors.dart, logging.dart, config.dart,
protocol.dart (v4 schemas), media_source.dart, sync_engine.dart
(RoomEngine + quorum in evaluate's HELD branch; PLAYING branch
unchanged).
- `packages/server/lib/src/`: session.dart, media_endpoint.dart,
room_hub.dart (forgetSocket, sweepClosedSockets via `isClosed` poll,
closeAllSockets, onError→forgetSocket), room_server.dart (1 Hz
heartbeat: sweepStale + sweepClosedSockets + dropSilentSockets +
broadcast; stop() closes client sockets first). `bin/server.dart`.
- `apps/desktop/lib/src/`: sync/ (sync_client.dart: hello/intents/
fan-out/match-by-`t`; room_diff.dart: diff + debounce + play gate),
playback/ (media_kit Player, 64 MB cache, pin props, 0.5 s seek
deadband, stop() on leave, SYNC_RELAY_VO/HWDEC env overrides),
session/ (session_coordinator.dart: THE brain — room→player,
reports, scrub/nudge→seek intents incl. HELD, seek-land retry,
idle 8/10 min, reconnect loop, 5 s silence timeout; health.dart),
sfx/ (SfxKind/Cue/Service + NoopSfxService — sounds deferred),
state/ (session_state.dart: route/mode/lastForm/prefs/host/join/
leave; connect_prefs.dart), screens/ (welcome/connect/session),
widgets/ (user_section.dart: ConnectProfileCard; member_avatar.dart:
THE avatar — memoized, no bg on images, dark ring+fallback;
dialogs.dart: kick confirm; media_input.dart: parseMediaInput).
- `apps/desktop/test/`: room_diff_test (9), media_input_test (6),
updater_test (7: version/manifest/layout + stub-server flows),
sync_playback_e2e_test (quorum: solo Space holds, both ready plays;
needs mpv + ~/Videos/chiyo.mp4), coord_presence_test (dead server
clears + reconnects; needs mpv).
- `tool/`: range_logger, shaper (chaos: KB/s cap, latency,
--kill-after), fake_viewer, e2e_check (sim timeline, still ALL PASS
post-quorum), package_linux.sh (release tarball + Discord card,
stamps APP_VERSION).
- `docs/`: PROTOCOL.md (v4 + quorum + 15/30 s timeouts + isClosed note,
normative), ARCHITECTURE.md (+health rule), mpv-http-contract.md.
- `deploy/`: systemd unit. Git repo initialized 09-12 (`git init`,
root `.gitignore`, initial commit 427d851 covering phases 0–4 +
tonight's connect/logging fixes). Bisection is possible again.
## 4. Protocol v4 + policy (see PROTOCOL.md for normative detail)
hello→welcome(token, memberId, mediaUrl?, room-state); intents:
ready{ready,source}/force{target:all|memberId,ready}/seek/seek-swap/
report{position,buffered,eventId}/chat/kick{memberId}. Broadcasts:
room @1 Hz + on change (diff by eventId), feed{actor,action,…}
(toasts/log/sounds), kicked{reason}, err. Media:
GET /m/<sid>/<token>/<name> (200/206×3 shapes/416/401/403/410/503).
Transport PLAYING/HELD; gate = non-empty set AND all in-set ready +
buffered≥4 s + fresh AND quorum (in-set≥2 unless ≤1 fresh).
Stale >15 s ⇒ out of gate; silent >30 s ⇒ closed + removed.
Closed sockets swept every second; socket errors remove immediately.
Client: 5 s room-silence ⇒ dead (clear room, warn, reconnect loop
1.5 s polite infinite + manual cancel/retry, prior-flag restore).
## 5. Phase log (all exits met, all green as stated above)
- P0 scaffold + CI + Flutter-free tests; binary binds/logs/exits.
- P1 Range table byte-exact, real mpv traces, shaper built. 503 tested.
- P2 sessions: secret→128-bit token, 401/403/410 gates, kick kills WS+
media, byte counters, fake_viewer ALL PASS live, proto→3.
- P3 engine + readiness: engine tests (fake clock), e2e 15/15 sim,
Syncplay audit. Sanity: NaN guards, caps, stale/leave feeds,
1 Hz flush, token redaction.
- P4 desktop shell→app: SyncClient framing, RoomDiff (eventId diff,
slowdown latch 0.95×/0.1 reset, seek-land check), media_kit wiring
(pinned buffer props, buffered from demuxer), Space=ready, avatars,
toasts+sheet, health display, reconnect loop. Sounds WIRED ONLY
(NoopSfxService) — see §7.
- P5 presence+sync hardening (user-reported, all reproduced live
first): stop() closes sockets; isClosed sweep (~1 s graceful);
onError forget (~2–4 s kill -9, was 56 s ghost); drop 60→30 s;
client silence-timeout + stuck-reconnect-flag fix + ready-future
leak fix; seek race fixed (2 s same-event debounce both
transports, edge-aware detector 1 s guard, 0.5 s apply deadband);
HELD seeks send intents (paused frame moves for all); arrows
nudge ∓10 s; local play gate (in-set+ready); autoplay off;
leave() stops player (was leaking audio); kick confirm; Cancel
connect; connect-form memory→Hive persistence; file/media
Browse buttons.
- P6 UI redesigns (two subagents, disjoint files — repeat this
pattern): full-bleed video + overlays, controls never autohide
(24 h hover) + Ready/Mark-everyone-ready IN the controls bar
(intents only, zero local transport buttons), center avatars-only
64 px, slide-in toasts, animated right sheet (users/log/chat/swap/
Leave w/ confirm), health pill only-when-unwell, ESC closes/
asks-leave, global focus-proof keys (Space/Tab/arrows/ESC/F,
350 ms throttle, ignored while typing; Tab traversal disabled on
the session screen 09-12 — game-style, Tab toggles the sheet), single media field
(parseMediaInput + tests), avatar pipeline (128 px PNG, keeps
transparency — JPEG did not; self-check measures encoded base64
against RelayConstants, single source of truth), tap to
pick, no path fields), Discord-like corner profile (connect only;
welcome is title+Host/Join; edits apply next join — no rename
intent on the wire), dialogs for terminal states.
- UX decisions locked along the way: Space flips self-ready only;
free seeks any transport (no flag churn); join catch-up never
halts; idle 8-min warn + 10-min auto-unready; reconnect = re-hello
with flag restore; kick → dialog + connect screen; fixed 1.5 s
reconnect + cancel/retry; GMod toasts (social only) + side sheet;
avatar fallback = initial circle; "waiting for X (n/m ready)".
- Open user questions for later: warning-tone timing; bar width at
minimum window; second-ESC on leave dialog (re-asks vs cancel);
cancel-stops-host-server (kept: idle means no bound port).
## 6. Gotchas (learned the hard way)
1. Relic gives Range as TYPED RangeHeader — convert via unit+first.
2. Relic server-side onDone DOES NOT fire for dead peers (proven
live both directions: clean close AND kill -9 both ghosted to
timeout). Presence = isClosed heartbeat sweep + socket-onError
forget + lastSeen timeouts. NEVER depend on onDone. (Docs pattern
confirms CloseReceived is a data event.)
3. Welcome races room broadcasts — match WS messages by `t`, never
by index.
4. web_socket_channel streams are single-subscription — one listener
per socket (test double-listens throw "already been listened to").
5. `sub.asFuture()` can resolve before `onDone` — poll with
deadlines in tests.
6. Comma-form mpv reconnect flags work here; colon-form rejected
(re-verify via set_property if touched).
7. Numeric literals corrupted once by tooling — verify with rg after
writes touching ports/consts.
8. Tool `bash` `&`-backgrounding is unreliable; use nohup + log file
in its OWN call, poll in later calls, kill via `fuser -k
PORT/tcp` (never `pkill -f`). Chain small calls, never long
`&`-chains (they hang the runner past timeout with zero output).
9. Tests must not assume threat model beyond trust-basis.
10. web_socket_channel 3.x: `channel.ready` errors unhandled on
refused connections — eat it (`unawaited(ch.ready.then(...,
onError: ...))`); stream onError is the reporting path.
11. MediaKit has NO public Player.setProperty — runtime props go via
guarded dynamic call on player.platform; cache size via public
PlayerConfiguration(bufferSize:).
12. NEVER `flutter test`-pump the session screen: media_kit Video
hangs the runner (a hung flutter_tester must be kill -9'd by PID;
pgrep carefully). Sync-path coverage lives in room_diff (pure),
lifecycle (server, raw WS), coord_presence + e2e (real mpv,
no widgets). Deleted session_overlays_test.dart for this reason.
13. No `flutter analyze` unless asked (IDE covers it) — except
deliberate per-change checks; kept clean throughout.
14. New `MediaDesktopVideoControlsThemeData` per build is fine
(stock guards subscriptions) but wasteful; leave it.
15. Env overrides for mpv experiments (no rebuild to switch):
SYNC_RELAY_VO (e.g. xv, gpu, gpu-next), SYNC_RELAY_HWDEC (e.g. no).
16. Hidden-but-mounted overlays keep focus: closing the sheet left
focus on an offstage button, so Space fell through (350 ms
throttle hole) into the hidden control. Fix pattern: ExcludeFocus
on the closed subtree + unfocus-on-close effect. Same reason new
overlay buttons reuse the shared no-focus node.
## 7. Sounds (deferred, wiring ready)
Base: Kenney Interface Sounds (CC0, confirmation_* ladder for
ascending-ready). Alt ladder: ObsydianX Pack 1 (CC0, avoid paid
packs). Soft layer: akx/Notifications (CC0 branch). Playback pick:
flutter_soloud (preload WAV, per-play pitch) — NOT added. Swap-in =
replace NoopSfxService; cues already fire with readyIndex. URLs +
mapping in 09-11 chat (unreliable — re-derive from sources above).
## 8. Black window while playing (09-12, FIXED)
Symptom was: room PLAYING ⇒ entire window black (audio + keys
alive, unready restored, zero errors). Cause: the waiting-avatars
center overlay unmounted its GPU textures (avatar Images) exactly
at the HELD→PLAYING flip; teardown racing mpv's frame uploads on
the shared GL context fails silently ⇒ black. Legacy chips never
unmounted and minimal never mounted, which is why they survived.
Fix: center overlay is Visibility(visible + maintainState +
maintainAnimation + maintainSize) — avatars stay mounted, paint
skipped when hidden. Verified by user with full tree, and holds
over internet (WAN 2-client test, port 6969, late 09-12) — CLOSED.
RULE: never unmount GPU-textured widgets on a transport-gated
branch; keep mounted + skip paint. Bisect screens
(legacy/minimal/nosheet + SYNC_RELAY_UI switch in app.dart)
DELETED 09-12 (commit tracks this); no commented bisect lines
remain in session_screen.dart.
## 9. Next (ordered, updated 09-12 late session)
1. §8 black screen — CLOSED (fix verified incl. over internet).
2. Remove bisect flags — DONE 09-12 (files deleted, app.dart
routes session directly; tree analyzes clean).
3. 2-client LAN soak — user reports DONE 09-12 (details pending).
4. 1.4 GB real-file media test — user reports DONE 09-12
(details pending; api_key must never land in files).
5.–8. MIGRATED 09-12 to `docs/ROADMAP.md` backlog (cache-exhaustion,
sounds, file-join wart, /health) — the roadmap is now the single
backlog; this section keeps session history + user-owned pendings
only.
9. FIXED 09-12: auto-unready fired while passively watching —
the 10-min check keyed off `_lastInput`, refreshed only by
explicit actions. Steady playback now counts as activity:
warn + auto-unready are skipped while room transport is
PLAYING (`session_coordinator.dart` idle block). HELD-and-idle
still unreadies after 10 min.
10. FIXED 09-12 (second attempt — first theory was wrong): one chat
send rendered as TWO log rows. Root cause is server dual
delivery, not double-send: `broadcastChat`
(`room_hub.dart:69`) emits both a `ChatMessage` (with text,
converted to a feed entry client-side) and a textless `feed`
entry, and the client logged both. The blank-guard in
`sendChat` was defense against a phantom. Real fix: client
drops textless `chat` feed entries (`sync_client.dart` feed
case) — one send, one row, one toast, one sfx. No wire
change, still v4. Chat field also keeps focus after send
(dedicated FocusNode, re-requested on submit/button).
11. KNOWN (non-breaking, fix later — roadmap backlog #2): seeks feel
jumpy (forward-back-forward) — suspect the seek-land retry
(`_checkSeekLanded`, `session_coordinator.dart:429`)
re-applying SeekAction against the 0.5 s deadband / 2 s
debounce and the 1 Hz room echo. Needs a live repro with a
timeline log before touching.
12. Updater v1 SHIPPED 09-12: `watch-together --upgrade`
(`apps/desktop/lib/src/update/updater.dart`, zero Flutter
imports, 7 tests) — manifest check, download, sha256, apply
over live install (extract-temp + delete-first move-over;
ETXTBSY forbids writing the running exe, delete+rename is
fine). Version stamped via `--dart-define=APP_VERSION`
(wired in `tool/package_linux.sh`; dev runs report
`0.0.0-dev`). Publishing is CI-driven: tag `vX.Y.Z` →
`.gitea/workflows/release.yml` builds, creates the Forgejo
release, uploads the tarball, commits the manifest back
(`tool/release_publish.sh` does the same by hand).
Manual trigger only: bump desktop pubspec in a release commit
first (script enforces tag == pubspec). No silent
proto jumps: updater warns when manifest protocol != local.
Environment: dart 3.14 beta, flutter 3.48 beta, mpv+ffmpeg present,
~/Videos/chiyo.mp4 sample. Never kill the user's Syncplay mpv
instance. Git repo at project root (init 09-12); commit code + docs
together; prefer FEW commits (one per topic/session, not per fix)
over bisect-granularity — user decision 09-12.
## 10. Window chrome — frameless, mpv-style (09-12, VERIFIED by user)
Native title bar gone everywhere (`window_manager ^0.5.2`,
`TitleBarStyle.hidden` in main.dart GUI path only, headless
untouched). New screens MUST bring their own drag + min/max/close
or they ship immovable — reuse `widgets/window_buttons.dart` +
`widgets/window_chrome_bar.dart` (welcome/connect pattern).
Session: drag is `widgets/window_drag_layer.dart`, topmost raw-
pointer observer inside the controls stack (a GestureDetector
below media_kit's overlay LOSES the arena — do not revert to
that); dead zones: bottom 120 px (seek stays exact), open sheet,
leave dialog. `modifyVolumeOnScroll: false` is load-bearing for
drag — side effect: volume is speaker-button-only, no scroll-
volume. Maximize button = media_kit fullscreen toggle in
session (no OS maximize there); NO aspect-ratio lock by user
decision (custom UI lives outside the video frame). Close =
graceful leave + stopServer, then window close. Min size
160x90, no max. Verified on KDE Wayland (Arch): bar-drag,
video-drag, fullscreen, free resize all good.
+480 -19
View File
@@ -1,28 +1,489 @@
# sync_relay_project <p align="center">
<img src="apps/desktop/assets/icon/icon_256.png" width="128" height="128" alt="Starsync icon">
</p>
Watch-together desktop app (working title). One binary, three modes: <h1 align="center">Starsync</h1>
- **Client** — GUI Join, sync WS + mpv. No server code runs. Watch a video with friends, in sync, with the quality of a real mpv player.
- **Server+Client (Host)** — GUI Host starts `packages/server` in-process, Pick from your Jellyfin library, paste any direct video link, or pick a file on your own disk;
listens on a port, self-connects via loopback. Host must open ports. everyone's play, pause and seek stay together.
Clients never open ports.
- **Headless** — `watch.exe --headless`, same server package, no window.
v1 order: links support (shared http(s) media URL, Syncplay-style sync) Why not Jellyfin SyncPlay: it runs in the browser and its sync is poor.
first, file relay (server-proxied bytes, whitelist case) after. This is a desktop app that drives mpv.
Jellyfin native + auto-update are deferred as their own undertakings.
Layout: Name: `watch-together` is a placeholder and the final name is undecided
(`sync-relay` is the working name). It appears in file names and the
update manifest until that is settled.
- Desktop only (Linux and Windows), always mpv. No web clients.
- No transcoding. Everyone plays the original file.
- Friends trust model: no accounts, no roles. A blank password means an open server.
How it fits together: [docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md).
## Install and update
From the Releases page of this repo:
- Linux: `watch-together-<version>-linux-x64.tar.gz`. Extract, run
`watch_together`. On start it adds itself to your app launcher (and its
icon), so the window shows its own icon on Wayland. Needs mpv's library installed (`mpv` or `libmpv2`).
- Windows: `watch-together-<version>-windows-x64.zip`. Extract, run
`watch_together.exe`. mpv is bundled. New and not yet tried on a real
Windows desktop; reports welcome.
Update from the app: when a newer version exists, an update indicator
appears in the strip at the top of the window and opens the update card. "Update and restart" downloads,
verifies, applies in place and restarts; the install folder must be writable
by you. Settings has Check now and an auto-check toggle. From a terminal:
`./watch-together-server --upgrade` (no display needed, so it works on a
server box too), `watch_together --upgrade`, or the shipped `update.sh` /
`update.bat` (which run the first). Each updates the whole folder, app and
server; restart whichever is running to use the new version. A
release with a newer protocol is a required update: everyone on a server
must run the same protocol version, and a mismatch is refused with a message
telling you to update.
## Getting started
Three ways in, all at the top of the Library when you are not in a server.
None of them needs Jellyfin or an account.
- **Join a friend** (most people): your friend sends you an address and,
maybe, a password. See [Join](#join).
- **Host**: you have the video and friends watch it with you. See
[Host from the app](#host-from-the-app).
- **Watch solo**: just play something from the Library. It plays for you
alone, with everything a server has (Jellyfin progress, next episode,
skip buttons); nothing is reachable from outside this computer. Invite
friends in the strip turns it into a hosted server (the Host card's port
and password) and the video carries on from where it was; Stop ends it.
## The window
One window. Along the top, the strip: your avatar (click it for your
profile: name, picture with a crop editor, colour, and links to the
settings), the Library / Schedule / Settings tabs, and the party. Offline
the party side has Join a friend and Host; watching solo it says Solo,
with Invite friends and Stop; in a server it shows everyone's avatar
with a ring for their state (green ready, amber buffering, grey not ready,
dimmed reconnecting, dimmed with a moon sitting out; a crown on whoever
hosts, a tower on whoever passes the video on). Click the avatars for the
member list (force ready, Nudge, Sit out, "Copy invite address"; when you host it lists this computer's
network addresses with the port, and friends outside your network need
that port forwarded or a server they can all reach, like a VPS). Beside them the server and "ready n/m", the chat button with unread
count, and Leave. Empty strip space drags the window.
The player fills the window under the strip. Pick a tab and it shrinks to
a mini player in a corner: drag it anywhere and it snaps to the nearest
corner; double-click or ⤢ makes it big again, × sits you out (below) and
× again closes it; the strip's screen button brings it back. F or
double-click on the picture goes fullscreen (the strip hides, the party
shows in a corner while paused, the window buttons sit in the player's top
bar while its controls are up), Esc leaves it. The controls show the
show's cover, title and episode top left (a web video: its thumbnail and
name; guests get the art too; right-click to copy the title, or the link
when you have it, or open it in the browser or Jellyfin); before
a video first plays, its banner fills the picture until playback starts.
One pill bottom centre, just above the controls, says what playback waits for ("Press Space
when you're ready", "Waiting for Bob and Mika · 2/3 ready", "Bob is
loading…") or what went wrong, with Retry or Cancel when it helps. Chat and activity open in a panel on the right (chat button or
Tab), ready to type; it shows messages (select text to copy it, or
right-click for Copy message and, with a link in it, Copy link / Open in
browser), and its "Activity" toggle adds
joins, ready, seeks and the like between them. Joins, leaves, chat and
other news also pop up bottom left with the person's avatar; click a message to open the chat
(Settings → Notifications → Chat messages: Only when paused by default,
or All, or Off; messages always reach the chat). Unread chat shows as a count on the chat
buttons and, in fullscreen with the controls hidden and the video paused,
as a small chip top right.
Your name and picture change for everyone in the server right away; the
colour is yours only.
**Sitting out.** Step away without leaving: Sit out (your profile, your
row in the member list, or × on the mini player) unloads the video on your
end, and nobody waits for you any more; you stay in chat. Anyone can sit
anyone out, the host too ("Alice sat you out" tells you who). The player
then shows the cover, title and where the others are; Join back (or Space)
loads the video there and readies you. Settings → Party & network → "When idle,
sit out instead of just unready" does it after 10 idle minutes.
**Nudge.** Nudge in the member list pokes someone, sitting out or not:
only they hear it (a sound, a pop-up, and the taskbar entry asks for
attention when the window is in the background).
## Host from the app
Host in the strip: set a port and an optional password (blank = anyone can
join), then Host. The app starts a server inside itself and joins it; you
are the host (the crown). Nothing is up yet: pick what to
watch in the Library tab (below) and put it up. Leaving or
closing the app stops the server. Friends need to reach your machine: open
the TCP port on your router (or use a LAN or VPN address). Friends open
nothing. Ports you hosted on before are one click away in the Host card.
If you put up a local file, the server streams it to everyone from your
disk. Nothing is uploaded or stored anywhere.
## Library
What to watch, from three sources (the switch at the top). Drop a video,
a folder or a link anywhere on the window, or press Ctrl+V outside a
text field, and a small card asks how to open it (put it up, add it to
Up next, add the folder to Files, or fill Link).
- **Jellyfin**: your linked servers (see below); with none linked it
points to Files and Link and offers Jellyfin as optional. Library chips beside the
switch, then one **Continue** row (what you were watching and what comes
next, one card per series, the one you watched most recently first;
series not touched for two months drop out) and **Latest in** each
library (one card per series: "S1E1" or "3 new"; Show all opens the
library newest first). Re-uploads (added long after they aired) are
hidden from Latest; the row's ⋮ menu turns that off. Search (/), library
grids (sort, unwatched, favourites) and series pages sit behind them.
Click a card to select it: its
details open on the right with "Put it up · S1E3", mark watched /
unwatched, remove from Continue and the episode list (click an episode
to choose it). Double-click a card or an episode to put it up at once.
Right-click a card or an episode to copy its title or open it in
Jellyfin's web page (the details panel has that button too).
- **Link**: paste an http(s) link: a video file, or a web page with a
video (YouTube, X, and whatever else yt-dlp knows). The preview shows
what it is (a page's title, uploader, length and thumbnail, else the
file name); in a server, pick how the
others get it (everyone fetches it, the server fetches it, or through
you). Your last 12 links are listed under it (right-click one to copy
or open it). A link seen before shows its preview at once, without
asking the site again. A link that does not
open here (needs cookies, 403, 404, not a video) says why and goes up
only with "Put it up anyway"; in the player, whoever cannot play it is
named ("Bob can't play this: Not allowed (403)") and nobody waits for them.
- **Files**: video folders you add on this computer, grouped by series
(click a series to list its episodes), or any file through Browse. The others stream it from you.
Put it up and the player opens. Not in a server, the buttons say Play
and it plays for you alone ([Watch solo](#getting-started)).
### Up next (the queue)
In a server (solo too), anything can wait its turn instead of going up
now: **Add to queue** on the Link page (in the way you picked), or
right-click a Jellyfin card or episode, a video in Files, a recent link
or an episode on the Schedule for **Add to queue** / **Play next**; the
Jellyfin details panel has a queue button (right-click it to play next),
and the card for a dropped or pasted video or link has both. A link is
checked first, like Put it up. Every app keeps the next item ready,
its first seconds already loaded, so skipping to it starts at once.
For a link passed on, a member's file or a link the server fetches,
the server starts getting the next item while the current one plays
(from the app of whoever added it, or by fetching the link itself), so
everyone has its start before it goes up; whoever added it keeps the
app open for that. Everyone shares one Up next and anyone can change it; what
you added stays when you leave.
In the player, Up next sits in the top bar next to the chat button: the
next item's picture and title (or the next episode's). Rest the mouse on
it for a larger preview (who added it, its length, whether it is ready
to start); ⏭ plays it now, a click opens the queue: drag rows to reorder, hover a
row to play it now (▶) or remove it (×), right-click it for more (copy its
title or link, open it in the browser or in Jellyfin). To add, press
Ctrl+V with the queue open (a copied link or video file goes in at once,
its title and picture showing while it is checked), type one after
**+ Add link**, or drop a link or a video on it. When a video ends, a card shows
what is up next with a 10 s countdown (Cancel stops it on your screen)
and it plays; `>` or Shift+N plays it at once. Under the queue,
**Continues with** shows the next episodes of the Jellyfin series
playing (greyed: they play only once Up next is empty); **+** adds one,
**Clear** drops them for this episode.
A link you pass on ("through me") or a file of yours can only be played
by your app: if you left when its turn comes, it is skipped ("Skipped …:
Foxyz left"). A Jellyfin item needs someone with an account on that
server. Up next is kept by the server while it runs; restarting the
server empties it.
## Schedule
The week's airing anime, one column per day (AniList), each episode
marked with what the Jellyfin server has of it when AnimeFlow is
connected (Settings, Jellyfin & integrations): **on Jellyfin** (Put it
up, right-click to queue), **downloading**, **waiting for release**
(aired in Japan, AnimeFlow downloads it by itself once a release
appears; with when AnimeFlow last searched, or **release rejected** when
it found releases it won't take, the reason on hover), **failed**, **not monitored** (Request asks AnimeFlow for it),
or the time left until it airs. A show AnimeFlow does not follow can be asked for with **Request
series**. Episodes you already watched on Jellyfin carry a check on the
cover; one you started shows how far you got.
## Dedicated server
For an always-on box (no display needed): the release tarball also contains
`watch-together-server`, a small standalone server without the app. Run it,
then everyone (you included) uses Join.
``` ```
/packages/core protocol, sync engine, messages (pure Dart, no Flutter) ./watch-together-server --port 8080 --secret <password>
/packages/server room + media HTTP endpoint (pure Dart, no Flutter)
/apps/desktop Flutter app embedding packages/server (the ONE binary)
/tool spikes + harnesses (range_logger, shaper, fake_viewer, e2e_check)
/deploy systemd unit, notes
/docs PROTOCOL.md, ARCHITECTURE.md, mpv-http-contract.md, ROADMAP.md
``` ```
Phases + status: `docs/ROADMAP.md`. Session handoff: `HANDOFF.md` (root). | Flag | Default | Meaning |
|---|---|---|
| `--port`, `-p` | 8080 | TCP port, all IPv4 interfaces |
| `--secret` | empty | password; empty = open server (a warning is printed) |
| `--name` | `server` | name reported by `/health` |
| `--media` | none | local file to serve as the starting video |
| `--advertise-host` | none | host put in file URLs instead of the one each viewer dialled |
| `--[no-]yt-dlp` | on | read web pages (YouTube, X, …) with yt-dlp when the server fetches a link |
| `--yt-dlp-dir` | next to the binary | writable folder for the server's yt-dlp (systemd: the unit's `StateDirectory`) |
| `--yt-dlp-cookies` | none | cookies.txt or cookie-extension JSON export for that yt-dlp (sites that refuse guests); copied into the yt-dlp folder at start, removed from it when not given. **An account login**: keep it private, use a throwaway account for YouTube |
| `--log-dir` | `logs` next to `--config`, else next to the binary (systemd: the unit's `StateDirectory`) | folder for the log files (`server-YYYY-MM-DD.log` and the session log `session-YYYY-MM-DD.log`, kept 7 days; passwords, tokens and cookies left out) |
| `--config` | none | YAML or JSON file with the same settings (`port`, `secret`, `name`, `mediaPath`, `advertiseHost`, `ytDlp`, `ytDlpDir`, `ytDlpCookies`); replaces the flags |
| `--upgrade` | | update the install folder to the latest release ([Install and update](#install-and-update)), then exit |
| `--check-update` | | print whether a newer release exists, then exit |
Enforcement: `core` and `server` must have zero Flutter imports (tested). It prints `READY port=... name=...` once listening and stops cleanly on
Ctrl+C or SIGTERM. A dedicated server has no host.
To run it as a service, unpack the tarball to `/opt/watch-together` and use
[deploy/watch-together.service](deploy/watch-together.service) (copy it to
`/etc/systemd/system/`, edit the flags, `systemctl enable --now watch-together`).
Something not playing? Settings → About → **Copy diagnostics** puts the
app's recent log (passwords, tokens and cookies left out) on the
clipboard: paste it to whoever helps you, from both sides if you can.
While you are in a server, the app also sends its log to that server, which
merges everyone's lines and its own into one session log
(`session-YYYY-MM-DD.log` next to the server's logs; the hosting app keeps
it in its own logs folder), so a problem can be read afterwards from every
side. Chat is never logged anywhere, nor passwords, tokens and cookies.
When something goes wrong, press **Mark a problem** (Ctrl+Shift+B, also in
the player's ⚙ and Settings → About) and add a few words if you like: a
marker with your name goes in the session log at that moment. In a server,
Copy diagnostics can add the session log of the last 6 hours (any member
may read it). Don't want to share? Settings → About → **Share diagnostics
with the server** off: your log stays on your machine (the server only
notes that you don't share; a problem you mark still goes in).
## Join
Join in the strip. Servers you got into are listed first (up to 6), with
live status (online and how many are watching, or offline, plus "needs
update" when the server runs another protocol): click one to rejoin, x to
forget it. Or enter the host's address, port, and the password if there
is one, then Enter or Join. Your name and picture come from your profile
(your avatar at the top left).
## Settings
The Settings tab (Ctrl+,) has a page per topic, each setting with a line
of help: Profile; Party & network (the Host card's default port and
password, recent servers with remove); Player (below); Jellyfin &
integrations (linked Jellyfin servers, below, the AnimeFlow connection:
"connected as <name>" with what you may do, or why not, and playing the
next episode automatically); Notifications & sounds (what pops up; the sound
volume and sound set, and every sound with a preview, its own set and a switch); Updates; Keyboard shortcuts (read-only); About
(the version; resting the mouse on the Settings tab shows it too).
Player settings live in the player: ⚙ at the right end of its controls
(both designs) opens them, and Settings → Player has the same ones (plus
the custom shader files). Player design: Standard or uosc (below);
progress line when hidden (on by default: a 2 px line at the bottom of
the video while the controls are hidden); upscaling shader (see below);
hardware decoding (auto, off or custom; the env var `SYNC_RELAY_HWDEC`
overrides it); in the player also the subtitle and audio track. Every
change applies right away.
Anywhere in a server (unless you are typing): Space = ready / unready,
Tab = chat & activity (Tab never moves between buttons). Anywhere:
Ctrl+Shift+B = mark a problem in the session log. Keys while the
player fills the window: Left/Right = seek 10 s, Up/Down = volume, Shift+Right = skip 1:30,
PgUp/PgDn = previous / next chapter (as in mpv), F = fullscreen, Esc =
close the chat, then fullscreen, then shrink the player to mini; from the
mini it asks to leave, Ctrl+R = reload video,
`<` / `>` or Shift+P / Shift+N = previous / next Jellyfin episode,
Ctrl+1..6 = Anime4K mode A, B, C, A+A, B+B, C+A, Ctrl+0 = shaders off.
Every seek, skip and chapter jump moves everyone.
Controls: the seek bar marks chapter starts with a diamond; hovering it
shows the time and the chapter title. The chapters button lists them
(current one checked; click to jump). "1:30" skips ahead 1:30. Audio and
Subtitles buttons (with the track count) list tracks as "Japanese · E-AC3
2.0 · title"; subtitles have Off. Volume (scroll anywhere on the video,
hover the speaker, or Up/Down) goes to 150 % like mpv: past the tick at
100 % the bar turns amber and mpv amplifies; a scroll or key shows the
volume for a moment. Lists and menus under the mouse scroll as usual.
The uosc player design (⚙ → Player design, Standard by default,
switches right away): video controls modeled on mpv's uosc theme. A
translucent timeline across the bottom marks the position with a thin
line, hatches the part not downloaded yet, shows chapter starts as
diamonds (click one to jump), the elapsed time left, the time remaining
right and, while less than a minute is buffered, the buffered seconds
next to the position; hovering it shows the time there and the chapter
title. With the controls hidden it stays as a thin line (unless
"Progress line when hidden" is off). Above it flat buttons: ≡ menu, Subtitles and Audio (with the
track count), the shader badge (the active Anime4K mode, e.g. "A HQ"),
ready, chapters, 1:30, previous / next episode, change video, volume, ⚙
and fullscreen; the title top left, Up next, chat and back-to-library top right. Keys and skip buttons work
the same.
Skipping: during the opening a "Skip OP" button shows bottom right, just
above the controls (it
jumps to the end of the opening), during the ending "Skip ED" (to the end
of the ending only; what comes after is yours to skip). "Skip OP" and
"Skip 1:30" show for 5 seconds when they come up, then only while the
controls are up (move the mouse); "Skip ED" and "Next episode" show only
while the controls are up. The opening and
ending come from Jellyfin's intro / outro segments when your Jellyfin has
them (needs a segment plugin, e.g. Intro Skipper), else from chapter names
(OP, Opening, Intro / ED, Ending, Credits, Outro). Without an opening or
any chapters, on anything longer than 20 minutes, "Skip 1:30" floats there
during the first 6 minutes.
## Ports
| What | Default | Notes |
|---|---|---|
| Sync WebSocket and `/health` | TCP 8080 | plain `ws://`, no TLS |
| Local file streaming | same port | path `/m/...`, needs a session token |
Only the host needs the port reachable.
## Jellyfin
Settings > Jellyfin & integrations > Add server: type the address, then sign in with
Quick Connect (the app shows a code; approve it in Jellyfin under Profile >
Quick Connect) or your username and password. Several servers can be
linked; each shows its status, and a star picks the default. Sign out
revokes the app's sign-in on the server. If a sign-in expires the server
shows "Reconnect" instead of disappearing.
The Library's Jellyfin source is a library browser: Continue watching (with
time left), Next up and the latest per library on the start page; library
grids with sort (name, date added, release date, rating, random) and
Unwatched / Favorites filters; series pages with seasons, episode progress
and a "Next up" button; details with the file's tracks; search. Clicking a
movie or episode picks it. On wide windows a side pane shows details of
whatever is under the mouse.
Keys in the browser: arrows move, Enter picks or opens, I = details,
Esc / Backspace / Alt+Left = back (Esc first clears a search), / or Ctrl+F =
search.
Putting up a movie or episode sends only the item to the server (which
Jellyfin server and which item), never a link or your sign-in. Pasting a Jellyfin link (web page or stream link) does the
same when the app can reach that Jellyfin; otherwise it is shared as a
plain link.
Watching:
- Everyone with their own account on that Jellyfin server plays the file
directly with their own sign-in. Jellyfin tracks progress per account:
your watched state, Continue watching and resume point follow what you
saw (reported on start, every 10 s, on pause, resume, seek and track
change, and when it stops). Watched means you actually played most of
it (or reached the ending after half), not where you stopped: opening
a watched episode to check a moment keeps it watched, and a quick peek
leaves no resume point.
- Friends without an account watch through the server: whoever put the
item up passes it on automatically, with their own sign-in, which never
leaves their computer (only the current item, only to people in the
session). They show "via server" in the member list (click the avatars). When the app hosts,
or a dedicated server has its own login (`--jellyfin <file>`, see
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), Jellyfin relay), the server
fetches it itself instead.
- If nobody can pass it on (the person who put it up left), the app says
"No Jellyfin account here for ..." with a button to link one; it plays as
soon as you are signed in.
Episodes: the ⏮ / ⏭ buttons next to the controls (or `<` / `>`,
Shift+P / Shift+N) put up the previous or next episode for everyone. From
the ending (when it is known, see Skipping above; else in the last two
minutes) a "Next episode" button floats in the corner while the controls
are up. When an
episode ends, a card shows what is next with Play next; with "Play the
next episode automatically" (Settings, on by default) it counts down 10 s
first, and Cancel stops it on your screen (seeking back stops it for
everyone). Anyone with an account can put the next one up; friends
watching via the host cannot.
Audio and subtitles: the Audio and Subtitles buttons in the controls pick tracks.
Your choice is remembered per series (and as a default) and the closest
match (language, then title, then codec and channels) is selected on the
next episode. This is per person; nobody else is affected.
"Change video" in the player's controls opens the Library tab (the player
goes mini): the library starts on the series you are watching with the
current season, else where you left it.
Anyone can put up a file from their own computer, also on a dedicated
server: it streams from your computer through the server to everyone (the
server keeps a few minutes in memory, so seeks and skipping the opening
are quick). Keep the app open while it plays. A link goes up "Through
me": your computer opens it and passes it on, so it plays for everyone
whenever it plays for you (you can't tell ahead which sites want cookies,
and YouTube may start asking any day). Under "Other ways" there are two more:
- Everyone fetches it: each app plays the link itself.
- Server fetches it: the server downloads it once for everyone, for links
that are slow or blocked for some. The server must be able to reach it,
and it must have a password (an open server refuses: anyone could make
it fetch addresses only it can reach).
Web pages work in all three: the app and the dedicated server each
download [yt-dlp](https://github.com/yt-dlp/yt-dlp) by themselves (checked
against the release's checksums, updated once a day; Settings → Player
shows its version and has "Update now"), so there is nothing to install.
Sites that refuse guests (Bilibili answers "HTTP Error 412", YouTube may
ask to sign in) need cookies: Settings → Player → yt-dlp → Cookies reads
them from a browser, or from a cookies.txt or a cookie extension's JSON
export (picked or dropped; every site in it is kept). They stay on this
computer and are used by its yt-dlp only (your own playback, "through
me", and a server you host). Cookies log yt-dlp in as you on those
sites: keep the file private, and for YouTube consider a throwaway
account (Google may flag accounts used with yt-dlp).
YouTube works in all three too, and so do pages and links that only
offer streaming playlists (HLS `.m3u8`, DASH `.mpd`, the HLS of X videos).
Through me and Server fetches it never show the link to anyone; they can't pass on live
streams or DRM-protected video (use "Everyone fetches it" for live).
Your upload has to beat the video's bitrate: a 25-minute 2 GB episode is
about 11 Mbit/s.
The member list (click the avatars) shows where the video comes from on
top: who passes it on, or that the server fetches it, everyone plays the
link, or everyone streams it from Jellyfin, with how many watch through
the server and, for a relay, how fast it arrives against what the video
needs (the bar's mark; past it is keeping up). It turns amber when the
source is too slow or reconnecting, red when it left (the others play
out what the server already has), and whoever passes it on is told once
when their upload is too slow. Each member's line says how they get it
("via server" with their speed, or "direct") after a dot for their
buffer: green enough to play on, amber low, red empty. A red dot on one
member while the source keeps up is their own connection.
## Video quality and shaders
Each viewer can run mpv GLSL shaders on their own picture; nobody else is
affected. The app ships [Anime4K](https://github.com/bloc97/Anime4K) v4.0.1
(MIT) with the presets of its mpv guide: Mode A (1080p sources), B (720p),
C (480p), A+A, B+B, C+A, each as HQ (strong GPUs, GTX 1080 and up) or Fast
(the guide's low-end set). "Custom shaders" runs your own `.glsl` files.
Pick one with the ✨ button in the player's control bar (also in
fullscreen) or Ctrl+0..6; the change applies live and is saved, so the next
session starts with it. The player's ⚙ and Settings → Player set it too,
with HQ or Fast (used by Ctrl+1..6); Settings → Player holds the custom
files. Shaders cost GPU time
every frame and need hardware video rendering: on media_kit's software
fallback they do nothing, and the app says so. A shader that is missing or
fails to load shows a toast and switches shaders off.
## Troubleshooting
- "host offline - check address and port": wrong address, or the port is not open.
- "wrong server password": password mismatch.
- "host runs a different version": update the app on both sides.
- Picture frozen but sound fine: Ctrl+R reloads the video.
- Everyone waits at "waiting for X": X has not buffered 4 seconds yet or has not pressed Space.
- Windows: the build is new; if something is off there, say so.
For contributors: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md),
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/PROTOCOL.md](docs/PROTOCOL.md).
+5 -16
View File
@@ -1,17 +1,6 @@
# watch_together # apps/desktop
A new Flutter project. The Flutter desktop app (what users run; hosting runs the server from
`packages/server` on an isolate). Layout and rules:
## Getting Started [../../docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md). Build and test:
[../../docs/DEVELOPMENT.md](../../docs/DEVELOPMENT.md).
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#2d2547"/><stop offset="1" stop-color="#14111d"/></linearGradient><linearGradient id="t1" gradientUnits="userSpaceOnUse" x1="50.7" y1="148.7" x2="184.6" y2="71.4"><stop offset="0" stop-color="#f0795a" stop-opacity="0"/><stop offset="0.45" stop-color="#f0795a" stop-opacity="0.85"/><stop offset="1" stop-color="#ffc56b"/></linearGradient><linearGradient id="t2" gradientUnits="userSpaceOnUse" x1="205.3" y1="107.3" x2="71.4" y2="184.6"><stop offset="0" stop-color="#ffc56b" stop-opacity="0"/><stop offset="0.45" stop-color="#ffc56b" stop-opacity="0.85"/><stop offset="1" stop-color="#f0795a"/></linearGradient></defs>
<rect x="8" y="8" width="240" height="240" rx="56" fill="url(#bg)"/>
<circle cx="128" cy="128" r="80" fill="none" stroke="#ffffff" stroke-opacity="0.10" stroke-width="6"/>
<path d="M205.27 107.29 A80 80 0 0 1 71.43 184.57" fill="none" stroke="url(#t2)" stroke-width="7" stroke-linecap="butt"/><path d="M50.73 148.71 A80 80 0 0 1 184.57 71.43" fill="none" stroke="url(#t1)" stroke-width="7" stroke-linecap="butt"/>
<path d="M110 96 L110 160 L162 128 Z" fill="#fff1d6" stroke="#fff1d6" stroke-width="16" stroke-linejoin="round"/>
<path d="M71.4 161.6 Q74.7 181.3 94.4 184.6 Q74.7 187.8 71.4 207.6 Q68.2 187.8 48.4 184.6 Q68.2 181.3 71.4 161.6 Z" fill="#f0795a"/>
<path d="M184.6 40.4 Q188.9 67.1 215.6 71.4 Q188.9 75.8 184.6 102.4 Q180.2 75.8 153.6 71.4 Q180.2 67.1 184.6 40.4 Z" fill="#ffc56b"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,36 @@
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// For more information, please refer to <https://unlicense.org>
//!DESC Anime4K-v4.0-AutoDownscalePre-x2
//!HOOK MAIN
//!BIND HOOKED
//!BIND NATIVE
//!WHEN OUTPUT.w NATIVE.w / 2.0 < OUTPUT.h NATIVE.h / 2.0 < * OUTPUT.w NATIVE.w / 1.2 > OUTPUT.h NATIVE.h / 1.2 > * *
//!WIDTH OUTPUT.w
//!HEIGHT OUTPUT.h
vec4 hook() {
return HOOKED_tex(HOOKED_pos);
}
@@ -0,0 +1,36 @@
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// For more information, please refer to <https://unlicense.org>
//!DESC Anime4K-v3.2-AutoDownscalePre-x4
//!HOOK MAIN
//!BIND HOOKED
//!BIND NATIVE
//!WHEN OUTPUT.w NATIVE.w / 4.0 < OUTPUT.h NATIVE.h / 4.0 < * OUTPUT.w NATIVE.w / 2.4 > OUTPUT.h NATIVE.h / 2.4 > * *
//!WIDTH OUTPUT.w 2 /
//!HEIGHT OUTPUT.h 2 /
vec4 hook() {
return HOOKED_tex(HOOKED_pos);
}
@@ -0,0 +1,90 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-De-Ring-Compute-Statistics
//!HOOK MAIN
//!BIND HOOKED
//!SAVE STATSMAX
//!COMPONENTS 1
#define KERNELSIZE 5 //Kernel size, must be an positive odd integer.
#define KERNELHALFSIZE 2 //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2).
float get_luma(vec4 rgba) {
return dot(vec4(0.299, 0.587, 0.114, 0.0), rgba);
}
vec4 hook() {
float gmax = 0.0;
for (int i=0; i<KERNELSIZE; i++) {
float g = get_luma(MAIN_texOff(vec2(i - KERNELHALFSIZE, 0)));
gmax = max(g, gmax);
}
return vec4(gmax, 0.0, 0.0, 0.0);
}
//!DESC Anime4K-v4.0-De-Ring-Compute-Statistics
//!HOOK MAIN
//!BIND HOOKED
//!BIND STATSMAX
//!SAVE STATSMAX
//!COMPONENTS 1
#define KERNELSIZE 5 //Kernel size, must be an positive odd integer.
#define KERNELHALFSIZE 2 //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2).
vec4 hook() {
float gmax = 0.0;
for (int i=0; i<KERNELSIZE; i++) {
float g = STATSMAX_texOff(vec2(0, i - KERNELHALFSIZE)).x;
gmax = max(g, gmax);
}
return vec4(gmax, 0.0, 0.0, 0.0);
}
//!DESC Anime4K-v4.0-De-Ring-Clamp
//!HOOK PREKERNEL
//!BIND HOOKED
//!BIND STATSMAX
float get_luma(vec4 rgba) {
return dot(vec4(0.299, 0.587, 0.114, 0.0), rgba);
}
vec4 hook() {
float current_luma = get_luma(HOOKED_tex(HOOKED_pos));
float new_luma = min(current_luma, STATSMAX_tex(HOOKED_pos).x);
//This trick is only possible if the inverse Y->RGB matrix has 1 for every row... (which is the case for BT.709)
//Otherwise we would need to convert RGB to YUV, modify Y then convert back to RGB.
return HOOKED_tex(HOOKED_pos) - (current_luma - new_luma);
}
@@ -0,0 +1,275 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.09991986, 0.13782342, -0.031251684, -0.06356843, -0.3437488, 0.05450952, 0.34347802, 0.46335372, 0.08607224, 0.044988394, 0.137179, 0.17976908, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.024212424, -0.09278509, -0.00040907756, 0.34552294, -0.13254678, 0.113105185, 0.005667946, -0.00036919137, -0.06375679, 0.009184115, 0.115518734, -0.115506776, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.14101827, 0.023523493, 0.044094566, -0.019271746, -0.44348842, -0.08818877, -0.4026149, -0.21995795, -0.15880394, -0.013732858, -0.020751135, 0.012719151, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.013001821, -0.34503505, 0.39219138, 0.18792126, 0.24760444, -0.016173402, 0.10154511, 0.15453082, -0.058132876, 0.016784398, -0.05808539, -0.11039915, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(0.37024534, 0.041440863, -0.3374568, -0.44994286, 0.19555596, 0.20855539, -0.27974075, -0.5372628, 0.21228147, -0.0295346, -0.56700057, 0.030042822, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.12940632, 0.057526, 0.090682045, -0.06985033, -0.13704006, -0.047685407, 0.44615674, -0.48056605, -0.06166251, -0.01883519, 0.2032237, -0.113287605, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.010856669, -0.35820737, 0.16757219, 0.082619876, -0.03967303, 0.038705572, 0.32652855, -0.012030017, 0.015120559, -0.15314877, 0.23442009, 0.09767922, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.046272673, -0.17752305, 0.082018286, -0.2512824, 0.58619463, -0.060903464, -0.022793597, 0.077803515, -0.17025311, 0.05136993, 0.029383298, -0.15475409, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.11212024, 0.13378005, -0.2027488, 0.08056421, -0.11176219, -0.048429377, -0.08396386, 0.10507829, 0.13326839, 0.0430627, 0.051362377, 0.06482755, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.061233472, 0.39222646, 0.029704979, 0.02586828);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.16410656, -0.40521824, 0.13121907, -0.02314597, 0.105412476, -0.060401272, -0.043063477, -0.13933973, 0.12558138, -0.020861467, 0.030370515, 0.13178016, -0.14220351, 0.20736893, 0.003321564, -0.29241714) * go_0(-1.0, -1.0);
result += mat4(0.18517321, 0.29162985, -0.26783395, 0.039760686, 0.025527012, -0.067319244, 0.055004176, 0.048916563, 0.12750523, -0.091435954, 0.13818842, 0.36704224, 0.0839921, 0.10186618, -0.17237376, 0.13282418) * go_0(-1.0, 0.0);
result += mat4(-0.1657887, 0.0131325135, -0.17222486, 0.091398895, -0.12756164, -0.08437298, -0.29052997, 0.3269337, 0.15870757, -0.013529402, -0.0581753, 0.11802371, 0.07099966, -0.024063632, 0.31834844, -0.11183859) * go_0(-1.0, 1.0);
result += mat4(0.46036887, -0.07654623, 0.22923063, 0.17463821, 0.10555414, -0.117430426, 0.12406777, -0.011399492, 0.028316498, 0.13684341, 0.009664087, 0.2022659, 0.04953974, -0.31342217, -0.6103131, -0.13605757) * go_0(0.0, -1.0);
result += mat4(0.03406955, -0.39819366, 0.61176, -0.46809456, -0.029321073, 0.46619493, 0.36700186, 0.02288561, 0.11464085, -0.10931452, -0.09154022, 0.07334147, -0.5609916, 0.31826234, -0.011012659, -0.46719545) * go_0(0.0, 0.0);
result += mat4(-0.056855045, 0.27037027, -0.09269696, -0.563572, -0.06816116, -0.22986612, 0.08693167, -0.16246101, 0.09954046, -0.05374176, 0.0071916827, -0.1788692, 0.3825241, -0.1609887, 0.055204768, 0.10213068) * go_0(0.0, 1.0);
result += mat4(0.0646626, 0.102358796, -0.45055822, 0.20557903, -0.23337309, 0.12633002, -0.19299199, -0.15085731, -0.13473304, 0.053790465, -0.10061193, -0.13393497, -0.04264752, -0.029740738, -0.07865285, 0.20883279) * go_0(1.0, -1.0);
result += mat4(0.010471527, -0.033218473, -0.46157447, 0.004866583, 0.23226471, -0.059343327, -0.1439596, 0.13619648, 0.013839963, 0.15930325, 0.043742355, 0.17467323, 0.33772305, 0.40261495, -0.08351293, 0.18129359) * go_0(1.0, 0.0);
result += mat4(-0.12493434, -0.1875134, -0.074943796, -0.0031701606, -0.037142616, 0.1667002, 0.16665547, -0.011248127, 0.0071619414, 0.0034872112, 0.120318964, -0.09625579, 0.14917047, -0.16310586, 0.07231737, 0.30447328) * go_0(1.0, 1.0);
result += mat4(0.093798615, 0.17074613, -0.08780678, -0.012520207, 0.118534856, 0.027508778, -0.2778478, -0.19509242, -0.34137097, 0.32000312, -0.22027159, 0.337515, 0.16220862, 0.108993016, 0.14070526, 0.12784284) * go_1(-1.0, -1.0);
result += mat4(-0.14325632, -0.1467453, -0.27502358, 0.09370837, 0.11821083, -0.012266484, -0.2100548, 0.4707502, -0.06766648, 0.58165014, -0.2512279, -0.33783755, 0.1318925, -0.04346277, 0.15454485, 0.044500057) * go_1(-1.0, 0.0);
result += mat4(-0.05683207, 0.0051946463, -0.108000524, 0.10133204, -0.50763863, 0.007308442, 0.8542404, 0.28387356, 0.022709515, 0.294523, -0.3822472, 0.66166407, 0.01404485, 0.031282708, -0.26756814, -0.123147786) * go_1(-1.0, 1.0);
result += mat4(-0.36455178, 0.3470555, -0.045303088, -0.03170764, -0.15802494, -0.0019141496, -0.25939587, -0.23875342, 0.130428, 0.03954273, -0.17985536, 0.105145946, 0.15804817, 0.12551713, 0.28371975, -0.085748516) * go_1(0.0, -1.0);
result += mat4(0.0060625463, 0.2443924, -0.017692259, -0.20214005, -0.09584515, -0.012805372, -0.13942227, 0.16143198, 0.12942013, 0.41785547, 0.046071563, 0.7030026, 0.10499644, -0.20566013, -0.031321276, 0.27830327) * go_1(0.0, 0.0);
result += mat4(-0.081274964, -0.14562319, 0.27200526, -0.20491314, 0.012910989, 0.024201397, 0.04816258, 0.21297328, -0.22015952, -0.44160756, -0.056035373, 0.33824417, -0.31645304, 0.15469243, 0.053187452, -0.20989445) * go_1(0.0, 1.0);
result += mat4(-0.046550367, 0.033185404, 0.33337244, 0.12853645, 0.23520172, -0.05909214, 0.0861368, 0.10706329, -0.07058717, -0.11759937, -0.18594047, 0.080006264, -0.055425353, -0.12506317, 0.15729053, -0.0915004) * go_1(1.0, -1.0);
result += mat4(0.042516407, 0.14844789, 0.16533111, 0.13502933, -0.0655417, -0.057256397, 0.076713726, -0.23448966, 0.12855926, 0.014219275, 0.051761385, 0.053433083, -0.2446715, -0.4008074, 0.19603717, -0.1796951) * go_1(1.0, 0.0);
result += mat4(0.14777803, 0.15524907, 0.043158617, -0.06996876, 0.19210646, -0.2144364, -0.47020787, -0.4207906, -0.18074386, -0.2163903, 0.0030754965, 0.36799973, -0.3837698, -0.0022661497, -0.37276733, -0.28934997) * go_1(1.0, 1.0);
result += vec4(-0.018297346, -0.080951825, -0.062163066, -0.08050014);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.31543177, 0.23095237, -0.06692611, -0.5867763, 0.003622504, 0.17948842, -0.14627707, 0.1745016, -0.052964583, -0.15551159, 0.05644786, -0.012665164, 0.13107763, 0.11369179, -0.09452995, -0.11973403) * go_0(-1.0, -1.0);
result += mat4(-0.2694661, -0.115382135, 0.3073268, -0.067228466, -0.25511482, -0.13922207, 0.36758214, -0.18821828, -0.022617863, 0.20333402, -0.11125889, 0.3552245, -0.013346653, -0.099095374, -0.25100616, 0.35521755) * go_0(-1.0, 0.0);
result += mat4(0.011012409, -0.13675085, 0.25642, -0.34851208, -0.23184675, 0.18012202, 0.57654136, 0.103173524, -0.16461405, 0.038177088, 0.1234096, 0.013202029, -0.19033363, 0.07469178, -0.017948546, 0.15287702) * go_0(-1.0, 1.0);
result += mat4(-0.05340533, 0.23797482, 0.20351392, -0.05333351, -0.12181174, -0.23363493, -0.20696607, 0.109941036, -0.11519453, 0.13842066, -0.10687832, 0.29040006, 0.022218632, 0.031238724, 0.2685182, 0.15300068) * go_0(0.0, -1.0);
result += mat4(0.22985318, -0.3103802, -0.22916415, 0.25238806, -0.11690287, -0.1947488, 0.118020535, 0.07814263, -0.06335474, -0.007870727, 0.076106325, 0.094677486, -0.16776285, -0.006570437, -0.29589584, 0.41413507) * go_0(0.0, 0.0);
result += mat4(0.43607962, -0.36456433, -0.123776875, -0.16634953, -0.091190875, 0.13035081, 0.28627968, 0.27249968, 0.12356344, -0.008616177, 0.09599816, -0.006144557, -0.23490307, 0.3013123, 0.14153156, 0.21837278) * go_0(0.0, 1.0);
result += mat4(0.060364585, 0.37860224, 0.039182413, -0.22805426, -0.089910224, -0.06817697, -0.2684275, -0.12528503, 0.036934495, -0.07826616, 0.06559976, -0.08253646, 0.13489649, 0.06237663, 0.126376, 0.21194184) * go_0(1.0, -1.0);
result += mat4(-0.12534817, 0.21225189, -0.27818045, -0.3070443, -0.006957577, -0.025105853, 0.12100924, -0.06916452, 0.23081483, 0.1802756, -0.18995638, 0.16603014, -0.2904096, -0.25292823, -0.21834068, 0.13719653) * go_0(1.0, 0.0);
result += mat4(0.017209655, 0.10757137, 0.21414296, -0.30885983, 0.10467716, -0.2184891, 0.100061476, -0.1527528, 0.2100472, -0.25768545, -0.22329919, -0.29153427, -0.06983842, -0.103854865, -0.051384352, 0.14629121) * go_0(1.0, 1.0);
result += mat4(0.0059623295, -0.26060802, 0.32115817, 0.021025505, 0.09783085, -0.15865178, 0.1473021, -0.24977303, -0.033508282, 0.17480391, -0.091310136, 0.09870876, 0.10504043, -0.06105686, 0.013493489, -0.11278855) * go_1(-1.0, -1.0);
result += mat4(0.14875248, -0.14859414, 0.19377062, -0.17456068, 0.101288855, -0.1113682, -0.48944646, 0.1018565, -0.037392337, 0.08539691, 0.1751306, -0.15428723, -0.059375558, 0.027663672, 0.051804014, -0.049813222) * go_1(-1.0, 0.0);
result += mat4(0.118846565, -0.19869871, -0.037388258, 0.08456728, -0.11662527, -0.43818352, -0.093285345, 0.038507205, -0.051991668, 0.21008292, 0.10792365, 0.2020924, 0.057021596, 0.09460527, 0.0016551288, -0.0015957063) * go_1(-1.0, 1.0);
result += mat4(0.11062174, -0.2639232, -0.060295466, -0.3217331, -0.050545212, 0.30989558, 0.30906132, 0.030323273, 0.028986752, 0.037429404, 0.20855664, -0.19848943, 0.034687653, -0.09599135, -0.06250494, -0.13215867) * go_1(0.0, -1.0);
result += mat4(-0.010391146, 0.07657845, 0.44491258, 0.0435906, 0.0075931503, 0.42632654, 0.47022533, 0.34737435, -0.15452717, -0.14613411, -0.45231065, 0.12094409, 0.0067911847, 0.057501152, 0.09876979, 0.044946447) * go_1(0.0, 0.0);
result += mat4(-0.15607435, 0.2293058, -0.09520331, 0.012836732, -0.15282455, 0.26437718, -0.1685477, -0.13211122, -0.055801593, -0.016778728, -0.34478986, -0.23228309, 0.12300962, -0.13235827, -0.13987203, -0.16550972) * go_1(0.0, 1.0);
result += mat4(0.13161735, -0.09039346, -0.033475474, -0.23686698, 0.1514885, 0.20977421, 0.031431954, -0.0049226107, 0.090661936, 0.15288061, -0.03316583, 0.09646573, -0.32651708, 0.18825398, -0.15777239, 0.17572704) * go_1(1.0, -1.0);
result += mat4(0.112157226, -0.08712878, 0.23453182, 0.1043877, -0.14686783, 0.28682423, -0.086443506, 0.059457052, -0.31530112, -0.2700583, -0.06028952, -0.070416875, 0.18053482, 0.16653341, 0.25215197, 0.061915852) * go_1(1.0, 0.0);
result += mat4(-0.20122242, 0.076313145, -0.0988483, 0.094337784, -0.35436687, 0.3762327, -0.07809558, 0.3055848, 0.10425242, -0.17087407, 0.030301496, -0.13911743, 0.01630275, 0.24247427, -0.006474477, 0.03842641) * go_1(1.0, 1.0);
result += vec4(-0.008952847, -0.0058945753, -0.08097229, 0.020968592);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_2_tf
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.2237721, -0.0064096362, -0.31808427, 0.73477733, 0.015353088, 0.23983319, 0.14967978, -0.34920225, -0.07456269, 0.093151815, -0.14331086, -0.24586205, -0.14183366, 0.06401045, -0.22044073, 0.29932275) * go_0(-1.0, -1.0);
result += mat4(-0.07968509, -0.3349146, 0.16529128, 0.08443499, 0.4095855, -0.17120704, 0.17425705, 0.15298946, 0.2981273, 0.2212369, 0.10392389, -0.28775454, -0.065247655, -0.15255849, 0.13094437, 0.18685219) * go_0(-1.0, 0.0);
result += mat4(0.015706737, -0.17755036, 0.2622526, 0.112057306, -0.15876788, -0.38466996, -0.33700845, -0.031711742, -0.023320962, -0.3145249, -0.21223734, -0.1314596, -0.1888095, -0.046370104, 0.09000896, -0.0046378844) * go_0(-1.0, 1.0);
result += mat4(-0.31127506, 0.31304324, -0.03965752, 0.03649018, -0.029851055, 0.05801377, 0.00040150844, -0.04422069, 0.18019931, 0.14415511, -0.09845236, 0.21895434, -0.013932474, -0.046454947, -0.3403935, -0.006705289) * go_0(0.0, -1.0);
result += mat4(-0.34878647, -0.5129283, 0.060250953, -0.16354133, 0.20644619, 0.08732273, -0.24118888, 0.24455065, 0.24449423, 0.44103387, 0.22455928, 0.25738943, -0.26914698, -0.21309987, 0.08386486, 0.021484816) * go_0(0.0, 0.0);
result += mat4(-0.057454903, -0.4121922, 0.022661546, 0.37178272, 0.03331408, 0.05044008, 0.04324371, 0.20727943, 0.2432641, 0.076906696, -0.20858039, 0.012439015, -0.19335061, 0.09217451, 0.1968369, -0.19435833) * go_0(0.0, 1.0);
result += mat4(-0.16960496, 0.24616167, 0.37977478, 0.14324574, -0.011531225, -0.11312143, -0.18141079, -0.23843932, 0.0086012175, -0.3564491, -0.12639481, 0.009799298, -0.29120612, 0.23756824, 0.18035695, -0.087133996) * go_0(1.0, -1.0);
result += mat4(-0.10081239, 0.29191494, 0.10434693, 0.08970636, 0.008997759, 0.104756236, 0.039641086, 0.02323888, -0.11627765, 0.023693223, -0.30801758, -0.120208986, 0.05086147, 0.18498175, 0.15595439, -0.09877306) * go_0(1.0, 0.0);
result += mat4(0.101321675, -0.2929976, 0.38810417, 0.5605376, -0.04073937, 0.030110704, -0.18147062, -0.09833952, 0.01927733, 0.15335669, -0.15384074, -0.110595055, -0.054297395, -0.077522054, 0.07918369, -0.068480626) * go_0(1.0, 1.0);
result += mat4(0.23263514, -0.11719232, 0.2903209, -0.007503795, -0.020222448, -0.17790157, -0.15600762, -0.08741775, 0.12529704, 0.25548857, -0.04585447, -0.10255033, 0.18350503, -0.29593533, 0.0868933, 0.027004737) * go_1(-1.0, -1.0);
result += mat4(-0.14958654, -0.006238835, -0.2928948, 0.1988557, -0.17057803, 0.12524141, 0.13978264, -0.019280292, 0.05967142, -0.07790818, -0.5893818, -0.022845713, -0.08596779, 0.07875358, -0.03316667, -0.4369282) * go_1(-1.0, 0.0);
result += mat4(0.19195688, -0.060883682, -0.25897828, 0.07063324, 0.090833396, 0.003422883, 0.109534174, 0.031180874, -0.05017118, 0.022862168, -0.270113, -0.057831235, 0.53920543, -0.10252776, -0.091807485, 0.004294343) * go_1(-1.0, 1.0);
result += mat4(-0.18494242, -0.119284816, 0.3821897, 0.07777979, 0.15568028, -0.2854859, -0.22441281, -0.049155876, -0.15292497, 0.21895619, -0.095677756, 0.15210424, 0.001643022, -0.026176987, 0.048463076, -0.4824009) * go_1(0.0, -1.0);
result += mat4(0.007215129, 0.17074333, 0.053930074, -0.027014816, -0.17180431, -0.15163863, -0.0012122132, -0.18934256, -0.08294297, -0.24580221, -0.46552867, -0.27923223, 0.4092668, 0.06288688, -0.1602188, -0.0030876845) * go_1(0.0, 0.0);
result += mat4(0.111870885, 0.03317145, 0.14155298, 0.20328505, -0.05104131, 0.13979794, 0.018966835, -0.07238511, 0.05493792, -0.14975783, -0.10293237, -0.21985306, 0.49054706, 0.18288186, -0.26925826, 0.35845932) * go_1(0.0, 1.0);
result += mat4(0.3747799, -0.096748486, -0.17139742, 0.25289854, -0.17421168, -0.018461818, 0.09747162, 0.01660535, -0.20580359, 0.56189656, 0.17151354, -0.26347768, 0.28350568, -0.21486014, -0.44330928, -0.008981037) * go_1(1.0, -1.0);
result += mat4(0.10169985, -0.18244018, 0.04760736, 0.41017643, -0.09468786, -0.024218475, 0.103733875, -0.22540338, 0.10630112, 0.3677178, -0.104170956, 0.057317447, 0.21764882, 0.0789158, -0.22041337, 0.15065216) * go_1(1.0, 0.0);
result += mat4(0.11633995, -0.008195114, -0.14501533, 0.07168025, 0.058413275, 0.055995367, 0.09362145, -0.13827963, 0.13760869, 0.040319785, 0.038895044, 0.2675253, -0.087339684, 0.1412073, -0.17166458, -0.2312994) * go_1(1.0, 1.0);
result += vec4(-0.059377354, -0.02055341, 0.07234869, -0.015452986);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_3_tf
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.29012984, -0.13150147, 0.31015614, 0.05992291, -0.050289866, 0.14845313, -0.09608898, 0.27913308, 0.060307387, -0.04160452, 0.035932682, -0.08137563, -0.07999419, 0.11818284, -0.27512288, 0.21948813) * go_0(-1.0, -1.0);
result += mat4(0.12916058, -0.21759962, -0.33868533, 0.021636661, 0.053470243, 0.1412425, 0.043395396, -0.26751056, -0.01689101, -0.2623835, 0.010809152, 0.062962815, -0.20692012, -0.1677863, -0.23313859, -0.17402615) * go_0(-1.0, 0.0);
result += mat4(-0.08204112, -0.23672083, -0.0064437394, -0.13200696, -0.056692924, -0.02708657, 0.12536962, 0.004428919, 0.14137582, 0.15404348, -0.105753876, 0.047957454, 0.15734316, 0.16562423, -0.010160829, -0.06602983) * go_0(-1.0, 1.0);
result += mat4(0.025653997, -0.10877775, -0.31258908, 0.18841636, -0.36005193, 0.1816357, -0.34537643, -0.0741087, 0.4663994, 0.0065186517, 0.08109033, 0.2976773, -0.35774228, -0.041366056, -0.37852773, 0.050565656) * go_0(0.0, -1.0);
result += mat4(0.04392313, 0.11316681, -0.14421389, 0.17985669, -0.1651274, -0.5656209, -0.124100484, 0.42774054, -0.1153939, 0.16829851, 0.2025612, 0.054007456, -0.06868256, -0.56935954, -0.12227961, 0.17688861) * go_0(0.0, 0.0);
result += mat4(0.34041, 0.499, 0.15234196, 0.21353458, -0.2732667, -0.049950935, 0.03550811, -0.21051687, 0.2609023, 0.016438454, -0.29874632, 0.37994128, 0.049288407, -0.31126305, 0.029235512, -0.012256015) * go_0(0.0, 1.0);
result += mat4(-0.0046853204, 0.15391374, -0.040689662, 0.20186873, -0.08137621, 0.35905558, 0.23733845, 0.21794793, -0.066420384, 0.029600656, -0.31421044, -0.050773863, -0.06260773, 0.04634221, -0.10948491, -0.045498934) * go_0(1.0, -1.0);
result += mat4(-0.082953, -0.025837064, -0.09928303, -0.14300232, 0.275064, 0.07793617, 0.22240888, 0.06637834, -0.4382666, -0.2932182, -0.27243167, -0.14221182, 0.5695728, 0.20719238, 0.5575927, 0.40816882) * go_0(1.0, 0.0);
result += mat4(-0.18510929, -0.15052167, 0.25277212, 0.06804461, 0.016387, 0.20310035, 0.2903229, -0.0615877, -0.28987274, -0.11942605, 0.013498961, 0.3184152, 0.29543474, -0.042830903, -0.018111207, -0.13263674) * go_0(1.0, 1.0);
result += mat4(0.25749087, 0.0053866603, -0.09391162, -0.06129529, -0.094091184, -0.07419633, 0.0013858611, 0.012000353, -0.062903, -0.0204224, -0.12113313, 0.017942557, -0.073379934, 0.052201986, 0.35864577, 0.023564404) * go_1(-1.0, -1.0);
result += mat4(0.100115694, 0.19451359, 0.23252094, 0.19506809, -0.12470779, 0.0027281935, -0.17488572, -0.018721964, -0.15159339, 0.18457152, 0.057712987, -0.08191495, 0.19735703, 0.07326743, -0.28563106, 0.01642815) * go_1(-1.0, 0.0);
result += mat4(0.068062514, 0.28356665, 0.07377898, 0.42776972, 0.28725025, -0.13045293, -0.17525704, -0.05885591, -0.16676305, -0.2555945, -0.10078422, -0.053032875, 0.084470876, 0.06460686, 0.13824362, -0.05231353) * go_1(-1.0, 1.0);
result += mat4(0.22637829, -0.028969254, 0.1968254, -0.13331996, 0.038017053, -0.008854481, -0.2031639, 0.09237089, -0.3821112, 0.1108527, -0.11029933, -0.24542028, 0.22416145, -0.031492114, -0.19144306, -0.0996271) * go_1(0.0, -1.0);
result += mat4(0.10776744, 0.16363445, 0.14656505, -0.3737814, -0.06642015, 0.5616549, -0.008412252, -0.37266847, 0.12506576, -0.15329036, 0.037538245, -0.10810259, 0.01706349, 0.1813702, 0.035651788, -0.012786579) * go_1(0.0, 0.0);
result += mat4(-0.4023338, -0.2098614, -0.18285121, -0.02727653, 0.26107362, 0.041306913, -0.036515504, -0.045217298, -0.39958602, -0.21229339, -0.021053292, -0.13427502, 0.36178818, 0.20934913, 0.1500852, 0.2634554) * go_1(0.0, 1.0);
result += mat4(0.07794611, -0.25937587, -0.06822529, -0.056336135, 0.094220124, 0.21588847, -0.0455218, -0.10968329, -0.08068449, -0.31366697, 0.07799637, 0.24252681, 0.23963861, 0.13715535, 0.010329345, 0.09094301) * go_1(1.0, -1.0);
result += mat4(-0.20975718, -0.12550138, 0.14453574, -0.0020878632, -0.07153068, 0.3249998, -0.056577377, 0.18166828, 0.37204072, 0.17018336, 0.3752895, 0.32178587, 0.2571982, -0.27258632, -0.25971004, -0.40536007) * go_1(1.0, 0.0);
result += mat4(-0.3243907, -0.06300621, -0.09398436, -0.19549188, 0.14906861, 0.061537784, -0.055284478, 0.11281728, 0.12964857, 0.09979093, -0.1810159, -0.4104283, 0.05807971, -0.056371246, 0.08072554, 0.18479007) * go_1(1.0, 1.0);
result += vec4(-0.048888464, -0.0561434, 0.030690912, -0.030496685);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_4_tf
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.15332128, 0.027258258, 0.14900503, -0.15982795, 0.17021236, -0.51046044, -0.15287271, -0.058167327, 0.51826185, -0.34817994, 0.004513167, 0.05395769, 0.1990321, -0.049979225, 0.11391989, -0.16062729) * go_0(-1.0, -1.0);
result += mat4(0.033682905, 0.019728886, 0.19931756, 0.17381927, 0.2585768, -0.2124572, -0.014632459, 0.39779893, -0.1146207, -0.2396625, 0.08960277, 0.38345298, 0.25497693, 0.11692859, -0.14207517, 0.12667973) * go_0(-1.0, 0.0);
result += mat4(-0.14911255, 0.08910706, 0.16136818, 0.03914566, 0.24204038, -0.03607149, -0.4571109, 0.10802461, -0.0021356856, 0.00885878, 0.22297303, 0.2367231, 0.045177583, 0.11120606, -0.009971904, -0.059262395) * go_0(-1.0, 1.0);
result += mat4(0.24565999, -0.2261384, 0.47373205, 0.024613412, -0.10923052, 0.039027315, -0.42707404, -0.3783373, 0.3544573, -0.5468578, -0.27599156, -0.09455918, 0.18760219, -0.19082001, 0.030565469, 0.20589156) * go_0(0.0, -1.0);
result += mat4(0.1973198, -0.03433863, 0.059960485, 0.045642868, 0.1819595, -0.14460869, 0.1286175, 0.2067575, -0.042632047, -0.11842967, -0.11224446, -0.18764776, -0.19563004, 0.027425969, 0.24056377, 0.5949649) * go_0(0.0, 0.0);
result += mat4(0.055027682, 0.16331595, -0.2608588, 0.12545955, 0.4588985, 0.03642909, 0.22187738, 0.45190734, -0.001210133, -0.057651415, -0.061199043, 0.11935476, -0.049561135, 0.27509886, 0.13778673, -0.124914035) * go_0(0.0, 1.0);
result += mat4(-0.02257459, 0.27705106, 0.044165276, -0.26521233, 0.05982374, -0.2824302, 0.3171142, 0.08430561, -0.10155528, 0.16182268, -0.09183147, -0.19447176, 0.3295707, -0.50616395, -0.036964044, 0.23166709) * go_0(1.0, -1.0);
result += mat4(-0.0232342, 0.07299799, -0.18038079, -0.13672702, -0.108305976, 0.15024792, -0.19531927, 0.0870979, -0.26488534, 0.19481428, 0.10737945, -0.14573483, -0.33094683, 0.24155116, -0.09850332, 0.2797003) * go_0(1.0, 0.0);
result += mat4(-0.24089853, 0.19506595, 0.4799156, -0.058313113, 0.36212957, -0.44844806, 0.23864488, 0.15477742, -0.07795971, -0.0033861927, -0.11216164, 0.033454563, -0.25893036, 0.23793478, -0.15769425, -0.00033481256) * go_0(1.0, 1.0);
result += mat4(0.05772507, -0.1640253, -0.13499664, -0.20460358, -0.024399966, 0.14966168, -0.090857334, -0.039677754, 0.00036956606, -0.24236615, -0.053542696, -0.0049544116, 0.026651502, 0.39019194, -0.2742246, -0.061242323) * go_1(-1.0, -1.0);
result += mat4(-0.016323274, -0.036179908, 0.029965919, 0.11151491, -0.00016685206, -0.29573023, 0.17996423, -0.20145437, 0.1324275, -0.18442132, -0.24618152, 0.061780427, -0.02770517, 0.28452995, 0.39804098, -0.1174389) * go_1(-1.0, 0.0);
result += mat4(-0.025068847, -0.053328387, -0.27053785, 0.26866457, -0.09866204, 0.057677213, 0.01850112, -0.18014707, -0.13319959, -0.14411181, -0.26355243, -0.022209354, -0.05062645, -0.036771543, 0.13294417, -0.18458557) * go_1(-1.0, 1.0);
result += mat4(-0.046194963, 0.038230438, -0.08993043, -0.07236354, 0.11031123, -0.16504908, -0.09517036, -0.16459833, -0.5279925, 0.12686682, -0.05726125, 0.055361677, 0.31593755, 0.027328093, 0.001839602, 0.30581662) * go_1(0.0, -1.0);
result += mat4(0.08608678, 0.03168437, 0.007713377, -0.26140293, -0.1268983, 0.13395861, -0.069848835, -0.24080403, 0.018839337, -0.049821075, -0.21461345, -0.14168301, -0.0872339, 0.47096667, 0.022512507, 0.14860632) * go_1(0.0, 0.0);
result += mat4(0.06293673, 0.22462969, 0.045494985, 0.021673543, 0.18227446, -0.2956555, 0.08010543, -0.01919729, -0.012190269, 0.241983, -0.046537094, -0.40094566, -0.3853647, 0.1081711, -0.16926058, 0.16138376) * go_1(0.0, 1.0);
result += mat4(-0.14854589, -0.17625804, -0.10849075, 0.221543, 0.099971965, 0.13901573, 0.29464146, 0.020068526, 0.054358527, -0.10351705, -0.0062914286, 0.24127026, -0.16914125, 0.12729423, -0.18377453, -0.6452375) * go_1(1.0, -1.0);
result += mat4(0.12603393, -0.10986093, 0.2314103, 0.16915044, -0.13619255, -0.09349073, 0.20594226, -0.34507084, 0.19077192, 0.052500796, 0.07185645, 0.029082738, -0.015576321, 0.08254907, -0.5501743, -0.38495848) * go_1(1.0, 0.0);
result += mat4(0.09300796, -0.079218306, 0.46825135, -0.08735625, 0.06321122, 0.16234867, 0.042932414, -0.013057422, 0.09697148, 0.23457524, 0.19417483, -0.16804664, 0.18379296, 0.17770062, -0.050235, -0.059676602) * go_1(1.0, 1.0);
result += vec4(0.011169491, 0.032399546, 0.138099, 0.023857072);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_5_tf
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.22753362, -0.08612073, 0.33140692, 0.08699529, -0.18788953, -0.056579117, -0.12905197, -0.06694621, 0.054559365, 0.15031597, -0.13430363, 0.021646025, 0.14884405, -0.0694291, 0.26149413, 0.11270503) * go_0(-1.0, -1.0);
result += mat4(0.17876762, -0.09637848, 0.11285323, 0.2004893, 0.1317187, -0.036162686, 0.17958368, -0.069625, 0.28760737, -0.12505141, 0.12760694, 0.047717955, -0.16811855, -0.16340709, 0.13278298, -0.08403954) * go_0(-1.0, 0.0);
result += mat4(-0.21917523, 0.079711854, -0.28642535, 0.2822416, 0.03001489, -0.014772918, -0.3487396, 0.10597145, -0.013841082, 0.17034237, 0.10810282, -0.08089695, -0.22184245, -0.59067357, 0.44113398, 0.13045649) * go_0(-1.0, 1.0);
result += mat4(-0.29906932, 0.013923749, 0.2031124, -0.11846688, -0.13953634, 0.08003455, -0.10164494, -0.21218559, 0.10563715, 0.31033117, -0.075903505, 0.047310907, -0.37824214, -0.14506383, 0.11866701, -0.21384487) * go_0(0.0, -1.0);
result += mat4(-0.1353849, 0.19258606, 0.063908584, -0.2043788, 0.27244982, 0.1665306, -0.29357895, -0.22441709, 0.18514316, -0.17840464, 0.20986097, 0.14351055, -0.057732623, 0.42166704, -0.23182064, -0.4957248) * go_0(0.0, 0.0);
result += mat4(-0.34830126, 0.109066755, -0.28285867, -0.048280068, -0.12290918, 0.04291651, -0.047484186, -0.03702595, 0.23047262, 0.09398974, 0.022467108, 0.08271034, 0.3066665, -0.54077, 0.057771873, 0.23194093) * go_0(0.0, 1.0);
result += mat4(-0.17731948, -0.3175927, 0.1452728, 0.09396786, -0.16433562, -0.01833653, -0.22345604, -0.04161193, -0.14827462, 0.18544114, -0.15544125, -0.06179007, 0.16989979, -0.20985202, 0.16391534, -0.09447268) * go_0(1.0, -1.0);
result += mat4(-0.053878862, -0.21034616, 0.023831524, 0.19772215, 0.31647214, 0.0126534775, -0.19130844, -0.049282108, -0.21446131, 0.067189045, 0.09117449, -0.25548774, 0.12109098, 0.22009392, -0.3924665, -0.13340388) * go_0(1.0, 0.0);
result += mat4(-0.16096684, -0.18495405, 0.10410178, 0.0015673033, -0.00183498, -0.044303037, -0.062745355, -0.090802394, 0.043269135, 0.06924481, -0.21367405, -0.14619029, 0.11555763, -0.20292862, 0.5799557, 0.14739846) * go_0(1.0, 1.0);
result += mat4(-0.21030277, -0.09578802, 0.013482288, -0.21484336, 0.12995781, 0.40431052, -0.3347856, -0.18183486, 0.15550353, -0.04402301, 0.4603779, 0.14874357, -0.07694621, -0.053523075, -0.19607326, -0.10850742) * go_1(-1.0, -1.0);
result += mat4(-0.2347211, 0.2697403, -0.0634794, -0.17925987, 0.17231455, 0.24999185, -0.5208536, -0.10491828, -0.233575, 0.52950364, 0.0038063182, -0.1380038, 0.022935199, 0.19369157, 0.14586553, 0.1938704) * go_1(-1.0, 0.0);
result += mat4(-0.10245223, 0.34150192, 0.25862157, -0.20165509, 0.5597771, 0.114510864, -0.122526556, -0.04010975, 0.1704679, -0.23335956, -0.16771887, -0.03783455, -0.056995615, 0.24153493, -0.08082429, -0.24210933) * go_1(-1.0, 1.0);
result += mat4(-0.103466526, 0.15278348, -0.30526164, -0.080755696, 0.103505425, 0.15862796, 0.14696524, -0.008358076, -0.09180311, -0.12505089, 0.28052542, -0.13551563, 0.07528779, -0.09636086, -0.10369617, 0.23656134) * go_1(0.0, -1.0);
result += mat4(-0.25752836, 0.099439755, -0.30716348, 0.035077725, 0.023509016, 0.23106368, 0.05277125, 0.34910464, 0.088015385, 0.26995596, 0.1390645, -0.40671825, 0.18096298, -0.100688554, 0.5492049, 0.2482101) * go_1(0.0, 0.0);
result += mat4(0.41411775, -0.107200556, -0.13813478, 0.13768874, 0.27137747, 0.06313619, -0.08522967, 0.03218302, -0.03166121, -0.3415683, -0.52242, -0.1741813, -0.36956537, 0.179129, -0.09742935, -0.11696616) * go_1(0.0, 1.0);
result += mat4(-0.07975504, 0.17964838, 0.37122533, 0.16064765, 0.14309953, 0.29473078, 0.0926391, -0.22333665, 0.34612748, -0.3387473, 0.0077308523, -0.07239449, 0.18522519, -0.21297298, 0.11493978, 0.16117814) * go_1(1.0, -1.0);
result += mat4(-0.17402779, 0.10023144, 0.11712206, 0.031971734, 0.18713303, 0.08736295, 0.013007052, -0.06943139, -0.20102951, -0.010721135, -0.2562522, 0.34877458, -0.13732676, -0.40258047, 0.25824392, 0.15720639) * go_1(1.0, 0.0);
result += mat4(0.044494305, 0.3296108, 0.0017603852, 0.09362289, 0.38839245, 0.40015858, -0.13395199, -0.044521853, -0.56266373, 0.251378, 0.5005789, -0.13106057, -0.18491416, -0.046887, 0.067797676, -0.14694957) * go_1(1.0, 1.0);
result += vec4(0.013687534, -0.08185164, -0.04755438, 0.290178);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-3x1x1x56
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_tf
//!BIND conv2d_1_tf
//!BIND conv2d_2_tf
//!BIND conv2d_3_tf
//!BIND conv2d_4_tf
//!BIND conv2d_5_tf
//!BIND conv2d_6_tf
//!SAVE MAIN
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.08837163, -0.065234736, -0.034704313, 0.0, 0.021405501, 0.013663729, 0.019249594, 0.0, 0.05328863, 0.03580334, 0.046457592, 0.0, -0.12216048, 0.022547891, 0.016400825, 0.0) * g_0;
result += mat4(0.061996464, 0.05631466, 0.06808407, 0.0, -0.005013109, -0.0044589997, -0.032367796, 0.0, 0.016481603, 0.13721058, 0.14924648, 0.0, 0.020035887, -0.07250003, -0.08034037, 0.0) * g_1;
result += mat4(0.24078514, 0.081361525, 0.053420708, 0.0, -0.009353794, -0.051077116, -0.058007747, 0.0, -0.14071098, 0.01035966, 0.005308949, 0.0, -0.1489842, -0.06711817, -0.05552926, 0.0) * g_2;
result += mat4(-0.13002375, 0.012733757, 0.017821986, 0.0, 0.17767483, 0.20204604, 0.1751779, 0.0, 0.12804912, 0.07381453, 0.05655911, 0.0, 0.17044514, 0.07301451, 0.06523978, 0.0) * g_3;
result += mat4(-0.1170986, -0.05130371, -0.027939914, 0.0, -0.16645707, -0.121526904, -0.09471366, 0.0, -0.04143118, 0.026693767, 0.034615446, 0.0, -0.084318705, -0.064990036, -0.054324172, 0.0) * g_4;
result += mat4(0.12094524, 0.09518409, 0.07387219, 0.0, 0.062216382, 0.053228356, 0.031372335, 0.0, 0.072797105, 0.026258165, 0.009804673, 0.0, 0.120719045, 0.073281154, 0.056623302, 0.0) * g_5;
result += mat4(-0.11141495, -0.11566289, -0.10398725, 0.0, -0.0651895, -0.06820691, -0.054204144, 0.0, -0.032746475, -0.008849683, -0.007610222, 0.0, -0.024655705, -0.048778858, -0.041144755, 0.0) * g_6;
result += mat4(0.058090195, 0.07538767, 0.059722915, 0.0, 0.044788487, 0.04212742, 0.027502589, 0.0, 0.04892866, 0.015416752, 0.008312418, 0.0, -0.011864114, -0.0074752793, -0.0060824654, 0.0) * g_7;
result += mat4(0.043446552, 0.061971307, 0.05758086, 0.0, -0.06379154, -0.053758245, -0.047204215, 0.0, 0.016307736, 0.03423424, 0.030179083, 0.0, 0.041445345, 0.03843772, 0.033059113, 0.0) * g_8;
result += mat4(-0.003803544, 0.0008906116, -0.00059585314, 0.0, 0.102071285, 0.11485224, 0.10007254, 0.0, -0.074306004, -0.08803551, -0.07972321, 0.0, -0.030704215, -0.021514274, -0.009049376, 0.0) * g_9;
result += mat4(0.0066058086, 0.0011408008, 0.0016199006, 0.0, -0.03916473, -0.042929266, -0.04018418, 0.0, -0.03153446, -0.039413508, -0.034767237, 0.0, 0.113516055, 0.12577052, 0.113335624, 0.0) * g_10;
result += mat4(0.02655948, 0.041905303, 0.03861737, 0.0, 0.048471425, 0.049788587, 0.050447535, 0.0, 0.12092813, 0.13564217, 0.12613249, 0.0, -0.0023508538, 0.0012828974, 0.0028730957, 0.0) * g_11;
result += mat4(0.0084758485, 0.008800083, 0.008206044, 0.0, -0.056123603, -0.06610845, -0.060320783, 0.0, -0.081793964, -0.101638645, -0.096699014, 0.0, -0.04402356, -0.04177539, -0.03829645, 0.0) * g_12;
result += mat4(0.10676299, 0.118409514, 0.10618478, 0.0, -0.05880252, -0.06488367, -0.06432695, 0.0, 0.019221924, 0.017602798, 0.017413978, 0.0, -0.07512528, -0.080483615, -0.066218294, 0.0) * g_13;
result += vec4(-0.010478934, -0.008364784, -0.010246552, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,137 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.19288683, -0.21397883, 0.111997396, -0.04791413, -0.26682988, -0.06144587, -0.03601853, -0.16693151, 0.038494494, -0.16651472, 0.147657, -0.083003886, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.14286195, 0.08746566, -0.40107322, 0.12390977, -0.33392772, -0.18703035, -0.21326795, 0.04780781, -0.15155545, -0.0010025925, -0.1554875, -0.10676251, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.28095165, 0.022872915, -0.21342312, -0.29982176, 0.025937587, -0.055012174, -0.33779636, 0.0015666655, 0.076416336, 0.06656033, -0.1557806, 0.1078894, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.31584853, 0.07527119, 0.30713862, -0.34014285, -0.50103146, -0.07217874, 0.512807, -0.09597398, -0.32097813, -0.051580857, -0.022466356, 0.01148551, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.026032459, -0.04193211, 0.37703893, -0.031916667, -0.27421117, 1.0906446, -0.049654085, -0.19814016, 0.07819544, 0.06003738, 0.1405805, -0.0064135445, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.041450135, 0.11319654, -0.23237701, 0.08443178, 0.53344345, 0.30857387, -0.057264958, -0.1575803, 0.2325609, -0.027797326, -0.04544767, -0.18720597, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.2531829, -0.074966915, -0.27800754, -0.3146097, 0.20126024, -0.5380133, -0.15082566, -0.19021043, 0.29951036, 0.17123336, -0.01681872, -0.12574998, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.25203633, 0.19882993, 0.14906439, 0.13593598, 0.40712556, 0.084902965, 0.42969635, 0.2961132, -0.057267334, -0.030388135, 8.8084314e-05, 0.0210724, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.13459359, -0.12199573, 0.12591946, 0.24736497, 0.2033463, -0.09388599, -0.094370656, 0.1071285, -0.18479438, -0.066625565, 0.08279283, 0.20130983, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.011108127, -0.07481861, 0.07640154, 0.4964964);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.056432575, 0.0028165397, -0.026325442, -0.14802271, 0.16885762, -0.062179096, -0.2332292, 0.17513658, -0.08011296, 0.02947316, 0.014771492, -0.17946689, 0.026012989, -0.09823925, 0.036625937, -0.06924322) * go_0(-1.0, -1.0);
result += mat4(-0.13571467, 0.09831142, 0.12911566, 0.06305893, -0.07188695, -0.20161287, 0.3858435, -0.21069056, -0.12294444, -0.1404628, -0.022659872, 0.23008968, 0.10969853, 0.17640765, 0.39796907, 0.20413099) * go_0(-1.0, 0.0);
result += mat4(-0.0061665224, 0.055102807, -0.0059629944, -0.021429887, 0.061626043, 0.16898955, -0.21215646, 0.16510476, 0.2238265, 0.19429931, 0.09874656, 0.06828208, -0.122404456, -0.00026717107, -0.28203064, -0.29979932) * go_0(-1.0, 1.0);
result += mat4(-0.22735378, 0.14538136, 0.11549746, 0.194148, -0.09841722, -0.0661309, 0.348576, -0.017375294, -0.044078812, 0.1298332, 0.04793373, -0.30687734, 0.08353025, 0.083519086, 0.10766399, 0.31796935) * go_0(0.0, -1.0);
result += mat4(0.048365135, -0.17566709, -0.33212858, -0.052667376, -0.26443407, -0.010216014, 0.1573303, 0.05725314, 0.08140953, -0.09664591, 0.076109104, -0.026773714, 0.07732627, 0.10188082, -0.28266954, -0.16230233) * go_0(0.0, 0.0);
result += mat4(0.29931107, 0.117944, -0.10414009, 0.12795551, 0.12576093, 0.17082554, -0.15803693, 0.13430743, -0.025801308, -0.10797019, 0.0721032, 0.2825884, -0.11025257, 0.12798019, 0.081827976, -0.050441865) * go_0(0.0, 1.0);
result += mat4(-0.11827391, 0.08306765, -0.3430314, 0.07898041, -0.023839617, -0.019507334, 0.23176382, -0.40992323, 0.09411734, 0.38415068, -0.25845516, -0.29984522, 0.1470966, -0.0684779, -0.07071314, -0.026773235) * go_0(1.0, -1.0);
result += mat4(0.19091596, 0.082110435, -0.5266589, -0.1744098, -0.015838385, -0.046316292, 0.023171103, -0.03731331, 0.2642396, 0.31824252, -0.041754793, -0.09525519, -0.14696182, 0.052168854, 0.039857205, -0.027555354) * go_0(1.0, 0.0);
result += mat4(0.15207373, 0.09845733, 0.0142631065, 0.096375965, 0.06089903, 0.17902578, -0.42391995, 0.22475442, 0.016356342, -0.06277531, -0.12173141, -0.18635495, -0.0013459618, 0.15725887, 0.019310836, 0.20293565) * go_0(1.0, 1.0);
result += mat4(-0.18395247, 0.30672902, 0.09034339, 0.1821889, -0.0419004, -0.2169228, -0.14052129, 0.11006559, 0.1709272, 0.51062274, 0.13758625, -0.2242552, -0.030382963, 0.3357568, -0.26491287, 0.02501938) * go_1(-1.0, -1.0);
result += mat4(0.040511727, 0.12523083, -0.27318433, 0.08388512, 0.25354835, 0.3404216, -0.2632471, -0.17784123, 0.2732347, 0.4468553, 0.084667034, -0.1856242, 0.034099877, -0.00954992, -0.32751867, -0.062207516) * go_1(-1.0, 0.0);
result += mat4(0.17564747, 0.11645554, -0.16362113, 0.105654195, -0.2762563, -0.1413764, 0.23264363, -0.14000498, 0.095402054, 0.0715738, -0.19346157, -0.028285999, 0.009799127, 0.04059529, 0.19688335, 0.1282381) * go_1(-1.0, 1.0);
result += mat4(0.23575781, -0.11446148, -0.20504695, 0.035568226, 0.36890212, -0.85968876, -0.18545328, 0.33796397, -0.30916876, -0.10445518, -0.3046253, 0.33271998, -0.06263589, -0.2160114, -0.16383372, -0.31173357) * go_1(0.0, -1.0);
result += mat4(0.20469664, 0.4039374, -0.070057206, 0.030353077, 0.39843914, -0.15490077, -0.24476516, 0.38238233, -0.21809858, 0.23496576, -0.051794037, 0.033664484, -0.14411364, -0.2515329, 0.124655396, -0.05818785) * go_1(0.0, 0.0);
result += mat4(-0.09065731, -0.16787091, 0.013269188, 0.23687351, -0.41504318, -0.048163068, 0.31760025, -0.33648986, 0.29752317, 0.2926866, 0.14408836, -0.33382463, -0.15873958, -0.121961035, 0.11797893, 0.09000567) * go_1(0.0, 1.0);
result += mat4(0.13356976, 0.013763947, 0.012169505, -0.109594524, 0.03417223, 0.7031121, 0.65146804, 0.5250268, -0.50132495, -0.419648, 0.2940041, 0.83051753, -0.17595838, 0.1633008, -0.018587278, 0.079596795) * go_1(1.0, -1.0);
result += mat4(0.07570128, -0.1581438, 0.03904949, 0.14890033, -0.054611947, 0.17469402, -0.44252598, 0.036181703, -0.4981031, -0.37507218, -0.18466389, 0.2645845, 0.25189674, -0.025896115, 0.034307647, -0.020462232) * go_1(1.0, 0.0);
result += mat4(-0.11645865, 0.02296537, 0.040909223, 0.015069485, 0.062284566, -0.22526766, 0.09241534, -0.32623053, 0.18208642, 0.3954284, 0.2884468, -0.25137675, -0.037232924, -0.10185309, -0.17956531, 0.018966453) * go_1(1.0, 1.0);
result += vec4(-0.16371979, -0.024620198, -0.035754893, 0.04176776);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.01921286, -0.26684764, -0.12663573, 0.31641877, -0.25313398, 0.12264074, 0.58750325, -0.14084283, 0.5837018, -0.042300556, -0.20435576, -0.009954825, 0.060783498, 0.05540401, 0.2205112, -0.06578902) * go_0(-1.0, -1.0);
result += mat4(-0.21930243, -0.03774968, 0.22615197, 0.18338196, 0.011201461, -0.271034, 0.00573116, -0.12248194, 0.47990513, 0.2982416, -0.1087603, -0.050099242, -0.07620939, -0.07148229, 0.03691984, -0.16796488) * go_0(-1.0, 0.0);
result += mat4(-0.14962853, -0.053769328, 0.02387081, 0.22002189, 0.052237745, -0.26160842, -0.08603077, 0.012542448, 0.08119985, 0.075785555, -0.33437458, -0.43373227, -0.13206963, -0.08759176, -0.03288923, -0.09799959) * go_0(-1.0, 1.0);
result += mat4(-0.1305593, -0.5974288, 0.06058367, 0.08406488, 0.013692483, 0.06646377, 0.16469325, 0.08990975, 0.42217395, -0.11289523, -0.06165009, 0.48556912, -0.15702641, -0.19922857, -0.0035429662, -0.0022089656) * go_0(0.0, -1.0);
result += mat4(-0.1964807, 0.038099788, 0.21587034, 0.039734077, -0.07063389, 0.11604167, -0.24558097, -0.08900199, -0.7684516, -0.1037487, -0.09380674, 0.33144563, -0.16653742, 0.0028585843, -0.33774406, -0.0528696) * go_0(0.0, 0.0);
result += mat4(-0.27298656, -0.05665099, 0.09661685, 0.19780266, 0.1025106, -0.22055034, -0.21218458, -0.040628925, 0.0095010325, 0.13118382, -0.42582452, -0.22197723, 0.21006055, -0.06189587, -0.15285942, -0.09526762) * go_0(0.0, 1.0);
result += mat4(-0.14494462, -0.046788953, 0.065877035, 0.09911713, 0.35096622, 0.16682479, 0.028363144, 0.36037162, 0.29413632, 0.28212717, -0.025364442, -0.3406269, 0.047262143, -0.11892685, -0.008032766, 0.29743317) * go_0(1.0, -1.0);
result += mat4(-0.15191558, -0.36980554, 0.14555687, 0.0043930537, -0.012661432, 0.15737776, -0.115250416, 0.10324491, 0.24491951, -0.15575431, -0.27802598, 0.21959937, 0.18063772, 0.4455559, -0.09693302, 0.33382267) * go_0(1.0, 0.0);
result += mat4(0.2717801, 0.13452889, 0.14105384, 0.16324317, -0.40111846, 0.1154301, -0.0076733204, -0.09697362, 0.44306824, -0.02831414, -0.2153124, -0.12075326, 0.060776163, 0.30347148, -0.0036976219, -0.12070682) * go_0(1.0, 1.0);
result += mat4(-0.39780128, -0.29875937, -0.12952097, 0.080333896, 0.07520163, 0.021689568, -0.23121156, -0.038140096, -0.1593877, 0.017156163, -0.06038025, 0.009244022, -0.13917233, 0.30957314, 0.243109, -0.104947075) * go_1(-1.0, -1.0);
result += mat4(-0.07965157, 0.06776501, -0.13288979, 0.005851189, -0.08768168, -0.03689969, 0.12034646, 0.22441491, 0.14453568, -0.17648841, -0.3378289, -0.018329712, 0.11722939, -0.34161824, 0.08424494, -0.01400687) * go_1(-1.0, 0.0);
result += mat4(0.08153887, 0.07222914, -0.14663404, -0.038526025, -0.07385973, 0.18440577, 0.35890242, 0.17084727, 0.26345527, 0.15280858, -0.007446105, -0.024403179, -0.30336383, -0.22978698, 0.11612946, -0.23614909) * go_1(-1.0, 1.0);
result += mat4(-0.07447396, 0.09023449, -0.13798, -0.086943336, -0.30787337, 0.15087669, 0.14418626, -0.03371195, 0.048989657, -0.13075387, -0.13458036, -0.059836224, 0.06495196, 0.269715, 0.3674355, 0.38956037) * go_1(0.0, -1.0);
result += mat4(0.34981915, -0.048779126, 0.31717536, 0.38080826, -0.20149232, -0.82969636, -0.10167862, 0.6382858, 0.25976858, 0.4370118, -0.04724865, -0.10014156, 0.19380626, -0.080370255, 0.09578106, -0.035166856) * go_1(0.0, 0.0);
result += mat4(-0.026443917, 0.4132611, 0.01822534, 0.12742202, -0.26652107, -0.2996705, 0.30905882, 0.07989903, 0.38249823, 0.21486135, 0.025314959, -0.14717339, -0.13344015, -0.32088286, -0.2833883, -0.30973712) * go_1(0.0, 1.0);
result += mat4(0.021517841, 0.006556378, 0.2025686, -0.12044382, -0.38583103, -0.0027515136, -0.06556736, -0.097090125, 0.04676486, -0.11954886, -0.051612873, 0.07831412, -0.18823163, -0.16542958, 0.04245155, 0.6437998) * go_1(1.0, -1.0);
result += mat4(-0.39475346, -0.2936861, 0.26768062, -0.28151843, 0.21935691, 0.2101108, -0.15455097, 0.19548604, 0.09188909, -0.020147726, 0.103328265, -0.12574542, -0.34167948, 0.07523185, -0.17669058, 0.62446547) * go_1(1.0, 0.0);
result += mat4(-0.37661025, -0.29630858, 0.05451026, 0.1611643, 0.14079669, -0.2170294, -0.038716137, 0.13514164, -0.21235192, -0.07860726, -0.005749412, 0.025625167, -0.13297133, 0.33012658, -0.27434957, -0.18416783) * go_1(1.0, 1.0);
result += vec4(-0.0036821906, -0.050239526, -0.01355402, 0.00048220603);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-3x3x3x8
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_2_tf
//!SAVE MAIN
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.15873, 0.17989138, 0.14648493, 0.0, -0.017379675, -0.017363746, -0.019855022, 0.0, 0.009670625, 0.0070157526, 0.0075994316, 0.0, 0.025388412, 0.027231036, 0.024052646, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.048195973, 0.041760173, 0.037366055, 0.0, -0.115950756, -0.12887983, -0.12535639, 0.0, 0.032125086, 0.03397254, 0.032950625, 0.0, 0.01223746, 0.020822672, 0.0161561, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.0890567, 0.094453335, 0.09014035, 0.0, 0.016081346, 0.017434116, 0.020783134, 0.0, -0.011775135, -0.010094134, -0.018522855, 0.0, 0.072103254, 0.07940666, 0.065876864, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.04841196, -0.06963968, -0.056574684, 0.0, 0.10912542, 0.11813441, 0.10643838, 0.0, -0.013013885, -0.01562045, -0.013802797, 0.0, 0.037505716, 0.04352026, 0.04645123, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.3472869, -0.36243078, -0.33530185, 0.0, 0.23654196, 0.2305048, 0.22150646, 0.0, -0.045226905, -0.041799217, -0.042511635, 0.0, -0.10267792, -0.1123385, -0.10845448, 0.0) * go_0(0.0, 0.0);
result += mat4(0.011987401, 0.012285043, 0.007813165, 0.0, -0.15911353, -0.17523928, -0.1535267, 0.0, 0.15675929, 0.16531634, 0.15948962, 0.0, -0.09240023, -0.09513292, -0.084187366, 0.0) * go_0(0.0, 1.0);
result += mat4(0.069052905, 0.07278333, 0.0756627, 0.0, -0.012180326, -0.018794727, -0.031050753, 0.0, -0.044663202, -0.04362803, -0.038904265, 0.0, -0.008540197, -0.011201734, -0.01556625, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.08261173, -0.09042543, -0.07589266, 0.0, 0.043515377, 0.045066774, 0.04037769, 0.0, -0.06262993, -0.07469342, -0.058593787, 0.0, 0.026696987, 0.028740842, 0.037405368, 0.0) * go_0(1.0, 0.0);
result += mat4(0.07975598, 0.09597654, 0.08997132, 0.0, -0.07844719, -0.07880916, -0.06835411, 0.0, 0.05668995, 0.050163813, 0.053357534, 0.0, -0.020040333, -0.019867316, -0.01907621, 0.0) * go_0(1.0, 1.0);
result += mat4(-0.017078733, -0.017393313, -0.008266595, 0.0, -0.0033478448, -0.0027439648, -0.0042334674, 0.0, -0.06354017, -0.062058125, -0.04652064, 0.0, -0.010787706, -0.0062706997, -0.007573461, 0.0) * go_1(-1.0, -1.0);
result += mat4(-0.019895451, -0.016341688, -0.008712399, 0.0, 0.026231976, 0.023955572, 0.0216376, 0.0, -0.061950512, -0.05481285, -0.05261985, 0.0, -0.018804235, -0.016235247, -0.0131616965, 0.0) * go_1(-1.0, 0.0);
result += mat4(-0.055628926, -0.063315354, -0.057192408, 0.0, -0.0256364, -0.028660972, -0.02937357, 0.0, -0.017604912, -0.020851422, -0.016070362, 0.0, -0.0870202, -0.0832279, -0.07525406, 0.0) * go_1(-1.0, 1.0);
result += mat4(0.062738225, 0.07106593, 0.061644047, 0.0, -0.06068257, -0.06983662, -0.066070385, 0.0, 0.024919355, 0.03227179, 0.028569462, 0.0, -0.07866227, -0.098967604, -0.092128105, 0.0) * go_1(0.0, -1.0);
result += mat4(0.040397774, 0.047241107, 0.03962998, 0.0, -0.09112752, -0.10057507, -0.09301817, 0.0, 0.10833967, 0.101835825, 0.10027467, 0.0, 0.27189335, 0.27433604, 0.26781923, 0.0) * go_1(0.0, 0.0);
result += mat4(-0.044211388, -0.042373534, -0.03658007, 0.0, 0.113148406, 0.12423258, 0.107804194, 0.0, -0.17081551, -0.18562958, -0.17475435, 0.0, 0.09636739, 0.10763415, 0.093332425, 0.0) * go_1(0.0, 1.0);
result += mat4(-0.03798545, -0.047811143, -0.050768293, 0.0, 0.018775463, 0.026812987, 0.03452908, 0.0, 0.0055677597, 0.0039081173, -0.0017878668, 0.0, -0.10728597, -0.12618187, -0.109045394, 0.0) * go_1(1.0, -1.0);
result += mat4(0.06359783, 0.064184755, 0.04934199, 0.0, -0.009819327, -0.006616115, -0.007431496, 0.0, 0.025055679, 0.024787048, 0.017360551, 0.0, -0.047140837, -0.061695747, -0.06440822, 0.0) * go_1(1.0, 0.0);
result += mat4(0.060199022, 0.06482763, 0.059514645, 0.0, 0.026998974, 0.028776823, 0.024897143, 0.0, 0.17968474, 0.19337215, 0.16760105, 0.0, 0.0075838566, 0.010503482, 0.011993149, 0.0) * go_1(1.0, 1.0);
result += vec4(-0.0052927984, -0.0060193934, -0.0048643993, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,275 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.073079124, 0.11507942, 0.028201895, -0.021776304, -0.25251916, -0.08662003, 0.38814726, 0.4146095, 0.06326891, 0.01635252, 0.06423356, 0.13488062, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.059791833, -0.03105604, 0.041643705, 0.35197195, -0.17314838, 0.067622855, -0.032012507, 0.09691628, -0.11094062, 0.007625051, 0.094762206, -0.05824145, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.120281175, 0.027440755, -0.026316144, -0.025291128, -0.41698205, -0.05966847, -0.28400028, -0.06946398, -0.10906026, -0.015854035, -0.028724853, -0.06626416, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.068752654, -0.12652585, 0.38200122, 0.17978846, 0.2749825, 0.015504972, 0.21765926, 0.2246602, -0.062151223, 0.07457783, 0.13588274, -0.037328478, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(0.3718559, 0.025637252, -0.4048626, -0.41484925, 0.24768798, 0.09984098, -0.5663632, -0.6659978, 0.212067, -0.08328392, -0.5277322, -0.016879432, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.11072714, -0.010321538, 0.11265787, 0.0055236337, -0.13345073, 0.004847663, 0.3744461, -0.4038564, -0.09893075, 0.031325124, 0.2883957, -0.04268903, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.10444391, -0.60588187, 0.02543334, 0.10911738, -0.10860678, 0.15701362, 0.29235297, 0.045803998, 0.076250754, -0.10799697, 0.08841044, 0.08145623, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.04246739, -0.1225759, 0.11280305, -0.12079673, 0.5289142, -0.011892906, -0.0800465, 0.05915611, -0.126204, 0.08239301, -0.0092391195, -0.07672885, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.11922264, 0.14036109, -0.09491126, 0.05112697, -0.12543046, -0.08662423, -0.041537095, 0.048038274, 0.11672854, -0.006516173, -0.023524825, 0.030505095, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.07697861, 0.41154122, 0.042374082, -0.087270625);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.09419103, -0.1178418, 0.09523275, 0.24648252, 0.03595256, -0.05417468, -0.029167585, -0.012279932, 0.08852021, -0.12534834, 0.0604663, 0.050634373, -0.19536541, 0.21548285, 0.040379744, -0.28046605) * go_0(-1.0, -1.0);
result += mat4(-0.13783203, 0.17191975, 0.06956328, 0.005270252, -0.029844455, -0.17657366, 0.03439078, 0.048861686, 0.12017991, -0.087307535, 0.11815637, 0.31309614, 0.08440897, 0.09969244, -0.06220224, 0.2633136) * go_0(-1.0, 0.0);
result += mat4(0.098606475, -0.05856224, -0.01163882, -0.020945825, -0.08988821, 0.18520717, 0.011407763, 0.20973705, 0.21017794, 0.038311377, -0.018910313, 0.053878684, -0.08751144, -0.0081623215, 0.29060364, 0.14363094) * go_0(-1.0, 1.0);
result += mat4(0.13354321, -0.38046083, 0.14157647, 0.10190452, -0.045502663, 0.0053245644, -0.10817685, -0.048371315, 0.16157807, 0.2086147, 0.07632662, 0.24636099, -0.0053555835, -0.19587666, -0.46687222, 0.002362032) * go_0(0.0, -1.0);
result += mat4(0.28275147, -0.1468291, 0.24075283, -0.35119128, 0.18727398, 0.3833064, 0.08667899, 0.15021381, -0.092296466, -0.25686404, -0.116076745, 0.2231862, -0.27637103, 0.12317917, -0.0341737, -0.40077657) * go_0(0.0, 0.0);
result += mat4(-0.007041629, 0.18089123, -0.21195571, -0.12346183, -0.06088577, -0.30784377, 0.0048495876, 0.06013008, 0.07200418, -0.0076884073, 0.02632822, -0.0011575016, 0.21025613, -0.2573419, -0.06994815, 0.32497165) * go_0(0.0, 1.0);
result += mat4(0.0016823286, -0.014366541, -0.5049525, 0.048534572, -0.0057915323, -0.0030526456, -0.028976317, -0.16376147, -0.15560333, -0.053708192, -0.055678204, -0.13087665, 0.0048869387, 0.027514834, 0.017380254, -0.06743363) * go_0(1.0, -1.0);
result += mat4(0.044514824, -0.1754644, -0.26664957, 0.1486667, 0.114894986, 0.061640915, -0.13305616, 0.06450565, 0.03552732, 0.2835473, 0.13800526, 0.005875215, 0.15751484, 0.41759813, -0.19406971, 0.071032055) * go_0(1.0, 0.0);
result += mat4(-0.18419577, -0.05527526, 0.017057603, -0.1146602, 0.15775396, -0.01188916, 0.09368113, 0.05765405, 0.064170234, -0.017833546, 0.12100514, -0.06250493, 0.2421206, 0.15719843, 0.23718071, 0.023142194) * go_0(1.0, 1.0);
result += mat4(0.079226464, 0.07877355, -0.022315226, -0.13507473, 0.14683898, 0.028739132, -0.24479519, -0.280197, -0.13223173, 0.21732429, -0.1546993, 0.045442928, 0.163642, -0.07062695, 0.03805918, 0.060860883) * go_1(-1.0, -1.0);
result += mat4(0.095216066, -0.16650215, -0.34863555, -0.025274571, 0.3064775, -0.034196265, -0.25773287, 0.19570488, -0.005434017, 0.26308087, 0.009404902, -0.24736062, 0.05558232, -0.014217521, 0.03667355, -0.15134114) * go_1(-1.0, 0.0);
result += mat4(-0.074846864, 0.010901994, 0.035149742, 0.12106729, -0.36042807, -0.011231913, 1.4317516, 0.6400351, 0.105860665, -0.11587906, -0.11065066, 0.19126756, 0.14132085, 0.021570992, -0.3618735, -0.081163004) * go_1(-1.0, 1.0);
result += mat4(-0.06937371, 0.3815214, 0.026842717, -0.04051589, -0.09472515, -0.027198657, -0.16502109, 0.114273794, -0.15207845, -0.15054241, -0.25099036, -0.10871029, 0.14311226, 0.07640166, 0.47051275, 0.0447809) * go_1(0.0, -1.0);
result += mat4(-0.25960425, 0.11150338, -0.042022616, -0.006633396, -0.29595324, -0.0149574205, 0.09806478, 0.03635802, 0.26789796, 0.41416678, 0.05145585, 0.61168057, 0.019582301, -0.118703716, 0.13974573, 0.04498941) * go_1(0.0, 0.0);
result += mat4(-0.04119621, -0.15503803, 0.33170196, -0.1158483, -0.06258357, 0.2574262, -0.07890287, -0.6929032, 0.004379942, 0.097908296, 0.009286624, 0.27194506, -0.2476702, 0.13828708, 0.05071857, -0.43693772) * go_1(0.0, 1.0);
result += mat4(-0.010842703, 0.13108006, 0.30126816, 0.20221521, 0.018797455, 0.0614624, 0.11102966, 0.019204421, 0.09975456, 0.04676902, -0.044540443, 0.118877, -0.04871634, -0.089208096, 0.027455999, 0.029557817) * go_1(1.0, -1.0);
result += mat4(-0.10421777, 0.3135469, 0.14557797, 0.0497297, 0.0034963787, -0.20342828, 0.08332032, -0.09004643, 0.06574797, -0.14168271, -0.08754358, 0.30385306, -0.3374016, -0.4360316, 0.15854433, -0.24081887) * go_1(1.0, 0.0);
result += mat4(0.1407836, 0.09678823, -0.02240152, -0.013985894, 0.012281648, -0.24124922, -0.46433777, -0.25915003, 0.042200714, -0.21701157, -0.016618999, 0.13135725, -0.34656256, -0.034729004, -0.29246503, -0.13514486) * go_1(1.0, 1.0);
result += vec4(-0.08458621, -0.023144595, -0.057707336, -0.081382714);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.27743992, 0.04277345, 0.019331178, -0.7335445, 0.006292013, 0.19800001, -0.0025032016, 0.16098699, -0.03186617, -0.060173523, 0.08878855, -0.10669283, 0.130609, -0.068515256, -0.03571823, -0.13751523) * go_0(-1.0, -1.0);
result += mat4(-0.2430821, -0.08233978, 0.082374334, 0.04843392, -0.18989052, -0.041925047, 0.40021122, -0.317836, -0.13517766, 0.032255337, -0.0746507, 0.22114721, -0.045706213, -0.12841983, -0.27830583, 0.05763077) * go_0(-1.0, 0.0);
result += mat4(-0.08436965, -0.04967552, -0.16798134, -0.1539139, -0.17429228, -0.10166739, 0.35864773, 0.12873615, -0.07667423, 0.04985163, 0.13391761, -0.054322604, 0.085659124, -0.078792974, 0.06481059, 0.058667548) * go_0(-1.0, 1.0);
result += mat4(-0.17568155, 0.56705236, 0.056562193, -0.020951264, 0.005879628, -0.2502103, -0.19619654, 0.019490348, -0.14527243, 0.16983634, 0.049245857, 0.18316677, 0.055053137, 0.10699275, 0.0016993808, 0.20105995) * go_0(0.0, -1.0);
result += mat4(0.36284775, -0.05856962, -0.42545465, 0.31931567, -0.15698905, -0.28837132, -0.028697362, -0.024917847, 0.04317283, 0.024557106, -0.052158598, 0.38654143, -0.1782944, 0.43094924, -0.11738149, 0.21554618) * go_0(0.0, 0.0);
result += mat4(0.22645079, -0.20319854, 0.20733371, -0.18697177, -0.05167819, -0.12845007, 0.5543688, 0.2453291, 0.08027872, -0.0628224, -0.06593836, -0.05795855, -0.24527508, 0.23632833, -0.043366548, 0.14135826) * go_0(0.0, 1.0);
result += mat4(0.08384414, 0.20807321, 0.030559694, -0.13640808, -0.07641805, -0.10919174, -0.19799095, -0.12955745, 0.093737304, -0.17856954, 0.035103753, -0.044699315, -0.07255943, -0.02331535, 0.2059249, 0.3058302) * go_0(1.0, -1.0);
result += mat4(0.022345139, 0.16286038, -0.27228013, -0.41105714, -0.0014384583, 0.089546144, -0.08296848, -0.0050463285, 0.07038578, -0.030679917, 0.031246305, 0.36761853, -0.34799108, -0.0405689, -0.19182852, 0.015853593) * go_0(1.0, 0.0);
result += mat4(0.1021783, -0.11396049, -0.08733628, -0.017449526, 0.042015605, -0.14808236, 0.10072531, -0.07403295, 0.15276712, -0.07807765, -0.10013386, -0.26110634, -0.04858846, 0.066066965, 0.13598624, 0.21687816) * go_0(1.0, 1.0);
result += mat4(0.07041569, -0.17775945, 0.15697548, -0.15425202, -0.06569677, -0.033233996, 0.22596005, -0.026170855, -0.20729817, 0.1316505, -0.058410037, 0.22166035, 0.09107114, -0.13078825, -0.05639485, -0.02716142) * go_1(-1.0, -1.0);
result += mat4(0.057966787, -0.15311252, 0.095924966, -0.055951685, 0.082777694, -0.08471956, -0.39918202, 0.10599212, 0.102710955, 0.21808124, 0.12083635, -0.38835892, 0.031709857, 0.13955092, 0.12647778, 0.011549966) * go_1(-1.0, 0.0);
result += mat4(0.09810508, -0.119743295, 0.06166254, 0.13595435, 0.036198203, -0.028710455, -0.40789905, -0.034894038, -0.12622337, 0.14379597, 0.039958883, 0.19636424, 0.047094557, -0.07987105, -0.04905092, -0.07875785) * go_1(-1.0, 1.0);
result += mat4(0.34118712, -0.2833933, -0.045028314, -0.40670308, -0.01961924, 0.37131935, 0.29099533, -0.19843055, 0.18604252, -0.0037280058, 0.1091072, -0.40579233, 0.11422739, -0.16490164, -0.0022396361, -0.21486944) * go_1(0.0, -1.0);
result += mat4(0.0010853866, 0.2223109, 0.2416471, -0.33326814, 0.2549397, 0.6442047, 0.18411863, -0.19081281, -0.43552014, -0.1793875, -0.58699155, -0.01900168, -0.26955804, -0.071371995, 0.07599079, 0.27434483) * go_1(0.0, 0.0);
result += mat4(-0.19644544, 0.14383379, -0.2599538, 0.001666124, -0.16369823, 0.009537702, -0.3690974, -0.048157427, -0.2040159, 0.01522431, -0.11007749, -0.07012568, 0.17536888, -0.012183123, -0.17366478, -0.15090804) * go_1(0.0, 1.0);
result += mat4(0.0855136, 0.06863859, -0.17249937, -0.12850079, 0.15325847, 0.22742507, 0.22535504, 0.24032994, -0.109522276, 0.24135293, -0.17784368, 0.08172238, -0.16143093, 0.1358853, -0.09399085, 0.012180792) * go_1(1.0, -1.0);
result += mat4(-0.04346881, 0.13367178, 0.10387612, 0.04705543, -0.10315795, 0.5816371, -0.090529, -0.017955385, -0.09032907, -0.52505773, 0.10958755, -0.26151448, 0.17246644, 0.011886279, 0.3566306, 0.32170597) * go_1(1.0, 0.0);
result += mat4(-0.27853554, 0.1558035, 0.070289604, 0.17052644, -0.31982365, 0.29085326, -0.09494764, 0.2270542, 0.10514691, -0.24606484, -0.02049181, 0.126686, 0.16719124, 0.013080999, -0.08577963, -0.07057233) * go_1(1.0, 1.0);
result += vec4(0.0061747693, -0.029145364, -0.026801255, 0.027419873);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_2_tf
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.07447851, -0.07888509, -0.28236163, 0.2479792, -0.065199964, 0.24733023, 0.099619575, -0.26430824, -0.03523585, -0.03547245, -0.10619345, -0.25326422, -0.116270036, -0.065133184, -0.30401528, 0.01563764) * go_0(-1.0, -1.0);
result += mat4(-0.19106275, -0.26104823, -0.14457102, -0.17298317, 0.24148639, -0.10950928, 0.062851585, 0.042540826, 0.13287601, 0.06975747, 0.15848075, -0.3854902, -0.13132331, -0.16468687, -0.029844414, 0.27754608) * go_0(-1.0, 0.0);
result += mat4(0.015378025, -0.14203559, 0.08058816, 0.32896644, -0.074871175, -0.26611313, -0.18830848, 0.091641426, -0.16522385, -0.23424402, -0.12279703, -0.13343342, -0.2509982, -0.0554576, 0.07286022, -0.028823337) * go_0(-1.0, 1.0);
result += mat4(-0.13543738, 0.049395677, -0.015148539, 0.13301241, -0.12827122, -0.012590744, 0.012936948, 0.008271658, 0.12442749, 0.3497426, -0.16126058, -0.2670464, -0.010479037, 0.07037434, -0.15527055, 0.13205245) * go_0(0.0, -1.0);
result += mat4(-0.09535385, -0.3931354, 0.24716614, -0.21284536, 0.14652656, 0.38149378, -0.09607391, 0.06350967, 0.48615915, 0.32634613, 0.146291, 0.2566475, -0.40927815, -0.05268087, -0.04110691, -0.0068722935) * go_0(0.0, 0.0);
result += mat4(0.089152284, -0.01860622, 0.016856732, 0.31244752, 0.022529159, -0.0071319416, -0.09786801, -0.13005258, 0.1524636, 0.21627748, -0.07395979, -0.087633945, -0.38435504, -0.08842507, -0.0058702417, -0.32936293) * go_0(0.0, 1.0);
result += mat4(0.0816838, 0.0012210817, 0.28217188, 0.36141106, 0.0014665248, -0.0636269, 0.042035818, -0.056671552, -0.032501306, -0.22908778, -0.2067977, -0.004497514, -0.23052917, 0.26728114, 0.15353456, -0.17732324) * go_0(1.0, -1.0);
result += mat4(-0.17229734, 0.0818218, -0.10076918, 0.030027041, -0.14819005, -0.085340135, 0.050100215, 0.05683199, -0.12653661, -0.036583595, -0.32319903, -0.15273796, -0.15346588, 0.20005536, 0.23097478, -0.19834782) * go_0(1.0, 0.0);
result += mat4(0.055430107, -0.2886931, 0.361814, 0.33160287, -0.084407054, 0.06254009, -0.02332793, -0.018134018, -0.014879812, 0.112346604, -0.20686437, -0.23408228, -0.01091196, -0.062669374, 0.085567676, 0.23738655) * go_0(1.0, 1.0);
result += mat4(0.080383554, -0.1172084, 0.19703126, 0.27777427, -0.07559937, -0.25445858, 0.3450109, -0.071967736, 0.2034805, 0.33716002, 0.15314537, -0.22953224, 0.113631405, -0.0058444734, 0.2890972, 0.06557255) * go_1(-1.0, -1.0);
result += mat4(-0.17646056, -0.025448758, -0.14952567, 0.017148364, -0.15238142, 0.1435677, 0.20273875, 0.22255951, -0.011660059, -0.003515217, -0.17305166, -0.13478355, -0.06558679, -0.032662887, -0.20914736, -0.5397283) * go_1(-1.0, 0.0);
result += mat4(0.1679393, -0.109410115, -0.117427185, 0.14982319, -0.06457877, -0.06607065, 0.0018200208, -0.0118605625, 0.046539318, -0.020642165, -0.14413542, -0.09530688, 0.22196163, -0.2187166, -0.10759705, 0.013234591) * go_1(-1.0, 1.0);
result += mat4(-0.13220267, -0.12540027, 0.26163217, 0.12791659, 0.16204996, -0.4023048, -0.13485721, -0.10187536, 0.059764992, 0.048170995, -0.25281772, 0.2090587, -0.06542371, -0.10791867, -0.21286209, -0.309109) * go_1(0.0, -1.0);
result += mat4(0.16233061, 0.120428756, -0.11460241, 0.24531102, -0.2670459, -0.24195078, -0.20964348, -0.12930301, -0.2343609, -0.22543164, -0.28909695, -0.33560297, 0.6009884, 0.39171818, -0.1276308, -0.020736236) * go_1(0.0, 0.0);
result += mat4(0.40162864, 0.045881115, 0.032667033, 0.31454235, -0.17351128, -0.009387306, 0.17341217, 0.30810982, -0.025815086, -0.10390133, 0.012544771, 0.036918722, 0.34386298, 0.23177734, -0.046727546, 0.20098232) * go_1(0.0, 1.0);
result += mat4(0.11541034, -0.11647824, -0.12874861, 0.004921287, -0.13921295, -0.25733355, -0.1112383, -0.033295818, 0.0035326157, 0.3782048, 0.055785846, -0.1547331, 0.17358719, -0.2789715, -0.13400431, 0.1583795) * go_1(1.0, -1.0);
result += mat4(0.4130191, -0.33944547, -0.064674884, 0.39617148, -0.11483455, -0.022601767, 0.1129301, -0.09713594, 0.14681247, 0.34442267, 0.08721343, -0.08309499, 0.088704996, -0.20943391, -0.2891408, 0.1709401) * go_1(1.0, 0.0);
result += mat4(0.19503653, 0.17490312, -0.23491044, -0.028934423, 0.04479765, -0.0334551, 0.0602648, 0.0019939998, 0.23314747, 0.21557438, 0.07273092, 0.15467109, -0.11194509, 0.0736583, -0.17628083, -0.3851578) * go_1(1.0, 1.0);
result += vec4(0.022887055, 0.01521631, 0.17967467, -0.0131908795);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_3_tf
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.10233342, -0.30233157, 0.24238978, -0.007108631, 0.14248851, 0.08486557, 0.0028373515, 0.122387215, 0.10996857, -0.17286511, 0.19819227, 0.07023527, 0.07579955, -0.16861476, -0.210025, 0.12760942) * go_0(-1.0, -1.0);
result += mat4(0.091181986, -0.41497424, -0.27567792, -0.09938067, -0.12210428, 0.20617811, -0.017644284, -0.22552875, 0.049019493, -0.18990634, 0.11057753, -0.043193225, -0.15278774, -0.18331046, -0.1837594, 0.029758787) * go_0(-1.0, 0.0);
result += mat4(-0.1757096, -0.199691, -0.034743477, -0.15369363, -0.1701244, -0.0459655, 0.10508695, -0.09795581, 0.13464944, 0.37202564, 0.14706515, 0.23416734, 0.08302458, 0.20696343, -0.13935481, 0.03092827) * go_0(-1.0, 1.0);
result += mat4(-0.49887478, 0.24046332, -0.029459715, 0.17374687, -0.15019977, 0.31086043, -0.28297687, -0.22239804, 0.12314376, 0.11594825, 0.17682782, 0.09753434, -0.26263535, -0.12739435, -0.57744014, 0.087381124) * go_0(0.0, -1.0);
result += mat4(0.08101439, -0.16185337, 0.112901986, 0.24439482, -0.44051018, -0.70680356, -0.23015513, 0.63106006, -0.08817581, -0.057614524, 0.15352182, -0.049620207, 0.17742544, -0.49583626, -0.3844133, 0.18385352) * go_0(0.0, 0.0);
result += mat4(0.17149475, 0.31255633, 0.19286609, 0.21052869, -0.11856372, -0.032373343, 0.06503625, -0.31664965, 0.040755365, -0.027614031, -0.33330554, 0.40148625, 0.056921627, -0.27068445, 0.047014963, 0.103712596) * go_0(0.0, 1.0);
result += mat4(-0.09326643, 0.13677256, 0.06390537, 0.08080093, -0.10685094, 0.124757454, 0.14696303, 0.10871933, -0.10971212, 0.01655797, -0.11052674, -0.17361104, 0.015513338, -0.1917502, -0.26384255, -0.022672707) * go_0(1.0, -1.0);
result += mat4(0.032367155, -0.087523445, -0.06951093, -0.08128242, 0.2627859, 0.14933161, 0.3114999, -0.007791172, -0.4146471, -0.2530298, -0.43175155, -0.06878434, 0.5724947, 0.25498095, 0.4838959, 0.15076154) * go_0(1.0, 0.0);
result += mat4(-0.13427481, -0.10134261, 0.087439895, 0.015921364, 0.15421022, 0.20205952, 0.22928835, -0.07339068, -0.33318612, -0.17467582, -0.04758165, 0.11858059, 0.17408857, -0.099393494, -0.06389948, -0.06494366) * go_0(1.0, 1.0);
result += mat4(0.15349221, 0.08508258, -0.09294437, -0.03204993, -0.22561033, -0.15088828, -0.020105945, 0.10041996, -0.024723593, 0.06610271, -0.24423431, -0.050512858, -0.100530736, 0.16394953, 0.16365045, -0.012055956) * go_1(-1.0, -1.0);
result += mat4(0.16342951, 0.23113559, 0.21289586, 0.28391558, 0.052211206, -0.17983536, -0.008415342, 0.08977486, -0.054481823, 0.17506577, -0.14162593, -0.070448756, 0.093877845, 0.05161232, -0.25006327, 0.007014646) * go_1(-1.0, 0.0);
result += mat4(0.104965575, 0.20048036, 0.024134496, 0.5442797, 0.19958296, -0.05165447, 0.076928124, 0.030868227, -0.0563495, -0.19757621, 0.10801544, -0.24202053, 0.0067657093, -0.17784451, -0.03134409, -0.06741009) * go_1(-1.0, 1.0);
result += mat4(0.33347723, -0.12338564, 0.23495969, -0.23091966, 0.059872203, 0.028045453, -0.06781438, 0.111325614, -0.21861015, -0.030451605, -0.04267672, -0.0039260075, 0.0911101, 0.054191053, -0.08498816, 0.04810343) * go_1(0.0, -1.0);
result += mat4(-0.05028896, 0.21515386, 0.16005337, -0.32279232, 0.19178568, 0.779363, -0.12682606, -0.4378189, 0.37980273, 0.063021325, 0.19370794, -0.05618088, -0.00088428083, 0.29736623, 0.24649377, -0.0021625878) * go_1(0.0, 0.0);
result += mat4(-0.45007992, -0.16040307, -0.1714593, -0.16251564, 0.070867635, 0.21317895, -0.070962, 0.17147541, -0.27786884, -0.33259448, -0.022767346, -0.17967366, 0.21208113, 0.19740848, 0.16877973, 0.09630738) * go_1(0.0, 1.0);
result += mat4(0.09235827, -0.35231477, -0.093315996, -0.035850406, -0.08311695, 0.054329164, 0.17788444, -0.020736141, -0.03739786, -0.1678283, 0.12676615, 0.17182353, 0.17408027, 0.07699043, 0.095501214, 0.0069830767) * go_1(1.0, -1.0);
result += mat4(-0.16631392, -0.16925642, -0.17081848, 0.017719474, -0.20530944, 0.19215193, -0.039511178, -0.08296625, 0.2240653, 0.100644305, 0.2901835, 0.32166973, -0.10026419, -0.14864013, -0.19926691, -0.11607018) * go_1(1.0, 0.0);
result += mat4(-0.13750182, 0.07445518, -0.033964884, -0.085812084, -0.03903257, -0.054933593, 0.06765632, 0.064338475, 0.27182797, 0.07721309, -0.0334218, -0.19344835, -0.14405386, 0.046106674, 0.16596143, 0.0879945) * go_1(1.0, 1.0);
result += vec4(0.049844168, 0.02670437, 0.050967637, -0.10779561);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_4_tf
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.034357008, 0.00082413113, -0.13382089, -0.05066409, 0.26684088, -0.31363875, 0.073608615, 0.20824149, 0.21509308, -0.07118628, 0.11287014, -0.09817389, 0.16107765, 0.17146803, -0.13836654, -0.05962866) * go_0(-1.0, -1.0);
result += mat4(0.029981667, 0.08738892, 0.17735903, 0.15817277, 0.041752994, -0.20031185, 0.064203605, 0.48786053, -0.0033609737, -0.42522693, 0.058846988, 0.22180536, 0.17181319, 0.13097888, -0.059532285, 0.062227458) * go_0(-1.0, 0.0);
result += mat4(0.13188283, 0.07971828, 0.28278515, 0.038570832, -0.12815465, 0.29860008, -0.2785862, -0.07612298, -0.14369671, 0.12457525, 0.11982623, -0.018675303, 0.14936846, 0.1284789, -0.0042489986, 0.042810377) * go_0(-1.0, 1.0);
result += mat4(0.2892425, -0.20834558, 0.07358541, -0.11190968, -0.16300741, 0.15674856, -0.04297203, -0.29218298, -0.036296643, -0.052267153, -0.22889943, -0.21203549, -0.03553075, -0.20343691, -0.07413655, -0.092966415) * go_0(0.0, -1.0);
result += mat4(0.2484468, -0.23412868, -0.070199326, 0.2061922, 0.5047224, -0.48216155, -0.5792768, 0.610787, 0.023935676, -0.040435325, -0.1238493, -0.09576053, -0.26183444, 0.14510648, 0.5365255, 0.5499143) * go_0(0.0, 0.0);
result += mat4(-0.058255754, 0.08133753, -0.18663554, 0.26190025, 0.26006857, -0.007619795, 0.14585225, 0.073580734, -0.0396066, 0.2821596, 0.31778112, -0.029853562, -0.19703479, 0.17809318, 0.21089044, -0.106730856) * go_0(0.0, 1.0);
result += mat4(0.20549655, -0.05962688, 0.1432124, 0.013446325, -0.19064854, 0.061387196, 0.1792527, 0.0010619498, -0.1456842, 0.18950678, -0.13990986, -0.37644413, -0.083257, -0.2937246, 0.032096215, 0.14719158) * go_0(1.0, -1.0);
result += mat4(-0.26601696, 0.4242341, -0.073702715, -0.3221337, 0.026037043, -0.0117916465, -0.024286825, 0.23183465, -0.030869482, -0.045915652, -0.040611852, 0.11372697, -0.25404635, 0.21859063, 0.13869432, 0.19651218) * go_0(1.0, 0.0);
result += mat4(-0.028276298, -0.11217159, 0.27144867, -0.010531775, 0.11032058, -0.09957206, 0.12570262, 0.14724332, 0.08758557, -0.11042305, 0.025948172, -0.010910802, -0.029466914, -0.041135952, -0.017091949, 0.05501236) * go_0(1.0, 1.0);
result += mat4(-0.12688768, -0.19051413, 0.052141912, -0.13618521, -0.16320245, -0.1601866, 0.16207355, -0.023218745, 0.2103894, -0.06212745, -0.07042835, 0.0996637, -0.1763831, 0.13890013, -0.12125462, -0.105104685) * go_1(-1.0, -1.0);
result += mat4(0.10485512, -0.49283037, -0.504295, 0.009089699, -0.17389494, -0.12835866, 0.14188384, -0.22946316, 0.006298799, -0.0348454, -0.0852419, 0.17956656, -0.08088888, 0.35675287, 0.16014415, -0.055372503) * go_1(-1.0, 0.0);
result += mat4(-0.17157564, 0.1557075, -0.17681694, 0.14834762, -0.13708206, 0.101721555, 0.17070566, -0.22522852, 0.08100986, -0.23204406, -0.38926315, -0.13165781, 0.1040296, -0.045591615, 0.15745829, -0.10410621) * go_1(-1.0, 1.0);
result += mat4(-0.20517144, 0.35896194, -0.0010962893, -0.18043008, -0.016253468, 0.035292216, 0.06781499, 0.015984116, -0.20261237, -0.28905126, 0.007414641, 0.008870292, 0.52166605, -0.0996688, -0.23151648, 0.2811893) * go_1(0.0, -1.0);
result += mat4(0.013482173, -0.04891998, -0.06094191, -0.14416319, -0.00087873987, 0.11979091, 0.06457245, -0.2305623, -0.1476981, 0.2634587, -0.058895197, -0.07394766, -0.27173743, 0.7530214, 0.037599873, 0.22086331) * go_1(0.0, 0.0);
result += mat4(-0.10357755, 0.23899554, 0.034912035, -0.14336212, -0.019786308, -0.085470654, -0.03096524, 0.108783185, 0.28971127, 0.24527478, -0.19110362, -0.49510127, -0.15574701, 0.10968643, -0.13727877, 0.04502924) * go_1(0.0, 1.0);
result += mat4(-0.10808282, -0.079148844, -0.3244773, -0.2210664, -0.0062175165, 0.1303082, 0.012592612, -0.38039228, 0.26899642, -0.16624425, -0.04438198, 0.42067865, -0.13381268, 0.03408099, -0.2999706, -0.3817641) * go_1(1.0, -1.0);
result += mat4(0.030872926, -0.26852018, -0.14650428, 0.16869825, -0.19185568, -0.06341456, 0.12261606, -0.26597574, 0.44865233, 0.21416639, 0.40359476, 0.12814924, 0.2542566, -0.23348318, -0.43142912, -0.35113996) * go_1(1.0, 0.0);
result += mat4(-0.03364283, 0.11002299, 0.3311268, -0.14580412, -0.10348537, 0.13331696, -0.0793144, -0.04116661, 0.040704627, -0.14875266, -0.09259674, -0.062087066, 0.063962296, 0.18420577, -0.085616685, -0.16555141) * go_1(1.0, 1.0);
result += vec4(-0.037546165, -0.015675364, 0.13989694, 0.027605768);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_5_tf
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.35835463, 0.038305778, -0.10198824, -0.021951782, 0.02142098, -0.072417736, -0.2577152, 0.054713376, 0.075116105, -0.21191697, -0.1213158, -0.105036296, 0.12030758, -0.17591658, 0.1726511, 0.17754573) * go_0(-1.0, -1.0);
result += mat4(0.32325825, 0.19869742, 0.333873, 0.39670366, 0.20716824, 0.09557955, 0.120742686, -0.2271023, 0.37509173, -0.031341635, 0.10247365, 0.031520665, -0.092765376, -0.13535516, 0.8333728, 0.05886494) * go_0(-1.0, 0.0);
result += mat4(-0.17573749, 0.16768494, 0.021141645, 0.19668253, 0.21080776, 0.31503728, -0.26834, 0.19103156, 0.21946241, 0.14559007, -0.09761235, -0.23565038, -0.49393657, -0.5332298, 0.09806347, 0.054431103) * go_0(-1.0, 1.0);
result += mat4(-0.042109374, -0.05564321, 0.27586877, 0.010382545, 0.007322007, 0.13193823, -0.18262729, 0.06399193, 0.14174329, 0.3898842, -0.10398105, 0.01846146, -0.24542394, -0.13020967, -0.16491668, -0.03544872) * go_0(0.0, -1.0);
result += mat4(-0.15291597, 0.1566557, 0.14745249, -0.23258151, 0.17843612, 0.15885495, -0.691466, -0.41177312, 0.40330106, -0.07991953, 0.2832403, 0.10656986, -0.19571523, 0.3670614, -0.62296015, -0.5666968) * go_0(0.0, 0.0);
result += mat4(-0.17513512, 0.011393021, -0.44352317, -0.059153114, -0.2227142, -0.033094753, 0.09624524, 0.051315393, 0.2632246, 0.09945105, 0.042561427, -0.1234722, 0.23755905, -0.506999, 0.114180565, 0.27887583) * go_0(0.0, 1.0);
result += mat4(-0.459564, -0.120326266, 0.17507194, 0.06701153, -0.14124362, -0.36653697, -0.2856802, -0.22955593, -0.08515889, 0.18788262, 0.23427077, 0.021544341, 0.31996533, -0.2668834, 0.08469808, -0.01347926) * go_0(1.0, -1.0);
result += mat4(-0.14092083, -0.31244513, -0.044023518, 0.013948701, 0.33119613, -0.011959397, -0.1494438, -0.111066826, -0.11994278, 0.116068155, -0.13032633, -0.037004936, 0.13851176, -0.006655432, -0.39841232, -0.079951204) * go_0(1.0, 0.0);
result += mat4(-0.08959123, 0.18297827, -0.0763483, 0.11364159, -0.04361797, -0.029816678, -0.19314721, -0.03484794, 0.044681285, 0.04669291, -0.30017474, -0.07453036, 0.090825416, -0.27414632, 0.36355078, 0.15742934) * go_0(1.0, 1.0);
result += mat4(0.18470702, 0.113800436, -0.18546791, 0.044184085, 0.12490399, 0.1826781, -0.01313173, -0.19048993, -0.026458051, -0.1693334, 0.21958382, 0.030458853, -0.059242606, 0.039351143, -0.061676584, -0.06904634) * go_1(-1.0, -1.0);
result += mat4(-0.114877924, -0.03781683, -0.19207929, 0.007679428, 0.2409049, 0.2965285, -0.38395065, 0.11604976, -0.22588749, 0.48505852, 0.09866521, -0.2585994, -0.011380872, -0.018334057, -0.047188547, 0.3038583) * go_1(-1.0, 0.0);
result += mat4(-0.2783936, -0.17609318, 0.4904369, -0.31848624, 0.39725313, 0.082951784, -0.15595853, -0.007526218, 0.2355193, -0.30003366, -0.27686292, 0.120900005, -0.1223885, 0.40760317, 0.0013726618, -0.24877374) * go_1(-1.0, 1.0);
result += mat4(0.1580051, -0.044973504, 0.00053594523, -0.057797022, 0.18895927, 0.23527777, -0.18095906, -0.076961614, 0.2544444, -0.05932328, 0.13717431, -0.024487074, 0.33157274, -0.09072586, -0.004386734, -0.05180953) * go_1(0.0, -1.0);
result += mat4(-0.21685815, 0.061656334, -0.066127226, 0.24831405, 0.26001146, 0.046466008, -0.047196623, 0.13538954, -0.06449239, 0.45951647, -0.13132116, -0.7079741, -0.06683439, -0.47628635, 0.42461708, 0.6475073) * go_1(0.0, 0.0);
result += mat4(0.2590011, -0.26020283, 0.0005333198, 0.01555692, 0.37920526, 0.29205114, -0.20281325, -0.1455974, 0.056119893, 0.022032745, -0.30095813, 0.48154855, -0.35761952, 0.07582935, 0.12462687, 0.068093665) * go_1(0.0, 1.0);
result += mat4(0.20434918, 0.26690874, 0.028224666, 0.042565826, 0.037406113, 0.5059272, -0.0047208676, 0.0019095197, 0.16626422, -0.23407575, -0.072687164, 0.00063299487, -0.10172441, -0.11645544, 0.008715937, -0.012423992) * go_1(1.0, -1.0);
result += mat4(0.08269191, 0.116322584, -0.08155921, -0.04790326, 0.09546776, 0.3632936, -0.08139031, -0.10399187, 0.06618616, -0.26862565, 0.25058737, 0.0410593, -0.07191658, -0.20559746, 0.21857823, 0.12776822) * go_1(1.0, 0.0);
result += mat4(0.54989135, 0.38051483, 0.015739547, -0.0068143173, 0.26107135, 0.2585036, -0.12345306, -0.13934542, -0.19018838, 0.2730626, 0.42644337, 0.16693048, -0.15189888, 0.023638237, 0.11272267, 0.039560657) * go_1(1.0, 1.0);
result += vec4(-0.20554838, -0.10647836, -0.02824578, 0.08658529);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(M)-Conv-3x1x1x56
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_tf
//!BIND conv2d_1_tf
//!BIND conv2d_2_tf
//!BIND conv2d_3_tf
//!BIND conv2d_4_tf
//!BIND conv2d_5_tf
//!BIND conv2d_6_tf
//!SAVE MAIN
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.030150581, -0.002168429, 0.014918388, 0.0, 0.020940892, 0.04591048, 0.049137186, 0.0, 0.111167125, 0.05311203, 0.0625381, 0.0, 0.020043287, 0.04785493, 0.040921766, 0.0) * g_0;
result += mat4(0.04158565, -0.008488135, 0.0020472286, 0.0, 0.049123142, -0.055042226, -0.06489915, 0.0, 0.09238876, 0.10387972, 0.09576964, 0.0, -0.054776173, -0.098954335, -0.09018853, 0.0) * g_1;
result += mat4(0.2081418, 0.08273068, 0.040325668, 0.0, -0.09937802, -0.13162258, -0.13989717, 0.0, -0.13983749, 0.01309777, 0.0023888077, 0.0, -0.18937743, -0.07021057, -0.047152344, 0.0) * g_2;
result += mat4(-0.09646629, 0.080605574, 0.10463501, 0.0, 0.22579835, 0.24077554, 0.22600271, 0.0, 0.049726978, 0.015292378, -0.0047161994, 0.0, 0.16281025, 0.048491795, 0.038338162, 0.0) * g_3;
result += mat4(-0.09772107, -0.043998875, -0.054745924, 0.0, -0.1257736, -0.13175423, -0.10889618, 0.0, -0.015900036, 0.07074481, 0.08210496, 0.0, -0.11321135, -0.12526917, -0.105605066, 0.0) * g_4;
result += mat4(0.14187162, 0.14032297, 0.13016908, 0.0, 0.018954534, 0.016011704, 0.010169183, 0.0, 0.04762765, -0.044460997, -0.06499567, 0.0, 0.11133751, 0.09464176, 0.08865274, 0.0) * g_5;
result += mat4(-0.16567162, -0.1744712, -0.1637222, 0.0, -0.02412003, 0.0074480795, 0.007903436, 0.0, -0.06161098, -0.046788957, -0.03971239, 0.0, 0.030736001, 0.036460854, 0.03660504, 0.0) * g_6;
result += mat4(0.084027, 0.10024112, 0.08152756, 0.0, 0.005087354, -0.026047802, -0.027264625, 0.0, 0.10519243, 0.08977278, 0.077558964, 0.0, -0.052826345, -0.06602686, -0.055083472, 0.0) * g_7;
result += mat4(0.007862721, 0.009936555, 0.012004831, 0.0, -0.042322706, -0.061728776, -0.05359773, 0.0, 0.030532641, 0.045623366, 0.04214089, 0.0, 0.030569768, 0.011892851, 0.0074041556, 0.0) * g_8;
result += mat4(0.03948997, 0.043119986, 0.039943404, 0.0, 0.0526772, 0.06820589, 0.058139592, 0.0, -0.062081397, -0.06755701, -0.054816127, 0.0, -0.004076369, 0.0061744447, 0.016273081, 0.0) * g_9;
result += mat4(0.0071622543, 0.004829105, -0.002032197, 0.0, -0.048541367, -0.059043564, -0.05662218, 0.0, 0.0015553127, 0.009178359, 0.009577062, 0.0, 0.114169896, 0.1349016, 0.11432262, 0.0) * g_10;
result += mat4(0.019324556, 0.028323999, 0.027396113, 0.0, 0.016746879, 0.01608199, 0.026891617, 0.0, 0.12068619, 0.13617857, 0.113496214, 0.0, -0.013930715, -0.014250072, -0.00824306, 0.0) * g_11;
result += mat4(-0.0024534757, -0.0064973077, -0.007905654, 0.0, -0.019158727, -0.024820521, -0.020509848, 0.0, -0.09608131, -0.11177871, -0.10503465, 0.0, -0.011210447, -0.010875943, -0.015295865, 0.0) * g_12;
result += mat4(0.09681486, 0.113604136, 0.10416855, 0.0, -0.08199983, -0.09013433, -0.08562243, 0.0, 0.041304465, 0.048315883, 0.042945288, 0.0, -0.09863276, -0.117853515, -0.09870226, 0.0) * g_13;
result += vec4(-0.0039074384, -0.0085585555, -0.0132283475, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,137 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(S)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(0.10922428, -0.16249932, 0.15452726, -0.15669551, 0.053448875, -0.16528402, 0.01697721, -0.049275912, 0.20947173, -0.10576949, 0.19738325, -0.025417482, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.3285196, 0.15909512, -0.5273671, 0.23778777, -0.40508887, -0.0609677, -0.4188177, 0.11137456, -0.24131267, 0.10453423, -0.36216277, 0.053446792, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.23072472, -0.082083695, -0.0041477727, -0.09136237, 0.11958912, -0.312698, -0.15842685, -0.013882424, 0.10933724, 0.017880991, -0.022167003, 0.014662608, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.2789985, 0.054727737, 0.22577816, -0.49625716, -0.48472273, -0.011525487, 0.5354349, -0.08814955, -0.27021924, -0.044563178, 0.008232271, -0.13480483, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.18203105, 0.09277001, 0.27071548, -0.17773713, -0.4335171, 1.2275106, -0.07663438, -0.29020032, 0.011992759, 0.060106967, 0.11002492, -0.046098012, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.08363418, 0.063420765, -0.10278259, 0.09357691, 0.38670546, 0.13577081, 0.048631024, -0.024960777, 0.0846784, -0.057097007, 0.06049236, 0.042082917, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.12315548, -0.056513585, -0.09826642, -0.17079762, 0.06479095, -0.36984903, -0.12512982, 0.042867575, 0.08360464, 0.12835538, -0.005067881, 0.02542005, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.18997705, 0.086363226, -0.0007131526, 0.19858918, 0.39745626, -0.0090341605, 0.27864447, 0.20052041, 0.010576528, -0.089242525, -0.025109483, -0.030768145, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(0.05427315, -0.060894873, 0.06548642, 0.095537595, 0.29116166, -0.16159569, -0.13293959, -0.112566955, 0.0059667625, 0.016041303, 0.03831561, 0.09869594, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.0113532655, -0.06449327, 0.035503868, 0.5683031);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.027102098, 0.2640691, 0.1169015, 0.030902913, 0.15404584, 0.1361459, -0.38066056, 0.096569136, -0.111836195, -0.0051853824, -0.0996669, -0.23538585, -0.07060754, -0.18889332, -0.10793357, -0.15154232) * go_0(-1.0, -1.0);
result += mat4(0.1378689, 0.21024452, 0.010976513, 0.0179521, -0.05993059, -0.28364083, 0.24486947, 0.21347582, -0.12522404, -0.16091396, 0.15499291, 0.08353191, -0.03342411, -0.08964405, 0.25111282, -0.07550899) * go_0(-1.0, 0.0);
result += mat4(-0.06398718, 0.05763278, 0.021394925, 0.14780094, -0.033050716, 0.03346528, -0.0846797, 0.0125302235, 0.18018652, 0.24586707, 0.050538495, 0.09879243, -0.100035876, 0.043505374, 0.042692907, -0.08768257) * go_0(-1.0, 1.0);
result += mat4(-0.11572878, 0.0545887, 0.16437739, 0.2775331, 0.10323911, -0.18938646, -0.17097469, -0.188723, 0.085762165, 0.14605838, -0.15568069, -0.16947642, 0.09042493, -0.087587915, -0.041969277, 0.27252352) * go_0(0.0, -1.0);
result += mat4(0.21475963, -0.018211678, -0.5711054, -0.09235345, -0.20367791, -0.23041399, 0.16346097, 0.007901888, 0.12542121, 0.16807431, 0.09862575, 0.16968751, 0.28490388, 0.40945014, -0.22364445, 0.14460565) * go_0(0.0, 0.0);
result += mat4(0.27512726, 0.14046481, -0.17684339, 0.102218024, -0.10503195, 0.3080809, 0.03681373, 0.2668656, -0.093752496, -0.07476867, 0.19900662, 0.06028286, -0.19815882, -0.0584525, 0.027984729, -0.02143819) * go_0(0.0, 1.0);
result += mat4(-0.16829525, -0.06818115, 0.0006509334, 0.01163159, 0.18918815, -0.10731989, -0.008126929, -0.47991323, -0.11022971, 0.19150843, 0.05272113, -0.34417602, 0.022882428, 0.1774031, 0.062597334, -0.09915319) * go_0(1.0, -1.0);
result += mat4(0.32131585, 0.05668815, -0.34203658, 0.05542482, -0.008077225, 0.009148517, 0.10953332, -0.050969962, 0.09904077, 0.46938205, -0.5148919, -0.22275375, -0.10536104, -0.23662373, 0.002147416, -0.14256701) * go_0(1.0, 0.0);
result += mat4(-0.19335353, -0.103732094, 0.17156832, 0.0059756916, -0.118641876, 0.14529023, -0.18662338, 0.0447326, 0.026719248, 0.042491894, 0.026437795, 0.05601309, 0.08645617, 0.08365193, -0.039582565, 0.16612953) * go_0(1.0, 1.0);
result += mat4(-0.014315469, 0.012588422, 0.037587024, 0.08707526, -0.08064868, -0.28149533, 0.27326405, 0.21468583, -0.04278333, 0.29369017, 0.18653142, 0.035729136, 0.079363555, 0.30725953, 0.0147137, 0.08527481) * go_1(-1.0, -1.0);
result += mat4(0.06659263, 0.03452449, -0.33752796, 0.0066543026, 0.48697233, 0.019602561, -0.32033685, -0.20538871, 0.3089118, 0.4315903, -0.13524854, -0.10791581, 0.3315688, 0.13135147, -0.26904663, 0.142365) * go_1(-1.0, 0.0);
result += mat4(0.13619833, 0.045271892, -0.029841429, 0.010704955, -0.29257727, -0.10563375, 0.35345638, -0.06734038, -0.043791633, -0.0056891907, -0.078411415, 0.075443126, -0.05746597, -0.19959894, -0.12797245, 0.18837726) * go_1(-1.0, 1.0);
result += mat4(0.25673476, 0.120482095, -0.23827696, -0.13557845, 0.300447, -0.3008584, -0.13834439, 0.5459493, -0.26155484, 0.06905137, 0.16247983, 0.039960653, -0.023218757, 0.07977591, -0.11354706, -0.25831422) * go_1(0.0, -1.0);
result += mat4(0.0842605, 0.282916, 0.14062001, 0.06356874, 0.55912817, 0.1743876, -0.30324093, 0.052068707, -0.20756413, 0.27321506, -0.26560605, -0.27695876, -0.3927334, -0.5439608, 0.39293098, -0.001130203) * go_1(0.0, 0.0);
result += mat4(-0.021890296, -0.12703396, 0.06660714, -0.03164527, 0.0018722567, -0.26552317, 0.06978973, -0.24030049, 0.46008193, 0.5595346, 0.081981994, -0.038414747, -0.010446991, -0.56102365, -0.079274766, -0.01851302) * go_1(0.0, 1.0);
result += mat4(0.052988984, 0.030581746, -0.06868741, 0.21545182, -0.5706256, -0.0034910638, 0.48361364, 0.9020033, -0.02242781, -0.13256042, 0.08997955, 0.21001706, -0.059571438, -0.040119104, -0.05029196, -0.127414) * go_1(1.0, -1.0);
result += mat4(-0.08275339, -0.05999088, 0.11068767, 0.014646892, -0.041986465, 0.1028236, -0.17218924, 0.026559748, -0.17412743, -0.38364175, 0.17410514, 0.13038695, 0.23155633, 0.2655843, 0.045085523, 0.13005458) * go_1(1.0, 0.0);
result += mat4(-0.013383197, -0.064526096, 0.049046878, 0.015992291, 0.123987064, 0.0104690585, 0.07065378, -0.009824511, -0.036109775, 0.13384768, 0.29676288, -0.39475223, -0.009368096, -0.05666906, -0.09132696, -0.082638375) * go_1(1.0, 1.0);
result += vec4(-0.106538564, -0.065693766, -0.03790106, 0.04776706);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.024004154, -0.26474997, -0.5256586, 0.051624652, -0.16621786, 0.2964122, 0.6044247, -0.14335106, 0.17002718, -0.2679876, -0.30162668, 0.1273794, -0.17601459, -0.1782376, 0.104725115, -0.16351137) * go_0(-1.0, -1.0);
result += mat4(-0.121676154, 0.047741555, -0.06738679, -0.056402843, 0.004424971, -0.35099635, -0.073440626, 0.039784692, 0.15204315, -0.1165704, 0.11231046, -0.27369732, 0.33737272, -0.11880767, 0.09637475, -0.14709689) * go_0(-1.0, 0.0);
result += mat4(-0.017987821, -0.08798823, -0.062515825, -0.046803873, -0.05871703, 0.27013004, 0.19397618, -0.052147817, 0.003271283, -0.0029015478, -0.07390092, -0.09348337, -0.1574738, 0.06750957, -0.07661155, 0.054327156) * go_0(-1.0, 1.0);
result += mat4(-0.15215784, -0.72508365, -0.3202069, 0.20295432, -0.19125701, 0.021401431, -0.051837035, -0.025939213, 0.25565025, -0.12872623, 0.13169816, 0.27377388, -0.008718429, -0.05864064, 0.028844763, 0.1144993) * go_0(0.0, -1.0);
result += mat4(-0.30012092, -0.1322455, -0.11868545, 0.09857058, 0.082795605, -0.075334676, -0.3752773, -0.02918163, -0.67764, -0.38598236, -0.21023573, 0.38274166, -0.07398165, -0.07213789, -0.28427607, 0.1266569) * go_0(0.0, 0.0);
result += mat4(-0.37507388, 0.18809201, -0.21982779, 0.27208912, 0.022066567, -0.27627763, 0.12345216, -0.30041683, 0.017002959, -0.091398515, -0.25207692, -0.29253414, -0.08231422, -0.14665812, -0.07868529, -0.24562219) * go_0(0.0, 1.0);
result += mat4(0.08686712, 0.080837384, 0.20736577, 0.008233064, 0.14957365, 0.21801959, -0.04870689, 0.42149112, 0.27255878, 0.33320278, -0.08467146, 0.10381615, 0.055278245, 0.085710146, 0.009097151, 0.29092705) * go_0(1.0, -1.0);
result += mat4(0.0012207404, -0.023874281, -0.027035477, 0.005157451, 0.19330226, 0.33711615, -0.16495204, 0.549021, 0.44879642, 0.1978837, -0.20492741, 0.28099406, 0.2631811, 0.40786585, -0.055340275, 0.2575511) * go_0(1.0, 0.0);
result += mat4(0.29127392, -0.06287165, 0.12715077, 0.14784902, -0.3183704, 0.42057636, -0.11483724, -0.3019506, 0.010730576, 0.29091576, -0.046116166, -0.23528357, -0.0037143505, 0.1191774, -0.06084074, 0.011641706) * go_0(1.0, 1.0);
result += mat4(-0.2579205, 0.036545023, 0.11691888, 0.04996418, 0.21318026, 0.21370813, -0.14114271, 0.031217605, -0.06979331, -0.0690704, 0.04618086, 0.025164584, -0.10994228, 0.109930746, 0.103678934, 0.12193115) * go_1(-1.0, -1.0);
result += mat4(-0.19843774, -0.11237926, 0.007291354, 0.16480611, -0.15669724, 0.46283355, 0.077065215, 0.112273656, 0.17143534, -0.19934891, -0.25481275, 0.034591813, -0.27032652, -0.2702769, 0.04816228, -0.031614583) * go_1(-1.0, 0.0);
result += mat4(-0.16307239, -0.11295217, 0.05861256, 0.14225823, -0.015648091, 0.11741865, 0.113366075, 0.023935538, 0.19560932, -0.10553561, -0.042583376, -0.048160724, -0.3116519, 0.13957061, -0.0044852323, -0.015472912) * go_1(-1.0, 1.0);
result += mat4(-0.15629178, 0.06463271, -0.13176678, 0.025518289, -0.021733627, 0.22236359, 0.019508492, -0.11629477, 0.10801276, -0.021957984, -0.11272639, -0.03615053, -0.121420704, 0.2520835, 0.043395765, 0.1699031) * go_1(0.0, -1.0);
result += mat4(0.2886654, 0.21755892, 0.21757497, 0.08442575, -0.109903164, -0.67295986, 0.22886126, -0.027185453, 0.3761606, 0.23199768, 0.05908783, -0.1496158, 0.10832971, -0.3530352, 0.20234483, -0.07615918) * go_1(0.0, 0.0);
result += mat4(0.11043024, 0.18943349, 0.42394367, 0.029350199, -0.15085667, 0.020204183, -0.081609115, 0.07907012, 0.33805525, 0.0066280114, 0.0018284445, 0.022983696, 0.004984607, 0.0429299, -0.14568979, -0.29143327) * go_1(0.0, 1.0);
result += mat4(-0.16376027, -0.20387048, 0.06522074, 0.17484841, -0.13885716, -0.04380927, -0.03535832, -0.16978237, -0.004799155, -0.25407305, -0.039976966, -0.011992087, -0.22535577, -0.09583549, 0.0334331, 0.016292758) * go_1(1.0, -1.0);
result += mat4(-0.38688713, -0.20232083, 0.23887886, -0.10438324, -0.24170811, -0.074868314, 0.03977399, -0.22810821, -0.08257971, -0.11902456, 0.106009185, -0.078289054, -0.11932821, 0.024207884, 0.10070917, 0.79348284) * go_1(1.0, 0.0);
result += mat4(-0.4018743, 0.050456528, 0.035341598, -0.03788609, 0.12964934, -0.44461823, 0.029031694, 0.29604837, -0.102386944, -0.13805065, 0.0055692918, 0.14659804, -0.22499937, 0.14680648, -0.3443954, -0.06994176) * go_1(1.0, 1.0);
result += vec4(-0.0063428865, 0.0057986965, -0.12526293, -0.059240736);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(S)-Conv-3x3x3x8
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_2_tf
//!SAVE MAIN
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.08631539, 0.09499331, 0.065609254, 0.0, -0.023760278, -0.027293118, -0.022839671, 0.0, -0.012447854, -0.008565141, -0.012041815, 0.0, -0.033292875, -0.031266093, -0.02874347, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.08709062, 0.09760889, 0.08988583, 0.0, -0.09099671, -0.102120616, -0.098076016, 0.0, 0.057814583, 0.06999608, 0.05961344, 0.0, 0.12246188, 0.1319784, 0.12254915, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.07694916, 0.0822054, 0.07549296, 0.0, -0.046808865, -0.051509347, -0.035890795, 0.0, 0.01599848, 0.014677793, 0.0086143715, 0.0, 0.033142705, 0.0426565, 0.035911378, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.0008269902, 0.0009082343, 0.014101725, 0.0, 0.0006387551, 0.005079344, -0.013034868, 0.0, 0.013909732, 0.011026747, 0.012485332, 0.0, 0.027028518, 0.022164145, 0.03183532, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.33575395, -0.36700967, -0.34140685, 0.0, 0.35850254, 0.37535715, 0.34613726, 0.0, -0.12680013, -0.1256115, -0.112494245, 0.0, -0.061541136, -0.059120018, -0.06552594, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.047570463, -0.050335366, -0.04665491, 0.0, -0.110970475, -0.12363716, -0.11072252, 0.0, 0.041563414, 0.059771337, 0.045290247, 0.0, -0.17999935, -0.19700716, -0.17459513, 0.0) * go_0(0.0, 1.0);
result += mat4(0.078488424, 0.07483357, 0.08347933, 0.0, -0.0063715233, 0.00035415235, -0.010886946, 0.0, 0.031237155, 0.02512343, 0.034399323, 0.0, -0.023146842, -0.026732154, -0.027644241, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.05906883, -0.06784104, -0.04506148, 0.0, -0.003939601, -0.0011749315, -0.006256036, 0.0, -0.1662408, -0.16871658, -0.16598499, 0.0, 0.051277652, 0.04837499, 0.05120855, 0.0) * go_0(1.0, 0.0);
result += mat4(0.08158806, 0.08674548, 0.07437206, 0.0, -0.05765347, -0.06196418, -0.057311118, 0.0, 0.26747537, 0.2668808, 0.2389857, 0.0, -0.010376844, -0.01690028, -0.008414153, 0.0) * go_0(1.0, 1.0);
result += mat4(0.030539425, 0.02415435, 0.039969034, 0.0, 0.006491679, 0.014436586, 0.005435709, 0.0, -0.0058292216, -0.013982021, -0.011243379, 0.0, 0.025942149, 0.015361476, 0.019134998, 0.0) * go_1(-1.0, -1.0);
result += mat4(-0.06322247, -0.07146787, -0.06673042, 0.0, 0.028702464, 0.039047733, 0.039646607, 0.0, -0.072553575, -0.08046175, -0.07027197, 0.0, -0.1447189, -0.1539398, -0.1466465, 0.0) * go_1(-1.0, 0.0);
result += mat4(-0.046430312, -0.054549117, -0.048076343, 0.0, 0.032971155, 0.02980819, 0.029172963, 0.0, -0.017612953, -0.015100736, -0.01202649, 0.0, -0.026717246, -0.028401854, -0.034548033, 0.0) * go_1(-1.0, 1.0);
result += mat4(-0.0020459262, -0.0008748501, -0.012601956, 0.0, 0.0054226154, 0.008867029, 0.018921215, 0.0, -0.0021330053, -0.0036601655, -0.0022091097, 0.0, -0.08636891, -0.10203159, -0.09741449, 0.0) * go_1(0.0, -1.0);
result += mat4(0.07306159, 0.08245483, 0.06548199, 0.0, -0.1933229, -0.20326294, -0.19189309, 0.0, 0.107496604, 0.11584994, 0.10907522, 0.0, 0.30877885, 0.31297725, 0.30890995, 0.0) * go_1(0.0, 0.0);
result += mat4(0.03192904, 0.035112645, 0.033732817, 0.0, 0.074100636, 0.08349646, 0.06659352, 0.0, -0.1136165, -0.12470947, -0.11192198, 0.0, 0.14465587, 0.16328491, 0.13984151, 0.0) * go_1(0.0, 1.0);
result += mat4(-0.05098033, -0.053096622, -0.05533725, 0.0, 0.0045651463, -0.007682458, 0.0026934785, 0.0, -0.021199327, -0.016210148, -0.030939564, 0.0, -0.031621892, -0.046702545, -0.02647333, 0.0) * go_1(1.0, -1.0);
result += mat4(0.055801813, 0.06430485, 0.05052402, 0.0, 0.0241233, 0.013879883, 0.017344628, 0.0, 0.08707151, 0.10031039, 0.095042154, 0.0, -0.109053336, -0.11414017, -0.111838564, 0.0) * go_1(1.0, 0.0);
result += mat4(0.030582374, 0.03604719, 0.040417343, 0.0, 0.038665913, 0.036998056, 0.030004544, 0.0, 0.09209076, 0.10010001, 0.08389406, 0.0, -0.014655714, -0.0074866647, -0.012227013, 0.0) * go_1(1.0, 1.0);
result += vec4(-0.008303841, -0.008251826, -0.0069884053, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,873 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(0.14361712, -0.16690509, 0.37253398, -0.45202538, -0.21331833, -0.32675815, -0.33971128, 0.20261937, -0.20606318, -0.215143, -0.079716705, 0.15640882, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.17360486, -0.3435545, 0.08199117, 0.56259036, -0.120246716, 0.24312893, -0.021436244, -0.11864853, 0.19452724, 0.106943935, -0.077393375, -0.3503661, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.072465785, 0.2772823, 0.25493625, 0.3098145, -0.115831695, 0.072458096, -0.014782132, -0.15310249, 0.12178311, -0.015555423, -0.2229811, 0.16469522, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.18652022, -0.30702665, -0.59921896, 0.079824045, 0.4426619, 0.049343713, 0.44902903, -0.2711445, 0.20470268, -0.029203767, 0.29092675, 0.15562426, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.21041247, 0.48450592, -0.110547826, 0.3842122, 0.5303875, -0.26512837, 0.19846216, 0.045673862, 0.12773214, -0.05117536, -0.03510946, -0.30123934, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.3186735, 0.052702922, -0.12499774, 0.055628903, -0.16476867, 0.12642322, -0.18314636, 0.018323101, -0.3609603, 0.25649396, 0.3185421, -0.0057759956, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.16603558, -0.09259665, -0.28760567, -0.14319661, 0.12511417, -0.12551902, -0.00070228375, 0.20914114, -0.22466865, 0.1064727, 0.32598525, -0.08596318, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.03163653, 0.026722813, -0.4361858, -0.21164834, 0.4176763, 0.08203146, 0.35289326, -0.06128859, 0.20506798, -0.07098943, 0.1807802, 0.2658414, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.09821681, 0.058886815, 0.39192092, -0.06791861, -0.15682612, 0.09503328, -0.23400265, 0.026475023, -0.08800713, -0.043749645, -0.18024494, -0.08045564, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.040999945, 0.075765304, -0.0911532, -0.10705836);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf1
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.16406488, -0.2506693, -0.15592022, -0.05529256, -0.3997277, -0.229681, -0.07762124, 0.1843808, 0.07895815, 0.14437248, 0.219114, -0.048090722, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.2150676, 0.09080163, 0.19598733, -0.40578827, -0.33846557, -0.02518622, 0.037079208, 0.20188439, -0.013777575, -0.2369007, -0.30985412, 0.0411912, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.119948365, 0.23014452, -0.14962277, -0.096262485, 0.09625151, 0.2025487, 0.03933539, 0.12268028, -0.24373281, 0.19730613, 0.11634144, 0.12293635, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.08030697, -0.40114692, 0.21532272, 0.20222071, 0.073098, -0.004463858, 0.02820587, -0.18861918, -0.20994501, -0.12444653, -0.23178193, -0.13965288, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(0.14150894, 0.14563078, 0.697704, 0.20918849, 0.26776335, -0.34291518, 0.06394055, 0.17925078, 0.4165139, -0.042595536, 0.105312675, 0.231854, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.024318576, 0.16668217, 0.0729521, -0.7071404, 0.3121693, 0.37295797, -0.015632952, 0.33763757, 0.00706697, 0.10836652, -0.11132417, 0.292844, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.14489831, 0.0027769986, -0.24509215, 0.5557927, -0.1104541, 0.005070684, -0.020032275, -0.5642205, 0.16048644, 0.07248175, 0.20387374, -0.38145426, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.33140838, -0.007438425, 0.26074782, 0.15947102, 0.219755, -0.14690271, -0.07412696, -0.24176367, -0.2230114, 0.027256912, -0.11255796, -0.05882673, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.19712369, 0.003842208, -0.10893768, 0.09047115, -0.10260409, 0.18662766, 0.009733428, 0.0039940844, -0.006444674, -0.15196493, 0.06641555, -0.06169452, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.029148052, -0.03215124, -0.6175828, 0.057135154);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.05195995, 0.15220909, -0.24354807, -0.109075695, 0.020483498, -0.14830725, 0.0018816335, -0.0072673927, 0.0649385, 0.046050787, -0.10789607, -0.046609525, -0.11455093, -0.009358115, 0.11280759, 0.18053898) * go_0(-1.0, -1.0);
result += mat4(-0.08619698, 0.091353096, -0.16379662, 0.07822936, 0.072919995, 0.1482446, 0.17846228, 0.04639898, -0.18998149, 0.1653338, -0.44187957, -0.010017503, -0.069953404, 0.08784785, -0.16391355, 0.35095468) * go_0(-1.0, 0.0);
result += mat4(0.088297926, 0.27259287, 0.013088447, 0.023461785, 0.10037149, -0.017414214, 0.08559885, -0.10822335, 0.10591637, 0.17240539, 0.15749931, 0.026641782, 0.11889612, -0.018095117, -0.08736018, 0.09934933) * go_0(-1.0, 1.0);
result += mat4(0.21426749, 0.0800268, -0.19816414, 0.07693414, 0.026270509, -0.11724047, 0.026078718, 0.13080709, 0.12207936, 0.056103867, -0.323923, -0.111454345, 0.059245165, 0.07257926, 0.032195322, 0.27225617) * go_0(0.0, -1.0);
result += mat4(-0.20130268, 0.026809234, -0.0020803472, -0.04394057, -0.1982125, -0.033678252, -0.12881789, 0.0025656687, 0.14193355, -0.2541802, -0.13239717, -0.05983356, -0.029376393, 0.33187667, 0.14438996, -0.21993925) * go_0(0.0, 0.0);
result += mat4(-0.12772562, 0.022498213, 0.24753313, 0.07440761, -0.17564529, 0.09971503, -0.013372, 0.09459552, -0.21597451, -0.40116546, 0.23446435, 0.1515452, 0.050813515, 0.19662157, -0.10604596, -0.24638489) * go_0(0.0, 1.0);
result += mat4(0.23866327, -0.2706382, -0.07480157, 0.03789501, 0.117716484, -0.095995456, -0.0435066, 0.013025061, -0.029759895, -0.036287807, 0.08570493, 0.030151363, 0.18863682, -0.27228612, 0.020479294, -0.07058746) * go_0(1.0, -1.0);
result += mat4(0.0026758043, -0.20750894, 0.2802277, -0.07761428, -0.012089615, -0.112726666, -0.03867965, -0.085082226, 0.034227375, 0.19662802, 0.26272395, 0.036822405, -0.23040786, -0.20173554, 0.07110236, -0.26939383) * go_0(1.0, 0.0);
result += mat4(-0.14012688, -0.067249745, 0.14726773, -0.0070919944, 0.19275497, 0.04460783, 0.18776374, -0.019941995, -0.076159865, 0.002261728, 0.238768, 0.039375026, 0.13200568, -0.023286859, 0.034387972, -0.01827453) * go_0(1.0, 1.0);
result += mat4(-0.0107542025, -0.13001555, 0.06596806, -0.03370635, -0.024291076, 0.10367739, -0.03396605, 0.041960735, 0.16230568, 0.024845246, -0.016806586, 0.22547007, -0.025378102, 0.064547986, -0.2113137, 0.042272836) * go_1(-1.0, -1.0);
result += mat4(-0.2219356, -0.049535394, 0.10289468, 0.14175911, 0.013058568, -0.15909089, -0.02546921, 0.11721571, 0.13020545, 0.39660174, -0.07601573, -0.16366366, -0.023935124, 0.06681424, -0.26143414, -0.07485668) * go_1(-1.0, 0.0);
result += mat4(0.1405031, -0.0645044, -0.15865614, 0.1829069, -0.22526503, 0.08991175, 0.041972812, -0.012462953, 0.3022753, 0.19457603, 0.022607598, -0.25460255, 0.028327515, 0.14420614, -0.077984214, 0.09278112) * go_1(-1.0, 1.0);
result += mat4(0.13224132, 0.13115089, -0.188987, -0.19428022, -0.080641985, 0.20909777, 0.067079, -0.19832124, 0.13150498, 0.04450851, -0.2770351, -0.010381239, 0.32295567, 0.04445836, 0.030657565, 0.020271502) * go_1(0.0, -1.0);
result += mat4(-0.08188993, 0.039709873, 0.16059989, -0.13279189, 0.11389818, -0.071865685, 0.09312801, -0.08816363, -0.65844774, -0.6854379, 0.21431407, 0.597198, -0.3734657, -0.116027676, 0.015932929, -0.0653176) * go_1(0.0, 0.0);
result += mat4(0.24136105, 0.21444799, -0.14235207, 0.08445492, 0.017335927, -0.49877876, -0.06224622, 0.1571534, 0.035594277, 0.059829034, 0.087631516, -0.17090686, -0.005452869, 0.13786094, 0.27586326, 0.046760406) * go_1(0.0, 1.0);
result += mat4(0.095078, 0.30310658, 0.010268592, 0.18540539, -0.20722823, -0.0005848572, -0.06464327, -0.111019135, -0.07837157, -0.12183798, -0.09187498, -0.3368629, -0.08216629, -0.20095807, 0.009563313, 0.024838416) * go_1(1.0, -1.0);
result += mat4(0.28712475, 0.0641969, -0.034764312, 0.13600683, -0.09211094, 0.009699817, -0.001104855, -0.026146285, 0.33425868, -0.16132632, 0.18051304, -0.104004376, 0.20768233, 0.0888418, 0.050057285, -0.020228952) * go_1(1.0, 0.0);
result += mat4(0.11642946, -0.021900529, -0.08910504, 0.15492517, -0.19726521, 0.1434987, -0.24708387, 0.006737377, 0.11353539, -0.15897587, -0.029491093, 0.06002862, -0.09640613, -0.11342702, 0.21375169, 0.0062719737) * go_1(1.0, 1.0);
result += mat4(-0.15513068, -0.3151456, 0.20799752, -0.07449935, -0.09226967, 0.112302735, -0.16211908, -0.37986508, -0.27418482, -0.10445544, 0.21112369, -0.06780466, -0.062341, 0.07758948, -0.012719117, -0.16481343) * go_2(-1.0, -1.0);
result += mat4(0.16382848, 0.14490448, -0.012869055, 0.1804095, -0.05304844, -0.14624795, -0.14816979, -0.17435774, 0.25356865, 0.11435022, 0.19412366, 0.19499794, -0.10189348, 0.023880519, 0.16822465, -0.17454338) * go_2(-1.0, 0.0);
result += mat4(0.04854064, 0.11944563, 0.022984248, -0.0852543, -0.0077684796, -0.044182744, -0.02888099, 0.27452356, -0.07887827, -0.15155658, -0.12841311, -0.21202831, -0.18533322, -0.05852455, 0.0761054, -0.22115342) * go_2(-1.0, 1.0);
result += mat4(-0.21520375, 0.11415518, 0.18909843, -0.16420493, -0.20909967, -0.3257246, 0.29332343, -0.029541709, -0.1679851, 0.14073059, 0.32720464, 0.13311239, -0.0021121972, -0.08773544, -0.045532625, 0.36960867) * go_2(0.0, -1.0);
result += mat4(0.58407414, -0.23632582, -0.16739567, 0.264173, 0.09584864, 0.18455075, 0.20051196, -0.04616608, 0.13441175, -0.0055764276, -0.08625195, 0.097847305, 0.19565724, -0.12183587, -0.11488796, 0.2520169) * go_2(0.0, 0.0);
result += mat4(0.01584208, -0.31471413, 0.017104283, 0.0682452, 0.18728764, 0.042960413, 0.06437809, -0.14483811, 0.13882554, 0.016576322, -0.029599546, 0.034904055, -0.20939542, -0.10213055, 0.08821727, 0.0030552552) * go_2(0.0, 1.0);
result += mat4(-0.2973797, 0.15791039, 0.10811437, -0.07947077, -0.26328024, -0.061920475, 0.12498813, 0.100570425, -0.018922925, 0.002256239, -0.094379805, -0.032315314, 0.48677605, -0.04879864, 0.028028104, -0.14557233) * go_2(1.0, -1.0);
result += mat4(0.016148027, 0.13884154, -0.19554809, -0.006344376, -0.013450252, 0.2581758, 0.10643088, 0.23465036, -0.078438915, -0.099644944, -0.1442203, -0.2285087, 0.33528957, -0.17052084, -0.26595074, 0.14794162) * go_2(1.0, 0.0);
result += mat4(0.041404825, -0.0813985, -0.19863169, -0.008302881, 0.023570588, -0.043578386, -0.20971186, 0.14654282, 0.048436746, 0.11266723, -0.25812748, -0.03340969, -0.18430679, -0.046258014, -0.007674466, -0.037139155) * go_2(1.0, 1.0);
result += mat4(-0.060693484, -0.08285047, 0.06638212, 0.18479855, 0.11099276, -0.14470962, 0.16915078, 0.32247669, -0.10845523, 0.0027510398, -0.014941873, -0.15779859, 0.051481526, -0.14748912, 0.12125527, -0.059839584) * go_3(-1.0, -1.0);
result += mat4(0.27571446, 0.01663349, -0.057985745, -0.089736536, -0.09541078, 0.18101417, 0.084854685, 0.11060913, 0.05631825, 0.066835634, -0.02837782, -0.049748126, -0.050051138, -0.05126488, 0.27121767, 0.06331115) * go_3(-1.0, 0.0);
result += mat4(-0.13630085, -0.03787764, 0.13351586, -0.024081819, 0.10403757, -0.0034796793, -0.04838045, -0.064052396, -0.34672704, -0.06271465, -0.024577484, -0.13450806, -0.013759927, 0.11706738, 0.07913658, -0.016639082) * go_3(-1.0, 1.0);
result += mat4(-0.023730129, 0.020174952, 0.048988737, -0.013395666, 0.0073305597, 0.059409764, -0.27721968, 0.13349204, -0.022947624, 0.112007216, -0.008175606, -0.14903043, -0.35755506, -0.02145208, -0.021762518, -0.17889674) * go_3(0.0, -1.0);
result += mat4(0.19315337, 0.16287236, -0.07667863, -0.020898499, -0.021058874, -0.20849414, -0.3571716, -0.13001479, 0.44977963, 0.016706442, -0.03471178, 0.35189477, 0.3050666, -0.019236205, 0.16278796, 0.3093703) * go_3(0.0, 0.0);
result += mat4(-0.1507458, -0.13747548, -0.05822537, 0.16035356, -0.08386089, -0.03476887, -0.0022021863, -0.032772254, 0.17572841, 0.004200287, 0.045312192, 0.27265742, -0.037853006, -0.056344658, -0.3095155, 0.15215549) * go_3(0.0, 1.0);
result += mat4(0.11428048, -0.19523771, 0.016499955, -0.03625986, 0.15670861, -0.077859454, -0.059640404, 0.023970904, -0.009806148, 0.0904747, -0.006978744, 0.15938658, 0.030886533, 0.13507655, -0.002613293, -0.1335748) * go_3(1.0, -1.0);
result += mat4(-0.20070468, 0.06281564, -0.026250493, -0.042895693, -0.06574456, 0.10412931, 0.12061968, -0.0750467, -0.10865931, -0.05715226, -0.022071969, 0.02608941, -0.21416737, -0.18582128, -0.091236554, -0.044943426) * go_3(1.0, 0.0);
result += mat4(-0.057988428, 0.21430638, -0.17991407, -0.051662743, 0.060244065, -0.021494022, -0.018070806, -0.09278776, -0.011404125, 0.064091586, 0.12852973, -0.16610947, 0.08740408, 0.045517463, -0.27932477, 0.11050971) * go_3(1.0, 1.0);
result += vec4(0.012687187, -0.11876551, -0.041985378, -0.10110911);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.07579397, 0.008718031, 0.03874428, -0.022123579, 0.064964466, -0.27502275, -0.0053009577, 0.11669645, 0.007708085, 0.009340055, -0.13001843, -0.03758108, -0.07045759, -0.08749642, -0.21329811, 0.13205966) * go_0(-1.0, -1.0);
result += mat4(-0.14087188, -0.12068241, 0.046639618, 0.05115712, 0.108357444, -0.05040456, 0.03280633, 0.09336891, -0.055509757, -0.036777936, 0.043575723, -0.041975956, -0.17782387, -0.12977566, -0.0736514, 0.17304243) * go_0(-1.0, 0.0);
result += mat4(-0.2638534, 0.0385968, 0.14743716, 0.18057759, -0.036564615, 0.107838504, 0.08324167, 0.13403444, -0.41366392, 0.072824344, -0.013165103, 0.06114856, -0.040475495, -0.14222278, 0.10455181, 0.0021660402) * go_0(-1.0, 1.0);
result += mat4(0.30221993, -0.06315301, 0.057081617, -0.020285107, 0.053984016, 0.13086873, -0.30863532, 0.028010197, 0.0070908144, 0.19940577, -0.013766302, -0.039389495, 0.28064504, 0.05970737, 0.074613005, -0.10217121) * go_0(0.0, -1.0);
result += mat4(0.042094592, -0.1725651, 0.3514404, 0.008126955, 0.08739713, 0.081543595, -0.12912413, 0.0854203, 0.28885832, 0.107783586, 0.22996111, 0.13907135, 0.071920335, -0.15172984, 0.07151959, 0.1406894) * go_0(0.0, 0.0);
result += mat4(-0.1072496, 0.03934067, 0.20014063, 0.051399443, -0.29610988, 0.18659018, -0.17761967, 0.08701774, -0.17493258, -0.08035252, 0.03155133, -0.13986085, 0.023490375, 0.083998375, 0.014006612, 0.03860323) * go_0(0.0, 1.0);
result += mat4(0.09324427, 0.10990628, -0.18758917, 0.0054821614, -0.09425237, 0.1192338, -0.063183226, -0.15047066, 0.15664004, 0.037881903, -0.06762073, 0.09622682, 0.028449943, -0.25338468, -0.18897526, -0.18360007) * go_0(1.0, -1.0);
result += mat4(0.030310342, 0.2083269, -0.04938559, -0.009608655, 0.019751158, 0.12257741, 0.090964966, -0.09864261, 0.058817703, -0.053385522, 0.15931179, -0.10585003, 0.06986225, 0.3435001, -0.33307528, -0.14035752) * go_0(1.0, 0.0);
result += mat4(0.13506691, -0.00015406386, -0.15279713, -0.2290177, 0.019568326, 0.41041428, 0.10566904, -0.08350839, 0.19839814, -0.31052053, -0.04471875, 0.07629561, -0.117245845, 0.19819061, 0.1683647, 0.11896638) * go_0(1.0, 1.0);
result += mat4(0.06920538, 0.2656798, -0.06529862, -0.1695985, -0.21614018, 0.17208195, 0.123307765, -0.061470803, 0.07827313, -0.18543327, 0.0937214, 0.098630935, -0.17667519, -0.01978596, -0.09126346, -0.034487445) * go_1(-1.0, -1.0);
result += mat4(0.030779282, -0.24423946, -0.08623178, 0.1490136, 0.029337894, 0.17548573, -0.05990294, -0.29123273, -0.10020608, -0.3527181, -0.105286725, 0.27502912, -0.25686985, 0.18521136, -0.110095225, -0.07999611) * go_1(-1.0, 0.0);
result += mat4(-0.03266192, 0.045139533, -0.03275437, -0.13748369, 0.15633966, 0.089048125, -0.07592367, -0.09013536, -0.18907873, 0.08265683, -0.069233745, 0.27151683, -0.0647864, -0.15308899, 0.021954, 0.05528693) * go_1(-1.0, 1.0);
result += mat4(0.10284642, -0.14667438, 0.18669777, 0.053000864, -0.12383836, -0.037600834, 0.29438737, 0.04739594, 0.07846367, -0.11676573, -0.048153553, -0.34298027, 0.028358897, 0.119508564, 0.08012271, -0.019992562) * go_1(0.0, -1.0);
result += mat4(-0.22123314, -0.2223458, 0.002969434, -0.07143056, 0.027859585, 0.010600199, 0.056626067, 0.15160584, -0.16350581, -0.044484995, -0.1805076, 0.33351076, 0.073631234, 0.0167081, 0.15704727, 0.107799366) * go_1(0.0, 0.0);
result += mat4(-0.006882137, 0.19744347, 0.041128602, 0.17459555, 0.10376277, -0.12519689, 0.0993647, -0.13044195, 0.10485074, 0.1712284, 0.13369127, 0.24649777, -0.038975652, -0.24550107, 0.19567624, -0.09961197) * go_1(0.0, 1.0);
result += mat4(0.24763626, -0.0902329, 0.21201743, 0.078442305, 0.013261817, -0.019013328, -0.07576136, 0.14993069, -0.24216306, -0.05666454, -0.064632, -0.38150248, 0.14649945, -0.020437164, -0.13821694, -0.026110074) * go_1(1.0, -1.0);
result += mat4(0.21790951, -0.08288076, 0.011415891, -0.1446542, -0.15910968, -0.21221179, -0.06154624, -0.028623452, 0.10872824, 0.17089185, 0.26339474, -0.42544034, 0.095593184, 0.20962211, 0.0034138034, 0.024243662) * go_1(1.0, 0.0);
result += mat4(-0.050784085, 0.06333505, 0.041011192, 0.17474842, 0.14517011, -0.4340653, -0.10313813, 0.12524489, 0.18353751, 0.4589042, -0.037463415, 0.07841999, -0.114173576, -0.10669665, 0.029463472, -0.14393249) * go_1(1.0, 1.0);
result += mat4(0.12771326, -0.06622126, 0.08327681, -0.15113758, -0.114005744, 0.059280578, 0.04071302, -0.11074485, -0.23312584, -0.032968838, 0.13736604, -0.15776984, 0.067029156, 0.0580463, 0.20655325, -0.2112593) * go_2(-1.0, -1.0);
result += mat4(0.16148107, 0.02879793, -0.24918973, 0.009605728, -0.102177374, 0.050518002, -0.00015101423, -0.046602443, 0.5081422, -0.044740383, -0.06243097, 0.076031074, 0.1157983, 0.03965003, 0.109161526, -0.36589798) * go_2(-1.0, 0.0);
result += mat4(-0.018941574, 0.000912917, -0.2585099, 0.13668273, 0.062664494, -0.09246434, -0.14594543, -0.11160076, 0.015663203, -0.02447256, -0.070794076, 0.11807077, 0.12931514, 0.14109722, -0.07506544, -0.012781477) * go_2(-1.0, 1.0);
result += mat4(-0.48816162, 0.16294348, 0.011336221, 0.107038386, -0.01978858, 0.039453425, 0.112853855, 0.007536018, -0.005471479, -0.11315905, 0.032013394, 0.11523904, -0.2504089, 0.04803124, -0.09689627, 0.24372064) * go_2(0.0, -1.0);
result += mat4(0.61343086, 0.09531598, -0.24803302, 0.23788263, 0.13495958, 0.24733612, 0.1575427, -0.06863399, 0.2341275, -0.15821049, -0.165848, 0.0290172, -0.010136783, 0.04415787, -0.2619951, 0.09987892) * go_2(0.0, 0.0);
result += mat4(0.19411229, 0.24528526, -0.250216, -0.33602244, 0.17639299, -0.052413136, 0.122578874, 0.028618507, 0.25713214, 0.22033587, -0.19680484, 0.028938502, -0.083384775, -0.06476429, 0.036840588, -0.14297847) * go_2(0.0, 1.0);
result += mat4(-0.2897587, -0.12176407, 0.19259763, -0.106649496, -0.026704982, -0.036201328, -0.06753124, 0.37967134, -0.20092241, 0.006229027, 0.12085137, -0.09810282, -0.1501556, -0.0099991355, 0.25044358, 0.08538966) * go_2(1.0, -1.0);
result += mat4(-0.11304407, -0.24147832, 0.21644448, -0.035938095, -0.036439262, -0.042730987, -0.04384442, 0.10325233, -0.32405272, -0.11873838, -0.15075137, -0.036929503, -0.10808143, 0.25799102, 0.13749036, 0.5451476) * go_2(1.0, 0.0);
result += mat4(-0.24142508, -0.04895773, 0.09022442, 0.2821465, -0.06298706, -0.1807906, 0.02960867, 0.22310257, -0.1915311, 0.2900501, 0.1670845, -0.080343634, 0.25779882, -0.27144584, -0.23575482, -0.14724477) * go_2(1.0, 1.0);
result += mat4(0.020742219, -0.10571064, -0.0010137435, 0.14439318, 0.32805952, -0.027505733, -0.07111945, 0.07043296, -0.09525604, 0.03175366, -0.14633068, -0.15810682, 0.18050082, 0.08191363, 0.07047039, 0.0018573351) * go_3(-1.0, -1.0);
result += mat4(-0.023874652, 0.14996628, 0.11298528, -0.1508891, -0.052415725, -0.02570088, 0.0055150646, 0.16365297, -0.046594325, 0.18095094, 0.09934885, -0.066233225, 0.2404304, -0.112728044, 0.14004207, 0.11369578) * go_3(-1.0, 0.0);
result += mat4(0.14799033, 0.025304591, 0.031008242, 0.03795376, -0.15800071, -0.043169834, 0.10797239, 0.17129694, 0.09674189, -0.11010672, 0.07283912, -0.11063907, 0.108249694, 0.025199141, 0.09162024, -0.1827302) * go_3(-1.0, 1.0);
result += mat4(-0.08983324, 0.07823903, -0.137839, 0.11909572, 0.11996334, -0.05947995, -0.25459376, -0.18159851, 0.044489045, 0.052461334, 0.13674203, 0.12579007, -0.33665392, -0.07313439, -0.013640538, -0.010538632) * go_3(0.0, -1.0);
result += mat4(0.0884388, -0.10034604, 0.047238693, 0.12025125, -0.16648497, -0.20305477, 0.08240087, -0.17453992, 0.19033237, 0.28438845, -0.32885036, 0.14011146, -0.13389368, -0.012868356, -0.15273216, -0.19119217) * go_3(0.0, 0.0);
result += mat4(0.09196779, -0.13800567, 0.08842335, -0.18658079, 0.17512907, 0.021311145, -0.06347847, -0.13827331, -0.10689703, -0.1707886, -0.15724367, -0.167876, 0.22493233, 0.3070637, -0.035266686, -0.0068385694) * go_3(0.0, 1.0);
result += mat4(-0.2739973, 0.07336105, -0.196827, 0.060224827, 0.05752693, -0.014346674, 0.025412507, -0.27530053, 0.27755278, -0.07631679, -0.053861864, 0.113329165, -0.31025892, -0.012681806, 0.06228483, -0.054306302) * go_3(1.0, -1.0);
result += mat4(-0.16827694, 0.16333361, 0.068389125, 0.24560109, 0.11659498, 0.052896734, -0.020310031, -0.17830387, -0.07551057, -0.01822214, -0.037451357, 0.24607496, -0.2033962, -0.11107965, 0.05005381, 0.13685274) * go_3(1.0, 0.0);
result += mat4(0.13665263, -0.24541081, 0.0012457973, -0.012630116, -0.09559698, 0.17756529, -0.039300505, -0.044217475, -0.22984356, -0.2294885, 0.104534455, -0.04131095, 0.084843494, 0.038027752, -0.106351435, 0.18853655) * go_3(1.0, 1.0);
result += vec4(0.010324113, -0.01262194, 0.0762259, -0.014071781);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.06961854, 0.06914646, 0.120440066, -0.04889646, -0.012870159, 0.01994181, 0.052958567, -0.14740478, -0.0027199117, -0.004924673, 0.10131955, -0.11496505, -0.06742836, 0.08287776, 0.11206167, -0.021625644) * go_0(-1.0, -1.0);
result += mat4(-0.025003597, 0.05389498, 0.14938618, 0.12255602, 0.050963886, 0.16300994, 0.17633909, 0.03229484, 0.2092038, 0.13367431, -0.09538967, 0.1636076, -0.022082182, 0.10898033, 0.0422286, -0.062253885) * go_0(-1.0, 0.0);
result += mat4(-0.0018258828, 0.08333001, 0.002765037, -0.022241322, 0.1628206, 0.14671557, 0.3001151, 0.030986495, 0.05225914, -0.04880372, 0.15963705, 0.17972782, 0.055128947, 0.114626616, 0.03460699, -0.07679627) * go_0(-1.0, 1.0);
result += mat4(-0.08866054, 0.0882386, 0.13833097, -0.079257324, -0.03060485, 0.049487974, 0.092268504, -0.17009564, 0.021603461, 0.20750603, 0.18884364, -0.10977116, 0.31758478, 0.053426504, 0.093257, 0.14912026) * go_0(0.0, -1.0);
result += mat4(0.13069148, 0.21368778, -0.4405162, -0.009193694, 0.090230525, -0.15897161, -0.005089127, -0.06011075, -0.27336648, -0.021869129, -0.2084852, -0.0850094, -0.10896211, 0.27229342, -0.044210993, -0.03346366) * go_0(0.0, 0.0);
result += mat4(0.05807779, 0.08506817, 0.23984064, 0.12547795, 0.036945213, 0.039088245, -0.10716132, -0.15966031, 0.13548918, 0.07746645, -0.248966, -0.15717135, -0.059498273, 0.0088413125, -0.02828682, -0.021795277) * go_0(0.0, 1.0);
result += mat4(0.013289853, 0.007272393, 0.06875863, -0.053158432, -0.03578172, 0.20148727, 0.1961931, -0.16910668, 0.03259818, 0.054221123, -0.0326064, 0.06493197, 0.053533003, -0.11878436, 0.14398894, -0.17543368) * go_0(1.0, -1.0);
result += mat4(-0.17906332, 0.1111989, 0.047910325, 0.11560207, 0.09790123, -0.2023765, 0.04265116, 0.0075303926, 0.012974969, -0.0853146, -0.04037416, 0.14489946, -0.0716403, -0.055603035, -0.30376709, -0.011667526) * go_0(1.0, 0.0);
result += mat4(-0.053314358, -0.012657763, 0.0077033425, 0.12168191, 0.016371705, 0.11979062, -0.08494259, -0.009617431, 0.1303907, 0.043279216, -0.17285421, 0.15823162, -0.030746695, 0.121796146, 0.13097613, 0.0024783302) * go_0(1.0, 1.0);
result += mat4(-0.11677548, -0.06592395, -0.022185773, 0.0031006308, -0.00906918, -0.0006412884, -0.00083743286, 0.083697535, -0.060518038, 0.14058606, 0.122444086, 0.17866874, 0.02376487, -0.06369968, -0.026537767, 0.21466877) * go_1(-1.0, -1.0);
result += mat4(0.12340551, -0.015656117, 0.051990572, 0.04361656, -0.05291406, 0.10119005, 0.17603071, 0.10464767, 0.03288951, 0.091776796, -0.17373918, -0.12871055, 0.10205503, -0.17783496, -0.17020486, -0.09781929) * go_1(-1.0, 0.0);
result += mat4(-0.01845568, -0.008877597, 0.14279746, 0.031775143, 0.041680444, 0.08784194, 0.044564564, -0.0011678484, -0.010219994, 0.10472676, 0.046920944, -0.110975444, -0.1197329, -0.11303071, -0.14893234, -0.091113724) * go_1(-1.0, 1.0);
result += mat4(-0.03856561, -0.12173735, 0.040876064, 0.13847597, -0.14995924, -0.13332345, 0.18687452, -0.22562599, 0.08920785, -0.0017916666, 0.019448435, 0.2306492, -0.054546747, -0.1465318, -0.10628867, -0.0073827514) * go_1(0.0, -1.0);
result += mat4(0.12689775, 0.11765595, 0.13039489, 0.06940679, 0.2672624, -0.03880143, -0.11693099, -0.05516293, -0.09665274, -0.2583138, 0.22954193, -0.19324702, -0.39629623, -0.35457405, 0.10052407, -0.19756024) * go_1(0.0, 0.0);
result += mat4(-0.06307673, -0.096393906, -0.0075868783, -0.25133502, 0.03436604, -0.008201423, 0.06386583, 0.106548436, 0.014626536, 0.03485315, -0.043418273, -0.1141408, 0.005102567, -0.11701804, -0.01645601, -0.057083) * go_1(0.0, 1.0);
result += mat4(-0.019062268, 0.020416953, -0.08854219, -0.037497565, 0.09449262, -0.09127615, -0.063330196, 0.08736769, -0.12394077, -0.17950213, -0.11101161, 0.16013645, -0.09370585, 0.0047447495, -0.04288296, 0.00314098) * go_1(1.0, -1.0);
result += mat4(-0.08263743, -0.14441489, -0.14886282, -0.05694989, 0.4254853, 0.10864832, 0.26322174, -0.042006254, 0.24269578, -0.053833783, -0.11558995, -0.066605136, -0.064816564, -0.25914803, -0.017624624, 0.0402331) * go_1(1.0, 0.0);
result += mat4(-0.100058846, -0.030422715, -0.19600148, -0.13322774, 0.1796998, 0.087852575, 0.07324559, -0.0047889417, 0.007248384, 0.08930289, 0.09643387, -0.0060126656, 0.16357517, -0.06628222, 0.030618697, 0.097391844) * go_1(1.0, 1.0);
result += mat4(0.09539377, -0.10802722, -0.014952347, 0.1683223, -0.03919409, 0.041155327, -0.012186347, -0.030456683, -0.015024977, 0.061710294, 0.00049987395, 0.27338788, 0.04845922, -0.014114694, -0.06371904, 0.008664) * go_2(-1.0, -1.0);
result += mat4(0.063082814, -0.02755945, -0.15663072, -0.053271208, 0.070173115, 0.038125586, -0.11840675, -0.016337764, -0.07963128, -0.06404943, 0.23033784, -0.007848355, -0.04434174, -0.092422634, -0.013985954, -0.038096108) * go_2(-1.0, 0.0);
result += mat4(0.037121523, -0.020622304, 0.086708754, 0.045878958, -0.13188364, -0.022858748, -0.22411314, -0.08116162, 0.048863005, 0.039260563, -0.04934298, 0.11015131, 0.028177079, 0.025245499, 0.1067935, 0.15324049) * go_2(-1.0, 1.0);
result += mat4(0.068235874, -0.14401375, -0.032677606, 0.02996807, -0.11290208, 0.114133574, -0.09627152, 0.053930115, 0.14560424, -0.15935057, -0.13495773, 0.29710987, -0.23231608, 0.14334352, 0.070753984, -0.08189047) * go_2(0.0, -1.0);
result += mat4(-0.22378983, -0.09858718, 0.30114698, -0.0048736916, 0.02198528, 0.21444769, -0.11228022, -0.14812283, 0.092372194, 0.1598949, 0.2534843, 0.4932573, -0.16642319, 0.12972073, -0.04147445, -0.09365905) * go_2(0.0, 0.0);
result += mat4(-0.132199, -0.0798279, -0.18289213, -0.15133642, -0.033057958, 0.007495456, 0.070398286, 0.049111973, -0.03361502, 0.032059964, 0.003850814, 0.22922683, 0.20279214, -0.07350396, 0.27681342, 0.11891455) * go_2(0.0, 1.0);
result += mat4(-0.095355205, -0.08533997, -0.043466177, 0.03183743, 0.0048090555, -0.07969942, -0.044769235, 0.15350139, 0.06485437, -0.027922742, 0.0850892, 0.00069019396, 0.035737295, 0.20380683, 0.03413393, 0.025630401) * go_2(1.0, -1.0);
result += mat4(0.26616514, -0.024066277, 0.09220501, 0.09643391, -0.014585791, 0.22894275, -0.053128377, -0.08719867, -0.08819027, 0.01932318, -0.113633566, -0.15435793, 0.10542983, 0.029819246, 0.33675614, -0.059085276) * go_2(1.0, 0.0);
result += mat4(-0.031325538, 0.040770013, -0.049561024, -0.2095101, -0.09537227, -0.075998954, -0.04323478, -0.05470401, -0.110066876, 0.059249427, -0.042351052, -0.047700178, 0.21932366, -0.12850443, 0.035361454, 0.013699006) * go_2(1.0, 1.0);
result += mat4(-0.08417607, 0.113477044, 0.03574209, 0.007835156, 0.2021717, 0.030678429, 0.19313626, -0.03506592, 0.04233059, -0.08540689, -0.07128929, -0.13245375, -0.08918939, -0.042622462, 0.19011301, -0.18228586) * go_3(-1.0, -1.0);
result += mat4(-0.19981891, -0.16255717, 0.042949058, -0.06921157, 0.279451, -0.11536949, -0.13747527, -0.10020231, -0.013784027, -0.06727259, 0.3556115, 0.08460814, -0.15348805, -0.07692103, -0.018658075, 0.0037634284) * go_3(-1.0, 0.0);
result += mat4(-0.09063814, -0.036312047, 0.13528036, 0.0070792423, 0.11834377, 0.02331524, 0.09386154, 0.07144935, 0.033078104, -0.1397121, 0.09283168, 0.2118868, -0.06313442, 0.032146804, 0.0060367053, 0.005822348) * go_3(-1.0, 1.0);
result += mat4(0.035949346, 0.06469895, -0.0051385965, -0.078584194, 0.43195483, 0.0045206803, -0.24012396, 0.21436183, -0.013394304, -0.04198491, 0.06645506, -0.23869638, -0.02311661, 0.06589808, 0.16800866, -0.21120183) * go_3(0.0, -1.0);
result += mat4(-0.24937367, -0.042277586, 0.08117994, 0.3105402, -0.26087892, -0.10325264, -0.08689298, 0.0064907144, 0.031937066, 0.09783758, -0.9514562, -0.104631096, 0.27990052, 0.36389935, 0.057687905, 0.14072314) * go_3(0.0, 0.0);
result += mat4(-0.19865227, 0.09398578, 0.06911146, 0.13077813, 0.024283953, -0.0036808057, -0.036725305, -0.024085987, 0.061556816, 0.0029027078, 0.24621862, 0.112107046, 0.068239614, 0.052718107, 0.20803368, 0.065064415) * go_3(0.0, 1.0);
result += mat4(-0.055511028, -0.08662344, -0.074801624, -0.021917107, 0.18730342, 0.047116343, 0.14872652, 0.10580926, 0.16962165, 0.16628978, 0.17343876, -0.1697205, 0.047853447, -0.22705628, 0.031780355, -0.09273609) * go_3(1.0, -1.0);
result += mat4(-0.17306295, -0.067308225, -0.17174196, -0.13221754, -0.24622467, 0.029901514, -0.12799668, -0.04145667, -0.14546, 0.013308366, 0.028113116, 0.1678875, 0.07922657, -0.015584258, 0.17059629, 0.07330948) * go_3(1.0, 0.0);
result += mat4(-0.09916512, 0.0623665, -0.022458963, 0.061962493, 0.18569344, -0.06590287, 0.111395456, 0.08477448, -0.03609452, 0.024279302, -0.083497405, 0.06459743, -0.22963138, -0.12262581, 0.006980887, -0.06653474) * go_3(1.0, 1.0);
result += vec4(-0.023354841, 0.0019475977, -0.0705355, -0.08216019);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf1
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.13703531, 0.06135254, -0.05032855, 0.0039429665, -0.05997914, 0.03737832, -0.09703001, -0.08112204, -0.096779875, 0.086732335, 0.03021232, -0.14636067, 0.079296306, 0.006656948, 0.08904937, 0.06196539) * go_0(-1.0, -1.0);
result += mat4(-0.26374274, 0.16698441, -0.08554561, 0.03734819, -0.08525629, 0.12257442, 0.015473835, 0.13266069, 0.008439022, -0.05002345, 0.03232084, 0.17349075, 0.014541135, -0.10353582, 0.13339484, -0.13474584) * go_0(-1.0, 0.0);
result += mat4(0.05637785, -0.049726896, 0.06597188, 0.0058668824, -0.10623723, 0.13441847, 0.015975956, -0.07811197, 0.05975957, -0.062021587, -0.06533749, 0.083735935, 0.02666556, 0.029904561, -0.0102926055, -0.10931666) * go_0(-1.0, 1.0);
result += mat4(-0.22616413, 0.042830274, -0.116208926, -0.053796053, -0.1112898, 0.20703097, -0.34109348, -0.065111674, -0.17255561, 0.16784647, 0.00193431, -0.043237597, -0.02353095, -0.1302526, 0.05119598, 0.01403269) * go_0(0.0, -1.0);
result += mat4(0.086109385, -0.053006437, -0.24992542, 0.007938272, -0.0027849772, 0.09198081, -0.17596659, 0.030577915, -0.31807357, -0.29618275, 0.0056317504, 0.3662508, 0.16753437, -0.12481447, -0.057597708, -0.14973637) * go_0(0.0, 0.0);
result += mat4(-0.14585754, 0.027715279, -0.039035518, 0.11505972, 0.0038059987, -0.20368981, -0.014822689, 0.094012834, -0.20693347, -0.37216228, -0.12690443, 0.2727411, -0.15475404, -0.01948714, -0.12414737, 0.10378582) * go_0(0.0, 1.0);
result += mat4(-0.11750072, 0.051394574, -0.011073509, -0.1100907, -0.1389209, -0.10706716, 0.0017484069, -0.059556484, -0.20038931, 0.24976069, -0.011129469, -0.080446415, 0.19259459, -0.14515446, -0.07275811, 0.039244935) * go_0(1.0, -1.0);
result += mat4(-0.101780266, 0.003889027, 0.010705813, 0.011088775, -0.20406786, -0.009807119, 0.23070864, -0.030722639, -0.012015954, 0.025211284, -0.29246482, 0.04907962, -0.10485314, 0.21213223, 0.15788344, -0.014188987) * go_0(1.0, 0.0);
result += mat4(0.1546438, -0.15895118, 0.010730076, 0.034053337, -0.018741185, -0.008467293, 0.13143812, 0.022905342, -0.27543658, 0.3054419, 0.07025048, 0.29454592, -0.0032350307, 0.01671764, 0.081928045, -0.10051137) * go_0(1.0, 1.0);
result += mat4(-0.014834404, 0.07487839, -0.16554666, -0.04127725, 0.15239598, -0.017607473, 0.09927426, 0.15027349, -0.2073968, 0.041613225, -0.10290223, -0.12565911, 0.022021815, -0.07609557, -0.16338238, 0.04468512) * go_1(-1.0, -1.0);
result += mat4(0.01768976, 0.0637369, 0.006542782, -0.0022799321, -0.14728844, -0.058199093, -0.029928437, 0.079634584, 0.095769696, -0.13526416, 0.20718366, -0.10116214, 0.1688786, -0.08906526, 0.020397741, 0.06541649) * go_1(-1.0, 0.0);
result += mat4(-0.033067044, 0.10095467, -0.13792777, 0.022673525, -0.012797848, -0.11222105, 0.11443862, 0.04893716, 0.11389547, -0.07337629, 0.21447009, -0.032212257, 0.23070163, -0.18156143, 0.14542435, -0.10207653) * go_1(-1.0, 1.0);
result += mat4(-0.22985588, 0.012290226, 0.018557416, -0.064000085, 0.012936774, -0.104329854, -0.0719669, 0.24160251, 0.03716294, -0.093069404, -0.12110873, 0.013251573, -0.12731232, -0.1995954, -0.07679729, 0.06823493) * go_1(0.0, -1.0);
result += mat4(-0.23359679, -0.052702624, -0.08710696, 0.19826421, 0.12880315, 0.19875911, -0.20581602, 0.32980308, -0.14479029, 0.099422045, 0.44737315, 0.13044962, 0.12935589, -0.13621494, 0.14902137, 0.09162335) * go_1(0.0, 0.0);
result += mat4(0.10801082, -0.22644557, 0.035719793, -0.12396268, 0.2906566, 0.119107775, -0.15470679, 0.17997102, -0.12866725, -0.12695445, -0.06832712, 0.017622665, 0.08215481, 0.065239124, -0.1256659, -0.06811625) * go_1(0.0, 1.0);
result += mat4(-0.097956754, 0.09383762, -0.19813508, 0.0035260199, -0.14278924, 0.0660843, 0.19110036, 0.11025648, 0.15489757, 0.011157471, -0.16014035, -0.050144047, 0.0032884583, 0.061513808, -0.03385016, -0.08534137) * go_1(1.0, -1.0);
result += mat4(0.09499595, 0.04162155, -0.26091605, -0.18066265, -0.21523187, -0.036668014, 0.09586408, 0.059850723, -0.10890033, 0.28857672, -0.32993382, 0.05107536, 0.012024929, -0.27968574, 0.15081042, -0.07215633) * go_1(1.0, 0.0);
result += mat4(0.15673614, -0.064684846, -0.13838115, 0.1264376, -0.23772664, 0.11594999, 0.0898036, -0.092647165, 0.26081505, 0.05110054, -0.017965768, 0.06740709, -0.24977967, 0.05645255, -0.08204664, 0.0435078) * go_1(1.0, 1.0);
result += mat4(0.02560865, -0.1613835, 0.05876215, 0.101586774, -0.00058163394, 0.0013674656, 0.039857507, -0.002919488, 0.05573127, -0.04311352, 0.05305971, 0.10097247, 0.036392104, -0.025071293, 0.029137935, -0.08593101) * go_2(-1.0, -1.0);
result += mat4(0.12406646, -0.21399136, 0.05611706, 0.021867402, -0.037916705, 0.05941278, 0.11277805, -0.12387807, 0.008577062, -0.045022104, 0.16465645, -0.07607619, 0.035939474, 0.07221297, -0.13557361, 0.07806311) * go_2(-1.0, 0.0);
result += mat4(-0.19589397, 0.011909766, -0.01258029, -0.065313555, 0.07366803, -0.0812486, 0.115863465, 0.019752543, -0.15854625, 0.11246406, 0.007201303, 0.0008530298, -0.0287012, -0.036224626, 0.059641607, 0.09416462) * go_2(-1.0, 1.0);
result += mat4(0.20361906, -0.20671111, -0.1126041, 0.049152024, 0.17586707, 0.10047246, 0.13149028, -0.16302691, -0.08559989, -0.17756243, -0.0061752857, 0.124775924, 0.020011704, 0.17147969, -0.0003063916, -0.015890911) * go_2(0.0, -1.0);
result += mat4(0.11051906, 0.13774526, 0.29333818, -0.029932505, -0.07021508, 0.046212852, 0.11793092, -0.081830084, -0.18609521, -0.108229816, -0.044969153, -0.041069634, -0.13936938, 0.11356429, 0.19260931, 0.093210496) * go_2(0.0, 0.0);
result += mat4(0.010555152, -0.15726428, -0.13187453, -0.12396212, 0.17309372, 0.100884624, 0.11547714, -0.030650318, -0.21877939, -0.0015167049, -0.090150684, 0.029793834, 0.1465573, -0.038805004, -0.033211514, -0.04926991) * go_2(0.0, 1.0);
result += mat4(0.10250675, -0.030922988, -0.008545946, 0.024706079, 0.105154864, -0.06838902, -0.12627976, 0.032457255, 0.21747419, -0.12865087, -0.056018118, 0.07152061, -0.11214344, -0.029831404, 0.044855718, -0.04316971) * go_2(1.0, -1.0);
result += mat4(0.12806997, 0.12385188, -0.06831653, -0.015933594, 0.08645126, 0.013043054, -0.19599608, -0.060719345, -0.23076192, 0.19181651, 0.1292978, 0.036317572, -0.061692618, -0.25434494, -0.10012762, 0.06366783) * go_2(1.0, 0.0);
result += mat4(-0.11098094, 0.034632366, -0.053560194, 0.08499573, 0.20842391, -0.020262053, -0.023394845, 0.048971336, 0.10436084, 0.12614205, 0.035942093, -0.07592917, -0.07455495, -0.012119416, -0.011834865, 0.21032205) * go_2(1.0, 1.0);
result += mat4(-0.00055114913, -0.06662242, -0.009248925, -0.0024843027, -0.22993802, -0.04828541, -0.08667693, -0.093717255, 0.14400347, 0.030130679, -0.01590651, 0.10399553, 0.14478837, -0.11228224, -0.039653912, -0.042144097) * go_3(-1.0, -1.0);
result += mat4(-0.011044514, -0.09870122, -0.24879128, 0.111903004, 0.092567004, 0.06100228, 0.0053522107, 0.065252475, -0.18228072, 0.25602147, -0.2863954, 0.103064165, 0.052214783, -0.017557586, -0.07434391, 0.021111684) * go_3(-1.0, 0.0);
result += mat4(0.04537496, -0.024985183, -0.15247425, -0.0009907635, -0.09677889, 0.09858206, -0.030702371, 0.03539458, -0.029408665, 0.24335481, -0.1918429, 0.08056781, 0.1548214, 0.2850923, -0.15131058, -0.052048493) * go_3(-1.0, 1.0);
result += mat4(0.055409238, -0.13090813, -0.016612396, -0.019183576, -0.18499215, -0.013184845, 0.038750056, 0.10953814, -0.18437819, 0.19183092, -0.09780726, -0.046532292, -0.10841146, -0.17717329, -0.1731886, -0.06741823) * go_3(0.0, -1.0);
result += mat4(0.27919188, -0.14904179, 0.22850563, -0.17785722, -0.32835802, -0.19134615, 0.32093298, 0.24667856, 0.51687604, -0.59745705, 0.23057328, -0.41411245, -0.4234339, -0.03083826, -0.13972719, 0.1729651) * go_3(0.0, 0.0);
result += mat4(0.042352367, -0.109207705, -0.31047532, 0.08896513, -0.2187999, -0.117951825, 0.060705405, -0.10287316, 0.013815159, -0.023699438, -0.053614594, 0.09065406, -0.15286967, -0.101803675, 0.019537682, 0.12476822) * go_3(0.0, 1.0);
result += mat4(0.0016159728, 0.04094818, 0.012745902, -0.051958837, 0.014557628, 0.00061195926, -0.11669799, 0.08763203, -0.27820277, 0.17871988, 0.10634548, 0.05234229, 0.03827577, -0.3117398, 0.027675012, 0.0655132) * go_3(1.0, -1.0);
result += mat4(-0.0025006514, -0.1457415, 0.053443488, -0.0050932285, 0.01582735, 0.18783967, -0.066718, -0.15485887, -0.039741408, -0.21280284, 0.1502977, 0.09507925, 0.17178543, -0.014238171, -0.35757875, 0.026410697) * go_3(1.0, 0.0);
result += mat4(-0.19434428, -0.079038315, -0.017264817, -0.04004242, 0.0063378955, 0.027904915, 0.02571677, 0.09895997, -0.036605608, -0.19889063, 0.015920812, -0.014095519, 0.4363826, -0.14143194, 0.015463533, -0.1656284) * go_3(1.0, 1.0);
result += vec4(0.08523788, 0.052322272, 0.08955637, -0.06945023);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.048841953, -0.010713874, 0.09238948, -0.0789676, -0.093295254, 0.063662216, -0.023454266, -0.06739832, 0.027439933, 0.007399632, -0.03550259, -0.013834889, 0.17168441, 0.06177229, 0.023950668, 0.14574073) * go_0(-1.0, -1.0);
result += mat4(0.117296845, -0.07858486, -0.02099164, -0.024150673, -0.11662526, -0.26440877, -0.05449493, -0.13366842, -0.06870016, 0.12457937, 0.25052628, 0.013982828, 0.15127566, -0.031653196, -0.13851896, 0.04148151) * go_0(-1.0, 0.0);
result += mat4(0.024360385, -0.31051615, 0.012448293, -0.11265428, 0.06123606, -0.0701936, 0.033618104, -0.064061284, -0.06969811, -0.108838804, 0.014163671, 0.02596177, 0.20071186, -0.0028744373, 0.13663651, -0.05592813) * go_0(-1.0, 1.0);
result += mat4(0.13492568, -0.0726796, 0.13431883, -0.085713945, 0.056370113, 0.115660414, -0.14475793, 0.0044200714, 0.027387753, 0.045452334, 0.28178552, 0.017371183, 0.17304336, 0.0582999, 0.14465337, 0.046005037) * go_0(0.0, -1.0);
result += mat4(0.064034574, 0.041531377, 0.08218889, -0.44529077, -0.010563538, -0.14926371, 0.051012456, 0.08209141, 0.24089444, -0.225398, -0.22259372, -0.26353076, -0.1687418, -0.11501685, -0.016655196, -0.09882357) * go_0(0.0, 0.0);
result += mat4(-0.019985389, -0.19189276, -0.104917, -0.11139956, -0.08406414, 0.031484302, -0.082132496, 0.025829919, 0.07512055, 0.31116992, 0.061163265, -0.074850895, -0.091695994, -0.26492774, -0.06617365, 0.06590624) * go_0(0.0, 1.0);
result += mat4(0.1326703, 0.13008863, -0.1659525, -0.058325157, -0.047528613, 0.06777741, 0.06953616, 0.010587038, 0.031675722, -0.08119788, -0.11269768, -0.06225964, -0.26593694, 0.03627298, 0.12866129, 0.17876588) * go_0(1.0, -1.0);
result += mat4(-0.29016155, -0.12549841, -0.050858997, -0.088932805, 0.002237332, 0.01287246, 0.30138868, -0.071756564, -0.061206467, -0.11114371, -0.25731218, -0.11551616, -0.069513, -0.004583348, -0.10647163, 0.01981785) * go_0(1.0, 0.0);
result += mat4(0.16387528, 0.03450354, 0.03422023, -0.014030813, 0.13418834, -0.010909722, -0.00447121, -0.03082622, -0.23983373, -0.020655053, -0.054034587, -0.07133469, 0.21171515, 0.06268651, -0.1738516, -0.15001713) * go_0(1.0, 1.0);
result += mat4(0.040721033, -0.037582736, -0.13819644, -0.123978324, 0.1650318, 0.033942625, 0.17534302, 0.06452234, 0.18384823, 0.0048657497, 0.20220642, -0.0025760103, 0.011163899, 0.027265374, -0.051284578, 0.19202651) * go_1(-1.0, -1.0);
result += mat4(-0.057493486, -0.031516504, 0.10835143, -0.040618125, -0.07762303, -0.06787725, 0.025559613, -0.0055560498, -0.0017830619, 0.020185964, -0.06656476, -0.008523214, 0.32331157, -0.21633361, 0.15338033, -0.104042485) * go_1(-1.0, 0.0);
result += mat4(-0.18544987, -0.090446, -0.26797467, -0.082941435, -0.15003708, -0.11446041, -0.0394892, 1.1379096e-05, 0.04978554, 0.3350256, 0.032780237, 0.034625802, 0.0596261, 0.045886245, 0.009002243, 0.04746998) * go_1(-1.0, 1.0);
result += mat4(-0.17104147, 0.0054165213, 0.09161088, -0.0673989, -0.119282715, -0.09094731, 0.47243354, 0.09914267, -0.13958418, -0.0050379517, 0.14352496, 0.18380567, -0.16128838, 0.08766813, 0.013876981, -0.09808636) * go_1(0.0, -1.0);
result += mat4(0.09617889, 0.045525175, -0.2550057, -0.02874332, 0.2743444, -0.20102581, 0.008461914, 0.16626629, -0.13309516, -0.19307104, 0.15780488, 0.15518525, -0.2790243, 0.056782067, 0.16836968, 0.17771688) * go_1(0.0, 0.0);
result += mat4(-0.10694667, 0.14490083, -0.037976455, 0.013456577, -0.1166783, 0.060722847, 0.07323464, -0.013812333, 0.03234213, 0.50859296, -0.20670377, -0.019631205, -0.022543924, 0.21776745, -0.093769215, 0.12193299) * go_1(0.0, 1.0);
result += mat4(-0.15260598, -0.04798592, -0.02370747, -0.005714705, 0.030857049, -0.16643822, 0.23971851, 0.08117996, -0.069645695, -0.06674784, 0.033509918, 0.06333286, 0.14010383, 0.02218942, -0.036704093, 0.043163314) * go_1(1.0, -1.0);
result += mat4(0.14653306, 0.002759894, 0.10548246, 0.24976018, 0.3212893, -0.07108953, 0.14068738, 0.29437128, -0.020556152, -0.17813908, 0.1989112, 0.12182122, -0.19231579, 0.06547012, -0.032785345, 0.089717634) * go_1(1.0, 0.0);
result += mat4(-0.23632105, -0.027022298, 0.00586518, 0.01836479, -0.2854795, -0.035417695, -0.07586866, 0.0715673, 0.17984483, 0.11210451, 0.032767817, 0.097993985, -0.010899036, 0.15933803, 0.05454052, 0.06768528) * go_1(1.0, 1.0);
result += mat4(-0.017289463, -0.058823984, 0.0807603, 0.32464716, 0.2756627, 0.036061637, -0.034578573, -0.08811335, 0.031841308, 0.11359879, 0.07553143, -0.028648997, 0.057192322, 0.07769366, -0.1998847, -0.06258051) * go_2(-1.0, -1.0);
result += mat4(0.0422091, 0.046305113, 0.028377453, -0.031071126, 0.06866086, 0.1538135, -0.009288249, -0.25543538, 0.07067607, -0.114061736, -0.024740022, -0.11824987, -0.17426041, 0.0028396242, 0.12849464, 0.057790644) * go_2(-1.0, 0.0);
result += mat4(0.057328146, 0.030677445, 0.07496485, 0.07847613, -0.22358766, -0.15659446, -0.18270054, -0.21316889, 0.084770195, 0.013863274, -0.001335942, -0.04027535, -0.15230416, -0.048156176, -0.04614562, 0.089494966) * go_2(-1.0, 1.0);
result += mat4(-0.117369525, 0.026577681, -0.1941765, 0.14904885, -0.16210394, -0.19549404, 0.19999947, 0.37138188, 0.14809363, -0.05078633, -0.092692114, -0.08533522, 0.12769112, 0.017061725, 0.104464866, -0.026744602) * go_2(0.0, -1.0);
result += mat4(0.0880251, -0.005333869, -0.10327546, 0.30419552, 0.107773595, 0.02335926, -0.19014318, 0.19670166, -0.09443473, 0.10621109, 0.36843884, 0.13197622, 0.24537645, 0.4032842, 0.21791221, 0.08400414) * go_2(0.0, 0.0);
result += mat4(0.06408587, 0.15366535, 0.042582024, 0.15629277, 0.028716238, -0.013479061, -0.23052843, -0.2992272, -0.050045617, -0.27255702, -0.038093377, 0.0031149297, -0.05625518, 0.52598304, -0.0845234, -0.09116851) * go_2(0.0, 1.0);
result += mat4(0.02294159, -0.011902539, 0.00079296535, 0.030631313, 0.02114366, 0.082455896, 0.09450867, -0.08027284, 0.042443607, 0.15427661, 0.11882799, -0.040319934, 0.23706424, -0.107808165, -0.1730313, -0.06340064) * go_2(1.0, -1.0);
result += mat4(0.2645207, 0.002157867, -0.095794424, 0.1141035, 0.08255855, -0.06977906, -0.04348005, 0.27864936, -0.1197219, 0.015997604, 0.09500464, -0.0010631803, 0.07198933, -0.053128377, 0.02176274, -0.001298847) * go_2(1.0, 0.0);
result += mat4(-0.045475803, 0.03626341, -0.00891833, 0.17907676, -0.2810277, 0.13725498, -0.02413441, -0.08605496, 0.08306595, -0.012227401, -0.0070282067, -0.019027572, -0.13443586, -0.041331865, 0.029120144, -0.00490357) * go_2(1.0, 1.0);
result += mat4(-0.13398282, 0.06475972, 0.2528711, 0.02553969, -0.13428321, -0.03931247, 0.11360386, -0.18912545, -0.3725821, -0.018747944, -0.20893294, -0.012743096, 0.07444533, -0.15381604, 0.29776138, 0.10601149) * go_3(-1.0, -1.0);
result += mat4(-0.21793252, 0.07817356, -0.109576665, 0.19185133, -0.072846025, 0.04960289, -0.07506936, 0.12839878, -0.0061091883, 0.093669325, 0.009295678, 0.03780657, -0.10901407, 0.1375137, -0.0745914, 0.1468883) * go_3(-1.0, 0.0);
result += mat4(0.10739044, 0.30611086, 0.1585515, 0.07903283, 0.05612715, -0.0061900485, 0.13646163, 0.15230569, 0.036846787, -0.15846778, -0.18765065, 0.06611226, -0.07209187, 0.056037188, 0.04302953, -0.03887873) * go_3(-1.0, 1.0);
result += mat4(0.05618538, -0.072312586, -0.018046018, 0.049542785, -0.033638306, -0.035169322, -0.25882784, -0.036425237, 0.43763217, -0.07049093, 0.08085481, 0.013634128, -0.2701461, -0.13007875, 0.09603447, 0.2479431) * go_3(0.0, -1.0);
result += mat4(-0.02283992, -0.24593964, 0.04616348, 0.023422526, -0.20994014, 0.064769074, -0.07680045, -0.30547765, 0.1518723, 0.31953967, -0.12841515, -0.19525428, -0.0076093865, -0.112106465, -0.04573789, -0.04834478) * go_3(0.0, 0.0);
result += mat4(-0.008045419, -0.20285496, 0.15290824, 0.036240693, 0.11959966, -0.15712506, 0.096806675, 0.008780234, -0.19716795, -0.3824029, 0.1376541, 0.13325086, -0.103316806, -0.31788048, -0.071698256, -0.25901568) * go_3(0.0, 1.0);
result += mat4(0.13714787, 0.020738773, 0.13716534, 0.12359137, -0.038154524, 0.053202964, -0.12023912, 0.09011213, -0.012448548, -0.026505312, -0.11293235, 0.10613704, -0.39916727, 0.041521315, 0.10659441, 0.027749784) * go_3(1.0, -1.0);
result += mat4(-0.26475835, 0.044597875, -0.31229413, -0.17121075, -0.21795374, -0.009583571, -0.13428004, -0.30734754, -0.017038794, 0.113667324, -0.1516075, 0.06525228, -0.13789397, -0.05770066, -0.016166758, -0.29457557) * go_3(1.0, 0.0);
result += mat4(0.054183286, 0.022085225, 0.086794585, 0.10968018, 0.1276148, 0.05739452, 0.08860957, -0.08131373, -0.081570424, -0.107991874, -0.03724999, 0.000843539, 0.20231429, -0.123543546, -0.19073018, -0.28328305) * go_3(1.0, 1.0);
result += vec4(0.013646388, -0.021442367, 0.0045393505, -0.037433166);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf1
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.13948695, 0.016643738, 0.08168136, 0.02315663, 0.017184775, 0.11487715, 0.05770107, 0.010102888, 0.04955321, -0.045132335, -0.05731744, -0.05798246, 0.2245112, 0.17406365, 0.08979801, -0.10607952) * go_0(-1.0, -1.0);
result += mat4(0.2812785, 0.022830509, 0.15164222, 0.13460225, 0.22263442, 0.2558749, -0.122489706, 0.10409658, 0.023308244, -0.19583783, -0.007824269, 0.06256542, 0.11161938, 0.14878923, 0.30865005, 0.08962341) * go_0(-1.0, 0.0);
result += mat4(-0.20843887, 0.012371968, -0.008279775, -0.042467568, -0.13022369, 0.056743186, -0.018389069, 0.13964763, -0.03361555, -0.053087234, 0.012521351, 0.0209293, 0.015771557, 0.11718523, 0.010176676, 0.021708367) * go_0(-1.0, 1.0);
result += mat4(-0.14373007, -0.114338934, -0.09077395, -0.11040866, 0.055298284, 0.022516333, 0.18901019, -0.05640152, -0.1413198, -0.08748339, -0.029985962, 0.00712751, -0.071436934, -0.18909407, 0.173448, 0.053675048) * go_0(0.0, -1.0);
result += mat4(-0.023129769, 0.42883545, -0.18110612, 0.24296297, -0.02441117, 0.18108079, -0.12298153, -0.19192219, -0.14139178, -0.069563635, 0.1524624, -0.17755614, -0.248875, 0.015161957, -0.16541803, -0.17773613) * go_0(0.0, 0.0);
result += mat4(-0.065477535, -0.113195814, -0.08284894, 0.11679537, 0.028445985, -0.026559185, -0.007267581, 0.14052133, 0.14847197, -0.040276285, -0.038166475, -0.030452784, -0.15184602, -0.22223297, 0.113732725, 0.11163395) * go_0(0.0, 1.0);
result += mat4(0.04990171, 0.08493333, 0.08668171, 0.14610586, -0.010766879, -0.05690133, 0.10706113, 0.13667485, 0.044783257, 0.029695645, -0.101674624, -0.02023205, 0.031889528, 0.14293797, 0.08712652, 0.08716896) * go_0(1.0, -1.0);
result += mat4(-0.21387868, -0.21650635, 0.2743992, -0.048781313, -0.027735803, -0.1543507, 0.11343657, -0.18251626, 0.15225998, 0.13158897, -0.41056108, 0.102582805, -0.09181491, -0.0042975787, 0.056065407, -0.16961528) * go_0(1.0, 0.0);
result += mat4(0.08966051, 0.09331515, -0.085415326, -0.022695992, 0.009771476, -0.07143986, 0.0590329, 0.07347928, -0.09033658, -0.06805735, -0.20129825, 0.017873045, 0.16908158, 0.014213783, 0.112663984, 0.10048714) * go_0(1.0, 1.0);
result += mat4(0.115590535, 0.08364541, 0.00864431, -0.094349444, -0.11073411, 0.05337711, 0.055587426, 0.12131219, -0.04710173, -0.046455074, 0.110379905, 0.25445566, 0.15154606, 0.04483541, 0.08708686, 0.113456205) * go_1(-1.0, -1.0);
result += mat4(-0.014296297, 0.24858733, 0.05035193, -0.09225393, 0.034625243, 0.06219943, 0.19825043, 0.04673499, -0.4083363, -0.39954248, -0.08299408, 0.048756655, 0.09862206, 0.01588621, 0.0070629907, 0.04173666) * go_1(-1.0, 0.0);
result += mat4(0.17356622, 0.1484559, -0.10054033, 0.013332302, 0.15200937, 0.08985606, -0.031668343, -0.026007611, -0.16339104, 0.054744486, 0.07386605, -0.033910174, -0.0018002358, -0.02968911, 0.054931052, 0.09970459) * go_1(-1.0, 1.0);
result += mat4(-0.07330346, 0.05938635, 0.01911963, -0.09856661, -0.081916444, -0.046957035, -0.043849826, 0.09572135, -0.13621825, 0.034347896, -0.21189907, 0.10592239, -0.060592845, 0.09957844, 0.050621815, -0.07447668) * go_1(0.0, -1.0);
result += mat4(0.044731334, -0.13406886, -0.04138754, -0.06764551, -0.018899845, 0.35320804, -0.10959127, 0.17435175, -0.17941645, -0.30889434, 0.10573405, 0.0319751, -0.15677677, 0.08164649, 0.16559398, -0.08152387) * go_1(0.0, 0.0);
result += mat4(0.057760764, -0.12145107, 0.06889264, -0.30627275, 0.011501002, -0.080296256, -0.18067095, 0.10592384, 0.12884894, -0.18973115, 0.18740658, 0.28362688, 0.12934786, -0.010292026, 0.0559999, 0.079962276) * go_1(0.0, 1.0);
result += mat4(0.048659086, -0.006250348, -0.041242067, -0.12078197, -0.07152629, 0.05699244, 0.0011704164, -0.023007339, 0.07814492, 0.02546712, -0.08957218, -0.036925297, -0.03383498, 0.12583385, 0.12207602, 0.03910942) * go_1(1.0, -1.0);
result += mat4(0.26151723, 0.23277281, -0.021892069, 0.052827276, 0.18268764, 0.28595275, -0.20529993, 0.19892794, 0.0038986763, 0.114547804, -0.020574905, 0.02405073, 0.11713121, 0.04491106, -0.07557327, 0.014374293) * go_1(1.0, 0.0);
result += mat4(-0.14276731, -0.06600894, -0.029757235, -0.099975966, 0.023050314, -0.07662015, -0.11542214, 0.087981045, 0.070319094, 0.12462511, 0.008152087, 0.12613884, -0.07071591, 0.0063393894, 0.08699723, -0.0242523) * go_1(1.0, 1.0);
result += mat4(0.035586607, -0.26826563, -0.10145326, -0.002177148, 0.022144236, -0.117452875, 0.021346297, 0.051908135, -0.022425706, 0.067299, 0.09406446, 0.078294896, 0.014900606, -0.05468236, 0.07241715, 0.061000507) * go_2(-1.0, -1.0);
result += mat4(-0.184133, 0.06229474, -0.13819578, -0.025011744, -0.01868356, -0.18940887, 0.092631504, -0.092806384, 0.0035951615, 0.11777577, 0.028149817, 0.0049419673, 0.22230826, 0.06337655, -0.20004818, -0.20937593) * go_2(-1.0, 0.0);
result += mat4(0.13852163, -0.094492316, -0.040309057, 0.10771662, 0.18963522, 0.08687606, -0.20030232, -0.082126215, 0.012181411, 0.044306785, -0.036970526, 0.04403363, 0.07911973, 0.0021176056, 0.26944208, -0.06657045) * go_2(-1.0, 1.0);
result += mat4(0.027229607, 0.12410596, 0.04348171, 0.0019921176, 0.088246435, -0.02828269, -0.26499373, -0.12566662, 0.025947344, -0.0078000715, 0.058063716, -0.0032702687, 0.0059978673, -0.04860002, 0.027650384, -0.23394564) * go_2(0.0, -1.0);
result += mat4(0.07892762, -0.13300626, 0.46678603, -0.033239357, -0.12306804, -0.079602, 0.20534003, 0.23873802, -0.035643574, 0.059950788, -0.26559883, 0.12206408, 0.25408483, 0.029933078, 0.32081822, 0.033947676) * go_2(0.0, 0.0);
result += mat4(-0.06847802, -0.017930118, -0.12299636, -0.12987946, 0.09267518, -0.0009083275, -0.035390552, -0.15379669, -0.1132433, -0.036670692, -0.08342377, 0.015636675, 0.022590527, 0.10533322, 0.0389949, -0.059033744) * go_2(0.0, 1.0);
result += mat4(-0.041753534, -0.014428097, 0.06999257, -3.546234e-05, -0.033465035, -0.040709455, 0.13118082, -0.21016484, -0.07846085, -0.030885663, 0.06934681, 0.12725256, -0.023784902, -0.13373604, -0.015261479, 0.05234782) * go_2(1.0, -1.0);
result += mat4(0.13798563, 0.12757827, -0.26978776, 0.102494285, 0.13285922, 0.35432795, -0.11997128, 0.17108068, -0.12235328, -0.24582328, 0.26962712, -0.086760186, 0.010127441, 0.08048835, 0.047505867, 0.19991067) * go_2(1.0, 0.0);
result += mat4(0.03584222, -0.13433793, -0.044629525, -0.0010440781, -0.0033084434, -0.026725832, -0.05386642, -0.13612603, 0.10066015, 0.10499841, 0.031767137, -0.04550841, -0.09391546, 0.1454157, -0.26962402, 0.21015608) * go_2(1.0, 1.0);
result += mat4(-0.21956864, -0.13502425, -0.02126954, 0.059263993, -0.13461533, -0.04001395, -0.0924258, -0.069165014, 0.22019973, 0.003270619, 0.022072528, -0.14173602, 0.0028843523, -0.13784003, -0.061057515, -0.0049253837) * go_3(-1.0, -1.0);
result += mat4(-0.0011410525, -0.16098002, -0.12883134, 0.018262507, 0.001481578, 0.19514659, -0.13703239, 0.096059754, 0.34194204, 0.13983466, 0.14021507, 0.011405113, -0.11303146, -0.17050214, -0.06992079, -0.05566986) * go_3(-1.0, 0.0);
result += mat4(-0.12307941, -0.02192472, 0.13193923, -0.061640862, -0.16841564, -0.0822524, 0.10141759, 0.02139286, 0.1599039, -0.050632223, 0.16702358, 0.111514546, 0.02397393, 0.037606515, 0.017971672, -0.048641708) * go_3(-1.0, 1.0);
result += mat4(-0.02697617, -0.08579184, -0.28045088, 0.05262136, -0.059576314, 0.107535526, -0.06188862, 0.0010509328, -0.18178311, -0.17288832, 0.20703638, 0.083048366, 0.03859681, -0.07548898, 0.011605782, -0.021842534) * go_3(0.0, -1.0);
result += mat4(0.13198483, 0.37200937, -0.0896539, 0.12450637, 0.037202634, 0.035985112, 0.16579124, -0.08967905, -0.24341385, 0.32482424, -0.3037812, -0.007154969, -0.007152382, -0.017435173, 0.12662841, -0.090513505) * go_3(0.0, 0.0);
result += mat4(-0.014726027, 0.08394915, -0.02100581, 0.24882795, -0.023793869, -0.006450114, 0.17093314, -0.06994153, -0.08689907, 0.113542505, -0.053211495, -0.1780173, 0.030043352, 0.2500714, -0.026940798, -0.0069258413) * go_3(0.0, 1.0);
result += mat4(0.037078895, -0.03033529, -0.066851325, 0.14718252, 0.066372745, 0.028897487, -0.036055963, 0.035399746, 0.06733992, 0.21021596, -0.18314466, -0.027192699, 0.020213274, -0.17751546, -0.050674338, -0.09382659) * go_3(1.0, -1.0);
result += mat4(-0.14761917, -0.22166072, 0.033172436, -0.21982265, -0.09172891, -0.20794454, 0.1738752, -0.13685037, 0.10981111, -0.23169234, 0.053787973, 0.12001196, -0.038242023, -0.047124114, 0.22503005, 0.1015142) * go_3(1.0, 0.0);
result += mat4(0.021231879, -0.015423476, 0.058986407, 0.032002006, -0.029305007, 0.008933183, 0.10777483, -0.112574644, -0.023935415, -0.06604598, 0.053859934, -0.08354717, 0.13703763, -0.078382134, 0.12914242, -0.022056468) * go_3(1.0, 1.0);
result += vec4(-0.002022359, -0.007333954, -0.038140967, -0.03819673);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.050738923, 0.15003614, -0.18880141, 0.16791905, 0.16549185, -0.26726744, -0.12813666, -0.021510791, 0.070805945, 0.043350577, 0.0035756908, 0.11776675, -0.01824196, 0.12618026, 0.07424072, 0.032886628) * go_0(-1.0, -1.0);
result += mat4(-0.11678059, 0.0565686, 0.04392921, -0.27621672, 0.2116695, 0.038044345, -0.015018062, -0.028636303, 0.049744565, -0.12935996, 0.027176194, -0.13208814, -0.21195693, 0.08980974, 0.013893243, -0.018403184) * go_0(-1.0, 0.0);
result += mat4(0.3214697, -0.03143518, 0.19927292, 0.12566878, 0.16190828, 0.11784847, 0.09943727, 0.11755882, 0.017959306, -0.064603634, -0.14054321, -0.11917774, 0.0056958874, 0.06461699, 0.104604125, 0.021947173) * go_0(-1.0, 1.0);
result += mat4(-0.24738057, -0.034892898, -0.03364674, 0.017340986, 0.02933764, -0.08090866, -0.034651175, -0.17391174, 0.08536477, -0.17446008, 0.22706915, -0.10555482, 0.0877744, 0.0681237, -0.035909466, -0.10355238) * go_0(0.0, -1.0);
result += mat4(-0.090646185, -0.12971672, -0.14531808, -0.060838025, 0.24902023, 0.1310588, 0.18602785, 0.21283495, -0.32160765, -0.070119165, -0.10350057, 0.19260244, -0.2610542, -0.3030521, 0.08432348, -0.22286619) * go_0(0.0, 0.0);
result += mat4(0.28333843, -0.053968847, 0.08344997, 0.19987041, 0.22163449, 0.22161576, 0.0030572868, 0.10848695, -0.20529847, 0.08406883, -0.07130339, 0.09987656, 0.29774663, -0.08768785, 0.15567012, -0.010313759) * go_0(0.0, 1.0);
result += mat4(-0.1260916, -0.071901485, -0.30566844, 0.19393384, -0.05133266, 0.07868844, -0.24817581, 0.055521224, 0.23277187, 0.16324161, 0.07110341, -0.042626668, 0.052509766, -0.014292625, -0.019677468, 0.041733738) * go_0(1.0, -1.0);
result += mat4(-0.04264262, -0.06528029, 0.0013520801, -0.02140956, 0.27304867, -0.029477939, -0.1859993, 0.01418354, 0.07256604, 0.14302284, 0.03309569, -0.15932149, 0.01500576, -0.053860538, 0.1131707, -0.06272606) * go_0(1.0, 0.0);
result += mat4(-0.0400483, -0.030188695, -0.108427785, 0.057873204, 0.42774406, -0.11353873, 0.110134825, 0.052191462, 0.00087113964, 0.040683694, 0.100507155, -0.16746339, -0.26971558, 0.06506685, -0.20950548, 0.040783025) * go_0(1.0, 1.0);
result += mat4(0.11394146, -0.10693933, 0.2377026, -0.03783948, -0.16496852, 0.046675198, -0.23396324, 0.05696911, -0.02770668, 0.12922443, -0.093586415, 0.102305606, 0.0040032533, -0.038440734, -0.0035825048, -0.22108772) * go_1(-1.0, -1.0);
result += mat4(0.17577791, -0.024538597, -0.19877498, -0.14544973, 0.16614193, -0.3279891, 0.14678721, -0.16355143, -0.012954231, 0.20982395, 0.044255227, 0.087878115, 0.11289659, -0.26981032, -0.10789584, 0.24094439) * go_1(-1.0, 0.0);
result += mat4(0.0041394173, -0.0937936, 0.15251775, 0.1026978, -0.01999847, -0.02865502, 0.16765144, -0.17490439, -0.016996933, 0.03891808, -0.01858217, -0.106255606, 0.027496144, -0.14120618, 0.023483312, -0.08291959) * go_1(-1.0, 1.0);
result += mat4(0.060642462, -0.2957824, 0.33968493, -0.04501478, -0.14999421, -0.0067213452, -0.018236576, 0.01627547, -0.07771579, 0.0124932695, -0.11797959, -0.090979554, 0.0096479915, 0.021336472, -0.07794724, 0.030138575) * go_1(0.0, -1.0);
result += mat4(-0.091704845, -0.20800348, -0.22158638, 0.048748583, 0.15139692, -0.2832814, 0.09610812, 0.41077513, 0.0007106381, -0.14465855, 0.0056652213, 0.031696238, -0.03384328, 0.1940933, 0.19262145, 0.014331562) * go_1(0.0, 0.0);
result += mat4(-0.16637586, -0.22008398, 0.102937706, 0.15260033, 0.039856806, -0.21082906, -0.19694057, 0.0712475, 0.015049883, 0.17320138, 0.06505415, -0.020279367, -0.018576574, 0.201407, -0.08108244, 0.04151909) * go_1(0.0, 1.0);
result += mat4(-0.12496581, 0.107817784, 0.10645319, 0.035113968, 0.0166165, 0.1316661, -0.045253787, -0.03863719, 0.09126881, 0.07553792, -0.029150097, -0.07629157, -0.17978054, -0.27080613, -0.028408276, -0.15366451) * go_1(1.0, -1.0);
result += mat4(0.081859134, -0.11599677, 0.027383117, 0.092724435, 0.059302155, 0.10008954, -0.12217131, 0.07471211, -0.20396213, -0.040741358, 0.118772194, -0.21725504, 0.099645875, 0.09691941, -0.07696025, -0.016445495) * go_1(1.0, 0.0);
result += mat4(-0.18712623, -0.14458412, 0.03693652, 0.014525352, -0.09607279, -0.19400409, 0.032149505, 0.07106094, 0.051436905, -0.07765334, 0.017043818, 0.17777587, 0.05274306, 0.0062209824, -0.080005355, 0.026041988) * go_1(1.0, 1.0);
result += mat4(-0.090594456, -0.041637532, 0.10346829, -0.09393943, 0.027663473, 0.20729685, -0.011156861, 0.021863503, 0.04781304, -0.039483577, -0.092933334, -0.25187445, 0.033062164, 0.010756357, -0.13035728, -0.008321023) * go_2(-1.0, -1.0);
result += mat4(0.07772912, 0.010776647, -0.018709056, 0.25634038, 0.00906326, 0.21411708, 0.122652486, 0.07725616, 0.15266491, 0.1274286, 0.10400329, 0.20354506, 0.013765407, -0.039089683, 0.25870228, -0.08919069) * go_2(-1.0, 0.0);
result += mat4(-0.14971368, 0.06935879, -0.089983195, 0.01406992, 0.16989979, -0.037809014, 0.07157283, -0.050660506, -0.032826405, 0.033794664, -0.0051332368, 0.089349195, 0.06263488, -0.07048108, 0.07263597, -0.11618368) * go_2(-1.0, 1.0);
result += mat4(0.013391823, -0.07888697, -0.13984044, -0.01241464, -0.06475807, 0.06978077, -0.20329754, 0.16602662, 0.013664227, 0.12317301, -0.10240692, -0.0657491, -0.31402445, -0.14472555, 0.1739024, 0.0005437834) * go_2(0.0, -1.0);
result += mat4(0.16330495, 0.02644609, 0.23837087, -0.07734767, 0.12377497, -0.18478604, 0.35040903, -0.05262452, 0.049074646, -0.0077528385, 0.15370984, -0.22888668, 0.3603141, 0.29372314, -0.4432887, 0.20702155) * go_2(0.0, 0.0);
result += mat4(-0.18785694, 0.21085343, -0.111042105, 0.0478716, -0.08214944, -0.0922987, 0.29570273, 0.025100114, 0.25403878, 0.01271447, 0.21851794, -0.1434596, -0.21153769, 0.023305666, -0.10386609, 0.043919638) * go_2(0.0, 1.0);
result += mat4(-0.117247805, 0.013329102, 0.0313911, -0.08055777, -0.0053445757, -0.2886372, 0.07938673, -0.06659165, 0.20798062, 0.030106818, -0.04811631, 0.036332276, -0.057687126, 0.03813657, 0.035860628, -0.11273985) * go_2(1.0, -1.0);
result += mat4(-0.0031557097, 0.027456097, -0.14444692, 0.08411739, 0.13466308, -0.13212901, -0.0034804344, 0.1464661, -0.21033211, 0.05913627, 0.10233881, 0.009844489, -0.15369488, -0.018978333, -0.07518442, -0.010549853) * go_2(1.0, 0.0);
result += mat4(0.112989105, -0.011166866, -0.08277204, 0.046827227, -0.08067428, 0.13465053, -0.1656419, 0.07280515, 0.037523627, -0.050147127, -0.17731906, 0.1067486, 0.119732924, -0.102017604, 0.31421226, -0.14060387) * go_2(1.0, 1.0);
result += mat4(-0.1106223, 0.09229271, -0.09355422, -0.02413533, -0.096457504, -0.13282233, 0.022983741, -0.13534859, -0.0056585902, -0.07214356, 0.14617127, -0.13723095, 0.058078192, -0.1038417, -0.10452195, -0.18855028) * go_3(-1.0, -1.0);
result += mat4(0.16357008, 0.080841675, 0.1663936, 0.20815827, 0.03813903, 0.34158087, -0.012987109, 0.39152008, -0.027927356, -0.14332302, -0.012866622, -0.016149148, -0.08733816, 0.1960951, 0.19572765, -0.2710826) * go_3(-1.0, 0.0);
result += mat4(0.024827998, 0.24175219, 0.030659903, -0.22227505, 0.026898654, 0.009930298, 0.088392995, 0.32644793, -0.10351868, -0.08717382, 0.22931585, 0.05197704, 0.06534648, 0.13636068, 0.062107667, 0.024806283) * go_3(-1.0, 1.0);
result += mat4(-0.18550465, 0.062058095, -0.08620093, 0.20158216, -0.1460996, 0.14275469, -0.28057688, -0.11685651, -0.09627509, 0.09029933, 0.03669734, 0.1257313, -0.07974307, 0.020742215, -0.0039170664, 0.11340528) * go_3(0.0, -1.0);
result += mat4(0.15225565, 0.171972, 0.13573253, 0.0056740018, -0.1667786, 0.06028638, -0.1255049, -0.23327217, -0.139949, 0.029957669, -0.16713464, 0.046236664, -0.05070503, 0.18714412, -0.20076098, 0.1672637) * go_3(0.0, 0.0);
result += mat4(0.18468563, 0.07733334, 0.14463845, -0.10712052, 0.36213547, 0.29404843, 0.2110929, 0.14646721, -0.059985258, -0.2709805, 0.073061034, -0.039072156, 0.015898943, -0.17166951, 0.20194982, -0.04723745) * go_3(0.0, 1.0);
result += mat4(-0.26353067, 0.050225407, -0.42643914, 0.06601958, -0.10513071, -0.1654714, 0.0593609, 0.027410276, -0.19465327, -0.13865606, 0.05579213, 0.07982532, -0.20893136, -0.008150932, 0.053529713, -0.0317475) * go_3(1.0, -1.0);
result += mat4(-0.012075693, -0.27574313, 0.22184552, -0.117393926, -0.49310133, -0.13997443, -0.079180904, -0.053438634, -0.07552426, -0.045796394, -0.037434675, 0.24076645, -0.04395852, 0.10325762, -0.19867313, -0.070216134) * go_3(1.0, 0.0);
result += mat4(-0.026107877, -0.030023552, -0.047810435, 0.20572239, 0.061861858, 0.1776161, -0.306099, 0.16332485, -0.1843373, 0.06758581, -0.23902373, -0.10575018, 0.03990962, -0.046113137, 0.14876197, -0.21280771) * go_3(1.0, 1.0);
result += vec4(-0.009669773, 0.036289547, -0.050454646, 0.051479716);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf1
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.14542116, -0.15827142, -0.20811677, -0.103433, 0.19787271, 0.33990738, 0.17085013, -0.059132278, 0.013047369, -0.1687924, 0.06732661, -0.050968684, 0.09197164, -0.041265316, -0.108277336, -0.014430892) * go_0(-1.0, -1.0);
result += mat4(-0.022837132, 0.20440012, -0.14266612, 0.019944299, 0.069084294, 0.3171199, -0.1521742, -0.35806596, 0.13581008, -0.13811131, 0.12219503, 0.17329764, -0.15100783, 0.0862648, 0.118227705, 0.18736814) * go_0(-1.0, 0.0);
result += mat4(0.013604392, 0.11496102, -0.18734755, -0.047555517, 0.05297245, 0.006461213, 0.06247472, -0.0202791, 0.02329791, 0.11530998, -0.148774, 0.0965498, 0.1487269, 0.061629567, -0.22488646, -0.005393787) * go_0(-1.0, 1.0);
result += mat4(-0.29286116, 0.11958281, -0.11193505, -0.17139061, -0.035151243, -0.2635945, 0.0002499315, -0.16346519, 0.23779829, 0.04454211, 0.21293561, 0.25617847, 0.12194803, -0.0017443774, -0.009216221, -0.034387548) * go_0(0.0, -1.0);
result += mat4(0.28791443, -0.25421545, -0.058626153, -0.1520494, -0.16808414, -0.39723453, -0.13199537, 0.056999452, -0.048155293, 0.38699663, -0.114719056, 0.001293743, -0.0959443, -0.08189709, 0.26921842, 0.061219636) * go_0(0.0, 0.0);
result += mat4(0.00781977, -0.07103863, -0.21942843, 0.2419546, 0.20016691, -0.28697264, -0.034715973, -0.03381459, -0.028126812, 0.046806023, -0.14423183, -0.13472253, 0.009225362, -0.086190686, 0.0041205613, 0.08953202) * go_0(0.0, 1.0);
result += mat4(-0.04926224, -0.099740155, -0.088695474, 0.09950333, -0.06495916, 0.20126842, -0.0062843356, -0.034764495, -0.10808971, -0.19946553, 0.075991094, 0.14746219, 0.08247818, 0.07382381, -0.056908615, -0.026823666) * go_0(1.0, -1.0);
result += mat4(-0.04837408, 0.12605472, -0.23957102, -0.14252385, -0.046534102, -0.07511751, -0.21040416, 0.2064639, -0.006026243, -0.25005546, -0.063780144, 0.076840036, -0.07484346, 0.017368162, 0.04657373, -0.022188455) * go_0(1.0, 0.0);
result += mat4(0.04545079, -0.002226373, -0.11695467, 0.12954631, 0.054903183, 0.15162702, -0.19222596, 0.05351421, -0.079599276, -0.036238387, 0.1362261, 0.037431743, -0.0015106505, 0.18739921, 0.122365154, -0.05871144) * go_0(1.0, 1.0);
result += mat4(-0.005558987, -0.13553315, -0.006372213, 0.06633917, -0.22141413, -0.15780807, 0.057122614, -0.057320844, -0.06306763, 0.19112623, -0.041758966, 0.03555483, -0.005718873, 0.009167371, 0.050909385, -0.14599234) * go_1(-1.0, -1.0);
result += mat4(0.18175003, 0.10442485, 0.052994236, -0.4001252, -0.08328538, 0.06380226, -0.055015627, 0.010929493, -0.22888647, -0.033181675, -0.07570874, 0.07933599, -0.07894686, 0.12202901, 0.13679314, -0.054344065) * go_1(-1.0, 0.0);
result += mat4(0.030145945, -0.06121175, -0.08550973, 0.10082535, 0.07198805, 0.21414264, -0.25636044, 0.028803539, 0.043738026, -0.0367658, 0.27998537, -0.06274612, -0.22862338, 0.002624325, 0.28519824, 0.18540645) * go_1(-1.0, 1.0);
result += mat4(-0.012136538, -0.07059324, 0.018098673, 0.12078888, -0.087637, 0.041642863, 0.034997553, -0.16741107, 0.04701011, -0.004160269, 0.122639626, 0.0043271836, 0.011551197, -0.16421974, -0.102481335, 0.014233497) * go_1(0.0, -1.0);
result += mat4(-0.37945676, 0.25232047, -0.03707734, -0.1985225, -0.11536396, 0.22039749, -0.21809638, -0.10596801, -0.17211124, -0.2035486, 0.011822896, 0.27510995, -0.105182275, 0.022503568, -0.0063389307, -0.071560584) * go_1(0.0, 0.0);
result += mat4(-0.16101715, -0.034247126, 0.16626042, 0.031131435, 0.03048031, -0.105447404, -0.05728527, -0.14518815, -0.019103229, -0.15152888, -0.119154684, 0.028724093, 0.05836196, -0.35943082, -0.016481897, -0.0437348) * go_1(0.0, 1.0);
result += mat4(-0.07719413, -0.33214888, -0.0541927, 0.16506542, -0.032792456, 0.016834807, 0.1724155, 0.073768586, 0.002303886, -0.001382793, -0.0562648, -0.10167158, -0.19101655, 0.052783452, -0.1422853, 0.09653729) * go_1(1.0, -1.0);
result += mat4(-0.30030164, 0.11637444, -0.23238538, -0.27238008, -0.077208534, -0.027645003, 0.10369907, 0.20162316, -0.14428844, 0.1766293, 0.024419712, 0.11301171, 0.07772854, 0.18613201, 0.20721672, -0.1751799) * go_1(1.0, 0.0);
result += mat4(-0.1026615, -0.12484944, 0.15386428, 0.038676128, -0.119472496, -0.032417197, -0.14208497, -0.05254358, -0.0035079278, -0.011276316, 0.043117497, -0.010022288, 0.031624593, 0.014969992, -0.031410277, 0.15284787) * go_1(1.0, 1.0);
result += mat4(0.018149922, -0.05906194, 0.054767277, 0.008161979, -0.076949194, 0.040888708, -0.006419542, -0.12897012, -0.0028229658, 0.20937827, 0.02741711, -0.04013348, -0.12731804, 0.008064522, 0.002870103, 0.027690327) * go_2(-1.0, -1.0);
result += mat4(0.023197446, -0.08888926, 0.15531142, 0.13745947, 0.054352283, -0.121785395, 0.16237587, 0.023567237, -0.36160588, 0.30499592, -0.033180915, -0.1515843, 0.04251452, -0.17903805, 0.03235283, -0.08062386) * go_2(-1.0, 0.0);
result += mat4(-0.0072868476, -0.2010616, 0.13061914, 0.12846659, 0.11725315, 0.14589547, -0.05373261, -0.081606135, -0.07010131, -0.025378224, 0.10265872, 0.18658938, -0.12165338, 0.036297683, 0.03925332, 0.16576236) * go_2(-1.0, 1.0);
result += mat4(0.10300252, -0.11548347, -0.08691649, -0.014866044, -0.3213804, 0.47206497, -0.16032113, 0.026284516, 0.046302956, -0.052474245, -0.025335522, -0.10957576, -0.16872157, 0.19049212, -0.023881195, 0.061396897) * go_2(0.0, -1.0);
result += mat4(-0.16202278, 0.52128345, -0.2601511, 0.06116799, -0.21123995, 0.39389637, -0.350544, -0.16157438, -0.02823116, -0.39056876, -0.14267299, 0.03262984, 0.342303, -0.20556125, -0.0019219286, -0.1824844) * go_2(0.0, 0.0);
result += mat4(0.23399737, -0.0912646, 0.11152403, -0.20945886, -0.053451832, -0.09786892, -0.059099484, 0.18103573, -0.117154315, -0.18342866, 0.12650815, 0.0067340015, -0.037984423, 0.17667364, 0.071636364, -0.011689163) * go_2(0.0, 1.0);
result += mat4(-0.099510275, -0.0925438, -0.009136904, -0.03774997, -0.13348748, 0.3605135, -0.078298144, -0.14712195, 0.22566219, 0.18659295, 0.05614545, 0.10792911, -0.12477693, -0.03587624, 0.08050775, -0.054740936) * go_2(1.0, -1.0);
result += mat4(0.10312337, -0.063681684, 0.16496794, 0.09038492, -0.08903926, 0.41163155, -0.013669214, -0.21472235, -0.054991595, 0.0033639956, 0.18160143, 0.17240305, -0.039428882, 0.17087695, -0.1729076, 0.09871825) * go_2(1.0, 0.0);
result += mat4(-0.13123736, 0.0802573, 0.077981554, -0.101768315, 0.089998, -0.13781744, 0.122858986, 0.054121554, -0.02640825, 0.13577555, -0.037485655, -0.04179625, 0.000106130996, -0.100183845, 0.00046665114, 0.21791616) * go_2(1.0, 1.0);
result += mat4(0.011894387, -0.030088445, 0.025817253, 0.08193235, 0.109322436, 0.10855583, -0.19661167, -0.09405307, 0.2073779, -0.33972177, 0.048635002, -0.14883177, 0.056954246, 0.3953476, 0.18765114, -0.014010224) * go_3(-1.0, -1.0);
result += mat4(-0.22594279, -0.014942035, -0.1519647, 0.25367293, 0.16330296, 0.03317176, -0.32148597, -0.46503916, 0.19944623, -0.26229686, 0.019909514, -0.059794176, 0.12912126, 0.044948537, -0.08649492, 0.08024645) * go_3(-1.0, 0.0);
result += mat4(-0.022943841, -0.068013534, 0.11032515, 0.011685601, 0.020096298, -0.3285243, 0.08196111, -0.089537136, -0.03976742, -0.1315977, -0.36306036, 0.24678081, 0.22115967, -0.017472323, -0.19451386, -0.035218123) * go_3(-1.0, 1.0);
result += mat4(-0.020891193, -0.12721714, -0.15030408, 0.026523203, -0.12413139, -0.11235275, -0.21476477, -0.11326953, 0.028815055, -0.18552732, -0.0076828003, -0.14679903, 0.020509586, -0.18695217, 0.06696879, 0.103938386) * go_3(0.0, -1.0);
result += mat4(0.057521313, 0.28509304, -0.2525733, 0.16745082, -0.26614547, 0.18545172, -0.27140215, 0.018639714, 0.19730581, 0.1659491, -0.058363054, -0.4048628, 0.024913948, -0.44124457, 0.13872208, -0.0371103) * go_3(0.0, 0.0);
result += mat4(0.100904405, 0.06700356, -0.035322092, 0.21781014, 0.018047005, -0.21737386, -0.3734802, 0.13506944, 0.012760691, 0.06620756, -0.0253398, 0.0030280363, -0.044015452, -0.055860534, -0.3547194, -0.04230283) * go_3(0.0, 1.0);
result += mat4(-0.19012743, -0.34408915, 0.18940191, 0.13152952, 0.107553795, -0.00694412, -0.07930157, -0.30964044, 0.034710668, -0.031806916, 0.019838978, 0.017044948, 0.110688254, -0.0029772928, 0.09414367, -0.10760175) * go_3(1.0, -1.0);
result += mat4(-0.05745392, 0.29022983, 0.014998233, 0.27365527, 0.08169933, 0.0734232, -0.09404464, -0.26870936, 0.21171738, -0.19529793, -0.064401075, -0.18972695, -0.08024953, -0.027122354, -0.11661348, 0.010131282) * go_3(1.0, 0.0);
result += mat4(0.07599435, -0.06851123, 0.06258365, 0.10296892, 0.15556085, -0.041609086, -0.11303363, 0.07082365, 0.013949174, -0.087201476, -0.0855705, -0.12979257, 0.04048528, 0.4211556, 0.04118289, -0.22093314) * go_3(1.0, 1.0);
result += vec4(0.07789114, 0.0024746545, 0.1891165, -0.0023716448);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.10883355, -0.14958352, 0.026701333, 0.090302855, 0.033934478, 0.120340124, 0.027125617, -0.16792692, -0.075757094, 0.28692973, 0.013230067, -0.040618937, 0.087148145, -0.05985753, -0.06352023, -0.05775848) * go_0(-1.0, -1.0);
result += mat4(-0.18206549, -0.10363482, 0.097648725, -0.08801144, 0.31633568, 0.058347676, -0.009121898, 0.02594872, 0.14757825, 0.4730546, -0.008518203, -0.3090668, -0.004052835, -0.14166127, -0.010156037, 0.21191326) * go_0(-1.0, 0.0);
result += mat4(0.05735183, 0.039180398, -0.12357178, 0.04830351, 0.120369986, -0.052775342, 0.005902798, 0.07695394, 0.00602021, 0.16758691, 0.10287989, -0.1718468, -0.1319741, 0.16932078, -0.2055026, -0.31820264) * go_0(-1.0, 1.0);
result += mat4(0.05427556, -0.28392607, 0.08579091, -0.0015861926, 0.062348455, -0.27778792, -0.07450379, 0.01616914, -0.012357131, -0.056992117, -0.1896176, 0.018156245, 0.06499259, -0.076558664, 0.10341699, -0.08993959) * go_0(0.0, -1.0);
result += mat4(-0.05741742, -0.05414434, 0.18006511, 0.09840777, -0.11849741, 0.40419933, 0.21349974, 0.40268886, 0.23218039, -0.0680356, -0.3130592, -0.21271054, 0.13776754, 0.19114101, 0.17373541, 0.43457666) * go_0(0.0, 0.0);
result += mat4(-0.060757063, 0.11339545, -0.042958036, -0.06483378, -0.06681766, -0.056395415, 0.037868995, 0.033861663, -0.1041215, 0.0046828864, 0.14360638, 0.087886184, -0.26808187, 0.19876598, -0.05276215, -0.07073776) * go_0(0.0, 1.0);
result += mat4(-0.24029991, -0.14217372, -0.011767948, 0.011623913, 0.33820602, -0.24501325, -0.11444902, 0.14536968, 0.16780593, 0.0065867775, -0.074971735, 0.021472024, -0.10853042, 0.09527126, 0.009436061, -0.09688826) * go_0(1.0, -1.0);
result += mat4(-0.31893802, -0.0016892607, -0.105592966, -0.116694786, -0.007851739, 0.1429722, 0.0741952, 0.050125953, 0.07185179, 0.1900389, 0.030889044, 0.15422693, 0.12550323, 0.3556344, 0.108276874, -0.099125646) * go_0(1.0, 0.0);
result += mat4(-0.33620578, -0.11113713, -0.15881014, 0.028243937, -0.12028756, -0.028566968, -0.002682634, -0.15635195, -0.06869284, -0.03309234, 0.03086361, 0.050773233, -0.08939835, 0.15237434, -0.024076303, -0.13092752) * go_0(1.0, 1.0);
result += mat4(-0.31200737, 0.32207087, -0.068700634, -0.39202076, 0.0676771, 0.083766654, -0.05696634, 0.03088338, 0.046761762, 0.09732023, 0.030844063, -0.03369749, -0.12664944, -0.029924957, 0.10551989, 0.086157694) * go_1(-1.0, -1.0);
result += mat4(-0.1919761, 0.17179352, -0.025805056, -0.05570367, -0.16736336, 0.07430868, -0.13228212, 0.10702857, -0.09723214, 0.1884809, 0.09422538, -0.16902041, -0.1964137, 0.17877853, 0.17453954, -0.11339361) * go_1(-1.0, 0.0);
result += mat4(0.11865004, 0.013131073, 0.17317963, -0.2077911, -0.1116894, 0.09672745, -0.023348883, -0.1176519, 0.15893579, 0.22941695, 0.18798698, 0.059098385, 0.09498779, 0.10118143, 0.08737761, -0.016268898) * go_1(-1.0, 1.0);
result += mat4(-0.025380889, 0.17163627, -0.014800655, 0.12669696, 0.050048903, -0.06513837, 0.020915661, 0.2144372, -0.17799327, 0.0068409992, 0.06751171, -0.16618991, 0.14637277, 0.010591964, -0.15909241, 0.02660789) * go_1(0.0, -1.0);
result += mat4(0.3178319, 0.15036377, -0.03386948, 0.13883169, -0.33842105, 0.061425313, -0.04195804, 0.22558802, 0.2250625, 0.060225345, -0.08467863, 0.0014776831, 0.080328, 0.03221249, 0.20838667, 0.11489719) * go_1(0.0, 0.0);
result += mat4(-0.0013924981, 0.28233197, -0.17997956, -0.10959627, -0.16253087, 0.016549526, -0.1571556, 0.017017027, -0.14697123, 0.0869202, 0.2104898, -0.15658243, 0.13424201, -0.022636503, -0.09512045, 0.0927298) * go_1(0.0, 1.0);
result += mat4(-0.038486905, -0.19215351, -0.2446516, -0.02958912, 0.06899297, 0.028667469, -0.05537665, 0.066711955, -0.0017354499, -0.07466053, 0.028587297, -0.042017035, 0.023596823, 0.0067433366, -0.14685915, 0.13400853) * go_1(1.0, -1.0);
result += mat4(0.0573442, 0.1424536, 0.19606829, 0.07141616, -0.032276712, 0.20030099, 0.16644277, 0.10393295, 0.27240822, 0.0071844175, -0.023368603, -0.14067268, -0.20310283, 0.039528254, 0.103837095, 0.08236034) * go_1(1.0, 0.0);
result += mat4(0.15616669, 0.3495403, -0.05678421, -0.069600284, -0.07361787, 0.079501756, 0.009530261, -0.032385882, 0.029831208, -0.095407076, 0.010261287, 0.15250465, -0.04868275, 0.058579214, 0.03779718, -0.10810775) * go_1(1.0, 1.0);
result += mat4(0.06492073, 0.018667994, -0.004712761, -0.032692235, 0.04027288, -0.114499666, -0.04327484, 0.13778907, -0.09373396, -0.08822919, 0.04796151, -0.057756703, -0.26161298, 0.07182931, 0.12998815, -0.14389744) * go_2(-1.0, -1.0);
result += mat4(0.19001032, 0.13091461, -0.2551175, 0.013365716, -0.031779066, 0.002531366, -0.13807543, -0.14165778, -0.2701911, -0.0890182, 0.34704998, -0.008494185, 0.16179956, -0.060182545, 0.060827415, -0.17249492) * go_2(-1.0, 0.0);
result += mat4(0.10665868, 0.15999752, -0.042796712, -0.14010513, -0.014244899, 0.017433831, 0.053657144, -0.0965679, 0.23623326, 0.0690172, 0.1290121, -0.025523739, 0.122357905, -0.18172716, 0.02829383, 0.10042929) * go_2(-1.0, 1.0);
result += mat4(-0.09273112, 0.09466892, -0.009225705, 0.16772579, 0.0813042, -0.16461512, 0.038097944, 0.19834967, -0.033650465, -0.12888893, 0.1414859, -0.021587005, -0.0047441716, 0.08880282, 0.020621201, 0.065779164) * go_2(0.0, -1.0);
result += mat4(0.0051817205, 0.20322648, -0.077459775, 0.07461627, 0.1817634, -0.5371515, -0.29336745, -0.57652086, 0.035826538, 0.41058993, 0.21512514, -0.041881148, -0.2490056, -0.07172767, 0.20821427, -0.69866294) * go_2(0.0, 0.0);
result += mat4(0.18961228, 0.027452804, -0.0075194626, -0.029665018, 0.28770384, -0.099777386, -0.12160496, 0.07690297, 0.30273837, 0.026466522, 0.18100439, -0.09078488, 0.2035407, -0.062081084, 0.06744994, -0.07512911) * go_2(0.0, 1.0);
result += mat4(0.008473044, 0.07501521, -0.11242355, -0.039451122, -0.21818535, -0.07779562, 0.13194147, 0.084983595, 0.0770609, -0.034488454, 0.08823556, -0.07168295, 0.041894365, 0.0789253, 0.06191209, 0.013991105) * go_2(1.0, -1.0);
result += mat4(0.10582237, 0.1514222, 0.10751824, 0.08231926, 0.23913008, -0.2673503, 0.036170945, 0.31463087, 0.026397424, -0.26629624, -0.07428361, -0.077513516, 0.0768238, -0.026638538, 0.12589583, -0.11521212) * go_2(1.0, 0.0);
result += mat4(0.30389515, 0.18963532, 0.023015842, -0.10240883, 0.045651495, -0.036785256, -0.13346411, 0.16431254, -0.030950911, -0.03381929, 0.09413111, 0.03924852, 0.11044091, -0.10149653, 0.14114548, 0.07801978) * go_2(1.0, 1.0);
result += mat4(0.029622428, 0.14528686, -0.034057826, 0.010664312, 0.059213262, -0.29354423, -0.08448559, 0.10569036, -0.02988314, -0.016480735, 0.042203777, -0.028342744, 0.36807576, 0.09301971, 0.123721026, 0.07806503) * go_3(-1.0, -1.0);
result += mat4(0.04849538, -0.09201287, 0.10069803, -0.031749677, 0.18774022, -0.27789372, 0.05288653, 0.08097265, 0.006918896, -0.060978457, -0.113319606, 0.008844536, 0.021804892, -0.0011744015, -0.35720357, -0.24996938) * go_3(-1.0, 0.0);
result += mat4(-0.07147501, -0.09339197, 0.16154395, 0.3372506, -0.0004858638, -0.056553435, -0.12463908, -0.0047342298, -0.009141984, -0.13796125, -0.14035304, -0.104403175, -0.07054226, 0.12142519, -0.24971877, -0.1914648) * go_3(-1.0, 1.0);
result += mat4(-0.008194284, -0.027617034, 0.004994261, -0.07672895, 0.25697777, -0.18313397, 0.03266311, -0.029157834, 0.010476624, 0.12394092, -0.059660904, 0.08561672, -0.0008583816, -0.044442356, 0.28336492, 0.065344445) * go_3(0.0, -1.0);
result += mat4(-0.3570137, -0.06802815, -0.10298613, -0.21256869, 0.3025278, -0.263425, 0.13547331, 0.038517762, 0.14951234, -0.16869017, 0.03293678, 0.21897063, -0.14688788, 0.21619378, -0.27550143, 0.048003722) * go_3(0.0, 0.0);
result += mat4(0.15607022, -0.111073844, 0.2733694, 0.05423378, 0.25116092, -0.17350473, 0.13460433, 0.09602139, 0.17372625, -0.0024815476, -0.30154657, 0.0062206364, -0.0051755225, 0.04985103, -0.06310478, -0.30450678) * go_3(0.0, 1.0);
result += mat4(0.057571005, -0.019051064, 0.054884393, 0.03993782, 2.6782007e-05, -0.05726912, 0.067192145, -0.08955987, -0.11937056, 0.15837386, -0.011670469, -0.06299701, -0.014917928, 0.23921679, 0.0054613873, -0.23099245) * go_3(1.0, -1.0);
result += mat4(-0.035849575, -0.06785954, -0.15053692, 0.011964653, 0.1975448, -0.1633047, -0.024539666, 0.03170174, -0.12585635, -0.021171011, 0.15862562, 0.10296358, 0.3114039, 0.10010659, -0.09519227, -0.12945092) * go_3(1.0, 0.0);
result += mat4(0.044433746, -0.058466546, -0.13258536, -0.033972915, 0.0037206819, -0.057343487, 0.13798106, 0.044445634, -0.22623023, 0.2408462, 0.048287082, -0.30717465, -0.13402344, 0.20024839, -0.026932377, -0.034217034) * go_3(1.0, 1.0);
result += vec4(-0.05988374, -0.23198523, -0.058251306, -0.038808554);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf1
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.045249436, -0.040327657, -0.2667367, 0.0913868, 0.14961123, 0.07253207, 0.29162952, -0.11320944, 0.017569833, 0.012350104, 0.22532712, 0.025312115, -0.12193993, 0.037391737, 0.03220835, 0.12102545) * go_0(-1.0, -1.0);
result += mat4(-0.020587588, -0.07043244, -0.28093454, 0.18336722, 0.08153308, -0.05914772, -0.15255487, 0.079236075, -0.4269835, -0.11470208, -0.19043571, 0.2723162, 0.0066251885, -0.17115718, 0.022036036, 0.07349558) * go_0(-1.0, 0.0);
result += mat4(-0.09441315, 0.042170826, 0.071251415, -0.13891962, 0.10236482, 0.05356262, 0.0291025, 0.063867815, -0.14530063, -0.08727925, -0.0048300857, 0.06766869, -0.3481536, -0.10943503, 0.014951926, 0.11993114) * go_0(-1.0, 1.0);
result += mat4(0.13420522, 0.095721036, -0.1756104, -0.09906728, 0.09808904, -0.27402034, -0.102161326, 0.40162942, 0.13465238, 0.20237032, 0.3192343, -0.061512157, -0.20711629, -0.09659007, 0.06838548, 0.30256763) * go_0(0.0, -1.0);
result += mat4(0.025805298, -0.0322599, 0.23653145, -0.2760735, 0.11291006, -0.10836205, 0.20742846, 0.06974535, -0.4191803, -0.10882523, 0.038603242, 0.22662747, -0.08845715, -0.26151156, -0.16670766, 0.008536192) * go_0(0.0, 0.0);
result += mat4(-0.085842185, -0.21239999, -0.032774646, 0.088163696, 0.038300447, -0.09510875, 0.10113864, -0.14712982, 0.14264707, -0.10895432, 0.03051617, -0.06791873, -0.35589013, -0.12884575, -0.09460007, -0.0879575) * go_0(0.0, 1.0);
result += mat4(0.19235751, -0.109611385, -0.037397474, -0.26632717, 0.07878826, 0.19749992, 0.0035685285, 0.11793927, 0.019899402, 0.085741036, 0.08433813, -0.018344546, -0.0901484, 0.08221562, 0.12735383, 0.12801875) * go_0(1.0, -1.0);
result += mat4(0.19123435, 0.007882246, -0.018564796, -0.09904253, 0.28052533, 0.6360808, 0.25001726, -0.30590564, 0.07646281, -0.34298185, -0.33293694, -0.036753535, 0.18719083, 0.22131144, -0.1420962, -0.0014709529) * go_0(1.0, 0.0);
result += mat4(0.23060241, -0.14145076, -0.113213465, 0.037221998, 0.22163334, 0.18520229, 0.2961799, -0.063605964, 0.022606356, 0.043340076, -0.3233993, -0.075055614, -0.0038865958, 0.19558622, -0.018503085, -0.22932632) * go_0(1.0, 1.0);
result += mat4(0.11712158, -0.03590364, 0.38039652, -0.019910801, 0.13338004, -0.07078425, 0.09404417, -0.27607328, -0.02205519, -0.013522961, 0.2924021, -0.16088538, -0.034280356, -0.063614614, -0.061583273, -0.22479968) * go_1(-1.0, -1.0);
result += mat4(-0.05624079, 0.32659104, 0.47335497, -0.14091404, 0.14739423, -0.07122778, -0.009384643, -0.058900848, 0.06260307, -0.17574102, 0.3538743, 0.2842822, -0.18150197, 0.26806462, 0.24673693, 0.19710627) * go_1(-1.0, 0.0);
result += mat4(-0.24837571, -0.01663848, -0.13093965, 0.30109972, -0.09680959, 0.074526474, 0.024111765, -0.012781737, -0.08591349, -0.100348584, 0.02363011, -0.02687084, -0.27630556, 0.14074354, -0.016993485, 0.084373675) * go_1(-1.0, 1.0);
result += mat4(0.1543391, -0.2008408, -0.21885285, 0.2320177, 0.06669948, -0.05171086, -0.25833863, -0.14085051, -0.035878573, -0.1632403, 0.09782713, 0.22973235, -0.14022017, -0.018347954, -0.29652777, 0.10912002) * go_1(0.0, -1.0);
result += mat4(-0.050962634, -0.040519282, -0.04381614, 0.084133334, 0.21222316, -0.091010064, 0.13157965, -0.21375372, -0.021148674, -0.044127557, -0.11400533, 0.097688414, 0.31571037, -0.05167655, 0.27606225, 0.12169133) * go_1(0.0, 0.0);
result += mat4(-0.1329087, 0.14291021, 0.043337896, -0.25970098, -0.11379552, -0.040157612, 0.08379851, -0.24104865, 0.1593102, -0.031879216, -0.004603848, -0.019003935, -0.24769545, -0.17577063, 0.16019398, 0.04640235) * go_1(0.0, 1.0);
result += mat4(-0.11615644, 0.12189521, 0.12919527, -0.104224406, -0.10143574, 0.14024515, -0.02759362, -0.1467619, 0.09028311, -0.06510291, 0.061612967, 0.10227729, -0.08785846, 0.06464871, -0.05048917, 0.09055746) * go_1(1.0, -1.0);
result += mat4(0.34443164, 0.013906371, -0.0595573, 0.09354196, 0.12184454, -0.02698316, -0.06208632, -0.11266858, 0.004904335, -0.33987018, -0.2494041, 0.127125, 0.040493876, 0.0280356, -0.037431944, 0.05823802) * go_1(1.0, 0.0);
result += mat4(-0.1762869, -0.20683959, -0.37788594, -0.1244979, -0.17202286, -0.038234763, 0.015924744, -0.014006752, 0.07097758, -0.25219876, -0.3164728, 0.022413896, -0.41423917, -0.03191542, 0.009464804, 0.0770316) * go_1(1.0, 1.0);
result += mat4(0.12442388, 0.031095076, 0.18799834, -0.18449762, -0.11995044, 0.11634828, -0.0055850362, 0.08558657, -0.025694892, -0.2854381, -0.32876188, 0.14690274, -0.1835963, -0.1786755, -0.44678628, 0.1678422) * go_2(-1.0, -1.0);
result += mat4(0.031241562, -0.1265462, 0.081369035, -0.1184643, 0.0010021052, -0.10810683, -0.039572187, 0.13850863, -0.010703417, -0.057981443, 0.30309856, 0.13869847, -0.16935349, 0.16969836, 0.045642667, 0.26460654) * go_2(-1.0, 0.0);
result += mat4(0.28779998, 0.04767888, -0.011856489, 0.114210494, 0.034624737, 0.19084676, -0.02740287, 0.035041407, -0.049002927, 0.10928203, 0.17362499, -0.1280889, 0.00077811617, -0.17594084, -0.18379052, 0.22303762) * go_2(-1.0, 1.0);
result += mat4(0.0008487252, -0.060438234, 0.109334275, -0.18768874, 0.13844973, 0.09226474, 0.18361697, -0.19385563, -0.29241335, -0.1033556, -0.3289991, 0.10027422, -0.09454755, -0.22817631, -0.2964217, -0.19499257) * go_2(0.0, -1.0);
result += mat4(-0.057920385, 0.06342629, -0.048577324, 0.15952215, -0.061343953, 0.16471362, 0.1501856, 0.027373426, 0.01837245, -0.0732048, 0.09776471, 0.14817989, -0.112215854, 0.109101914, 0.058316242, 0.29969788) * go_2(0.0, 0.0);
result += mat4(-0.12411656, -0.033170763, -0.08715826, 0.110862456, 0.1871076, 0.14550175, 0.23373431, 0.19281025, -0.37016305, -0.11924462, 0.026793748, 0.092801645, 0.04318573, 0.20969667, -0.39267823, 0.1938874) * go_2(0.0, 1.0);
result += mat4(-0.15932916, 0.22217506, 0.007901788, -0.04037383, 0.09095982, -0.043115042, 0.098845564, -0.073432215, -0.14535685, 0.11504512, -0.07950504, -0.010718905, -0.050012022, -0.13089752, -0.3323894, -0.005423676) * go_2(1.0, -1.0);
result += mat4(0.007320675, 0.21108273, 0.20758918, -0.04005568, -0.13234317, -0.15708306, 0.41804615, -0.09720499, -0.09623786, 0.2441289, 0.33276868, 0.17716111, -0.45670444, -0.026252905, -0.01958701, 0.24028622) * go_2(1.0, 0.0);
result += mat4(-0.14936383, -0.023504466, -0.028479185, -0.053541556, -0.060263615, -0.087681144, 0.2435555, 0.08470686, -0.17713271, -0.2303349, 0.09337386, 0.039068084, -0.16263027, 0.034289114, 0.16604292, 0.10550447) * go_2(1.0, 1.0);
result += mat4(-0.16556105, 0.12211341, -0.0036831333, 0.13802956, 0.065256506, 0.03395266, -0.2296282, 0.21284704, 0.017770419, -0.1722762, -0.1741687, 0.10708671, 0.331979, 0.11924846, -0.09410989, -0.123036265) * go_3(-1.0, -1.0);
result += mat4(-0.096586555, -0.30475244, -0.24065268, 0.053860847, 0.19413544, 0.05542323, -0.06327867, 0.012265184, -0.08913778, 0.13779551, -0.099127166, 0.007493773, -0.07125554, -0.0011684593, -0.003005287, -0.094847135) * go_3(-1.0, 0.0);
result += mat4(0.21711998, -0.13086027, 0.07825239, -0.21121782, 0.055840425, -0.0019166623, -0.05480048, 0.019817038, 0.007626905, 0.14126389, 0.04515749, -0.029315706, 0.18555732, -0.114861906, -0.21993469, 0.031716693) * go_3(-1.0, 1.0);
result += mat4(-0.06716353, -0.11964145, 0.09711908, -0.061763637, -0.0948045, 0.14189975, 0.2810092, 0.2505306, 0.08872909, 0.086749084, -0.17528322, -0.048835423, 0.124959685, -0.12602286, 0.065660164, -0.06783225) * go_3(0.0, -1.0);
result += mat4(-0.23066516, -0.0068310793, -0.0021060852, 0.09136854, 0.09919007, 0.2259628, -0.026603302, 0.1367709, -0.07940821, 0.14962214, 0.00652088, -0.3114987, -0.18900892, -0.20450105, 0.09329685, -0.19482759) * go_3(0.0, 0.0);
result += mat4(0.095197074, 0.06346413, -0.05207484, -0.086378016, 0.19733003, 0.1448027, -0.02410627, 0.024829205, -0.20296144, -0.09551166, 0.022987023, 0.09035918, -0.15824226, 0.1350293, -0.06641893, 0.11739518) * go_3(0.0, 1.0);
result += mat4(0.08381447, -0.13171835, -0.030271608, 0.14649504, 0.0007350431, 0.15303299, -0.001797464, 0.30294403, -0.07635094, -0.102541, -0.12176348, 0.053775523, 0.08070882, -0.035387367, -0.09521456, 0.22530125) * go_3(1.0, -1.0);
result += mat4(-0.04650126, 0.12029137, 0.009236626, -0.1371486, -0.119391896, 0.20490645, 0.17123316, -0.015455403, 0.05842872, 0.14354227, 0.37586045, 0.054906923, 0.062954046, 0.07285954, 0.12260665, -0.08675996) * go_3(1.0, 0.0);
result += mat4(0.22510684, -0.010087092, 0.005660375, 0.05069907, 0.10297958, 0.1411009, 0.09538159, 0.00922383, -0.31313825, -0.06449414, 0.109746836, 0.30148697, 0.35861742, -0.045380104, 0.09908991, -0.1933117) * go_3(1.0, 1.0);
result += vec4(0.012253057, 0.13434875, -0.10318777, -0.074252345);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.04279202, -0.01698567, 0.18318103, -0.18172316, 0.04757184, 0.07232096, -0.054900512, 0.11956132, 0.048900753, 0.0006714882, -0.09200336, 0.16104606, 0.38940707, 0.2754208, -0.12735553, -0.30017206) * go_0(-1.0, -1.0);
result += mat4(0.2469705, 0.103162065, 0.10321547, -0.1292231, 0.3013039, -0.018333653, -0.19897339, 0.122247696, 0.14719778, 0.003909129, -0.19585025, 0.03670547, -0.2132921, 0.33642963, 0.17569672, 0.07414473) * go_0(-1.0, 0.0);
result += mat4(0.015335451, 0.15161209, 0.0447609, -0.042884503, 0.14257035, 0.07775234, -0.2064044, 0.03842874, -0.1660166, -0.19817057, -0.10740875, -0.123968095, 0.14156081, -0.2197906, -0.08622206, 0.4185408) * go_0(-1.0, 1.0);
result += mat4(-0.33392438, -0.12483512, -0.062084857, 0.16336447, 0.09862199, 0.1659862, 0.034751434, -0.11968266, -0.017155796, 0.21001562, -0.053017724, 0.10386376, 0.07066254, 0.50014263, 0.31065208, -0.026068505) * go_0(0.0, -1.0);
result += mat4(-0.34320992, -0.030056434, -0.24118581, -0.024320357, 0.327435, -0.036838267, -0.19433706, 0.24561343, -0.1489437, 0.225435, 0.18421564, 0.021147838, 0.264245, 0.16846146, -0.51724315, 0.039252095) * go_0(0.0, 0.0);
result += mat4(-0.25945047, 0.12058094, 0.2889452, -0.061687145, -0.10309796, -0.19476385, -0.10393912, 0.16837607, -0.05198191, -0.036113493, -0.11847194, 0.16367626, 0.018113747, 0.059499823, 0.0062132217, 0.15846115) * go_0(0.0, 1.0);
result += mat4(0.094601326, 0.053219795, 0.027610637, 0.12041253, 0.21425363, 0.15754686, 0.08518286, -0.00661778, -0.021661628, -0.17554528, -0.014842315, 0.22240937, 0.15908821, -0.20964032, 0.21754523, 0.30307937) * go_0(1.0, -1.0);
result += mat4(0.13757955, 0.06684095, -0.03616685, -0.014618309, 0.04168136, -0.17148526, -0.16317028, 0.14210777, 0.102521434, -0.19108291, -0.14441934, 0.14435884, 0.24228935, -0.10589834, 0.24029285, 0.27317202) * go_0(1.0, 0.0);
result += mat4(-0.16239886, -0.073841535, 0.067964345, -0.11332664, 0.07695667, -0.047180675, -0.08260769, 0.09427637, 0.09471068, 0.012713836, 0.14605078, -0.062490974, -0.11498225, 0.04150893, 0.37402585, 0.21953487) * go_0(1.0, 1.0);
result += mat4(-0.07445113, -0.14220217, 0.09271495, -0.014715529, -0.37606132, -0.14938155, -0.024809113, 0.22279873, -0.011379667, -0.04545505, -0.033382278, 0.08971831, 0.016359061, -0.016230864, 0.052939463, -0.07754285) * go_1(-1.0, -1.0);
result += mat4(0.10961948, 0.09230085, 0.061259165, 0.0015837378, 0.053883027, -0.22557226, 0.018400123, 0.43234614, 0.08967873, 0.06687854, -0.4389578, -0.01658211, -0.040707946, 0.0048945122, 0.1433802, 0.049759727) * go_1(-1.0, 0.0);
result += mat4(-0.027641231, 0.026085567, 0.109188825, -0.19011945, 0.19309571, 0.0084956605, 0.05034047, -0.08674781, -0.008124587, 0.031490494, -0.0744263, 0.084508896, -0.007835403, 0.13120581, 0.0021786217, -0.025225073) * go_1(-1.0, 1.0);
result += mat4(0.020191731, 0.24703082, -0.36845222, 0.0032569442, -0.1497622, 0.05968502, 0.09595371, 0.008410154, 0.119981945, -0.09983294, -0.19541258, -0.111814305, -0.25664008, 0.31031236, -0.23063917, -0.13823026) * go_1(0.0, -1.0);
result += mat4(-0.092747286, 0.23009373, -0.29804415, 0.05036082, 0.031480987, 0.18805481, 0.3676576, 0.06004687, 0.19841099, -0.058367446, -0.44229323, -0.19645047, 0.037667975, 0.12398346, -0.25753063, -0.26919344) * go_1(0.0, 0.0);
result += mat4(-0.019061154, 0.03841801, -0.28433323, 0.38128456, -0.059526864, 0.29960185, 0.014484517, -0.10234412, 0.05444907, -0.12615138, 0.14936689, -0.079120934, 0.028092088, 0.096715964, 0.0037780635, -0.12791039) * go_1(0.0, 1.0);
result += mat4(0.26949528, 0.015951393, 0.15355164, -0.030336212, -0.100286454, -0.052609976, 0.03197625, -0.092190474, 0.06131517, 0.18291938, -0.15216532, -0.026021928, 0.18581273, -0.10659101, 0.14806952, 0.20509768) * go_1(1.0, -1.0);
result += mat4(-0.2205839, 0.11654808, 0.43800604, 0.03188946, 0.13840868, 0.020377772, 0.038510147, 0.03779825, -0.23494276, 0.08624197, 0.036650848, -0.115041405, -0.03776705, -0.32108167, 0.0094707385, 0.37881464) * go_1(1.0, 0.0);
result += mat4(-0.031778246, -0.38020673, 0.16956653, 0.33444092, -0.042172886, -0.03465591, -0.17585713, 0.025507452, 0.07595919, -0.06807453, -0.100295454, -0.019174794, 0.07763043, -0.09321411, -0.05212223, 0.112239085) * go_1(1.0, 1.0);
result += mat4(-0.048172995, -0.012284629, 0.12846173, -0.13459995, 0.25443402, -0.013064909, 0.15480834, 0.14016332, 0.036635883, -0.049085367, 0.0506487, 0.26623604, -0.023176057, 0.012088936, -0.1844897, 0.040488705) * go_2(-1.0, -1.0);
result += mat4(0.2147455, 0.17323543, -0.2943051, -0.053386763, -0.023367947, 0.090753146, -0.011997397, -0.0626111, -0.13558747, -0.035944186, -0.014752113, 0.25506687, 0.055502877, 0.31465453, -0.16283247, -0.08967175) * go_2(-1.0, 0.0);
result += mat4(0.033773236, -0.09510872, -0.09313707, 0.046486538, -0.1699796, -0.11685979, 0.22197925, -0.013884658, 0.12514, -0.12129843, -0.09695589, -0.075202964, -0.12321221, 0.18949097, -0.03694664, -0.2306249) * go_2(-1.0, 1.0);
result += mat4(0.08668444, -0.22983012, -0.30873656, 0.07371376, 0.082137264, -0.014844924, 0.2283955, 0.24782042, 0.31113505, 0.14810014, 0.32804835, -0.12014127, -0.17742543, -0.15872951, -0.080107674, -0.16898526) * go_2(0.0, -1.0);
result += mat4(0.29746926, 0.19479977, 0.13996765, -0.4268552, -0.16478531, 0.0835479, 0.45685142, -0.05510062, -0.1282004, 0.12359051, 0.34026766, -0.26152933, -0.13128015, 0.329812, 0.27172327, -0.06600192) * go_2(0.0, 0.0);
result += mat4(-0.06552484, 0.19600633, 0.12407863, -0.13815112, 0.17426166, 0.040930413, 0.06495108, 0.034157254, -0.029772963, 0.015127817, 0.10718436, -0.13752984, -0.0205358, 0.1884735, 0.104591034, -0.020779913) * go_2(0.0, 1.0);
result += mat4(-0.053475305, -0.13616458, 0.05487909, 0.13256747, -0.10030239, -0.12376705, 0.062755466, 0.03264356, 0.068466686, 0.05019395, -0.034875803, -0.17806669, -0.21720818, 0.25592342, -0.2685692, -0.27576914) * go_2(1.0, -1.0);
result += mat4(-0.04562929, 0.04225299, -0.22311088, -0.09517893, -0.19886662, -0.11944208, 0.11044239, -0.10464355, 0.037634842, 0.124069214, 0.0927385, 0.108838566, -0.088783056, 0.17008123, -0.1007014, -0.23137446) * go_2(1.0, 0.0);
result += mat4(0.10306672, 0.027472405, -0.069015354, -0.14412996, 0.24068132, -0.10624665, -0.25597134, 0.05208812, -0.10230778, 0.006520562, -0.11931577, 0.26738268, -0.09168354, 0.13557245, -0.008878644, -0.22292739) * go_2(1.0, 1.0);
result += mat4(-0.09403718, 0.11993688, -0.036254726, -0.053109076, 0.18422048, 0.25203657, 0.10025996, -0.11272799, -0.22040273, -0.05758331, -0.07059054, -0.054108664, -0.20009018, -0.22061199, 0.057880517, -0.26669186) * go_3(-1.0, -1.0);
result += mat4(-0.08534496, 0.0027822452, -0.01112169, -0.13484463, -0.09446875, -0.057457812, -0.03910888, -0.2816038, -0.096015625, -0.03636662, 0.12532772, 0.092033, 0.038156748, -0.101240925, 0.024886698, -0.086328045) * go_3(-1.0, 0.0);
result += mat4(0.2349796, 0.19884427, -0.0734711, 0.08422328, -0.07201622, 0.020658491, 0.1331021, 0.039766714, 0.19280422, 0.13086005, -0.11339721, -0.14782044, 0.19341573, 0.16767374, -0.03593828, 0.18139753) * go_3(-1.0, 1.0);
result += mat4(-0.040663462, -0.15233721, 0.524604, 0.26603413, 0.07202415, 0.053382196, 0.030758869, -0.06144292, -0.010495834, 0.13868876, -0.020688854, -0.15551737, -0.2958513, -0.32805985, -0.25359175, -0.036683984) * go_3(0.0, -1.0);
result += mat4(-0.06644081, -0.145321, 0.24945419, 0.031560224, 0.17245345, 0.23418438, 0.20341763, -0.2619872, 0.038787205, 0.16488725, 0.0019107185, 0.03820528, 0.04169643, -0.34155026, -0.11183654, 0.028614044) * go_3(0.0, 0.0);
result += mat4(-0.028469078, 0.010781976, 0.05263661, -0.15337946, -0.20491667, -0.13879907, 0.13934538, 0.061196275, 0.056804053, 0.063193604, -0.2389496, 0.037072126, -0.058510017, 0.036215063, 0.3074709, 0.10517675) * go_3(0.0, 1.0);
result += mat4(0.028534278, 0.0022668538, 0.04492863, -0.060705435, 0.06349762, -0.016823182, -0.09148226, 0.03930522, -0.083295114, 0.14799853, -0.08089152, -0.21993661, -0.23298621, 0.05106244, -0.013708201, -0.16311577) * go_3(1.0, -1.0);
result += mat4(0.05885827, 0.122300275, -0.16086812, -0.21892425, -0.07548077, 0.09286181, -0.027564062, -0.028723463, -0.0056181233, 0.23472206, -0.0049285595, -0.45054138, 0.07592325, -0.044704806, 0.019616256, -0.06956836) * go_3(1.0, 0.0);
result += mat4(0.036423888, 0.20839189, -0.16420732, -0.15954947, -0.11311323, -0.24191359, 0.19845375, 0.084540576, -0.20946553, 0.09259613, 0.03234368, -0.056766506, -0.11992363, -0.06882079, -0.020428827, -0.093375795) * go_3(1.0, 1.0);
result += vec4(0.013113342, -0.2905848, -0.029724011, 0.1769613);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf1
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.093678355, -0.08574688, 0.007699401, -0.038818456, -0.10667588, 0.043627866, 0.23127791, 0.061317544, -0.32790044, 0.08618836, 0.009400048, -0.17129329, 0.23541448, -0.015561885, -0.11172365, -0.1190039) * go_0(-1.0, -1.0);
result += mat4(-0.0052874424, 0.08136584, -0.12633958, -0.016064916, 0.14033778, 0.07755252, -0.26242834, 0.063312635, 0.06861756, 0.14867078, -0.2561066, 0.33325562, -0.106489345, -0.10068009, -0.039633382, -0.016305668) * go_0(-1.0, 0.0);
result += mat4(-0.27784392, -0.14990395, -0.35981888, -0.2564094, -0.07480205, -0.026457628, 0.1027643, 0.19381845, -0.07160986, -0.15616457, -0.032070953, 0.32998616, 0.15383582, 0.16622585, -0.1435993, -0.02287804) * go_0(-1.0, 1.0);
result += mat4(-0.09360053, 0.58019537, 0.02028909, 0.413114, 0.025173154, -0.030326266, -0.028177274, -0.12964654, -0.25432733, -0.06556034, 0.023097439, -0.09458851, -0.21772051, -0.10324596, -0.36674342, -0.14803977) * go_0(0.0, -1.0);
result += mat4(-0.1227467, 0.20252965, 0.2559927, 0.08719227, 0.030749539, -0.2526622, -0.25694713, -0.2960799, -0.34960067, -0.25393236, -0.28439638, 0.086787805, -0.34202877, 0.21933395, 0.23473133, 0.079260886) * go_0(0.0, 0.0);
result += mat4(-0.00147522, -0.16591258, -0.030617915, 0.10052425, -0.1822102, 0.038774874, -0.04285007, 0.07312042, 0.052175622, -0.33510515, 0.027545406, 0.2995306, -0.08535316, 0.11144203, 0.27999434, -0.09770663) * go_0(0.0, 1.0);
result += mat4(-0.04394928, -0.26842886, -0.08354109, 0.04077001, -0.009221606, 0.0328837, 0.006459338, 0.08984004, -0.13035133, 0.20004508, 0.21950854, -0.12742348, 0.32386312, 0.085903555, -0.29273173, -0.056370437) * go_0(1.0, -1.0);
result += mat4(0.019171638, -0.1824711, -0.10899421, -0.16201603, 0.054712642, -0.020315547, -0.048609916, -0.068621606, -0.055706583, -0.25671515, -0.019494208, 0.08366393, 0.09531471, -0.05988052, -0.024995802, 0.019303525) * go_0(1.0, 0.0);
result += mat4(-0.08694609, 0.26762635, 0.10477892, -0.15392998, -0.059596587, -0.047562487, -0.25932398, -0.054960977, -0.00015596532, 0.07196634, -0.017385524, -0.18826845, -0.017969077, -0.27291682, -0.153906, -0.107691295) * go_0(1.0, 1.0);
result += mat4(0.17340474, -0.1285696, -0.04484238, 0.15782213, -0.06190358, 0.27896214, 0.28475145, -0.042519942, -0.19862229, -0.1354097, 0.14344497, 0.015599392, 0.18698554, 0.035121564, -0.018465763, 0.0010143917) * go_1(-1.0, -1.0);
result += mat4(-0.13428356, -0.06612225, 0.19397905, 0.14209093, 0.1526626, 0.2617573, -0.15316434, 0.35452205, 0.05003259, 0.07679617, -0.008399171, -0.0062716682, 0.11833864, 0.1331285, -0.006803729, 0.22615404) * go_1(-1.0, 0.0);
result += mat4(0.0020632436, -0.173174, -0.15404437, 0.05430569, 0.21100305, 0.39063898, -0.019479724, 0.17396629, -0.061121427, -0.13424753, -0.008459669, -0.04975768, 0.20599939, -0.11374013, -0.21116278, 0.063624285) * go_1(-1.0, 1.0);
result += mat4(-0.0073831948, -0.12009769, -0.16402034, 0.054093774, 0.061061747, -0.009054565, -0.02815144, -0.17071937, -0.22791979, 0.073427565, 0.25161973, 0.1011713, -0.23804636, 0.13810354, 0.09063126, -0.23065178) * go_1(0.0, -1.0);
result += mat4(-0.31885087, 0.21730177, -0.20516786, 0.04075695, -0.2736768, -0.38779113, -0.19445951, -0.14024325, -0.11824961, -0.102919355, -0.17858729, -0.013441498, 0.16320607, -0.27105078, -0.00019549616, 0.024509901) * go_1(0.0, 0.0);
result += mat4(-0.16024838, -0.3132909, -0.15461555, 0.34874174, -0.0051668375, 0.1811257, 0.3384939, 0.16381103, 0.047184363, -0.20424844, -0.1330078, -0.13795874, 0.21890834, -0.08242861, 0.22677775, 0.031102268) * go_1(0.0, 1.0);
result += mat4(0.19408257, 0.016361775, -0.202373, 0.2245766, -0.008954751, -0.047279913, -0.09170596, 0.01567793, -0.0019059096, -0.07785436, 0.0756357, 0.09683383, 0.034215495, -0.030802004, -0.077977195, -0.1101297) * go_1(1.0, -1.0);
result += mat4(-0.1060503, -0.0044663083, -0.14942732, -0.11696249, -0.04550482, 0.11463188, 0.17801443, 0.07229662, -0.14176941, 0.02773344, -0.10770335, -0.08745911, -0.023052111, -0.17474785, 0.016645849, -0.059080444) * go_1(1.0, 0.0);
result += mat4(-0.050500304, -0.14716387, 0.04525464, 0.23543595, 0.08411192, 0.16031684, 0.1659825, -0.03595111, -0.012943453, 0.13354135, -0.051425032, -0.0075654723, 0.11174184, 0.1266808, -0.18799087, 0.10571744) * go_1(1.0, 1.0);
result += mat4(-0.15583408, 0.09837484, 0.19239932, -0.03557196, -0.05406335, 0.096456856, -0.13921897, -0.2212671, 0.28973594, 0.04017474, -0.25423512, 0.1522156, -0.10563249, -0.033190794, 0.101713456, -0.08922746) * go_2(-1.0, -1.0);
result += mat4(-0.0787607, -0.14545321, 0.099762656, -0.2824299, 0.10130184, 0.019948835, -0.1013831, 0.06604923, 0.089561954, 0.28344154, 0.05757009, 0.04981809, -0.15927236, 0.008129835, -0.04280382, 0.10653281) * go_2(-1.0, 0.0);
result += mat4(0.28149363, 0.019583186, 0.25983065, 0.30190885, 0.055435803, -0.01970755, 0.04546505, -0.027456624, 0.43886992, -0.032305803, -0.23557569, 0.12753153, -0.18509789, -0.073295385, 0.0083466545, -0.08271229) * go_2(-1.0, 1.0);
result += mat4(0.016040009, -0.20475672, -0.015803276, 0.18247975, 0.21178837, -0.041543446, -0.24716362, 0.10105528, 0.19479224, -0.06583694, -0.09192672, -0.037776746, 0.09636229, -0.12086331, 0.13989103, 0.014564729) * go_2(0.0, -1.0);
result += mat4(0.19923596, -0.4132588, -0.4254784, -0.33433357, -0.16956097, -0.25086832, 0.23311833, -0.08976422, 0.06432824, -0.0071802614, 0.0033370545, -0.11073493, -0.46609998, -0.09332235, -0.27287352, 0.052513942) * go_2(0.0, 0.0);
result += mat4(-0.06954148, -0.06908355, -0.01875471, -0.35067585, 0.038715206, 0.08843527, 0.28899097, -0.024983376, 0.05879495, 0.110363334, 0.055481512, -0.0046147215, -0.035302363, -0.2722019, -0.0829261, 0.21088009) * go_2(0.0, 1.0);
result += mat4(-0.101971015, -0.18584369, 0.1469676, 0.025965, 0.07205807, 0.08838771, 0.08537094, 0.023344917, -0.106373414, -0.09254277, -0.25996596, 0.24570447, 0.00590166, -0.20074098, -0.05443169, -0.10562662) * go_2(1.0, -1.0);
result += mat4(0.12980327, -0.16834956, -0.1635997, 0.23437372, -0.07374834, 0.0062907683, 0.17292136, 0.0018093853, 0.04122969, -0.025285576, 0.29646805, 0.13402736, -0.040267725, 0.0011441729, -0.18658921, 0.12006417) * go_2(1.0, 0.0);
result += mat4(0.13221453, 0.15109141, 0.07707579, 0.05148666, -0.039716493, 0.12869143, -0.012840577, 0.10953536, -0.05721115, -0.120122276, -0.07632444, 0.32949027, 0.00022400127, 0.22217369, 0.2180494, -0.028773604) * go_2(1.0, 1.0);
result += mat4(-0.08405412, 0.11332542, 0.120847605, 0.00520135, -0.13689686, -0.1459117, -0.029643068, 0.16147274, 0.21844815, -0.036921967, -0.12862785, -0.15930249, -0.11265427, -0.17471205, 0.0026749703, 0.2048758) * go_3(-1.0, -1.0);
result += mat4(-0.03768306, -0.07585988, 0.046583172, -0.35557657, 0.012359812, -0.05498573, 0.19581361, -0.08186999, -0.008727976, -0.16623624, -0.03647879, 0.22760212, 0.048297524, -0.12502927, 0.08636729, -0.26437047) * go_3(-1.0, 0.0);
result += mat4(-0.19518375, 0.17423135, 0.19473018, -0.22721744, -0.25087392, -0.17043075, -0.021999557, -0.27388734, -0.096786864, -0.012226921, 0.16101876, 0.030362492, -0.017619403, -0.2494354, -0.07336028, 0.06842719) * go_3(-1.0, 1.0);
result += mat4(0.13816363, 0.14551367, -0.08497621, 0.15563537, -0.01600614, -0.010629245, 0.007773828, 0.2733634, 0.13066974, -0.2223056, -0.12664202, -0.19242655, -0.13211249, 0.065143794, 0.23912583, 0.19819915) * go_3(0.0, -1.0);
result += mat4(0.001870705, -0.0028601827, 0.14014813, 0.14659253, -0.037523735, 0.3726274, 0.13139205, 0.0112125, -0.16308945, -0.17571904, 0.12799808, -0.032106552, 0.013872656, 0.432307, -0.14197885, 0.24013121) * go_3(0.0, 0.0);
result += mat4(0.117900506, -0.08039036, -0.17504077, -0.08337764, -0.0068703834, -0.07430392, -0.17125578, -0.3470726, -0.20989974, -0.019394008, -0.027336912, 0.18668686, 0.052886557, -0.023217537, 0.004054446, 0.055974416) * go_3(0.0, 1.0);
result += mat4(-0.055653654, 0.08726097, 0.01206228, -0.25783783, -0.08736529, 0.19947968, -0.010166337, 0.36168414, 0.20298903, -0.15769973, -0.21389212, -0.19638214, -0.093130395, -0.067289785, 0.10245741, -0.14167903) * go_3(1.0, -1.0);
result += mat4(0.04559992, -0.102125205, 0.21949212, -0.07308472, -0.15511832, 0.23785073, 0.04275021, 0.085007004, 0.079402514, 0.10851189, -0.151969, -0.29738536, -0.0776658, 0.1113102, -0.18987878, -0.045522977) * go_3(1.0, 0.0);
result += mat4(0.073690206, -0.016468357, 0.122353435, -0.023995928, 0.095143944, 0.23051415, 0.17702249, 0.030164838, -0.09111423, -0.14219609, -0.19734482, -0.24854833, -0.0067356345, -0.1760497, 0.22637916, 0.119141534) * go_3(1.0, 1.0);
result += vec4(0.22705397, -0.029518934, -0.026397338, -0.08183741);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_7_tf
//!WIDTH conv2d_6_tf.w
//!HEIGHT conv2d_6_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.16737834, 0.35369134, 0.14049083, 0.017871622, 0.0058661173, -0.035960242, -0.039154284, -0.01920433, 0.0729212, -0.03617972, -0.42717552, -0.019914677, -0.30816802, -0.07726792, 0.2088459, -0.09198307) * go_0(-1.0, -1.0);
result += mat4(-0.0991125, 0.11411345, 0.15300295, -0.09510225, 0.014268626, -0.42914182, -0.13365223, -0.19440699, -0.27214321, 0.085696176, 0.1527733, -0.21056797, -0.062475704, -0.023041902, -0.29080424, -0.54386055) * go_0(-1.0, 0.0);
result += mat4(-0.30736786, -0.16801229, 0.07400606, -0.31128535, -0.11047924, 0.16556956, -0.33445996, -0.09190697, -0.06132585, -0.11021996, 0.014628762, -0.45183894, 0.08186993, 0.19378273, 0.113438204, 0.038364496) * go_0(-1.0, 1.0);
result += mat4(0.24129803, 0.29174972, -0.1250327, 0.14254767, 0.0026774528, 0.1742466, -0.021835174, 0.01668921, 0.13646975, 0.313305, -0.23293279, -0.16737306, -0.059818722, 0.06404477, 0.108172625, 0.22065729) * go_0(0.0, -1.0);
result += mat4(-0.3504013, 0.20759478, 0.28683922, 0.2771802, 0.13761812, -0.21180478, -0.17020214, -0.21419087, -0.031916566, -0.040439468, 0.39206958, 0.715565, 0.46198523, 0.05055317, -0.07409331, -0.050633535) * go_0(0.0, 0.0);
result += mat4(0.122958206, 0.0071205017, -0.21314384, -0.22197853, 0.016202174, -0.15960938, -0.14601983, -0.023609173, -0.07586023, 0.099936776, -0.0480375, -0.08681468, -0.14976887, -0.38979456, 0.16078879, -0.12263952) * go_0(0.0, 1.0);
result += mat4(0.1687149, 0.108331114, 0.10112296, 0.01738403, -0.06773097, -0.19410455, -0.09728116, 0.0013846151, -0.038603816, -0.05495021, 0.2453317, -0.40052003, -0.022453755, 0.045039784, 0.0474246, -0.2665161) * go_0(1.0, -1.0);
result += mat4(0.06805519, -0.052276067, 0.052459523, -0.0033053474, 0.13439268, -0.06845637, -0.20462433, -0.09088968, -0.00096404477, -0.35103628, 0.15096465, 0.3285226, 0.018747555, -0.06623108, 0.1754265, 0.3211156) * go_0(1.0, 0.0);
result += mat4(-0.04583627, 0.122267574, -0.44002235, -0.20039988, 0.039372742, -0.16505809, -0.26659602, 0.12207268, 0.03337428, 0.23131758, -0.009866899, 0.010381569, 0.29676, -0.020599596, 0.17816995, 0.32852224) * go_0(1.0, 1.0);
result += mat4(0.09469788, -0.12531966, -0.11786524, -0.3115985, -0.2213199, -0.012536277, -0.13176842, 0.14986996, 0.12069894, 0.2744789, 0.21674646, 0.46060535, -0.4101697, -0.55295914, 0.29993954, 0.114459395) * go_1(-1.0, -1.0);
result += mat4(0.18347421, -0.29010707, 0.29127017, 0.087738656, 0.17509815, 0.03982794, 0.1731455, 0.38041735, 0.110374, -0.25045586, 0.36446962, 0.016104888, -0.012112869, 0.10154983, -0.45384112, -0.11416608) * go_1(-1.0, 0.0);
result += mat4(-0.033837743, -0.020894403, -0.287127, -0.21196121, -0.03255823, 0.2599821, -0.38386443, 0.30563655, 0.39044768, -0.112917066, -0.021323297, 0.12623324, 0.06885038, -0.20750642, 0.07642818, -0.103580445) * go_1(-1.0, 1.0);
result += mat4(0.1723114, -0.3726216, -0.21184283, 0.1761503, -0.24993578, -0.31068864, 0.19998416, -0.23127908, -0.052656204, -0.04243976, 0.4397144, 0.01863219, -0.04796025, -0.11009142, -0.0073631364, 0.2716381) * go_1(0.0, -1.0);
result += mat4(0.04202001, 0.27142277, -0.027491128, 0.27428457, -0.11009916, 0.39839938, -0.7223327, -0.124673314, 0.08123618, -0.11884722, -0.20375855, -0.7179687, 0.30648115, -0.28195357, -0.3350774, -0.29778734) * go_1(0.0, 0.0);
result += mat4(0.071278594, -0.09155223, 0.06417857, 0.08250104, -0.45117077, -0.023316784, 0.38917172, -0.19110887, -0.09265943, -0.2643835, -0.09707039, -0.33238646, -0.0818088, 0.17623149, -0.28457013, 0.13986786) * go_1(0.0, 1.0);
result += mat4(0.019971045, -0.046649583, -0.03036858, 0.07944429, 0.26344573, 0.054998036, 0.07139812, 0.21139374, 0.08021858, -0.025791258, -0.0423707, 0.25174072, -0.021300986, 0.13209766, 0.19120613, 0.3840775) * go_1(1.0, -1.0);
result += mat4(-0.11456406, -0.33503455, 0.21409267, -0.056933913, -0.12204284, -0.37379473, 0.33474764, 0.38634798, 0.12618992, 0.1353635, -0.22651522, -0.3160159, 0.18621005, 0.024818055, -0.11935204, 0.014005666) * go_1(1.0, 0.0);
result += mat4(0.1501391, 0.0014716414, -0.22049955, -0.10928345, -0.07085164, -0.08778668, 0.19251469, -0.4932493, 0.071784936, -0.06903646, -0.060333923, 0.020552203, -0.33637995, -0.22848415, 0.21518159, 0.23815839) * go_1(1.0, 1.0);
result += mat4(-0.04230713, -0.19312756, -0.0613665, 0.058912925, -0.17639293, -0.029920885, -0.027867602, -0.16602923, 0.10262268, -0.0743682, 0.15286638, 0.08042581, -0.042299524, 0.0022034592, 0.15304253, 0.049871147) * go_2(-1.0, -1.0);
result += mat4(0.004346093, -0.07895582, 0.02089975, 0.13429636, -0.1020282, 0.5270822, 0.017983409, 0.1531299, -0.02891241, -0.07050933, -0.18729019, 0.13855362, -0.11538968, 0.20733222, 0.1546878, 0.11550679) * go_2(-1.0, 0.0);
result += mat4(0.21800312, 0.20944421, -0.1817274, 0.022868395, -0.019241469, 0.038916696, 0.088702604, 0.1467791, 0.0048542274, 0.10344671, -0.0107803065, 0.23302868, 0.049728952, -0.016042534, -0.08694045, -0.0028224774) * go_2(-1.0, 1.0);
result += mat4(-0.1570157, 0.08688841, 0.03926086, -0.040503077, -0.052700017, -0.1432353, -0.04516745, -0.09649034, -0.053716175, 0.07059194, -0.07360609, 0.26307717, 0.121471435, -0.13640986, -0.1113535, -0.38560814) * go_2(0.0, -1.0);
result += mat4(-0.014722592, -0.39773384, 0.28259715, -0.10905738, 0.07889424, 0.1415529, -0.15419348, -0.2064834, -0.15126482, -0.28288555, -0.0014232624, -0.26178944, -0.025823193, 0.008017357, -0.08547297, 0.26373458) * go_2(0.0, 0.0);
result += mat4(0.2978961, -0.020236012, -0.101216674, 0.15498216, -0.0069343713, -0.088363856, 0.20511419, 0.23958007, 0.045810107, -0.19189738, -0.14137349, 0.04177724, -0.1394684, 0.0071990825, 0.06991723, -0.21052721) * go_2(0.0, 1.0);
result += mat4(-0.05615232, 0.22506002, -0.12479586, -0.0070057763, 0.092545755, 0.096306436, 0.041890718, 0.1226944, -0.07541768, -0.08369033, -0.15144373, 0.09310172, 0.28388003, 0.09935607, 0.11299509, 0.0014283776) * go_2(1.0, -1.0);
result += mat4(-0.005848455, 0.117699094, 0.23539856, 0.11006195, 0.10962903, 0.28139547, 0.18785141, -0.11635996, 0.057289902, 0.2370178, -0.29825503, -0.13706475, -0.3869794, 0.024066223, 0.36742347, 0.35919484) * go_2(1.0, 0.0);
result += mat4(0.13744523, 0.09239356, 0.01173183, 0.119055405, -0.07841836, 0.0668925, 0.22598477, -0.016510552, 0.07971727, -0.17154713, 0.03333588, -0.13790733, 0.15421963, 0.2895701, -0.28440917, 0.015132756) * go_2(1.0, 1.0);
result += mat4(-0.054354303, 0.36663428, 0.02634933, 0.18688667, 0.0607547, 0.17321853, 0.086784445, -0.023283, 0.0027200899, 0.026914112, -0.07438439, 0.27042162, 0.09985293, 0.012430832, -0.20694605, -0.20363812) * go_3(-1.0, -1.0);
result += mat4(-0.42759168, 0.15540305, -0.18979609, 0.0073875943, 0.034251947, -0.34551802, 0.53327596, 0.17446762, -0.25879666, 0.2780996, 0.11094055, 0.17597, 0.13790102, 0.2615357, 0.09666047, 0.36155468) * go_3(-1.0, 0.0);
result += mat4(0.052614138, -0.1880028, 0.361331, 0.07957976, 0.12552904, -0.0042941784, 0.096562445, -0.041199915, 0.07412456, 0.16379668, 0.05464284, 0.050022952, -0.028281605, 0.09332573, 0.21379845, 0.21396561) * go_3(-1.0, 1.0);
result += mat4(-0.07546953, 0.16393837, -0.3060623, -0.64610606, -0.013715101, 0.18005042, 0.045286633, -0.21057944, -0.12779316, -0.10310629, 0.14360385, 0.011625261, 0.05597252, 0.023864657, -0.00018915108, -0.24224915) * go_3(0.0, -1.0);
result += mat4(-0.08550672, 0.2438917, -0.30383766, -0.2463794, 0.13835424, -0.079946786, -0.060197506, 0.051599402, -0.24983203, -0.06691107, -0.0041784844, 0.07539119, -0.030340329, -0.23565106, -0.17968354, -0.10262371) * go_3(0.0, 0.0);
result += mat4(0.19315718, -0.045718513, 0.120446794, -0.225136, 0.22922774, -0.046026126, 0.11448238, 0.114267804, -0.22327735, -0.03368635, 0.29763463, 0.03673529, -0.0583939, -0.092253424, 0.045279544, 0.04475646) * go_3(0.0, 1.0);
result += mat4(-0.062286656, -0.06241419, -0.23600577, -0.24818502, -0.058666106, 0.17710151, -0.1751668, 0.05758226, 0.18278669, 0.033297777, 0.046349872, 0.09178792, -0.0745512, 0.20019765, 0.037281513, 0.22204825) * go_3(1.0, -1.0);
result += mat4(-0.24708512, -0.1318695, -0.24966322, -0.31206796, 0.079176836, 0.11837155, -0.12882641, -0.01013533, -0.009065797, 0.0789075, 0.016151598, 0.00020127615, 0.1450729, 0.10825556, 0.09322918, 0.07283566) * go_3(1.0, 0.0);
result += mat4(0.2604332, 0.25550258, 0.07709474, 0.28426003, 0.10387355, 0.09152259, 0.18742633, -0.0073229484, -0.20327723, -0.26013616, 0.055792782, -0.1713302, 0.14862068, 0.06698207, 0.17608787, -0.11622757) * go_3(1.0, 1.0);
result += vec4(-0.20551574, 0.073114716, -0.21843387, -0.28057778);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_7_tf1
//!WIDTH conv2d_6_tf.w
//!HEIGHT conv2d_6_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.18413043, -0.12355504, 0.2708789, 0.17259507, -0.069752574, 0.12640886, 0.01075919, -0.028221423, -0.020598855, -0.17259665, 0.16907778, -0.10040477, 0.017177016, 0.0176426, 0.23724149, 0.14657862) * go_0(-1.0, -1.0);
result += mat4(0.16921899, -0.33950835, 0.37508205, 0.09996622, 0.13377811, -0.036743056, -0.11633877, -0.23046862, -0.009307903, 0.027441062, 0.054166224, 0.011627087, -0.22831611, 0.043198805, -0.12695734, 0.0062862337) * go_0(-1.0, 0.0);
result += mat4(0.17216596, -0.15588646, -0.14179194, 0.12487524, 0.10507964, 0.124544986, -0.0046104924, -0.116668865, -0.006100901, -0.022074439, 0.03376759, 0.10498887, 0.109659016, -0.03567928, 0.29972833, -0.045950003) * go_0(-1.0, 1.0);
result += mat4(-0.29127, 0.21912472, 0.16494286, 0.027708547, 0.043136686, 0.04409876, -0.07686145, -0.13180132, -0.16630307, 0.15650205, -0.005864527, 0.03916553, 0.15750135, 0.1705246, 0.21626697, 0.06906506) * go_0(0.0, -1.0);
result += mat4(0.055395894, 0.28228188, 0.114794776, 0.020619212, -0.031812593, 0.11964309, -0.24317431, -0.36277202, 0.54564184, -0.032843567, -0.118973784, -0.40999004, -0.118530475, 0.09256661, 0.06583871, -0.36627474) * go_0(0.0, 0.0);
result += mat4(0.17914769, 0.33976436, -0.11220768, 0.1325754, 0.40586957, 0.3064959, -0.19086123, 0.014164092, -0.17376979, -0.0037554938, 0.11771888, 0.44933778, -0.15937245, -0.10635065, 0.084963776, 0.14630255) * go_0(0.0, 1.0);
result += mat4(-0.3723194, 0.21509883, 0.020062352, 0.094394304, 0.030794155, -0.11394617, -0.09103134, -0.0042343247, -0.28981096, -0.061873477, -0.17772584, 0.36440176, 0.007828069, -0.012121627, 0.25862312, 0.24646287) * go_0(1.0, -1.0);
result += mat4(0.10368119, -0.06185447, -0.022830853, 0.10918094, 0.18888599, -0.09235343, -0.055134308, -0.2210923, 0.15334128, -0.3084707, 0.31606838, 0.39931116, 0.29489174, -0.24794856, -0.4799932, -0.2617589) * go_0(1.0, 0.0);
result += mat4(0.32550937, -0.17103608, 0.3257806, -0.23358762, 0.20370598, 0.13325407, -0.020303056, -0.105462655, -0.22264756, -0.034177396, 0.36885822, 0.20504399, 0.36375418, -0.26149705, 0.022433946, 0.15646128) * go_0(1.0, 1.0);
result += mat4(0.007481421, 0.005642636, -0.170087, -0.08915849, 0.6329519, 0.06880098, -0.20856442, -0.1801066, -0.1342754, 0.13643123, 0.26994216, -0.27503812, 0.018052012, 0.058687408, -0.19784917, 0.021157453) * go_1(-1.0, -1.0);
result += mat4(-0.1486918, 0.12212738, -0.03104796, 0.08664756, 0.3464865, 0.27309546, -0.022896903, -0.32080007, -0.28113958, 0.74847424, -0.33735126, -0.04616876, -0.23119605, 0.4214322, -0.16457441, 0.09162191) * go_1(-1.0, 0.0);
result += mat4(0.15863913, 0.1303683, -0.06339421, 0.06328312, -0.3100047, -0.33906308, 0.13805804, -0.14923394, 0.4997829, -0.14977637, 0.02265068, -0.04585939, 0.29802153, 0.3767994, -0.031849556, -0.051892217) * go_1(-1.0, 1.0);
result += mat4(-0.04541847, -0.13645087, 0.14119779, 0.06409465, -0.29877988, -0.0009743694, 0.028256422, 0.14978185, -0.13014801, -0.24171488, -0.10782599, 0.010709664, 0.21880737, -0.34132662, 0.22972895, -0.07159475) * go_1(0.0, -1.0);
result += mat4(-0.1510528, 0.115773134, 0.036761034, -0.284284, -0.35684052, 0.16348189, -0.105475456, 0.08259931, -0.6489164, -0.033928663, -0.04243186, 0.25324553, -0.31829014, 0.066608824, -0.11131264, 0.51919967) * go_1(0.0, 0.0);
result += mat4(-0.06517726, 0.1933327, 0.044391852, -0.013346896, -0.3033368, 0.106350735, -0.1351003, -0.13414839, 0.11720078, -0.24844061, -0.2900742, -0.047861837, 0.42789885, -0.47915378, -0.09643217, -0.22915216) * go_1(0.0, 1.0);
result += mat4(0.109821886, 0.31451595, 0.13300805, -0.08792569, -0.023928089, -0.038061168, 0.17821129, 0.003772247, 0.14684688, -0.12646271, 0.16072205, 0.011095222, 0.09209181, 0.005167038, -0.08823252, 0.079890974) * go_1(1.0, -1.0);
result += mat4(-0.20074554, 0.39979288, -0.007316405, -0.047838025, 0.10849111, -0.22469573, -0.059183244, -0.13663793, 0.07881898, 0.105663374, -0.3152222, 0.08104766, -0.22965154, 0.118780024, -0.07886757, 0.073527716) * go_1(1.0, 0.0);
result += mat4(0.1304303, 0.023158893, -0.081089824, -0.15955788, 0.42183343, -0.12898655, -0.14028409, 0.011985, 0.3977131, -0.313598, -0.148818, -0.048350018, -0.13534498, -0.12760727, -0.014968193, 0.06646305) * go_1(1.0, 1.0);
result += mat4(0.18085147, -0.11859402, 0.117530234, -0.10420847, 0.1848264, -0.12192718, -0.18729533, -0.10098887, 0.011134682, -0.23658146, 0.12963286, 0.117404245, 0.054487415, -0.030003065, -0.32175776, -0.08044254) * go_2(-1.0, -1.0);
result += mat4(-0.07251758, 0.073430285, -0.22191651, 0.030512359, -0.029650904, -0.15816379, 0.0418705, 0.04776615, -0.014070836, -0.14669086, -0.009874937, -0.015444495, -0.2747725, -0.061624944, -0.11261252, 0.14757589) * go_2(-1.0, 0.0);
result += mat4(-0.09274913, 0.046194065, 0.05642919, -0.07803342, 0.23578037, 0.01224276, 0.015608659, 0.05847865, -0.091819406, -0.14424564, -0.034869857, 0.019276984, -0.031180726, -0.21905676, 0.100375675, -0.13659117) * go_2(-1.0, 1.0);
result += mat4(-0.072157644, -0.13294607, 0.24301524, 0.048643183, -0.04338094, -0.0021709928, -0.06530963, -0.22672611, 0.07479903, 0.08388352, -0.07460508, -0.14517406, -0.072923675, -0.26912874, -0.2769797, 0.054033212) * go_2(0.0, -1.0);
result += mat4(-0.5648679, -0.28059873, -0.039906785, -0.39112374, -0.3841447, -0.20383365, 0.12607281, 0.16049421, -0.34394273, -0.022326993, 0.16646549, -0.23433913, 0.071224056, 0.048073303, 0.122035526, 0.14941359) * go_2(0.0, 0.0);
result += mat4(-0.11803124, 0.114169255, 0.018188128, 0.0053847185, -0.07537228, -0.048262373, 0.073838905, -0.041833423, 0.044405136, -0.03813592, 0.076818384, -0.06015139, -0.085042655, -0.14306667, -0.21477652, 0.31548396) * go_2(0.0, 1.0);
result += mat4(0.19307283, -0.014985916, -0.14332882, -0.05549754, 0.14551677, 0.11406769, 0.2744144, -0.031179624, 0.17578745, -0.11309805, 0.010072839, -0.07453384, -0.23163621, 0.19061968, 0.11016298, 0.108093746) * go_2(1.0, -1.0);
result += mat4(0.23180474, -0.12522835, -0.03218773, -0.0031955864, -0.14057393, 0.07269213, -0.20883523, 0.09332164, -0.16037942, 0.25845763, -0.002303125, -0.014625506, 0.17063208, -0.11648214, 0.13988028, -0.024688654) * go_2(1.0, 0.0);
result += mat4(0.043369994, 0.12473897, 0.108142346, 0.10268199, 0.16159926, -0.17804666, -0.007889351, 0.07232418, 0.26326916, 0.0474316, -0.41637155, -0.11879895, 0.14051722, 0.08747377, 0.1162202, -0.06443569) * go_2(1.0, 1.0);
result += mat4(0.0041097966, 0.109841965, 0.097240336, 0.08123332, -0.081065506, 0.12650634, 0.23450434, 0.09631333, 0.21942414, -0.108897425, -0.033703003, 0.047280088, -0.017764917, -0.058596086, -0.15305139, 0.09055131) * go_3(-1.0, -1.0);
result += mat4(0.26824722, 0.014116421, 0.11844865, -0.156046, 0.057152968, 0.21287468, -0.3243975, -0.18181354, -0.07131152, -0.17860547, 0.18918999, 0.15399154, 0.20270234, 0.11524436, 0.05146645, -0.18196748) * go_3(-1.0, 0.0);
result += mat4(-0.2745638, -0.026905773, 0.045458756, 0.22942849, -0.21052304, 0.20649272, -0.03713028, 0.33655703, -0.12467089, -0.015030098, 0.15504798, -0.05647672, 0.18751477, 0.08505986, 0.04756538, -0.058810517) * go_3(-1.0, 1.0);
result += mat4(0.1737789, 0.06552432, -0.34797582, -0.05370679, -0.036056817, 0.085242435, -0.12802805, 0.03710984, -0.09883285, 0.08946925, -0.0446528, 0.07734006, -0.10973603, 0.262812, 0.14010249, -0.1543792) * go_3(0.0, -1.0);
result += mat4(0.316673, -0.16414417, -0.23147403, -0.3080756, -0.056620106, -0.11389848, 0.0948114, 0.13236332, -0.40048537, -0.090742044, 0.12090404, 0.024549136, -0.19124876, -0.3007761, 0.16159211, -0.28620452) * go_3(0.0, 0.0);
result += mat4(0.032962102, -0.05481415, -0.1185786, 0.18153866, -0.2105442, -0.03802839, 0.14060515, 0.072460145, -0.1523761, -0.11426362, 0.02610123, -0.053477813, -0.20768824, 0.04533907, 0.14381588, -0.041578818) * go_3(0.0, 1.0);
result += mat4(-0.021694858, -0.028784249, -0.09928565, 0.07335764, 0.1315628, 0.11288982, 0.078681685, -0.1229723, -0.09618894, -0.07387309, 0.04340066, -0.036534667, 0.37295115, -0.08176548, -0.16579813, -0.13485877) * go_3(1.0, -1.0);
result += mat4(0.45979, -0.289226, -0.15456465, 0.0117592, 0.22803205, 0.15497394, -0.38995707, 0.005227681, -0.20515667, 0.17184737, -0.069968715, -0.24724679, -0.048521046, 0.013277072, 0.049562644, -0.05522196) * go_3(1.0, 0.0);
result += mat4(0.14561136, -0.18995416, 0.18104567, 0.063063085, -0.09728072, 0.018328888, -0.17258182, 0.069259025, 0.15187183, 0.16760696, -0.14086077, 0.013297849, -0.07579904, -0.09294852, -0.24227127, -0.048749007) * go_3(1.0, 1.0);
result += vec4(0.31939298, 0.03303962, -0.010749771, 0.084496394);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-Soft-(VL)-Conv-3x1x1x112
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!BIND conv2d_7_tf
//!BIND conv2d_7_tf1
//!SAVE MAIN
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
#define g_0 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_1 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_8 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_9 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_12 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_13 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_16 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_17 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_20 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_21 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_24 (max((conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))
#define g_25 (max((conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.121882804, 0.055417646, 0.037575886, 0.0, 0.040015355, 0.10440659, 0.120197006, 0.0, 0.008896276, 0.07269119, 0.09253319, 0.0, 0.009000448, -0.033739295, -0.059260685, 0.0) * g_0;
result += mat4(-0.048027042, 0.09210703, 0.123745404, 0.0, -0.007914943, 0.05483587, 0.054822505, 0.0, -0.005998682, 0.005822986, 0.009868176, 0.0, -0.05866792, -0.04236153, -0.022935968, 0.0) * g_1;
result += mat4(-0.091270015, -0.033997003, -0.012321896, 0.0, -0.037983265, -0.078790314, -0.085029654, 0.0, 0.10656225, 0.0008334142, -0.0041227583, 0.0, 0.077364065, 0.033960085, 0.029391684, 0.0) * g_2;
result += mat4(0.15057671, -0.037442014, -0.037083894, 0.0, 0.015493511, -0.016119987, -0.027061606, 0.0, -0.012329675, 0.0060544596, -0.019787522, 0.0, 0.12182345, 0.11346318, 0.08640806, 0.0) * g_3;
result += mat4(0.19254518, 0.009179287, 0.023821035, 0.0, 0.020269603, 0.025629226, 0.040180814, 0.0, -0.025135614, -0.07785793, -0.099851295, 0.0, -0.122886, 0.03322616, 0.0509256, 0.0) * g_4;
result += mat4(0.060054794, 0.053996198, 0.047226787, 0.0, 0.038959846, -0.025839888, -0.030583512, 0.0, -0.034999896, 0.011966571, -0.011057454, 0.0, 0.05765179, -0.041760337, -0.0694113, 0.0) * g_5;
result += mat4(-0.20393562, -0.0055942894, -0.02089636, 0.0, 0.14781304, -0.01954523, -0.0746086, 0.0, 0.071556985, 0.07512172, 0.067927115, 0.0, 0.084076844, -0.0561336, -0.06856403, 0.0) * g_6;
result += mat4(-0.039552618, -0.04448951, -0.04170605, 0.0, -0.00886809, 0.06708884, 0.07120977, 0.0, 0.04834384, -0.10599933, -0.11024835, 0.0, -0.015948117, 0.084044695, 0.10778199, 0.0) * g_7;
result += mat4(0.050153337, 0.012563414, 0.014994658, 0.0, 0.10498867, 0.07151875, 0.06761489, 0.0, 0.061650798, -0.035183728, -0.050987806, 0.0, 0.0017240314, 0.041055307, 0.020366805, 0.0) * g_8;
result += mat4(0.110105395, -0.044468552, -0.072567016, 0.0, -0.049364448, -0.015713394, -0.021540897, 0.0, -0.01636263, -0.084110685, -0.08281401, 0.0, -0.08940374, 0.047863875, 0.051104594, 0.0) * g_9;
result += mat4(-0.081597924, 0.002422661, 0.01143175, 0.0, -0.07504751, -0.09938017, -0.1063178, 0.0, -0.10390281, 0.0262197, 0.060155805, 0.0, -0.24289346, -0.0054961476, 0.045964316, 0.0) * g_10;
result += mat4(-0.1829316, 0.047622137, 0.07963877, 0.0, 0.048703995, -0.0026299425, -0.003712008, 0.0, 0.029338706, 0.096882835, 0.102083966, 0.0, 0.078538164, -0.07247937, -0.06820231, 0.0) * g_11;
result += mat4(-0.02302231, -0.035528302, -0.030674051, 0.0, 0.029780716, 0.031591274, 0.045867007, 0.0, 0.01335752, 0.037001595, 0.04351411, 0.0, -0.11126892, 0.038589563, 0.06444906, 0.0) * g_12;
result += mat4(0.0047764573, -0.063372664, -0.065609895, 0.0, 0.0478139, 0.025694113, 0.025097322, 0.0, -0.1019169, 0.029989049, 0.050038517, 0.0, 0.07504127, -0.017047737, -0.026222635, 0.0) * g_13;
result += mat4(0.0024485083, 0.00640911, 0.008171829, 0.0, -0.014622121, -0.06078096, -0.0800138, 0.0, -0.0062360805, -0.014344496, -0.021332184, 0.0, 0.117842786, -0.103745885, -0.13756834, 0.0) * g_14;
result += mat4(-0.01942775, 0.08720701, 0.104858086, 0.0, -0.05545872, -0.041375194, -0.035368554, 0.0, 0.080331706, -0.021207837, -0.043905254, 0.0, -0.12515299, 3.445463e-05, 0.018742712, 0.0) * g_15;
result += mat4(0.013106969, 0.010379314, 0.012753471, 0.0, 0.07086715, -0.020893, -0.03968904, 0.0, -0.06114372, 0.029510446, 0.035070244, 0.0, 0.11180839, -0.087067656, -0.124039896, 0.0) * g_16;
result += mat4(-0.056521703, -0.001166792, -2.3704073e-05, 0.0, 0.011961608, 0.01848977, 0.019861937, 0.0, 0.012167056, 0.018613879, 0.020505793, 0.0, 0.009734187, -0.0308419, -0.035206888, 0.0) * g_17;
result += mat4(0.0048758825, 0.018046578, 0.014597015, 0.0, -0.061724614, 0.040989272, 0.05644141, 0.0, 0.070315465, 0.008318584, 0.0028647361, 0.0, -0.11316492, 0.043919202, 0.07653594, 0.0) * g_18;
result += mat4(0.031487904, -0.010548384, -0.009984509, 0.0, -0.0022647562, 0.0043304027, 0.0029451603, 0.0, -0.0063251094, -0.013420807, -0.011919729, 0.0, -0.022760967, 0.019141173, 0.01782793, 0.0) * g_19;
result += mat4(0.023055293, 0.028219413, 0.024810018, 0.0, 0.031653803, 0.050207954, 0.04504577, 0.0, 0.03877294, 0.0280465, 0.025589157, 0.0, 0.0019387804, 0.023891818, 0.016049948, 0.0) * g_20;
result += mat4(0.006562233, 0.03880659, 0.037682824, 0.0, -0.021441424, -0.011277022, -0.012471097, 0.0, -0.030526241, -0.013880651, -0.014213582, 0.0, 0.0075785257, -0.0017350517, -0.0024610942, 0.0) * g_21;
result += mat4(0.015097556, 0.020325955, 0.015611413, 0.0, -0.014755199, -0.034323387, -0.032325987, 0.0, -0.008603291, 0.010346807, 0.011044969, 0.0, -0.004739154, -0.026397636, -0.01995132, 0.0) * g_22;
result += mat4(0.0097906375, -0.015094543, -0.016887931, 0.0, -0.0007786067, -0.0069163437, -0.008449091, 0.0, 0.025534432, 0.018064791, 0.017047096, 0.0, 0.00055667467, 0.001493328, 0.003636564, 0.0) * g_23;
result += mat4(-0.042251963, -0.042396102, -0.040224236, 0.0, -0.004492444, -0.0069470624, -0.0065821502, 0.0, 0.062203273, 0.06213223, 0.053592753, 0.0, 0.06424337, 0.07964681, 0.07316769, 0.0) * g_24;
result += mat4(0.026366957, 0.02789826, 0.027239393, 0.0, -0.006712127, -0.0035723334, -0.0032348586, 0.0, -0.04960562, -0.062758155, -0.058574595, 0.0, -0.02896146, -0.020999067, -0.021301663, 0.0) * g_25;
result += mat4(-0.013106142, -0.017057793, -0.014653614, 0.0, -0.04254173, -0.043040022, -0.041918345, 0.0, -0.011146975, -0.0043820064, -0.003768677, 0.0, -0.0027743059, -0.0114479, -0.0082087545, 0.0) * g_26;
result += mat4(-0.10087762, -0.10447133, -0.1005168, 0.0, -0.04165659, -0.04558967, -0.040086865, 0.0, 0.0016493691, 0.0055392827, 0.0070476984, 0.0, -0.018665023, -0.035552308, -0.03375731, 0.0) * g_27;
result += vec4(0.018580848, -0.022256816, -0.0266178, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,873 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(0.1690102, -0.2560719, 0.39658326, -0.3679659, -0.27616683, -0.35619372, -0.3748396, 0.08430813, -0.29574734, -0.31511316, -0.09773105, 0.13616018, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.1326393, -0.259433, 0.025070239, 0.58914864, -0.036478516, 0.30723435, 0.007458902, 0.012962684, 0.2493056, 0.13007334, -0.08448256, -0.38414413, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.11539356, 0.35253766, 0.26143202, 0.2760807, -0.09371543, -0.028165473, -0.028452158, -0.27050856, 0.06718067, -0.0056619495, -0.17654495, 0.17288211, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.16145481, -0.3204927, -0.54317135, 0.11830119, 0.49315026, 0.12008072, 0.50857407, -0.30382085, 0.25807253, 0.020755528, 0.29388228, 0.106109895, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.22728722, 0.50484747, -0.07904469, 0.33114597, 0.50306976, -0.22760947, 0.14773269, 0.17628263, 0.14788547, -0.08223464, -0.10880935, -0.3151985, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.3414351, 0.057279214, -0.14419858, 0.09761111, -0.11794496, 0.021717256, -0.22750235, 0.13986664, -0.38932344, 0.28996095, 0.3773904, 0.13175532, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.1376552, -0.19587159, -0.35147396, -0.097646296, 0.1686707, -0.14385861, 0.031198, 0.12383533, -0.23089902, 0.08707301, 0.3362293, -0.100579016, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.056774966, 0.047585852, -0.36395878, -0.20211312, 0.4077735, 0.12631284, 0.39813092, -0.033365678, 0.2307249, -0.09131807, 0.20823865, 0.31084216, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.12456089, 0.09755632, 0.31490886, -0.06579996, -0.13386595, 0.07564795, -0.26605195, -0.075180635, -0.11182657, 0.06757017, -0.14351276, -0.16828312, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.046043985, 0.055581126, -0.08791638, -0.13022089);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf1
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.15485518, -0.29363206, -0.22610365, -0.14291525, -0.45240572, -0.18319772, -0.12209436, 0.15031648, 0.09878383, 0.06711082, 0.25763842, -0.084633484, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.10204406, 0.16167697, 0.22371867, -0.37947702, -0.24476196, -0.038824454, 0.060157117, 0.15764871, -0.08072927, -0.2210841, -0.31835055, 0.009979876, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.20506924, 0.21132155, -0.0922578, -0.07430473, 0.14529926, 0.20549752, 0.0077948375, 0.13246094, -0.32353187, 0.21074104, 0.092629515, 0.17590871, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.04125819, -0.44050243, 0.23729716, 0.3218237, 0.12943116, -0.011674174, 0.10390632, 0.027775545, -0.20308031, -0.16904089, -0.2121676, -0.022515794, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(0.09664124, 0.20127031, 0.60345304, 0.16697013, 0.23093723, -0.38116834, 0.109695725, 0.0007595324, 0.4092646, 0.009624758, 0.11229678, 0.25326383, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.014879592, 0.19204311, 0.07102085, -0.7312604, 0.34860876, 0.3429918, -0.027331594, 0.27636307, 0.1342437, 0.107820466, -0.12645108, 0.21081445, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.12687613, -0.09247973, -0.25973785, 0.4350873, -0.18987224, 0.028678741, -0.0903819, -0.63974863, 0.205591, 0.11308998, 0.18458389, -0.4149041, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.34691808, -0.025498383, 0.3428986, 0.21663484, 0.23404741, -0.1725327, -0.0036315925, -0.13299675, -0.1873967, 0.031331502, -0.08785591, -0.0013278709, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.35846514, 0.048703704, -0.104165934, 0.16529736, -0.15378916, 0.26030356, -0.07134151, 0.03692383, -0.15807101, -0.18885155, 0.044707954, -0.11444462, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.0022791293, -0.024132347, -0.57621074, 0.028573977);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.010346764, 0.07230188, -0.24734616, -0.09937907, 0.02228549, -0.19550583, -0.019540425, -0.1037373, 0.033996485, -0.075554, -0.20228972, 0.07090153, -0.09194035, -0.058972966, 0.1768268, 0.27517542) * go_0(-1.0, -1.0);
result += mat4(0.020078976, 0.12433655, -0.1620775, 0.036401592, 0.079748705, 0.11660013, 0.17917652, -0.017513236, -0.18936846, 0.24478136, -0.45726213, -0.045004416, -0.08295188, 0.067733586, -0.080548316, 0.2744211) * go_0(-1.0, 0.0);
result += mat4(0.024916803, 0.27562472, 0.043771956, -0.012240604, 0.0786355, 0.042651594, 0.16049327, -0.14577515, -0.032735053, 0.17658092, 0.16382934, -0.02337374, 0.11551492, 0.056343183, -0.17930213, 0.14259394) * go_0(-1.0, 1.0);
result += mat4(0.20010485, 0.06747722, -0.19026905, 0.11013709, 0.13062745, -0.044626113, -0.0062261797, 0.2189639, 0.1403497, -0.022713251, -0.19452858, -0.010305412, -0.06407589, 0.09836748, 0.025805516, 0.23430973) * go_0(0.0, -1.0);
result += mat4(-0.14664203, 0.034910418, 0.024714258, -0.066872925, -0.15717538, -0.14179383, -0.14091893, 0.05859166, 0.18919097, -0.18544437, -0.09068573, -0.08615929, -0.051434122, 0.2170678, 0.18409058, -0.17461225) * go_0(0.0, 0.0);
result += mat4(-0.11354446, 0.10745854, 0.2682663, 0.05949201, -0.10695986, 0.1407851, -0.03551388, 0.10691649, -0.17148238, -0.38287184, 0.2074456, 0.11828914, 0.048535194, 0.1464864, -0.18169662, -0.14074169) * go_0(0.0, 1.0);
result += mat4(0.22160622, -0.1513045, -0.053284165, 0.033202525, 0.15574448, -0.043640967, -0.0093824165, -0.0019965349, -0.097964935, -0.08289824, 0.08239996, 0.07868361, 0.05731752, -0.20441617, -0.013016076, -0.253108) * go_0(1.0, -1.0);
result += mat4(-0.031249097, -0.2272863, 0.23573665, 0.03357689, 0.011395065, -0.10885564, -0.06287508, -0.031719524, 0.10331069, 0.17560169, 0.18303394, 0.022961004, -0.17011635, -0.24371737, 0.10678694, -0.3222825) * go_0(1.0, 0.0);
result += mat4(-0.1275465, -0.08844758, 0.10994917, -0.00910273, 0.09393154, 0.03894992, 0.14367905, -0.11811715, -0.09077633, -0.015776094, 0.27427456, -0.13283503, 0.18724327, -0.08139094, 0.04933602, -0.051852766) * go_0(1.0, 1.0);
result += mat4(-0.06764611, -0.27426586, 0.12045272, 0.09410856, -0.14258035, 0.11802992, -0.09093882, 0.0022018093, 0.4590643, 0.046258576, -0.07827223, 0.448011, -0.103631735, -0.016930219, -0.15421398, 0.11045997) * go_1(-1.0, -1.0);
result += mat4(-0.17295076, 0.00151352, 0.14938255, 0.08336512, -0.07496541, -0.07561223, -0.0846474, 0.14979269, -0.09142163, 0.23925088, -0.015199518, -0.37749895, -0.20636298, -0.022585187, -0.20371509, 0.0745308) * go_1(-1.0, 0.0);
result += mat4(0.06458832, -0.009722021, -0.123604394, 0.06548835, -0.3039139, -0.022024399, 0.05297587, -0.0626883, 0.23556642, 0.1516464, -0.07004877, -0.1845364, -0.05918428, 0.19158973, -0.14983447, 0.030489758) * go_1(-1.0, 1.0);
result += mat4(0.36604697, 0.17516142, -0.10853731, -0.22694224, -0.107650936, 0.23013335, 0.094055794, -0.17047717, -0.3006048, -0.08621717, -0.18815655, -0.03570218, 0.09676118, -0.017718751, 0.059138596, 0.073388465) * go_1(0.0, -1.0);
result += mat4(-0.12791575, 0.101956226, 0.13091874, -0.046373338, 0.04955811, -0.04030444, 0.13869923, -0.046699073, -0.42611042, -0.7173929, 0.052184317, 0.6178025, -0.02929954, -0.07638965, -0.15000828, 0.030710017) * go_1(0.0, 0.0);
result += mat4(0.057806686, 0.20842272, -0.20148766, 0.006666912, 0.13356528, -0.45265228, -0.07354092, 0.21447696, 0.019552143, -0.13645506, 0.14643854, -0.0071413796, -0.15487236, -0.002250615, 0.30622452, 0.0033902125) * go_1(0.0, 1.0);
result += mat4(0.06896002, 0.24397352, -0.06479052, 0.20676947, -0.24259068, 0.055320013, -0.09032122, -0.11222854, -0.08982342, -0.114818625, -0.06399291, -0.3024516, -0.06302166, -0.1925528, 0.03458982, 0.028828239) * go_1(1.0, -1.0);
result += mat4(0.09764086, 0.09599894, -0.0073313303, 0.14418933, -0.045712367, 0.12657364, 0.04620374, -0.069778584, 0.30047333, -0.012418192, 0.15516461, -0.18087754, 0.08178273, 0.14262857, -0.01741533, -0.12509112) * go_1(1.0, 0.0);
result += mat4(0.04697884, -0.1506804, 0.031823065, 0.13397239, -0.18396698, 0.10681781, -0.29586303, -0.0039136545, 0.17560847, -0.12486726, -0.018646788, -0.20688744, -0.030614454, -0.0527634, 0.23593572, -0.10542146) * go_1(1.0, 1.0);
result += mat4(-0.19182229, -0.32615846, 0.26283535, -0.1371942, -0.071202695, 0.12056063, -0.11450658, -0.27711076, -0.42096004, 0.0014352369, 0.1559669, -0.14464542, -0.17973948, 0.079166576, -0.12501791, -0.20623216) * go_2(-1.0, -1.0);
result += mat4(0.12469872, 0.32190827, -0.059510354, 0.1393449, -0.12845798, -0.019571869, -0.22630808, -0.14031963, 0.36072046, 0.05858427, 0.19278921, 0.121090546, -0.067538865, -0.018770566, 0.14318037, -0.15561756) * go_2(-1.0, 0.0);
result += mat4(0.024663208, 0.21110268, -0.016415706, 0.060093414, -0.03739678, -0.107412934, -0.077527136, 0.30331334, 0.17196326, -0.15512557, -0.09499732, -0.15748607, -0.16680105, -0.015185634, 0.16114107, -0.21288376) * go_2(-1.0, 1.0);
result += mat4(-0.17739037, -0.1190967, 0.13191372, -0.2527187, -0.14992718, -0.30511454, 0.19145966, 0.002194003, -0.12888977, 0.19152176, 0.27528167, 0.099714965, 0.12865707, -0.12051514, -0.055013947, 0.26231763) * go_2(0.0, -1.0);
result += mat4(0.46433613, -0.11708138, -0.20157282, 0.32022122, 0.079468675, 0.029407484, 0.2559102, -0.15651533, 0.08644574, -0.09747344, -0.07528584, 0.17354868, 0.19167562, -0.17698488, -0.09896657, 0.17093097) * go_2(0.0, 0.0);
result += mat4(0.20283653, -0.33680332, 0.2282385, 0.18832158, 0.20866042, 0.00076752366, 0.16471444, -0.21548858, 0.16193539, 0.17141372, 0.03140222, 0.03913644, -0.030161971, 0.00014570929, 0.08993654, -0.064823024) * go_2(0.0, 1.0);
result += mat4(-0.3075755, 0.19942546, 0.015526995, -0.120868504, -0.254515, -0.07791228, 0.03271691, 0.11794217, 0.11258601, 0.045204375, -0.061196107, -0.115958795, 0.3861869, 0.048215542, 0.07016682, -0.009975758) * go_2(1.0, -1.0);
result += mat4(-0.07623697, 0.16094944, -0.02283455, 0.14112763, -0.051149167, 0.20429814, 0.011314802, 0.18914083, -0.24240434, -0.08784008, -0.16763984, -0.08492233, 0.31062725, -0.11925119, -0.33195966, 0.2060798) * go_2(1.0, 0.0);
result += mat4(-0.016709225, -0.14472668, -0.3677625, -0.09832719, 0.030297454, -0.05775362, -0.1401375, 0.08119674, -0.01795042, 0.05183797, -0.24320887, 0.066842034, -0.22245285, -0.02740993, 0.06316751, 0.053399116) * go_2(1.0, 1.0);
result += mat4(-0.039214406, -0.08876633, 0.045552462, 0.19226661, 0.1355001, -0.13942362, 0.17398876, 0.2914014, -0.191809, 0.037143208, 0.013333581, -0.16632195, 0.113767646, -0.106692605, 0.1589787, 0.030107044) * go_3(-1.0, -1.0);
result += mat4(0.21997562, 0.13855208, -0.05783191, -0.033682413, -0.010961168, 0.10524961, 0.02177416, 0.18289444, 0.043692037, 0.07853899, -0.039936125, -0.1004449, 0.04494073, -0.020680292, 0.17578089, -0.106598996) * go_3(-1.0, 0.0);
result += mat4(0.026852835, -0.16037546, 0.11278316, 0.12656097, -0.006857894, -0.03400118, -0.051564034, 0.00085412664, -0.37556714, -0.05279987, 0.029383834, -0.14246808, -0.056380164, -0.002399925, 0.16025752, 0.036324855) * go_3(-1.0, 1.0);
result += mat4(0.022709966, 0.046350412, 0.03390721, 0.02810572, -0.14394265, 0.04215361, -0.3206118, 0.15034916, -0.0028448137, 0.1682989, -0.042686664, 0.020543462, -0.2786501, -0.007482015, -0.040313292, -0.20745736) * go_3(0.0, -1.0);
result += mat4(0.05417556, 0.18728684, -0.046121832, -0.27939513, 0.05907976, -0.09191223, -0.16625418, -0.26038164, 0.39956605, -0.052594025, -0.0596556, 0.29517552, -0.015181923, -0.0763375, 0.25131205, 0.13038464) * go_3(0.0, 0.0);
result += mat4(-0.036903054, -0.0066989153, -0.062650286, 0.05614359, -0.0064960583, 0.028512698, -0.10906273, -0.010047654, 0.23030473, 0.049983572, 0.10439064, 0.26643834, 0.05041243, 0.09185424, -0.32352915, 0.11295159) * go_3(0.0, 1.0);
result += mat4(0.09724027, -0.34962535, 0.06586686, 0.016635379, 0.13831381, 0.01707076, -0.04690347, 0.022350075, 0.018352794, 0.022000022, 0.070613205, 0.117735535, -0.025971051, 0.18832101, -0.09643588, -0.08512127) * go_3(1.0, -1.0);
result += mat4(-0.17324433, 0.06810613, -0.057295907, -0.05115964, -0.101570815, 0.12491774, 0.08762367, -0.005862404, -0.05342927, -0.031942457, -0.039624047, -0.04298937, -0.1303138, -0.11869282, -0.024832053, 0.070463404) * go_3(1.0, 0.0);
result += mat4(-0.010514842, 0.1376259, -0.11750346, -0.03786737, 0.03459249, 0.015408171, -0.031430878, -0.060825355, -0.072958425, -0.0037895301, 0.041686177, -0.12352204, -0.06261361, 0.054514423, -0.34072715, 0.13860728) * go_3(1.0, 1.0);
result += vec4(0.018166734, -0.11002478, -0.05554318, -0.0988193);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.040142782, 0.0288423, 0.07569487, -0.01490842, 0.14402796, -0.13682005, 0.027765118, 0.03907358, 0.07117706, 0.058157545, -0.23862502, -0.057674367, -0.19220531, 0.0147159435, -0.18028538, 0.0963821) * go_0(-1.0, -1.0);
result += mat4(-0.1676744, -0.11937339, 0.12137117, 0.07119485, 0.14148116, -0.043578617, -0.029261118, -0.0016938087, -0.057269357, -0.080076694, 0.12193026, 0.07326153, -0.056278303, -0.01630716, -0.03792076, 0.1483611) * go_0(-1.0, 0.0);
result += mat4(-0.3021578, 0.011601693, 0.11266048, 0.19086999, -0.0122412145, 0.08431291, 0.11615175, -0.008039614, -0.39987534, 0.07820729, 0.03509667, 0.1963505, -0.08839513, -0.21571854, 0.059425723, -0.06830175) * go_0(-1.0, 1.0);
result += mat4(0.23135209, -0.12452708, 0.0943565, 0.0028859286, -0.09836373, 0.10681712, -0.3535964, 0.08457615, 0.045332734, 0.16579892, -0.03809797, -0.021596594, 0.2937497, -0.028294371, 0.046484597, -0.037604347) * go_0(0.0, -1.0);
result += mat4(0.072675414, -0.16431206, 0.28952035, 0.0076831076, -0.020242939, 0.029483542, -0.092415355, 0.08673106, 0.12109694, 0.14307201, 0.23134442, 0.11731775, 0.09981601, -0.16968462, 0.037470713, 0.14948717) * go_0(0.0, 0.0);
result += mat4(0.0029752052, 0.06526503, 0.1866458, 0.07451277, -0.31836876, 0.17115082, -0.13969697, 0.23844297, -0.03244903, -0.08832665, 0.023691226, -0.18230624, -0.074933805, -0.00044301842, 0.050572682, 0.081511915) * go_0(0.0, 1.0);
result += mat4(0.039502528, 0.051221415, -0.13968123, -0.091212444, -0.016925618, 0.15409444, -0.017455677, -0.11653652, 0.03539446, -0.00087720866, -0.12839639, 0.037198763, 0.03674469, -0.26444665, 0.019721227, -0.13013805) * go_0(1.0, -1.0);
result += mat4(0.039229527, 0.25667152, 0.0032586441, -0.00718359, 0.1617932, 0.10409968, 0.07182867, -0.09810605, 0.07789241, -0.02014911, 0.025767172, -0.14604759, 0.07175764, 0.32513744, -0.20473222, -0.16266066) * go_0(1.0, 0.0);
result += mat4(0.13418433, 0.061813723, -0.13927278, -0.2498272, 0.03468218, 0.29483125, 0.063289374, -0.04726235, 0.1898295, -0.33132064, 0.032045014, 0.02159535, -0.1148363, 0.31306976, 0.06456038, 0.048988886) * go_0(1.0, 1.0);
result += mat4(0.07151646, 0.2799246, -0.107190795, -0.16431166, -0.28007045, 0.07206954, 0.06775463, 0.009758042, 0.07032184, -0.20843789, 0.087045245, 0.1360676, -0.25718534, 0.028249472, -0.12614648, 0.009949602) * go_1(-1.0, -1.0);
result += mat4(0.020241471, -0.23390484, -0.0083223935, 0.08344701, 0.08222297, 0.12026539, -0.08652223, -0.08228822, -0.039576706, -0.24677879, -0.1157289, 0.2590508, -0.23809408, 0.19911982, -0.116798095, -0.035870325) * go_1(-1.0, 0.0);
result += mat4(0.024991842, 0.050509237, -0.024134455, -0.12659028, 0.24089767, 0.122712664, -0.10482493, -0.19403952, -0.19177693, -0.06538376, -0.041478425, 0.32176673, -0.1534002, -0.18680622, 0.06763643, 0.020806564) * go_1(-1.0, 1.0);
result += mat4(0.03437814, -0.28067374, 0.2830681, 0.038812317, -0.021698112, -0.120865285, 0.22695538, -0.045419116, -0.030475847, -0.01977341, -0.1265364, -0.3109814, 0.012255813, 0.053917278, -0.018620957, -0.14599285) * go_1(0.0, -1.0);
result += mat4(-0.016204128, -0.04093018, 0.054571863, 0.02679643, 0.01756274, -0.057685968, 0.16148666, 0.17370272, -0.11065411, 0.06378157, -0.09331551, 0.22985275, 0.057905316, 0.12323568, 0.07748665, 0.09878629) * go_1(0.0, 0.0);
result += mat4(-0.018112244, 0.063234635, -0.013184602, 0.16241394, 0.08877139, 0.02145378, -0.02490027, -0.038920373, 0.13127136, 0.14391647, 0.020553736, 0.14401346, 0.06685973, -0.25398204, 0.10369067, -0.055949755) * go_1(0.0, 1.0);
result += mat4(0.07710333, 0.047412727, 0.13813803, 0.18624061, 0.16907091, -0.039532468, 0.06234584, 0.06408178, -0.054543987, -0.045220226, -0.11093376, -0.37399602, 0.20372874, 0.004580967, -0.07742308, 0.017989937) * go_1(1.0, -1.0);
result += mat4(0.003485311, -0.08897399, -0.013108594, -0.19473282, -0.27081844, -0.16812073, 0.0052992934, -0.055331517, 0.09446357, 0.019280333, 0.16560757, -0.3230032, 0.043096773, 0.059222896, -0.064184934, -0.059852477) * go_1(1.0, 0.0);
result += mat4(0.06794279, -0.034135245, 0.083064295, 0.13506731, 0.13064219, -0.44978833, -0.03513717, 0.08999715, 0.1124541, 0.42208397, -0.0038724816, -0.014332087, -0.13751853, -0.04929869, 0.09134992, -0.17687531) * go_1(1.0, 1.0);
result += mat4(0.100909084, -0.0131197255, 0.082274795, -0.2138443, -0.08515947, -0.021058358, 0.10951775, -0.06349191, -0.29129833, -0.029262653, 0.25235432, -0.11748315, 0.121980384, 0.062347785, 0.10916932, -0.15993518) * go_2(-1.0, -1.0);
result += mat4(0.28893283, -0.05677308, -0.2641288, -0.058937225, -0.16187571, 0.006647366, -0.063294955, 0.04766719, 0.60601914, -0.07831864, -0.15710756, -0.011491797, 0.15587467, -0.08105375, 0.07847514, -0.2803333) * go_2(-1.0, 0.0);
result += mat4(-0.077989794, -0.09871811, -0.3516344, 0.15292728, 0.010889273, 0.0011189661, -0.16118282, -0.018821161, -0.039708678, -0.00060983415, -0.06367813, 0.009148068, 0.03919827, 0.18782744, 0.028040757, -0.10230145) * go_2(-1.0, 1.0);
result += mat4(-0.4079609, 0.18640275, -0.12475227, 0.13891742, 0.25121725, 0.16942379, 0.14409852, 0.087600805, 0.045335658, -0.12683709, -0.0077387216, 0.06563413, -0.19857128, 0.106910795, -0.048285246, 0.10768945) * go_2(0.0, -1.0);
result += mat4(0.5989075, 0.20941062, -0.20086494, 0.13344856, 0.073034994, 0.22358665, 0.101664364, -0.13463663, 0.18816395, -0.061176624, -0.14712185, 0.027320342, -0.09529667, 0.031148786, -0.28744993, 0.18698911) * go_2(0.0, 0.0);
result += mat4(0.14799193, 0.39471942, -0.23340325, -0.4031061, 0.18926248, -0.11091216, 0.118981816, -0.09155061, 0.17049436, 0.19803695, -0.1513267, 0.023817873, 0.0090933135, -0.04134864, 0.060486555, 0.03536634) * go_2(0.0, 1.0);
result += mat4(-0.39094314, 0.01779997, 0.12710269, 0.0067333193, -0.31255835, -0.08206612, -0.048528638, 0.369439, -0.19351655, -0.03420455, 0.15831526, -0.052294146, -0.08481741, 0.0787108, 0.1312136, -0.108919285) * go_2(1.0, -1.0);
result += mat4(-0.16068119, -0.42190582, 0.19383872, -0.018445708, 0.09803051, -0.020769652, -0.022599563, -0.052448895, -0.20645833, -0.031432863, 0.0025441595, 0.03410379, -0.20268854, 0.04481527, 0.05191063, 0.42317194) * go_2(1.0, 0.0);
result += mat4(-0.12786235, -0.23936178, 0.116561726, 0.30756372, -0.09420156, -0.044529166, -0.03585749, 0.1829332, -0.23939075, 0.24030831, 0.019878127, -0.015069802, 0.24300557, -0.22558568, -0.104956664, -0.09393648) * go_2(1.0, 1.0);
result += mat4(-0.04607054, 0.012677649, -0.027597688, 0.1618836, 0.29210827, 0.014221155, -0.13591036, -0.06895336, -0.09559534, 0.07956421, -0.11112994, -0.13325493, 0.24562472, 0.11046177, 0.057847694, 0.0016315983) * go_3(-1.0, -1.0);
result += mat4(-0.03365951, 0.027391057, 0.09653403, -0.14718771, -0.049631152, -0.06467214, -0.058545876, 0.1424002, -0.06320376, 0.181183, 0.10249362, -0.16052136, 0.3013475, -0.04156266, 0.08862033, 0.06888033) * go_3(-1.0, 0.0);
result += mat4(0.10045977, -0.004198456, -0.025856055, 0.05739418, -0.1328637, -0.025975171, 0.06553717, 0.11301186, 0.0704087, -0.083569765, 0.16066101, -0.24453588, 0.25370175, 0.037184533, 0.062386766, -0.20025635) * go_3(-1.0, 1.0);
result += mat4(-0.017958941, 0.06417776, -0.1525265, 0.12451173, 0.14567685, -0.0049682115, -0.23973411, -0.0783304, -0.010629432, 0.08055161, 0.2028341, 0.17640644, -0.20445108, -0.055524793, -0.019326134, 0.081288636) * go_3(0.0, -1.0);
result += mat4(0.007882519, -0.03722546, 0.053249408, 0.00071846246, -0.07053029, -0.21583866, 0.1415364, -0.19486657, 0.20685542, 0.17660026, -0.32156837, 0.1746825, -0.14957622, -0.09224378, -0.098153435, -0.13054638) * go_3(0.0, 0.0);
result += mat4(0.10051427, -0.17398237, 0.09842799, -0.14187703, 0.116901085, -0.1229543, -0.0007776771, -0.20410055, -0.11373484, -0.111150615, -0.1974002, -0.11641459, 0.024105398, 0.24985977, 0.015871854, -0.10724633) * go_3(0.0, 1.0);
result += mat4(-0.18081793, 0.1209351, -0.12867971, -0.019415248, 0.062617876, -0.037130393, -0.07803658, -0.22862352, 0.2586428, -0.030090366, -0.11894069, 0.18087515, -0.40921417, 0.070013195, 0.030540073, 0.035120826) * go_3(1.0, -1.0);
result += mat4(-0.13185939, 0.12992652, 0.08125049, 0.075331174, 0.064219765, 0.056629725, -0.020012032, -0.0855444, -0.044063166, -0.05396545, -0.028002812, 0.21837157, -0.15206428, -0.12681007, 0.14895032, 0.12339962) * go_3(1.0, 0.0);
result += mat4(0.08066341, -0.14773634, -0.0212227, -0.014011867, -0.048505764, 0.075407125, -0.020620076, 0.0003291325, -0.21815202, -0.23136546, 0.10853532, -0.036058456, 0.10952532, -0.052677035, -0.13005799, 0.18398996) * go_3(1.0, 1.0);
result += vec4(0.022609137, -0.028548084, 0.024431901, 0.010504478);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.069641694, 0.104958326, 0.14786446, 0.027633663, -0.004279524, -0.020451711, 0.0883571, -0.016224537, 0.13585235, 0.11078269, 0.20198658, -0.042161036, 0.020466218, 0.20994963, 0.20072585, -0.028024657) * go_0(-1.0, -1.0);
result += mat4(0.050872434, 0.12874635, 0.1298729, 0.115810685, 0.07087254, 0.09885682, 0.23018982, 0.19187538, 0.10953604, 0.0033836907, -0.13325337, 0.09830315, -0.06528767, 0.05096927, -0.016355392, -0.039334368) * go_0(-1.0, 0.0);
result += mat4(0.027010268, 0.018263958, 0.0360758, 0.016791478, 0.2815702, 0.15517488, 0.43415815, 0.044976447, -0.0070842914, -0.12546758, 0.16874593, 0.077622116, 0.02252915, 0.1769774, 0.07181055, -0.15128697) * go_0(-1.0, 1.0);
result += mat4(0.057129618, 0.118046716, 0.07237424, -0.07842637, -0.044214778, -0.12886304, 0.08603301, -0.10416606, -0.15852053, 0.3788151, 0.26181692, -0.09092249, 0.31635332, 0.064212754, 0.21923725, 0.07500004) * go_0(0.0, -1.0);
result += mat4(-0.16981383, 0.044409662, -0.3717617, -0.031610407, 0.03658662, -0.09459229, -0.09449437, -0.014000666, -0.19656453, 0.03934163, -0.16304104, -0.12761801, -0.06235523, 0.16438273, -0.036933117, -0.095564745) * go_0(0.0, 0.0);
result += mat4(0.09725091, 0.034022827, 0.17699842, 0.1079676, -0.13236652, 0.03718181, -0.06968635, -0.23288171, 0.10275666, 0.08464966, -0.37162134, -0.35782215, -0.11023659, 0.2519236, -0.035197742, -0.019324787) * go_0(0.0, 1.0);
result += mat4(-0.09968464, 0.01102193, 0.0073735216, 0.011999313, -0.004998707, 0.09518938, 0.045727003, -0.21544908, 0.006879454, -0.06398254, -0.12584935, -0.06759933, -0.0820037, -0.07775104, 0.021957919, -0.122708224) * go_0(1.0, -1.0);
result += mat4(-0.08869767, 0.031296413, -0.0034280645, 0.13778855, 0.10073061, -0.08393937, -0.032959275, -0.0500518, 0.010908757, -0.09189417, -0.057760105, 0.17652664, -0.08729078, -0.09639096, -0.25654703, 0.055152636) * go_0(1.0, 0.0);
result += mat4(0.0027847723, -0.12885433, 0.038065907, 0.17450769, 0.0864409, 0.04592345, -0.015443841, 0.077010944, 0.08967368, 0.06800111, -0.23636387, 0.35023567, 0.03165923, 0.03132063, 0.17964344, 0.035610788) * go_0(1.0, 1.0);
result += mat4(-0.032017227, -0.0022808525, -0.08470573, 0.05332408, -0.14674746, 0.025374275, -0.018281924, 0.041163016, 0.00096549373, 0.014724006, 0.004913065, 0.18494442, 0.034953076, -0.15731992, -0.13792977, 0.08041999) * go_1(-1.0, -1.0);
result += mat4(0.08305006, 8.6318905e-05, -0.007895379, 0.02731387, -0.061324496, 0.050034665, 0.22662131, -0.013876427, -0.074468784, -0.008136604, -0.23337875, -0.1742574, 0.011753501, -0.11666686, -0.22541048, -0.14549944) * go_1(-1.0, 0.0);
result += mat4(-0.028333234, 0.121047184, 0.06720256, -0.058930036, 0.030258363, 0.07292774, 0.06455556, 0.0019076486, 0.0073987027, 0.17144889, 0.06084024, -0.08762086, -0.114422195, -0.16595861, -0.08706028, -0.10736261) * go_1(-1.0, 1.0);
result += mat4(-0.02519315, -0.14611271, 0.0388848, 0.19481422, -0.05970354, -0.08391417, 0.18982239, -0.10447052, 0.15587378, -0.023997072, 0.0781739, 0.2182389, -0.023886079, -0.1422596, -0.13352804, 0.005008043) * go_1(0.0, -1.0);
result += mat4(0.08842712, -0.100292705, 0.18925671, 0.12198875, 0.061771665, -0.04473232, 0.025053164, 0.039047796, -0.1672479, -0.08934517, 0.33099812, -0.20269585, -0.21640155, -0.22029749, 0.16539703, -0.2442679) * go_1(0.0, 0.0);
result += mat4(-0.16332205, -0.101898365, 0.02919932, -0.11900455, 0.14442924, 0.0916815, 0.037550304, 0.024123482, 0.02042624, 0.033472955, -0.059437107, -0.18735693, -0.013749093, -0.06199881, -0.08685079, 0.04252364) * go_1(0.0, 1.0);
result += mat4(-0.09047013, -0.055188328, -0.09106191, -0.048969727, 0.05114009, -0.12753403, 0.07116141, 0.060749624, -0.074034564, -0.21952136, -0.09479503, 0.2753584, -0.014141759, -0.14883812, -0.0673838, -0.012279045) * go_1(1.0, -1.0);
result += mat4(0.013816464, -0.0747162, -0.19202435, -0.064403646, 0.34980014, 0.04375546, 0.20264609, 0.006684355, 0.11523799, 0.024674915, -0.08697566, -0.04662527, -0.12743855, -0.39463726, 0.0057380227, 0.01286557) * go_1(1.0, 0.0);
result += mat4(-0.08146522, 0.074080914, -0.16856177, -0.183158, 0.19228102, 0.12373886, 0.017574452, -0.01753772, 0.045071773, 0.07725093, 0.023422163, -0.011545186, 0.20751388, -0.10795588, 0.07606346, 0.10282933) * go_1(1.0, 1.0);
result += mat4(0.12512013, -0.102208994, -0.09125398, 0.12043188, -0.066011876, 0.08831903, -0.017038671, -0.005541508, -0.049607087, 0.08654939, -0.02037085, 0.26887566, 0.005012545, 0.01869507, -0.013064982, -0.010649147) * go_2(-1.0, -1.0);
result += mat4(0.006824864, -0.05071593, -0.20786697, -0.07327317, 0.011382597, 0.030494886, -0.04754353, -0.018284699, 0.01305972, -0.036589053, 0.26637617, 0.021887446, -0.026669119, -0.037982125, -0.063445956, -0.009104248) * go_2(-1.0, 0.0);
result += mat4(0.032602567, 0.07094331, 0.052653246, 0.08342047, -0.085082285, -0.14674088, -0.23073354, -0.07915851, 0.0017120204, 0.032407638, -0.039819505, 0.16942178, 0.023192152, -0.0353237, 0.10930186, 0.22939779) * go_2(-1.0, 1.0);
result += mat4(0.0010455973, -0.11821993, -0.12639599, 0.12250084, -0.12756817, 0.11478416, -0.1862587, 0.016819192, 0.02110181, -0.25492984, -0.1766048, 0.22188173, -0.21305011, 0.113442205, 0.04599144, -0.15840286) * go_2(0.0, -1.0);
result += mat4(-0.15086032, -0.17428935, 0.39080557, 0.07576757, 0.121703945, 0.17944208, -0.003140103, -0.11231332, 0.12102969, 0.15310267, 0.17578171, 0.40631834, -0.21299168, 0.024928993, 0.030104794, 0.020753227) * go_2(0.0, 0.0);
result += mat4(-0.098734386, -0.020072265, -0.14308836, -0.08490801, 0.017175158, 0.02250534, 0.04060829, 0.033022214, 0.0046218676, 0.17923212, 0.0112105915, 0.09574084, 0.14819936, -0.14692923, 0.12634254, 0.060762513) * go_2(0.0, 1.0);
result += mat4(0.030521613, -0.097913325, -0.016720278, 0.11273997, 0.013019863, -0.06557118, 0.0405774, 0.0915019, 0.022414956, -0.053254984, 0.18639986, 0.07820968, 0.06498986, 0.058922634, -0.02240318, -0.086019725) * go_2(1.0, -1.0);
result += mat4(0.2058775, 0.01502064, 0.05847032, 0.007249146, 0.086483665, 0.19420148, 0.03892261, -0.013546935, -0.07980237, 0.04347281, -0.10376214, -0.1366535, 0.05285337, 0.07213318, 0.3642818, -0.11331124) * go_2(1.0, 0.0);
result += mat4(-0.025740806, 0.14551482, -0.037410017, -0.17477523, -0.11853099, -0.060820814, -0.102599286, -0.13267937, -0.103053465, -0.014044828, -0.01888072, -0.06499249, 0.22311528, -0.051850274, -0.034120858, 0.044562567) * go_2(1.0, 1.0);
result += mat4(-0.21360217, 0.10093803, -0.0016407765, -0.1473997, 0.26524043, 0.02112132, 0.23173104, -0.013157391, 0.05945182, 0.044635538, 0.06031638, -0.21435826, -0.10147484, 0.069090195, 0.09641844, -0.09581093) * go_3(-1.0, -1.0);
result += mat4(-0.08576515, -0.122861005, 0.049567085, -0.085854456, 0.23809357, -0.024966082, -0.10294079, 0.046241313, 0.008621132, -0.08323767, 0.20277941, 0.163423, -0.07386535, -0.088738985, 0.05274358, -0.025479877) * go_3(-1.0, 0.0);
result += mat4(-0.041135542, -0.008365642, 0.17088248, 0.04025207, 0.13809255, -0.056895368, -0.01582834, 0.07361908, -0.00068995473, -0.09300962, 0.19117641, 0.24832036, -0.06572358, -0.026025, -0.019093119, -0.049720034) * go_3(-1.0, 1.0);
result += mat4(0.024900286, 0.11525501, 0.025882801, 0.037742402, 0.36976853, 0.052211333, -0.15143296, 0.1802276, -0.059080046, 0.017990451, 0.026395092, -0.12689115, -0.07705386, 0.1232379, 0.13273561, -0.12521964) * go_3(0.0, -1.0);
result += mat4(-0.19788785, 0.044887315, 0.07663442, 0.16688696, -0.2842248, -0.15684547, 0.028387763, 0.0063470444, -0.012245601, -0.038382255, -0.8187406, -0.25245667, 0.23014604, 0.22746666, 0.1594356, 0.16469443) * go_3(0.0, 0.0);
result += mat4(-0.12663333, 0.014730006, 0.03765697, 0.15704912, -0.106595434, -0.05317512, -0.081759915, -0.08797109, 0.064620756, -0.06341419, 0.16493447, 0.23102313, 0.068325415, -0.088058695, 0.16885915, 0.036382258) * go_3(0.0, 1.0);
result += mat4(0.035389822, -0.11811836, -0.035656307, -0.0680554, 0.1338908, 0.065852076, 0.023307983, 0.0675308, 0.09690683, 0.18170924, 0.09862692, -0.20964378, -0.08601271, -0.20016764, -0.01879598, -0.14629345) * go_3(1.0, -1.0);
result += mat4(-0.27183273, 0.013525998, -0.14995874, -0.23938845, -0.26218823, -0.0009874097, -0.13385512, -0.10664239, -0.048931994, 0.039898522, 0.047444753, 0.10934722, 0.10969629, 0.123539805, 0.11692802, 0.14172275) * go_3(1.0, 0.0);
result += mat4(-0.1656506, 0.019683002, 0.0221048, 0.12596753, 0.20420644, -0.07930122, 0.04653823, 0.11492255, -0.0050175437, -0.03271697, 0.013389486, 0.034583613, -0.2196601, -0.1615663, -0.013763388, -0.056037936) * go_3(1.0, 1.0);
result += vec4(-0.022956269, 0.029688787, -0.070148066, -0.07163476);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf1
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.15104648, 0.05522861, -0.0654341, -0.053517453, -0.08264124, -0.0062249107, -0.20364265, -0.05015117, -0.18837251, 0.030655831, 0.046844713, -0.20673253, -0.14042036, -0.05655449, 0.13994302, 0.011745607) * go_0(-1.0, -1.0);
result += mat4(-0.16517559, 0.1489214, -0.09149559, 0.025003506, -0.124926426, 0.16974348, -0.020857265, 0.08017403, 0.21836148, 0.0025619378, 0.2331612, 0.085599184, -0.030934382, -0.055194855, 0.09527726, -0.10081552) * go_0(-1.0, 0.0);
result += mat4(0.041800212, 0.028859638, 0.09395546, 0.05211183, -0.038541477, 0.021495212, 0.04862346, -0.007864793, 0.038407274, -0.13841268, -0.14963801, 0.26470762, 0.16691841, -0.07262008, 0.034374326, -0.14709206) * go_0(-1.0, 1.0);
result += mat4(0.00094978884, -0.028974704, -0.0900548, -0.08401967, -0.08935931, -0.043606587, -0.14497143, -0.05226239, -0.21516493, 0.19410603, -0.089924194, -0.04335071, -0.012618276, -0.2671613, 0.020422975, -0.037739716) * go_0(0.0, -1.0);
result += mat4(-0.13403237, -0.02524383, -0.03474901, 0.054432765, 0.11946775, 0.107336655, -0.1431715, -0.13370377, 0.015087512, -0.1917613, 0.073493585, 0.2788855, -0.010510839, 0.06891479, -0.06741307, -0.05271205) * go_0(0.0, 0.0);
result += mat4(-0.15432046, 0.04021662, -0.16979513, 0.13660534, -0.10518303, -0.10095502, -0.13092068, 0.022805348, -0.16676381, -0.4273298, 0.020867536, 0.3506733, -0.29459694, -0.055828743, -0.069241956, 0.04106382) * go_0(0.0, 1.0);
result += mat4(-0.08890133, 0.07549666, -0.040735144, -0.1506932, -0.22227979, -0.0762723, -0.17766447, -0.05741318, -0.21885683, 0.2379157, -0.15525854, -0.07306285, 0.15580738, -0.04394069, -0.19175608, 0.018283797) * go_0(1.0, -1.0);
result += mat4(-0.08503275, -0.105500385, -0.114987396, -0.07166016, -0.2147138, 0.09378708, 0.24550334, -0.0834075, -0.033147786, -0.022304727, -0.31062204, 0.027651973, 0.109098755, 0.18889032, 0.1163026, 0.13863255) * go_0(1.0, 0.0);
result += mat4(0.15266588, -0.14901319, 0.033916786, 0.09381096, -0.08196443, -0.16194504, 0.035789456, 0.21234898, -0.48724765, 0.2619442, -0.11215393, 0.25061038, 0.022344576, 0.0116525125, 0.111661114, -0.15242295) * go_0(1.0, 1.0);
result += mat4(0.020475458, 0.0797404, -0.13576819, 0.009681671, 0.030504882, 0.049232908, 0.022025917, 0.16912088, -0.23914136, -0.084663324, 0.020925451, -0.1023938, 0.035916872, -0.07538111, -0.11470242, 0.15238516) * go_1(-1.0, -1.0);
result += mat4(-0.12941381, 0.08509899, -0.029489802, -0.09148447, -0.089406274, -0.116145454, -0.08979843, 0.11908148, 0.15473351, -0.21687616, 0.12607013, -0.08244334, -0.079580925, -0.16613089, -0.09287793, -0.03412643) * go_1(-1.0, 0.0);
result += mat4(-0.023578499, 0.07394217, -0.13069086, -0.1060499, -0.07559958, -0.21839201, 0.1090753, 0.0787872, 0.07677037, -0.25998843, 0.20039314, 0.046882212, 0.31871012, -0.3048051, 0.15118991, -0.00518087) * go_1(-1.0, 1.0);
result += mat4(-0.15338503, -0.11057532, 0.075839415, -0.18592294, -0.0155324, 0.038140323, -0.10498194, 0.09070477, 0.05108992, -0.047939524, -0.091004305, 0.09649005, -0.10967152, -0.051909525, -0.05314551, 0.09661584) * go_1(0.0, -1.0);
result += mat4(-0.14458802, -0.053263694, -0.0010885567, 0.23342133, 0.01918937, 0.12026143, -0.15691495, 0.30480555, -0.08725869, 0.19082253, 0.3594973, 0.016653897, 0.045152336, -0.088590585, 0.0069655925, 0.1392425) * go_1(0.0, 0.0);
result += mat4(0.17944881, -0.17950764, 0.13282645, 0.030974053, 0.32233685, 0.18067117, -0.11472813, 0.097301506, -0.047649745, -0.1053861, -0.081039384, 0.035132434, 0.10204545, 0.085582554, -0.13153993, -0.021741152) * go_1(0.0, 1.0);
result += mat4(-0.15573682, 0.16409989, -0.22574787, -0.03877603, -0.18285516, 0.11638645, 0.18321282, -0.017770218, 0.18230622, 0.16433364, -0.12795393, -0.03805153, 0.14386104, -0.0891527, -0.056928284, -0.10961495) * go_1(1.0, -1.0);
result += mat4(0.257622, 0.052519716, -0.25421762, -0.1887382, -0.083568096, -0.0064690276, -0.029110614, 0.103327505, -0.17006217, 0.2254096, -0.29366904, 0.04302887, -0.10198446, -0.24423616, 0.16781262, -0.005019004) * go_1(1.0, 0.0);
result += mat4(0.103393994, -0.059044626, -0.18192382, 0.0990813, -0.26143607, 0.11036474, 0.04788275, -0.096738026, 0.12825653, 0.13631694, -0.077904984, -0.020790676, -0.25118098, 0.122588515, -0.049440473, -0.10758222) * go_1(1.0, 1.0);
result += mat4(0.06693113, -0.13647175, 0.131139, 0.13143918, 0.081720434, 0.117537096, 0.15387627, -0.008771362, 0.08513583, 0.023794742, -0.0661625, 0.115793936, 0.0023350024, 0.02215075, -0.0494433, -0.013404977) * go_2(-1.0, -1.0);
result += mat4(0.041419264, -0.17622781, 0.028418267, 0.12114493, -0.23587078, 0.08457395, 0.014364018, -0.103271864, -0.051572207, -0.026424447, 0.16755055, -0.10763651, -0.033440586, 0.068594255, -0.050668504, 0.1941505) * go_2(-1.0, 0.0);
result += mat4(-0.2780181, 0.037816502, -0.11516711, -0.09822884, 0.13762361, -0.14317706, 0.14350282, 0.000623895, -0.08601606, 0.08118504, 0.15497385, -0.04721711, -0.008936935, -0.014223618, -0.09641698, -0.013884213) * go_2(-1.0, 1.0);
result += mat4(0.14349665, -0.03144472, -0.057813704, 0.0667044, 0.09026094, 0.051366236, 0.11139983, -0.015782114, -0.18314016, -0.18774192, 0.0014838242, 0.15759028, 0.062388215, 0.13626057, 0.02576217, -0.06317815) * go_2(0.0, -1.0);
result += mat4(0.07151769, 0.14508991, 0.1736844, -0.11487795, -0.07999805, -0.07797908, 0.037923355, -0.059138823, -0.23531209, -0.040207293, -0.068355694, -0.024296658, -0.114820175, 0.19726487, 0.21772414, 0.03659222) * go_2(0.0, 0.0);
result += mat4(0.16858695, -0.12135113, 0.009391182, -0.081519485, 0.13340487, 0.07007004, 0.094124354, 0.035519842, -0.3320139, -0.06624027, -0.14716229, -0.09205287, 0.12664132, -0.05655441, 0.0123263765, 0.04641279) * go_2(0.0, 1.0);
result += mat4(0.19018422, -0.15428329, -0.009354114, 0.04165953, 0.11024837, -0.107493006, -0.05807292, -0.048029456, 0.24319384, -0.10542357, -0.013699952, 0.06228662, -0.06808749, -0.023227982, 0.16528323, -0.05610251) * go_2(1.0, -1.0);
result += mat4(-0.008616222, 0.077674195, -0.08638503, 0.09293109, 0.072474636, 0.05004233, -0.20591061, -0.005301386, -0.15486047, 0.15038474, 0.1262478, 0.021724822, 0.02274613, -0.3088281, -0.08437887, -0.10684698) * go_2(1.0, 0.0);
result += mat4(-0.16960032, 0.09365251, -0.030414175, -0.010766254, 0.18181023, 0.12130318, 0.08913089, -0.06070321, 0.05200306, 0.092584535, 0.17694671, 0.033796314, -0.038107123, -0.04335955, -0.049443472, 0.30465958) * go_2(1.0, 1.0);
result += mat4(0.07661484, -0.009945252, 0.12866217, -0.07592757, -0.21030053, 0.014371748, -0.072458774, -0.04700072, 0.15534303, 0.2007125, -0.15699059, -0.032897495, 0.08110436, -0.11243608, 0.008632577, -0.10153441) * go_3(-1.0, -1.0);
result += mat4(-0.034697928, 0.06928288, -0.2796273, 0.14405379, 0.12248569, 0.036539096, 0.06607706, 0.077684596, -0.16473202, 0.1665916, -0.29977503, 0.21047153, 0.13114224, -0.091579035, -0.045458574, 0.03254245) * go_3(-1.0, 0.0);
result += mat4(0.053284872, 0.053366095, -0.26152626, -0.03123967, -0.031794485, 0.17670582, -0.07450994, 0.017521491, -0.040290453, 0.38342363, -0.25021288, -0.014660264, 0.1621895, 0.25041878, -0.12124821, 0.068036206) * go_3(-1.0, 1.0);
result += mat4(0.11366693, -0.030863572, -0.07411263, 0.12475283, -0.046070684, -0.09033321, 0.013222701, 0.06798592, -0.32814804, 0.057653826, -0.14082801, -0.00217398, -0.22856179, -0.19058353, -0.20992154, -0.03701372) * go_3(0.0, -1.0);
result += mat4(0.20345633, -0.1332355, 0.27152926, -0.13477845, -0.25242096, -0.28281286, 0.31289554, 0.14284514, 0.53362453, -0.46766588, 0.4518293, -0.39291728, -0.3573227, -0.014670052, 0.0051881406, 0.16552156) * go_3(0.0, 0.0);
result += mat4(-0.15017267, -0.07792945, -0.204405, 0.13964304, -0.13642666, -0.10228306, 0.03238279, -0.08689329, -0.072262034, -0.0258388, 0.05689183, 0.055701543, -0.19800112, 0.012217054, -0.033292748, -0.047611095) * go_3(0.0, 1.0);
result += mat4(-0.014704416, -0.12203891, 0.066083655, -0.1409769, 0.0041513643, -0.087383606, -0.17498164, 0.11327789, -0.25947225, -0.0016027623, 0.08202566, 0.042270098, 0.006429511, -0.26576808, -0.08461341, 0.049376782) * go_3(1.0, -1.0);
result += mat4(0.0695189, -0.14753938, 0.09578246, -0.16607563, -0.0105561055, 0.17166016, 0.027422488, -0.14175262, -0.009492696, -0.23449713, 0.018270867, 0.14635146, 0.33451268, 0.030959005, -0.46468422, 0.024256868) * go_3(1.0, 0.0);
result += mat4(-0.16865666, -0.00015881563, -0.054488145, -0.06222717, -0.032101758, 0.06485387, -0.0028512608, 0.046645947, 0.017593225, -0.19447896, -0.024742266, 0.03970127, 0.29845607, -0.16168733, 0.035172883, 0.07924657) * go_3(1.0, 1.0);
result += vec4(0.103826486, 0.045373913, 0.11565896, -0.06568643);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.1851775, 0.053705044, 0.033816848, -0.018555025, -0.21204336, -0.01706974, 0.088259794, -0.13126148, 0.10729598, -0.043457437, 0.08634712, 0.09220895, 0.062131613, -0.01995871, 0.05181067, 0.18520063) * go_0(-1.0, -1.0);
result += mat4(0.1662002, -0.14197104, -0.052809287, 0.025287712, -0.08330898, -0.08998097, -0.15642618, -0.14941245, -0.03481203, 0.061857622, 0.26051775, -0.0005498248, 0.086427025, 0.024108192, -0.12418039, 0.022286376) * go_0(-1.0, 0.0);
result += mat4(0.058200672, -0.3073398, 0.17150162, -0.13394679, -0.075118184, -0.14607768, -0.006172172, 0.007731589, -0.21818224, -0.06449433, -0.038958784, 0.037722416, 0.28699976, -0.027563032, 0.23295315, 0.028444216) * go_0(-1.0, 1.0);
result += mat4(0.12871371, 0.0064904913, 0.14985761, -0.10923005, 0.17413563, 0.1599109, -0.08457703, 0.108153716, -0.08871187, -0.06661137, 0.2754416, -0.009667768, 0.39819396, 0.12392097, 0.14145902, 0.0019376524) * go_0(0.0, -1.0);
result += mat4(0.13893189, 0.12715353, 0.015191678, -0.21003054, -0.030412354, -0.01676613, -0.19799289, -0.006130075, 0.37676954, -0.14475077, -0.2065198, -0.30432892, -0.14944535, -0.09121536, -0.107600585, -0.24462196) * go_0(0.0, 0.0);
result += mat4(-0.11653076, -0.0068671284, -0.02249137, -0.17877012, -0.15063138, -0.13514869, 0.107643366, -0.03196477, -0.086422764, 0.3079287, 0.17584166, -0.032449376, -0.06917114, -0.2682637, -0.18978168, -0.037039287) * go_0(0.0, 1.0);
result += mat4(0.12014731, -0.030360512, -0.12954475, -0.110275604, -0.077214256, 0.019689744, 0.22149551, -0.002266716, 0.09697784, -0.124532826, -0.16776511, -0.034212478, -0.36935154, 0.016926935, 0.1363609, 0.20415346) * go_0(1.0, -1.0);
result += mat4(-0.11199535, -0.001692563, -0.09058429, -0.08437503, 0.092625685, 0.06046257, 0.25509837, -0.011657033, -0.17949764, -0.10718947, -0.1180669, -0.24681842, -0.1747311, 0.0014518246, -0.042863015, 0.06103357) * go_0(1.0, 0.0);
result += mat4(0.14979295, -0.037154514, 0.01957725, 0.012282435, 0.09168596, -0.05552286, 0.111671515, 0.0078630615, -0.10319766, -0.06416261, -0.23097566, -0.13931875, 0.2110811, 0.013095802, -0.2306504, -0.025639111) * go_0(1.0, 1.0);
result += mat4(-0.10091975, -0.10095426, -0.023449723, -0.022170888, 0.054953706, -0.13049407, 0.08289061, 0.023241632, 0.08735388, -0.0058387457, 0.17897247, 0.011434436, 0.008181139, -0.0034718404, -0.015372735, -0.07657766) * go_1(-1.0, -1.0);
result += mat4(-0.023442164, 0.07535702, 0.024391165, -0.050532013, 0.044168636, 0.0062343236, -0.019756999, -0.009695123, 0.10102337, 0.0052776975, -0.14944167, -0.060957722, 0.24367364, -0.08069369, 0.12170072, -0.047048368) * go_1(-1.0, 0.0);
result += mat4(-0.18376935, -0.08407229, -0.12943378, 0.0738419, -0.12404976, -0.13367929, 0.11265896, -0.021353, 0.003783386, 0.50088304, 0.14058582, 0.041053623, 0.038247623, -0.014179976, 0.007905778, -0.042492237) * go_1(-1.0, 1.0);
result += mat4(-0.046272535, 0.052449115, 0.17190954, -0.004745371, -0.045572635, -0.09292636, 0.36309823, 0.16673928, -0.099154025, -0.109614775, 0.17803112, 0.19907133, -0.14306267, 0.06898593, 0.11493454, 0.06795014) * go_1(0.0, -1.0);
result += mat4(0.26181114, -0.044014625, -0.21605036, -0.08646438, 0.21038742, -0.084986, 0.0504626, 0.17514943, -0.25218952, -0.18691514, 0.057650108, 0.08653614, -0.101205684, 0.03176334, 0.18569492, 0.17973189) * go_1(0.0, 0.0);
result += mat4(-0.0339215, 0.20112811, -0.12986277, 0.028961731, -0.056813832, 0.04451147, -0.07827432, -0.0860976, 0.096853435, 0.3483546, -0.35758162, -0.11749375, -0.035918653, 0.06140711, -0.08520154, 0.02418808) * go_1(0.0, 1.0);
result += mat4(-0.09643022, -0.10491069, 0.0068604187, 0.023679713, 0.096521445, -0.29323488, 0.33353668, 0.112864286, -0.1172182, -0.07233183, 0.06607239, 0.08589609, 0.055790007, 0.14396138, -0.14191268, 0.00034840964) * go_1(1.0, -1.0);
result += mat4(0.15357164, -0.038462736, 0.08143956, 0.1744909, 0.40503287, -0.114508316, 0.003937322, 0.2536635, -0.042445306, -0.15622465, 0.09155284, 0.010992155, -0.20646071, 0.022801135, 0.08894491, 0.069300614) * go_1(1.0, 0.0);
result += mat4(-0.12663515, 0.023849454, -0.053604446, 0.12082873, -0.247968, -0.020969635, -0.03831894, -0.014617553, 0.22630337, 0.037801865, 0.052950703, 0.04285706, -0.14487264, 0.20786528, -0.08719664, 0.1752347) * go_1(1.0, 1.0);
result += mat4(-0.073527604, -0.050752833, 0.051830504, 0.32868716, 0.17474994, 0.016937364, -0.08792601, -0.024481766, -0.022229593, 0.030706186, 0.09213566, -0.076506205, 0.073404044, 0.10368055, -0.175889, -0.08453031) * go_2(-1.0, -1.0);
result += mat4(-0.06838216, 0.007698341, 0.063972116, -0.015604406, 0.16135305, 0.18044342, 0.024137018, -0.23326185, 0.13235588, -0.009096587, -0.058368143, -0.077040404, 0.0011419816, -0.09246194, 0.061036937, 0.049564146) * go_2(-1.0, 0.0);
result += mat4(0.023225296, -0.00060856267, -0.07775185, 0.016958566, -0.2641349, -0.08263046, -0.15350416, -0.30203494, 0.113956556, -0.010813236, -0.017738314, -0.13689043, -0.10318342, 0.025793184, -0.010336172, 0.09733422) * go_2(-1.0, 1.0);
result += mat4(-0.04462596, 0.052866418, -0.34754288, 0.05540498, -0.24492586, -0.32016864, 0.18145293, 0.24873725, 0.32388234, -0.034801524, -0.1347588, -0.07565546, 0.015183539, 0.05059595, 0.08090056, 0.05930932) * go_2(0.0, -1.0);
result += mat4(0.045346696, -0.052527856, 0.052270077, 0.13417454, 0.05200045, 0.028119288, 0.005115497, 0.22952151, -0.2158375, 0.12241308, 0.3507457, 0.08616576, 0.07592416, 0.28470486, 0.3432788, 0.24857087) * go_2(0.0, 0.0);
result += mat4(0.21311626, 0.052607164, 0.1248861, 0.20193806, 0.045226507, 0.14512901, -0.15103437, -0.17926466, 0.11657411, -0.32711068, -0.16332194, -0.07793982, -0.21802668, 0.5183869, -0.13567342, 0.07823041) * go_2(0.0, 1.0);
result += mat4(0.00796368, 0.048073012, -0.14537893, -0.021708772, 0.036246423, 0.1062395, 0.12605369, 0.007073524, -0.1572743, 0.07439501, 0.089162275, -0.0039608316, 0.332032, -0.05461242, -0.17615359, -0.10240517) * go_2(1.0, -1.0);
result += mat4(0.20636982, -0.0024615112, -0.10625786, 0.024270926, 0.061810836, -0.13585201, -0.16581286, 0.23549418, 0.01928842, 0.07404979, -0.054449487, 0.04096373, 0.046939734, 0.003980803, 0.02111498, 0.064925276) * go_2(1.0, 0.0);
result += mat4(0.10485388, 0.06850885, -0.11292169, 0.16991565, -0.15282536, 0.124175504, -0.050431166, -0.06689582, -0.00059811946, 0.033696912, 0.11055047, 0.033060126, -0.17472714, 0.0048819613, -0.04478706, -0.1344572) * go_2(1.0, 1.0);
result += mat4(-0.20473132, 0.056477875, 0.059559986, 0.115130566, -0.058425788, -0.035971727, 0.08334707, -0.096510135, -0.23206294, 0.10635798, -0.21575621, -0.07063254, 0.03877511, -0.107549034, 0.22248401, 0.21702304) * go_3(-1.0, -1.0);
result += mat4(-0.02557767, 0.09886609, -0.100499466, 0.16687396, -0.084830604, 0.03150401, -0.049512494, 0.05595696, -0.13193256, -0.08585273, 0.14247662, 0.12290477, -0.07168309, 0.14531752, -0.048359327, 0.27716598) * go_3(-1.0, 0.0);
result += mat4(0.13297586, 0.20674329, 0.14469388, 0.08981846, -0.004231366, -0.02819193, 0.15470329, 0.17299837, 0.113062344, -0.22716297, -0.21754944, -0.00083956274, -0.14160508, 0.1808253, 0.11268379, 0.27335623) * go_3(-1.0, 1.0);
result += mat4(0.07497518, -0.06799594, -0.018158078, -0.00038999433, -0.15169668, -0.06928238, -0.33672288, -0.105485775, 0.33106267, 0.06698315, 0.019718744, -0.06810211, -0.35186404, -0.29145968, -0.056863394, 0.21498048) * go_3(0.0, -1.0);
result += mat4(-0.013215512, -0.24763754, 0.20965266, 0.1068435, -0.13234195, 0.053566497, 0.05061848, -0.28645232, 0.15518288, 0.23247199, 0.017553907, -0.25181335, -0.048030723, -0.06663929, -0.111026704, -0.12663394) * go_3(0.0, 0.0);
result += mat4(-0.010501938, -0.17995767, 0.06010859, 0.050185587, 0.108627126, -0.101203434, 0.07558728, 0.060466755, -0.106942676, -0.35854608, 0.16015992, 0.16823332, -0.06543775, -0.37310675, 0.014043972, -0.18328045) * go_3(0.0, 1.0);
result += mat4(0.09712849, 0.013983463, 0.07291423, 0.031715546, 0.030862397, 0.045510456, -0.22066842, 0.063464865, 0.11721659, -0.10596602, -0.20611264, 0.052158818, -0.3961766, -0.03781582, 0.17633812, 0.1316111) * go_3(1.0, -1.0);
result += mat4(-0.25029674, 0.07153423, -0.35125682, -0.18255402, -0.19569087, 0.00432772, -0.0969035, -0.24648514, -0.0040922165, 0.037500706, -0.038137026, 0.056214277, -0.048258524, 0.03567822, -0.05033007, -0.24696785) * go_3(1.0, 0.0);
result += mat4(-0.03465209, -0.012495964, 0.22782089, 0.012034795, 0.2916752, 0.08264436, 0.15387125, -0.1473455, -0.15614432, 0.05536727, -0.027079755, 0.010725311, -0.03325222, -0.089212805, -0.10559839, -0.19647683) * go_3(1.0, 1.0);
result += vec4(0.0001705175, -0.031081453, 0.010100773, -0.027214011);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf1
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.026301445, -0.021575214, 0.22165509, 0.059994068, 0.03341161, 0.1831188, 0.20342293, 0.110160105, 0.03908121, 0.020673111, 0.07239561, 0.038754333, 0.15266368, 0.16526422, 0.062376205, -0.09759537) * go_0(-1.0, -1.0);
result += mat4(0.19817191, 0.10267733, 0.17744653, 0.23283184, 0.18810122, 0.2708428, -0.12651879, 0.020756349, 0.039632563, -0.22201295, 0.04873703, 0.09159713, 0.13838065, 0.21169297, 0.30816007, 0.044463675) * go_0(-1.0, 0.0);
result += mat4(-0.27859214, 0.07277634, 0.0021458792, 0.0089682285, -0.069680706, 0.090415835, -0.057762265, 0.18703683, -0.03514389, -0.102816254, -0.036509827, 0.038066104, -0.0168311, 0.094478935, 0.04079697, -0.049064912) * go_0(-1.0, 1.0);
result += mat4(-0.20913245, -0.110538535, -0.08584027, -0.1222067, 0.05414807, -0.045247085, 0.07351766, -0.002078549, -0.1270987, -0.10164512, -0.1857815, 0.08845066, -0.03743333, -0.098948084, 0.21244387, 0.10441866) * go_0(0.0, -1.0);
result += mat4(0.015990427, 0.36396438, -0.24094687, 0.30236533, -0.13271736, 0.06057376, -0.19678196, -0.28577125, -0.25427434, -0.08400598, 0.07284403, -0.18552442, -0.16425897, 0.097259276, -0.32386774, -0.2190484) * go_0(0.0, 0.0);
result += mat4(-0.004581924, -0.13954072, -0.122360416, 0.14132866, -0.08529257, -0.013296556, 0.0848472, 0.09336581, 0.10332182, -0.016313016, 0.07103558, 0.032564916, -0.13478759, -0.20207484, 0.12986964, 0.1219679) * go_0(0.0, 1.0);
result += mat4(0.09817874, -0.10573357, 0.100535244, 0.19608764, -0.13303067, 0.024192972, -0.030689823, 0.02574889, 0.051233094, 0.03489235, -0.18465245, -0.06943822, -0.031604882, 0.1519888, 0.09348508, 0.09187296) * go_0(1.0, -1.0);
result += mat4(-0.21365458, -0.23696984, 0.13097638, -0.09435498, 0.16467983, -0.066370346, 0.1269104, -0.095128186, 0.09954892, 0.12489504, -0.43418056, 0.106512725, -0.17860703, -0.07114084, -0.07630834, -0.26642478) * go_0(1.0, 0.0);
result += mat4(-0.009044342, 0.02711196, -0.14873673, 0.015405045, 0.0071443473, -0.025285944, 0.07409282, 0.06338527, 0.0149676185, 0.011741382, -0.2133069, -0.028912885, 0.19420496, 0.039629057, 0.057636812, 0.15214856) * go_0(1.0, 1.0);
result += mat4(0.07629928, 0.25540486, -0.050925937, -0.18136702, 0.02261603, 0.22343902, 0.003270321, 0.10735731, -0.12541203, -0.10208828, 0.012832783, 0.2591262, 0.08122926, -0.009837677, 0.10308358, 0.19236866) * go_1(-1.0, -1.0);
result += mat4(0.0896358, 0.27571487, 0.04406029, -0.047453407, -0.08587119, 0.16366854, 0.20622262, 0.08347545, -0.3501584, -0.28434548, -0.07592983, 0.09098784, 0.07605388, 0.09677056, 0.0015295541, 0.05102585) * go_1(-1.0, 0.0);
result += mat4(0.18255898, 0.18618028, 0.0017002645, -0.013004655, -0.06436534, 0.13967068, 0.063077755, -0.10632303, -0.20803222, -0.028537111, -0.03144366, -0.08555215, 0.05154303, 0.02431626, 0.15246728, -0.013708507) * go_1(-1.0, 1.0);
result += mat4(-0.020998938, -0.05026291, 0.03700117, 0.00830308, -0.1949294, 0.0026698054, -0.034649856, 0.19784226, -0.083901435, -0.069783084, -0.1504053, 0.16595264, -0.07480141, 0.16067508, 0.06010996, -0.021359695) * go_1(0.0, -1.0);
result += mat4(-0.040828142, -0.20158486, 0.034770954, -0.1894161, 0.11665004, 0.29729164, -0.10584386, 0.13165873, -0.18863006, -0.26719162, -0.047613148, -0.12728356, -0.2033613, 0.10550052, 0.20095508, -0.11275811) * go_1(0.0, 0.0);
result += mat4(-0.0785033, -0.1896073, -0.051492307, -0.1694358, 0.1368308, 0.049355216, -0.05707422, 0.079159185, 0.024578957, -0.0923136, 0.089215435, 0.28670043, 0.027932687, 0.06510816, 0.10810999, 0.05990052) * go_1(0.0, 1.0);
result += mat4(0.08135192, 0.0001326522, -0.16098668, -0.18663193, -0.10280192, 0.078255914, 0.047648013, 0.08326376, 0.055962667, 0.06302574, -0.080121025, -0.031820554, -0.019117938, 0.12515336, 0.09794088, -0.03276838) * go_1(1.0, -1.0);
result += mat4(0.280923, 0.24079335, 0.007883573, 0.06270414, 0.3055441, 0.19291803, -0.16041607, 0.14836526, 0.0013885222, 0.04538063, 0.10742898, -0.064491205, 0.048174977, 4.237692e-05, -0.15194727, 0.024381457) * go_1(1.0, 0.0);
result += mat4(-0.0009164131, -0.031949926, 0.0076425644, -0.036870714, -0.0031292974, 0.017726978, -0.20172147, -0.0770472, 0.26379177, 0.108997814, 0.08069395, 0.2126177, 0.012075376, -0.029457828, 0.062730506, -0.15754452) * go_1(1.0, 1.0);
result += mat4(0.09167904, -0.2657421, -0.03443356, 0.03315832, -0.015365421, -0.1029612, -0.108251, 0.04261033, -0.097120754, -0.05616668, -0.09275983, 0.024902184, 0.050058514, -0.013761632, 0.07555132, -0.0046676896) * go_2(-1.0, -1.0);
result += mat4(-0.10743835, -0.0007361781, -0.042085417, -0.08237517, -0.10094376, -0.24007876, 0.13924706, -0.07526801, 0.01158322, 0.15491122, 0.0069442675, -0.004242352, 0.11429785, 0.02994726, -0.11829945, -0.04108612) * go_2(-1.0, 0.0);
result += mat4(0.073622055, -0.064717196, -0.0025231615, 0.13256475, 0.20159899, 0.047977835, -0.10289233, -0.18419135, -0.00888952, 0.059428576, -0.053062655, -0.02730631, 0.14545685, -0.08686949, 0.17454128, 0.035443828) * go_2(-1.0, 1.0);
result += mat4(-0.010146019, 0.06712568, 0.12614638, 0.023590917, 0.025756737, 0.06603747, -0.17108095, -0.06179699, 0.027241204, -0.13196802, 0.043475866, -0.0397495, 0.05306092, 0.035672903, 0.047219284, -0.16680142) * go_2(0.0, -1.0);
result += mat4(0.079427816, -0.06716479, 0.19028603, -0.19694683, -0.061598092, -0.07471188, 0.21170339, 0.30140215, -0.0023369973, 0.04688297, -0.14154115, 0.19283508, 0.1339858, -0.09116279, 0.15305163, 0.029108394) * go_2(0.0, 0.0);
result += mat4(-0.14902157, -0.03339153, -0.08532003, -0.10736339, 0.08702709, 0.07607574, -0.09955836, -0.016585784, -0.030078214, -0.060374748, -0.2854279, 0.02441719, 0.034877967, 0.2099041, 0.11125731, -0.059071556) * go_2(0.0, 1.0);
result += mat4(-0.08436325, 0.06893047, -0.045362443, -0.02237741, -0.07583875, -0.034830183, -0.024008518, -0.2882329, -0.011109783, 0.101859994, 0.091137715, 0.0020565533, -0.044729806, -0.18168025, 0.069466636, 0.04994174) * go_2(1.0, -1.0);
result += mat4(0.11915174, 0.089596465, -0.18965814, 0.015218237, 0.13500094, 0.19921367, -0.008298205, 0.29650384, -0.049439427, -0.27590424, 0.36169067, -0.030582754, 0.02151196, 0.019915426, 0.04543398, 0.16126189) * go_2(1.0, 0.0);
result += mat4(0.1620274, -0.08264547, 0.082442135, -0.0034478644, 0.09888509, -0.0034957859, -0.107241705, -0.17729597, -0.05138647, 0.02052103, -0.019507123, 0.037574988, -0.1694345, 0.17871588, -0.22510391, 0.019049853) * go_2(1.0, 1.0);
result += mat4(-0.10962245, -0.1329873, -0.060855392, 0.025941676, -0.19536193, -0.120365486, -0.04313703, -0.052912965, 0.20854498, 0.08341353, 0.008687068, -0.20432276, 0.15677948, -0.19000018, 0.01821201, -0.041512605) * go_3(-1.0, -1.0);
result += mat4(0.012287526, -0.14180368, -0.098788455, 0.025949089, 0.09442778, 0.2247651, -0.12453263, 0.10435483, 0.274603, 0.06133054, 0.10506106, 0.14727746, -0.048299775, -0.082819685, 0.07319359, -0.047460355) * go_3(-1.0, 0.0);
result += mat4(-0.070726536, -0.034744017, 0.07521428, 0.070649154, -0.05958955, -0.100232825, -0.010651838, 0.045392875, 0.2930271, -0.04952355, 0.3112155, 0.117203265, 0.025166962, 0.11176862, 0.06716659, 0.07175864) * go_3(-1.0, 1.0);
result += mat4(-0.011560962, -0.14032063, -0.17424704, 0.07652749, -0.04220116, 0.052874275, -0.00225693, -0.031843517, -0.07520102, -0.13775803, 0.2449317, 0.069658786, 0.052280303, -0.105218224, 0.03574522, -0.020500354) * go_3(0.0, -1.0);
result += mat4(0.08793712, 0.26712346, 0.08315631, 0.23813692, -0.04439029, 0.031587064, 0.09561177, -0.13380238, -0.24982157, 0.31701845, -0.3875432, 0.10487225, 0.09201869, -0.037252493, -0.006935219, -0.14650282) * go_3(0.0, 0.0);
result += mat4(0.077635325, 0.13732299, -0.071563005, 0.096517466, -0.15051986, -0.111744404, 0.03996857, -0.052670125, -0.1819665, 0.054554947, -0.13774712, -0.20061246, -0.0023742192, 0.15647805, -0.024121126, 0.075497724) * go_3(0.0, 1.0);
result += mat4(0.0073632775, -0.06535298, 0.039895996, 0.20666869, 0.13625242, 0.04823007, -0.07135618, 0.04787906, 0.01383074, 0.15382123, -0.15519714, 0.056721795, 0.061946746, -0.0586851, 0.028934354, -0.02264129) * go_3(1.0, -1.0);
result += mat4(-0.19791882, -0.111910924, -0.010451344, -0.30566537, -0.1416239, -0.14523096, 0.116883226, -0.18241516, 0.2680614, -0.18487626, 0.17472346, 0.08346682, -0.14510359, -0.029229192, -0.005879142, 0.050247498) * go_3(1.0, 0.0);
result += mat4(0.030153519, -0.092469186, -0.022912916, 0.10200855, -0.04237032, -0.05917764, 0.10479645, -0.05619482, -0.18949397, -0.019547248, 0.013868889, -0.1524476, 0.14048979, -0.032521486, 0.1322921, 0.070972025) * go_3(1.0, 1.0);
result += vec4(0.012053958, -4.6962363e-05, 0.0020099226, -0.033494607);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.06738501, 0.034009207, -0.21538448, 0.14296548, 0.12896985, -0.23526315, -0.08848608, 0.019602662, 0.14937137, 0.11353096, 0.11884168, -0.016765572, 0.030985225, 0.046430565, 0.06614828, -0.19202724) * go_0(-1.0, -1.0);
result += mat4(-0.10326068, 0.11014975, 0.17069744, -0.21474148, 0.16761585, 0.13434832, -0.101021074, 0.006307025, 0.07478008, -0.1060066, 0.035315692, 0.033488914, -0.24906659, 0.06269967, 0.11120735, -0.040928528) * go_0(-1.0, 0.0);
result += mat4(0.09334615, 0.057705753, 0.12213245, -0.06402275, 0.30694544, 0.034585163, 0.20345578, 0.07489286, 0.07483618, -0.14240396, 0.034846418, -0.03811241, 0.010882573, 0.13204294, 0.017563924, -0.047203008) * go_0(-1.0, 1.0);
result += mat4(-0.21673942, -0.024010994, -0.10238504, -0.041160326, 0.06838163, -0.20950818, 0.06526309, -0.079094924, 0.02208821, -0.28130978, 0.086275116, -0.089067616, 0.12133826, -0.062600106, -0.020521903, -0.07654401) * go_0(0.0, -1.0);
result += mat4(-0.03055029, -0.15683146, -0.20331301, -0.06252028, 0.13350682, 0.20338707, 0.038425338, 0.1581342, -0.27322498, -0.14999662, -0.16681097, 0.0971585, -0.20014858, -0.081635274, -0.0781877, -0.20625232) * go_0(0.0, 0.0);
result += mat4(0.38375977, -0.019825654, 0.1886721, 0.22616312, 0.3402173, 0.1825304, -0.05531195, 0.30973226, -0.2676023, 0.14413352, 0.021706983, 0.01732799, 0.23466855, -0.13805965, 0.22570935, 0.018103868) * go_0(0.0, 1.0);
result += mat4(-0.15169825, 0.0270689, -0.2503316, 0.17289825, -0.16437647, 0.039233048, -0.35572487, -0.048393793, 0.19270042, 0.24260359, 0.12041881, -0.0009793913, 0.11656858, 0.11007414, -0.0757491, 0.047933612) * go_0(1.0, -1.0);
result += mat4(-0.18657999, -0.11252566, -0.05237504, -0.07368097, 0.13882741, -0.13710637, -0.006996468, -0.062354874, 0.23452504, 0.15333645, -0.0022776406, -0.17910439, 0.03629509, -0.16264829, -0.010011833, -0.15313338) * go_0(1.0, 0.0);
result += mat4(-0.060544558, -0.04913478, -0.061717357, 0.02323648, 0.28739056, -0.07434013, 0.19110644, 0.100050166, 0.0073363045, 0.08185653, -0.024797903, -0.14424153, -0.20838726, 0.16154376, -0.048517212, -0.025453888) * go_0(1.0, 1.0);
result += mat4(0.14975396, -0.13142908, 0.36210674, -0.054021083, -0.10632155, 0.045697935, -0.18946633, 0.02228141, -0.08919603, 0.09800842, -0.17634438, 0.09512711, -0.03425503, -0.12298555, -0.05354435, -0.17112055) * go_1(-1.0, -1.0);
result += mat4(0.09958265, -0.057276618, -0.16262266, -0.06415915, 0.14579074, -0.36784375, 0.08034197, -0.04537706, 0.005460582, 0.22313322, 0.07382161, 0.014990379, 0.044636846, -0.2811128, -0.22621547, -0.06044004) * go_1(-1.0, 0.0);
result += mat4(0.10569276, -0.03738662, 0.16100396, 0.058593616, -0.048862137, -0.08796426, 0.20101094, -0.11039573, 0.17196764, -0.04601554, 0.008571281, -0.073729075, 0.051433694, -0.051276565, 0.087334655, -0.0360379) * go_1(-1.0, 1.0);
result += mat4(0.011119538, -0.28781965, 0.28637868, -0.1742508, -0.07121849, 0.10379717, 0.012615981, -0.029563965, -0.18678424, 0.05291095, 0.039143506, -0.028248642, -0.014103922, 0.029155696, 0.10433492, 0.16305852) * go_1(0.0, -1.0);
result += mat4(-0.2231037, -0.13697462, -0.29124337, 0.08519773, 0.15893684, -0.17763218, 0.06950923, 0.34361118, -0.024844287, 0.044008408, -0.033844844, -0.086971916, -0.07884748, 0.2543499, 0.056884114, 0.10068364) * go_1(0.0, 0.0);
result += mat4(-0.07710048, -0.23218372, 0.04346047, 0.21769643, 0.06473219, -0.18066105, -0.2511205, 0.15309611, 0.04535977, 0.16450433, 0.10846344, 0.0016952346, -0.010874939, 0.28966382, -0.121990964, 0.12956186) * go_1(0.0, 1.0);
result += mat4(-0.007910202, 0.17766511, 0.14364475, 0.1016258, 0.0051045395, 0.18691733, 0.005813767, -0.0070582186, 0.019418601, -0.1604435, 0.016088275, -0.18265302, -0.15719391, -0.17369832, -0.036745597, -0.19647408) * go_1(1.0, -1.0);
result += mat4(0.08938396, -0.0073808245, 0.11225727, -0.012303106, 0.096785046, 0.030483445, 0.027719889, -0.052584838, -0.14887555, -0.03422243, 0.12646855, -0.1722482, 0.010239037, 0.06406088, -0.20053658, 0.01964698) * go_1(1.0, 0.0);
result += mat4(-0.120734036, -0.12450362, -0.06582111, 0.1639675, -0.19787048, -0.08049789, -0.014257596, 0.058436662, -0.0009387449, -0.08698089, -0.017400503, 0.06295286, 0.09890349, -0.057190523, -0.103520766, -0.04207548) * go_1(1.0, 1.0);
result += mat4(-0.0118413875, -0.031288836, 0.09749554, -0.012266401, -0.07998591, 0.22615653, -0.06207416, 0.03257896, -0.076378696, -0.079426095, -0.13968349, -0.15423697, -0.1091681, -0.02893125, -0.032659534, -0.063735925) * go_2(-1.0, -1.0);
result += mat4(0.119372696, 0.013176554, -0.029381052, 0.21919228, 0.045041792, 0.24844484, 0.26363325, 0.08480674, 0.087083444, 0.11984778, -0.088715754, 0.06421046, 0.05225977, -0.05140334, -0.055052705, -0.049854077) * go_2(-1.0, 0.0);
result += mat4(0.0035781674, 0.0861361, -0.07675145, -0.056479637, 0.16973391, -0.12113791, 0.10729832, -0.03773517, 0.058618728, 0.12148276, 0.17260705, -0.06968724, 0.076358154, -0.15307103, 0.17700425, -0.13467014) * go_2(-1.0, 1.0);
result += mat4(-0.02752418, -0.06366472, -0.025610954, 0.0013539721, -0.06465272, 0.0806373, -0.07336035, 0.10114861, 0.0041146413, 0.15878421, -0.044668555, -0.12150811, -0.1071482, -0.05086587, 0.18589285, 0.05065092) * go_2(0.0, -1.0);
result += mat4(0.07200056, 0.021739854, 0.29476613, -0.08475931, 0.15018553, -0.07886365, 0.36336347, -0.020576432, 0.25866082, -0.059272554, 0.054249667, -0.17822553, 0.1755872, 0.3244387, -0.39173844, 0.33894604) * go_2(0.0, 0.0);
result += mat4(-0.11570926, 0.1342677, -0.19511898, 0.0075454637, -0.01890476, -0.14239742, 0.18921931, 0.033990458, 0.31306365, -0.006998358, 0.029190077, -0.005679954, -0.15341778, 0.07766778, -0.25691047, -0.0964161) * go_2(0.0, 1.0);
result += mat4(0.019746238, 0.0021332854, -0.00879096, -0.1338671, -0.0001600663, -0.29465106, 0.0867611, -0.114963025, 0.07874301, -0.012734178, -0.11124061, -0.010926616, -0.04941506, -0.07516841, 0.116663, -0.29018974) * go_2(1.0, -1.0);
result += mat4(-0.01651721, 0.05955898, 0.023618208, 0.098695934, 0.018553663, -0.054378513, 0.1436929, 0.1693743, -0.27483663, 0.029127488, 0.09619316, -0.06109113, -0.08619361, 0.09315214, -0.02478657, 0.18544984) * go_2(1.0, 0.0);
result += mat4(0.09570196, -0.016528936, -0.1559397, 0.14312246, 0.04029428, 0.08773151, -0.043646842, 0.17894371, -0.082413055, 0.0027082344, -0.100171275, 0.01547501, 0.18122818, -0.11933676, 0.26404107, -0.3169703) * go_2(1.0, 1.0);
result += mat4(-0.12073344, 0.08683522, -0.09249099, 0.058786053, -0.14480567, -0.121013954, 0.033335857, 0.009353379, -0.055087596, -0.13002734, 0.08890566, 0.05508963, -0.0075715426, -0.15936922, -0.03968994, -0.1690259) * go_3(-1.0, -1.0);
result += mat4(0.2011206, 0.23898427, 0.23656492, 0.1287573, 0.14850396, 0.40532517, -0.107408255, 0.40119782, 0.099813245, -0.03830304, 0.101520434, -0.026478073, -0.048469637, 0.106440455, 0.056632314, -0.17825997) * go_3(-1.0, 0.0);
result += mat4(-0.076735444, 0.05965795, -0.0052469415, -0.21785147, 0.11887833, 0.067560315, 0.051149055, 0.23626682, -0.1297049, -0.035512198, 0.20352256, -0.025064934, 0.04958706, 0.0454198, 0.0113334535, 0.0417486) * go_3(-1.0, 1.0);
result += mat4(-0.09055751, 0.033915352, -0.21836667, 0.22006813, -0.099022895, 0.11720966, -0.15686816, -0.13586599, -0.094427735, -0.08831514, -0.06182928, 0.09213704, -0.03642064, 0.18129414, -0.012926811, 0.12179882) * go_3(0.0, -1.0);
result += mat4(0.19389409, 0.09512252, 0.14768016, -0.16623649, -0.031052284, -0.026814984, 0.106168024, -0.2026781, -0.04581419, -0.0016849053, -0.04101923, 0.038959503, -0.011938445, 0.20096186, -0.26666564, 0.4824324) * go_3(0.0, 0.0);
result += mat4(0.17727576, 0.07309147, 0.12131863, -0.163096, 0.17225246, 0.26256254, 0.27685758, 0.09094053, 0.029605515, -0.20217367, 0.047564875, 0.043115832, 0.15089568, -0.09670934, 0.24131384, 0.03337442) * go_3(0.0, 1.0);
result += mat4(-0.34192136, 0.12063195, -0.31159517, 0.04170889, -0.30147067, -0.21330686, -0.1514457, -0.121126845, 0.04409098, 9.2206596e-05, 0.027680017, 0.03230512, -0.27993527, -0.093485355, 0.07568645, -0.23585452) * go_3(1.0, -1.0);
result += mat4(0.0537712, -0.20847629, 0.1740093, -0.013894753, -0.32719997, -0.059484575, -0.006098233, -0.10336451, -0.14706188, -0.07424865, -0.07045905, 0.17093194, -0.22147557, 0.09086218, -0.11033544, -0.05306482) * go_3(1.0, 0.0);
result += mat4(0.00489003, -0.11509064, -0.021005848, 0.16637677, -0.089347586, 0.17545725, -0.17313693, 0.13742085, -0.14577347, 0.07951095, -0.092139855, 0.017118992, -0.053472433, 0.079414465, 0.0330263, -0.11189824) * go_3(1.0, 1.0);
result += vec4(-0.034743138, 0.012946433, -0.082333155, 0.07721756);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf1
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.25835788, 0.050451655, -0.1845038, -0.07232528, 0.1323318, 0.26276684, 0.10842882, -0.083056524, 0.17426784, -0.3594826, 0.2728965, 0.08388844, -0.004007842, 0.020535901, -0.051425606, 0.07750436) * go_0(-1.0, -1.0);
result += mat4(-0.11410436, 0.014572361, -0.27057216, -0.023974562, 0.05234827, 0.15328228, -0.17502303, -0.3199359, 0.12188045, -0.095813684, 0.024145132, 0.0856916, -0.027453909, -0.043129764, 0.16971985, 0.021623038) * go_0(-1.0, 0.0);
result += mat4(0.06611095, 0.038625732, -0.13717118, -0.04497733, 0.15213469, 0.04770935, 0.0729271, -0.062052976, 0.004571303, 0.035141192, -0.059409596, 0.044652313, 0.17520894, 0.09665589, -0.1479193, 0.06528058) * go_0(-1.0, 1.0);
result += mat4(-0.1845968, 0.091479465, -0.09394898, -0.13545018, -0.029501775, -0.21426639, 0.09255898, 0.1257644, 0.20256902, 0.06267267, 0.10378081, 0.13494423, 0.058310498, 0.03642236, -0.16268995, -0.048100803) * go_0(0.0, -1.0);
result += mat4(0.2155119, -0.3683131, 0.049449228, -0.20559964, -0.11761922, -0.2518804, -0.020712897, 0.12895772, -0.07543782, 0.5805017, -0.11301444, -0.038493153, -0.06710986, -0.09321189, 0.108671665, -0.03259695) * go_0(0.0, 0.0);
result += mat4(0.035307787, 0.108389005, -0.27493554, 0.27029404, 0.25523573, -0.28636125, -0.20766719, -0.008661457, -0.004480811, -0.046390545, -0.16221444, 0.008979624, -0.061375532, 0.035076566, -0.018924266, 0.01380219) * go_0(0.0, 1.0);
result += mat4(-0.051922515, -0.12463486, -0.10383422, 0.02220095, -0.1573033, 0.13980615, 0.13248625, -0.16803266, -0.0692132, -0.21552645, 0.13744529, 0.23034313, 0.0052666534, 0.028977966, 0.07720251, -0.06477756) * go_0(1.0, -1.0);
result += mat4(-0.14097473, 0.2770271, -0.172289, -0.03000696, -0.028684044, 0.040578447, -0.2290285, 0.082329154, -0.042402364, -0.20926563, 0.08233207, 0.11862443, -0.07038536, -0.02273004, 0.091550544, -0.065856494) * go_0(1.0, 0.0);
result += mat4(0.14879914, -0.023923844, -0.23569296, 0.20306346, 0.17502785, 0.28776234, -0.2788995, 0.10012439, -0.05635638, -0.025840463, 0.09222198, 0.118032, 0.08057015, 0.1286071, 0.060189806, -0.052669708) * go_0(1.0, 1.0);
result += mat4(0.07076086, -0.15111323, -0.07427972, 0.008372168, -0.17791592, -0.16254742, 0.013961132, -0.0944912, -0.23380096, 0.17377278, -0.09683394, 0.019931393, -0.12042098, 0.0016406325, 0.09393333, -0.06882231) * go_1(-1.0, -1.0);
result += mat4(0.21465093, 0.04142968, 0.06840044, -0.37831602, -0.05549571, 0.044905066, -0.07873589, -0.026804, -0.34764197, 0.022487951, -0.077293746, 0.089457795, -0.110094436, 0.24233972, 0.06285107, -0.10851744) * go_1(-1.0, 0.0);
result += mat4(0.093270175, 0.084138945, 0.03938272, 0.063565865, -0.010733802, 0.13554469, -0.06650261, 0.033002816, 0.011187271, -0.12821455, 0.20785914, -0.030438649, -0.124710515, -0.022294303, 0.09732408, 0.057609864) * go_1(-1.0, 1.0);
result += mat4(-0.12833868, 0.021577539, -0.02700365, 0.11799592, -0.03655647, -0.04225167, 0.11049353, -0.16036157, 0.049277548, -0.033842396, 0.10020137, 0.095509745, 0.08060231, -0.09237418, -0.035598125, -0.035926737) * go_1(0.0, -1.0);
result += mat4(-0.32829186, 0.3492363, 0.030671779, -0.12606762, 0.010437313, 0.2757115, -0.21517593, -0.15800527, -0.12592544, -0.20578934, 0.10444053, 0.12993255, -0.046079267, 0.03834173, -0.19277227, -0.22124454) * go_1(0.0, 0.0);
result += mat4(-0.052546192, 0.026082167, 0.13831234, 0.10982424, 0.012946818, -0.12439852, 0.10134106, -0.10050398, -0.04472338, -0.14325236, -0.20579574, 0.0044005127, 0.22013672, -0.32955512, 0.12404084, -0.008160738) * go_1(0.0, 1.0);
result += mat4(-0.10774314, -0.31650826, -0.06601711, 0.19635755, -0.12622592, -0.06396423, 0.13856032, 0.16540553, 0.021387719, 0.23377723, -0.053738154, -0.1000186, -0.08338395, -0.052813534, 0.008122962, 0.13732094) * go_1(1.0, -1.0);
result += mat4(-0.18270823, 0.06966014, -0.17788303, -0.27303055, -0.077971615, 0.013978423, -0.02039098, 0.12715338, -0.11924171, 0.18900296, -0.085199654, 0.215198, 0.18587974, -0.009749325, 0.0173584, -0.12018259) * go_1(1.0, 0.0);
result += mat4(0.052129295, -0.107416354, 0.12711766, 0.03708665, -0.14369462, -0.055359814, -0.16639823, -0.045143317, -0.06925672, -0.040696755, 0.01999809, -0.016040625, -0.02484878, 0.07417094, 0.050875198, 0.2145528) * go_1(1.0, 1.0);
result += mat4(0.055696912, -0.16680926, -0.021987487, 0.024941636, -0.0927883, 0.022136632, 0.033782948, -0.10646058, -0.14944647, 0.25457275, 0.046682496, -0.022462368, -0.07886781, 0.08165927, 0.06848105, 0.0063734027) * go_2(-1.0, -1.0);
result += mat4(0.037053242, 0.033215813, 0.18291366, 0.12340375, 0.08491059, -0.28442004, -0.0127422465, -0.039834313, -0.23321372, 0.26676926, -0.05636355, -0.15672484, -0.12891728, -0.15486577, -0.032004442, -0.092745155) * go_2(-1.0, 0.0);
result += mat4(0.015779478, -0.18457565, 0.24996394, 0.036197674, 0.15694007, 0.15863103, -0.07332398, 0.0016235278, -0.15536517, -0.056062788, 0.14102836, 0.16915025, -0.08001087, 0.07073164, 0.13796777, 0.123867124) * go_2(-1.0, 1.0);
result += mat4(0.045792986, -0.15135059, -0.1354885, -0.043678258, -0.35655212, 0.51232076, -0.12816145, -0.046569496, -0.014127674, -0.06282611, -0.098873, -0.06359104, -0.0919222, 0.11822437, 0.079254694, 0.00579688) * go_2(0.0, -1.0);
result += mat4(-0.15683417, 0.61610246, -0.3024612, 0.12917964, -0.09303367, 0.23612969, -0.40842506, -0.12374661, -0.07572449, -0.2613284, -0.09970177, -0.015227848, 0.106239066, -0.21411185, 0.051998455, -0.1364518) * go_2(0.0, 0.0);
result += mat4(0.23850034, -0.14394449, -0.0031468747, -0.2380617, -0.027200876, -0.041352056, -0.01864445, 0.033848196, -0.12064239, -0.110480845, 0.08450956, -0.22328654, 0.17664163, 0.22268307, 0.050886698, -0.17475672) * go_2(0.0, 1.0);
result += mat4(-0.17808256, 0.010803805, 0.03315186, 0.033143792, -0.14205995, 0.25039625, -0.08784382, -0.13454252, 0.19576813, 0.10755282, 0.22821628, 0.019456752, -0.0422955, -0.016182603, -0.12066697, 0.0548465) * go_2(1.0, -1.0);
result += mat4(0.11563777, -0.257929, 0.0010403778, 0.080267854, -0.0025255163, 0.2855168, -0.060352214, -0.07816255, -0.00090574916, 0.049510725, 0.03720483, 0.059250016, -0.08674136, 0.20522198, -0.28694284, 0.1299507) * go_2(1.0, 0.0);
result += mat4(-0.14638457, 0.04063328, 0.03139636, -0.007934521, 0.07689684, -0.09467145, 0.10607347, 0.054510128, 0.003306194, 0.05347124, 0.062762424, -0.041480847, -0.07677865, -0.139573, 0.010972524, 0.21957156) * go_2(1.0, 1.0);
result += mat4(-0.026845628, -0.043439507, 0.034738723, 0.07281683, 0.14474197, 0.031586993, -0.22767854, -0.0707655, 0.105201736, -0.28805482, 0.008668302, -0.16329518, 0.06157049, 0.3803886, 0.26345953, -0.011096537) * go_3(-1.0, -1.0);
result += mat4(-0.23328833, 0.085731484, -0.07755016, 0.33559516, 0.07704345, 0.115106605, -0.24114038, -0.44630137, 0.2726737, -0.32170138, -0.009236524, -0.11666051, 0.0457048, 0.07876708, 0.13134004, -0.035318643) * go_3(-1.0, 0.0);
result += mat4(-0.05140272, 0.011605703, 0.13899171, -0.05071015, 0.18413687, -0.31413674, -0.13043414, -0.15118152, -0.15326938, -0.10720126, -0.23738635, 0.13481396, 0.25115076, -0.009316611, -0.2584441, -0.14389823) * go_3(-1.0, 1.0);
result += mat4(-0.039723795, -0.14869407, -0.1692942, 0.026501274, -0.10685166, -0.121267825, -0.08584318, -0.09580693, -0.10626739, -0.068417974, 0.11321909, -0.13664317, 0.061380867, -0.2587898, 0.14850819, 0.008178645) * go_3(0.0, -1.0);
result += mat4(0.06912782, 0.24230564, -0.048150286, 0.2203717, -0.17417085, 0.105546735, -0.16648416, -0.0045053074, 0.09764028, 0.37122592, -0.1939995, -0.27899942, -0.088152565, -0.53869057, 0.21676709, -0.08056594) * go_3(0.0, 0.0);
result += mat4(0.07651754, 0.03704878, -0.0197015, 0.1660726, 0.07002748, -0.11820414, -0.23360898, 0.1481592, 0.029847002, 0.054057185, 0.013176299, 0.06552942, -0.13865773, -0.20105527, -0.37550658, 0.005769631) * go_3(0.0, 1.0);
result += mat4(-0.22697811, -0.17426412, 0.10148018, 0.008134666, 0.10771455, 0.16943407, -0.016319012, -0.40176705, -0.06854668, -0.049045276, 0.20919096, 0.13240765, -0.050125647, 0.14902508, 0.052697595, -0.13817468) * go_3(1.0, -1.0);
result += mat4(0.04301619, 0.23184754, -0.023551717, 0.3768405, 0.028999053, 0.06709736, -0.05993663, -0.059861984, 0.15499207, -0.22217415, 0.111131504, -0.09082529, -0.19389243, 0.024621522, -0.15305442, 0.010799284) * go_3(1.0, 0.0);
result += mat4(-0.035496738, 0.010802548, -0.028718363, 0.19263634, 0.16900502, -0.16661702, -0.027631328, 0.18309957, -0.015860107, -0.03309961, -0.091390446, 0.14000848, -0.0036591904, 0.47659522, -0.09373507, -0.29020965) * go_3(1.0, 1.0);
result += vec4(0.08895955, -0.027667087, 0.20500831, 0.00037762933);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.018134737, -0.2296755, -0.07276725, -0.029795367, 0.05382051, 0.092847414, -0.024469728, -0.1674685, 0.0017946451, 0.30074653, 0.0034195695, -0.04892261, 0.18229689, -0.20116119, -0.12702174, -0.08259108) * go_0(-1.0, -1.0);
result += mat4(-0.1357695, -0.08149211, 0.09314453, -0.21966846, 0.34740716, 0.043606415, 0.04225903, 0.034449834, 0.17248215, 0.39148283, -0.13868807, -0.010550686, 0.044238456, -0.09693464, -0.005044985, 0.24383289) * go_0(-1.0, 0.0);
result += mat4(0.19959371, 0.098685324, 0.058746945, 0.010580748, 0.08051514, 0.031898864, 0.017556064, 0.13004355, -0.01727653, 0.11044019, 0.040673427, -0.20064595, -0.23321067, 0.06398686, -0.19126236, -0.2430858) * go_0(-1.0, 1.0);
result += mat4(-0.12870286, -0.113455534, 0.23722827, 0.070718594, 0.19049989, -0.1927299, -0.06343845, 0.113127775, 0.082530305, -0.10972526, -0.090779535, 0.05731582, 0.11018802, -0.18049154, 0.09269507, -0.10304576) * go_0(0.0, -1.0);
result += mat4(0.15513484, 0.06659583, 0.08125296, -0.012350324, -0.09492788, 0.5048303, 0.13206847, 0.39554298, 0.28953737, -0.20913891, -0.26781562, -0.17539899, 0.023778774, 0.29716817, 0.15768486, 0.37702608) * go_0(0.0, 0.0);
result += mat4(0.0724462, 0.015571356, -0.032217246, 0.0050658924, -0.22708446, 0.03968809, 0.016753826, 0.0025668752, -0.055932112, 0.113931604, 0.19766758, -0.030027265, -0.17384295, 0.15013468, -0.0070017707, -0.09469028) * go_0(0.0, 1.0);
result += mat4(-0.078361556, -0.0954201, -0.006358101, 0.040500037, 0.4190454, -0.17622913, -0.07234791, 0.05462559, 0.18641087, 0.058313597, -0.0180785, 0.13818781, -0.14640772, 0.0699486, 0.0073663946, -0.076789856) * go_0(1.0, -1.0);
result += mat4(-0.21421191, 0.08736062, 0.09041226, 0.03608585, 0.02769972, 0.09641289, 0.11824623, 0.05653645, 0.16464607, 0.19839554, -0.13379547, 0.054417104, 0.067530684, 0.18971571, 0.13785432, -0.097639814) * go_0(1.0, 0.0);
result += mat4(-0.32658005, -0.14606023, -0.069448665, 0.032998275, -0.28331423, 0.0011900732, -0.020304207, -0.13535896, 0.08298347, 0.045509677, -0.030503955, -0.037504148, 0.049955815, 0.0925771, 0.00058534974, -0.12398032) * go_0(1.0, 1.0);
result += mat4(-0.2955836, 0.29059318, -0.018196672, -0.35866606, -0.01309431, 0.03540315, 0.010609202, 0.11956812, 0.10296229, 0.22536302, 0.015201129, -0.23797737, -0.16960852, -0.11414787, -0.034440614, 0.112644605) * go_1(-1.0, -1.0);
result += mat4(-0.14952518, 0.07024436, -0.083184876, -0.0814617, -0.13303639, 0.016159372, -0.13521518, 0.2221334, -0.056617837, 0.12958299, 0.064461656, -0.20146395, -0.16023181, 0.2640758, 0.27528805, -0.1426518) * go_1(-1.0, 0.0);
result += mat4(-0.04382363, 0.09856003, -0.08561442, -0.15699928, -0.121069774, 0.04685383, -0.009170197, -0.031489655, 0.18730178, 0.238442, 0.22497098, 0.032015145, -0.03709115, 0.1535079, 0.21674158, 0.10678019) * go_1(-1.0, 1.0);
result += mat4(-0.12200952, 0.24224263, 0.034097504, -0.028179523, -0.011962496, -0.04489487, -0.05198827, 0.22194928, -0.045400873, -0.049828544, 0.111477956, -0.098361604, 0.12788995, -0.016093334, -0.19886433, -0.011161484) * go_1(0.0, -1.0);
result += mat4(0.30563712, 0.013071727, -0.004799883, 0.12888052, -0.259498, -0.041566677, 0.07311124, 0.162324, 0.28371668, -0.004693743, -0.0019395344, 0.029358242, 0.08730285, 0.12184509, 0.05508437, 0.048439097) * go_1(0.0, 0.0);
result += mat4(0.12760857, 0.115813166, -0.217695, -0.10629871, -0.227366, 0.09030426, -0.15313712, 0.020528946, -0.20743734, 0.088583544, 0.04594053, -0.22891994, 0.18949282, -0.042186577, -0.17330512, -0.010711361) * go_1(0.0, 1.0);
result += mat4(0.029503195, 0.0063797613, -0.17004286, -0.096844055, 0.010218098, 0.04247233, 0.02362808, 0.14700809, -0.08082364, 0.11159672, -0.018505255, -0.15228583, 0.15693732, -0.025359154, 0.024829186, 0.1943192) * go_1(1.0, -1.0);
result += mat4(-0.03912932, -0.21989027, 0.12203028, 0.18702275, -0.118537985, 0.21039696, 0.09102061, 0.012288879, 0.031666897, 0.1318455, -0.04901404, -0.07516063, -0.44782668, 0.04884501, 0.047070876, 0.008728358) * go_1(1.0, 0.0);
result += mat4(-0.08669101, 0.3053463, -0.08963947, 0.0034188698, -0.070004664, 0.064788476, 0.093737036, 0.070050925, 0.12728429, -0.13179256, -0.014913502, 0.09308136, -0.027638942, 0.008638711, 0.08794172, -0.05531093) * go_1(1.0, 1.0);
result += mat4(0.0728421, 0.07872358, 0.11454748, 0.08497922, 0.071820416, -0.11789207, -0.08184197, 0.1359588, -0.2143346, -0.05876081, 0.023172129, -0.08430511, -0.19276723, 0.14283359, 0.15604696, -0.055187486) * go_2(-1.0, -1.0);
result += mat4(0.068641685, 0.2732106, -0.2809107, 0.12736696, -0.08642367, 0.023898933, -0.17859498, -0.18299665, -0.06684587, -0.12204666, 0.45898953, -0.24240111, 0.25182098, -0.04395751, 0.10637211, -0.22135144) * go_2(-1.0, 0.0);
result += mat4(0.0852072, 0.051133018, 0.03333165, -0.0008938216, 0.10251267, 0.0550774, 0.041769378, -0.21259712, 0.286912, 0.123342015, 0.282759, -0.0730124, 0.14275575, -0.15580742, -0.15224406, 0.045376908) * go_2(-1.0, 1.0);
result += mat4(0.03328225, 0.11563978, -0.07451964, 0.030546209, -0.04698351, -0.18544962, 0.037350416, 0.13969816, 0.0556746, -0.06359919, 0.06478219, -0.031694926, 0.13396506, 0.09443612, -0.01922686, -0.06290365) * go_2(0.0, -1.0);
result += mat4(0.07495407, 0.063429266, -0.106221214, -0.085107304, 0.2497817, -0.46598253, -0.18833177, -0.2731128, -0.13024822, 0.56053543, 0.055704467, -0.12331414, -0.031199086, 0.05061188, 0.22097112, -0.6611177) * go_2(0.0, 0.0);
result += mat4(0.08276988, -0.044184342, -0.03562185, -0.06159881, 0.27694225, -0.07192965, -0.08663714, 0.020221777, 0.14095962, -0.06229397, 0.051374253, -0.038158998, 0.10664802, -0.041305423, 0.051260717, -0.054698635) * go_2(0.0, 1.0);
result += mat4(0.12800686, 0.03485072, 0.039914366, 0.034041498, -0.08305794, -0.046292894, 0.22765331, 0.10904922, 0.0013937047, -0.08750301, 0.009126207, -0.065589435, 0.2837707, 0.08884436, -0.07234862, -0.093502745) * go_2(1.0, -1.0);
result += mat4(0.113439895, 0.06081726, 0.1122302, -0.022936966, 0.10329637, -0.31816107, -0.051597945, 0.23846027, -0.083913095, -0.29872265, -0.040147282, -0.08981918, -0.04329814, -0.12339693, -0.034489952, 0.013393211) * go_2(1.0, 0.0);
result += mat4(0.33091688, 0.1726297, 0.034332044, -0.091396205, 0.15434311, -0.0022870845, -0.15506189, 0.08710491, -0.16063525, 0.042252056, 0.017086457, 0.08134797, 0.08631321, 0.037843138, 0.088296555, 0.0064518084) * go_2(1.0, 1.0);
result += mat4(0.09161051, 0.114355795, -0.15304486, -0.030537153, 0.1835368, -0.3287635, 0.031197926, 0.09717476, 0.04276852, 0.113250345, 0.05949038, -0.10599563, 0.43574792, -0.060788117, 0.18409383, 0.12678055) * go_3(-1.0, -1.0);
result += mat4(-0.018356865, -0.0072578182, 0.12020777, -0.013127592, 0.20136636, -0.22984362, 0.06896224, 0.00044982752, 0.008428429, -0.123316936, -0.09989286, 0.078248784, -0.16313677, -0.003020313, -0.46285018, -0.08967125) * go_3(-1.0, 0.0);
result += mat4(-0.03451497, -0.10864502, 0.13207638, 0.17194521, 0.0037514758, -0.20222199, -0.12535086, 0.001511977, 0.056294486, -0.2112898, 0.078261316, 0.10118746, -0.044742294, 0.21793383, -0.19927903, -0.21338293) * go_3(-1.0, 1.0);
result += mat4(-0.034903776, -0.10167085, 0.031066334, 0.0379958, 0.20532596, -0.17457838, 0.16556816, -0.0021619152, 0.02682665, 0.03396325, -0.059273884, 0.1922813, -0.072151475, -0.010240544, 0.2302027, 0.12385962) * go_3(0.0, -1.0);
result += mat4(-0.20170145, -0.08203941, -0.028107846, -0.18003726, 0.44744352, -0.13190243, 0.13233365, 0.03626546, 0.085763134, -0.25613126, -0.11213388, 0.15529087, -0.271649, 0.050587676, -0.062583975, 0.057289865) * go_3(0.0, 0.0);
result += mat4(-0.040649455, -0.17949733, 0.35847965, -0.040587306, 0.24314344, -0.23811667, 0.13958354, 0.04961874, 0.09858903, -0.04202913, -0.21850993, 0.0700419, -0.09130745, -0.096835814, 0.0022782686, -0.25416258) * go_3(0.0, 1.0);
result += mat4(-0.08215545, -0.019647893, 0.055263475, 0.053733055, 0.098485716, -0.1041945, -0.06541415, -0.08868577, -0.07262986, 0.03513784, -0.110529095, -0.03369232, 0.056786604, 0.2569229, -0.05931065, -0.22081214) * go_3(1.0, -1.0);
result += mat4(0.066926084, 0.029664058, -0.10779271, 0.11026963, 0.23927264, -0.16914488, 0.022947345, 0.12303853, -0.07066212, -0.013205378, 0.15348643, 0.035568032, 0.20966691, 0.010149819, -0.08814468, -0.064854674) * go_3(1.0, 0.0);
result += mat4(0.11493852, -0.074924305, -0.14840698, -0.16956823, 0.056806292, -0.06387947, -0.06880271, -0.04637334, -0.1929893, 0.18226422, 0.064644486, -0.1594863, 0.027403917, 0.13951495, -0.06569123, -0.07700207) * go_3(1.0, 1.0);
result += vec4(-0.043347504, -0.20504741, -0.037821215, -0.014486937);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf1
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.047881734, -0.09396414, -0.2839081, 0.3140853, 0.052613556, 0.09940423, 0.23960467, -0.022228222, -0.12065009, 0.07898222, 0.08657881, 0.010852739, -0.050450284, 0.01683982, 0.031813968, 0.053060856) * go_0(-1.0, -1.0);
result += mat4(-0.10252411, -0.03116448, -0.30114275, -0.0316799, -0.017501019, -0.03006003, -0.2095696, 0.10134927, -0.3901916, -0.15335023, -0.11955071, 0.1337449, 0.101239376, -0.25044814, 0.2128469, 0.018979514) * go_0(-1.0, 0.0);
result += mat4(-0.13392173, 0.052036732, 0.1682114, -0.026263753, 0.027221246, -0.15121374, 0.13723798, 0.08950682, -0.1182108, -0.07294226, 0.023392374, 0.052329235, -0.05632852, -0.07036173, 0.06872573, 0.05238042) * go_0(-1.0, 1.0);
result += mat4(0.18112028, 0.18242362, -0.06812871, 0.032463413, 0.124638766, -0.26765212, -0.07678663, 0.33806562, 0.09674393, 0.15574542, 0.23634006, -0.02873782, -0.1626769, -0.14760062, -0.007274849, 0.09866139) * go_0(0.0, -1.0);
result += mat4(-0.10726673, -0.10925056, 0.19967109, -0.19936769, 0.15942842, -0.14870064, 0.15493345, -0.08489036, -0.49053356, -0.17321263, 0.28426084, 0.18721215, -0.09898434, -0.2751838, -0.11833524, 0.028445128) * go_0(0.0, 0.0);
result += mat4(-0.11788817, -0.23724948, -0.046072144, 0.035621114, 0.04527003, -0.0073492974, 0.11097195, 0.06806836, 0.04814677, -0.1408476, -0.1325629, 0.00929532, -0.16699041, -0.03034791, 0.08320368, -0.15429299) * go_0(0.0, 1.0);
result += mat4(0.2729515, 0.008244692, -0.17441982, -0.39026466, 0.17381759, 0.31194404, 0.055934936, 0.20744409, 0.20119062, 0.0734271, 0.0796807, 0.0031037466, -0.0016392237, 0.033733975, 0.07149338, 0.042083208) * go_0(1.0, -1.0);
result += mat4(0.07985744, 0.10945015, 0.018472541, 0.1397503, 0.2005682, 0.42641, 0.23022486, -0.2916921, 0.028285174, -0.31885162, -0.27070364, -0.10390779, 0.0751492, 0.12752363, -0.2279459, 0.08998453) * go_0(1.0, 0.0);
result += mat4(0.18450491, -0.140783, -0.008006845, 0.09029298, 0.12536179, 0.26949662, 0.09491545, 0.063907005, 0.11212244, 0.09778506, -0.1835966, -0.053119674, 0.0072294096, 0.25018227, 0.010868525, -0.22721334) * go_0(1.0, 1.0);
result += mat4(-0.028011927, -0.20073172, 0.5976166, -0.19494139, 0.17958745, -0.03838646, 0.058325976, -0.29409218, -0.12793432, 0.03245129, 0.35662368, -0.05048354, -0.13368197, -0.06151968, -0.012714591, -0.1763054) * go_1(-1.0, -1.0);
result += mat4(0.18468465, 0.31682113, 0.12818255, -0.117110476, 0.13709468, -0.10034022, -0.07994527, -0.1259309, 0.04067299, -0.1147398, 0.28361055, 0.27916273, 0.03696692, 0.16829546, 0.27819383, 0.08305029) * go_1(-1.0, 0.0);
result += mat4(-0.28920117, -0.033877946, 0.01586206, 0.04681198, 0.024248574, -0.045777842, -0.03342128, 0.07525412, -0.063377544, -0.016737273, 0.11235511, -0.04325238, -0.24170023, -0.09993599, -0.03205371, 0.14339828) * go_1(-1.0, 1.0);
result += mat4(-0.008357902, -0.11038377, 0.03709221, 0.26775306, 0.07963845, -0.25377446, -0.17630441, -0.10966474, 0.057311732, -0.083327, 0.044497233, 0.06903858, -0.26531395, -0.103399664, -0.14806591, 0.269314) * go_1(0.0, -1.0);
result += mat4(0.05450808, -0.041993964, -0.07217651, 0.034468375, 0.2117634, 0.0075620585, 0.05825411, -0.2252478, -0.0527787, 0.049732126, -0.032040413, -0.09361454, 0.29585132, 0.018413153, 0.18384546, -0.024226356) * go_1(0.0, 0.0);
result += mat4(-0.031109914, 0.19351351, 0.07405522, -0.06313074, -0.09983541, -0.011495182, 0.11749038, -0.16775608, 0.2790974, -0.09338754, 0.07913264, 0.103792936, -0.18679164, -0.15639925, 0.112943865, 0.07930375) * go_1(0.0, 1.0);
result += mat4(0.004106195, -0.036833283, 0.12908752, 0.12869535, -0.02472107, 0.17561707, -0.025890926, -0.18789047, 0.096218705, -0.16306408, -0.02198454, -0.010134957, -0.09710009, 0.002062143, -0.046785697, 0.0029441968) * go_1(1.0, -1.0);
result += mat4(0.19648251, -0.015663045, -0.0730215, 0.028611008, 0.13529862, -0.015256192, -0.04119306, -0.24628192, 0.02601027, -0.21184283, -0.1962902, 0.09109358, -0.06792383, 0.092336476, 0.12215351, -0.08596062) * go_1(1.0, 0.0);
result += mat4(-0.17530201, -0.0351919, -0.31872514, -0.13933206, -0.07000922, -0.049807087, 0.0010997375, -0.033573963, 0.07442056, -0.33290103, -0.40381998, 0.09435, -0.3280128, -0.09953127, -0.11283648, 0.20685865) * go_1(1.0, 1.0);
result += mat4(-0.052573867, -0.035328753, -0.11132943, -0.17515652, 0.05021051, 0.058642425, -0.046640664, 0.0799107, -0.027398815, -0.33619994, -0.22135767, 0.07894002, -0.14941697, -0.0940996, -0.11655085, 0.049795926) * go_2(-1.0, -1.0);
result += mat4(-0.039301276, 0.041062318, 0.20312686, -0.009338705, 0.013706282, -0.0245852, 0.03458311, 0.09601228, -0.18203016, -0.012260314, 0.17984508, -0.056576703, -0.102844186, 0.24047872, 0.05307189, 0.16066082) * go_2(-1.0, 0.0);
result += mat4(0.1478775, 0.0046362123, 0.05459521, 0.07162838, -0.01896149, 0.23700175, -0.14174299, 0.06988599, -0.32545477, -0.08065096, -0.061227743, -0.0010796773, 0.094327345, -0.20760082, -0.19523263, 0.19859222) * go_2(-1.0, 1.0);
result += mat4(-0.049676366, -0.10381536, 0.02546116, -0.13127093, 0.10954914, 0.0048147943, 0.06962328, -0.30456528, -0.11956627, 0.0150488885, -0.10711722, 0.1684613, -0.1939089, -0.10577047, -0.11980919, -0.036988296) * go_2(0.0, -1.0);
result += mat4(-0.054795764, 0.09491116, -0.08494948, 0.059765853, 0.0131597435, 0.20786162, 0.11999637, 0.024381055, 0.22830428, 0.027053319, -0.011646274, -0.12145409, -0.07899559, -0.012688263, 0.10684157, 0.3824219) * go_2(0.0, 0.0);
result += mat4(-0.23994572, -0.0031532666, -0.0050638164, 0.14236279, 0.05690383, -0.06259682, 0.052624144, 0.20461404, -0.19230312, -0.11072268, 0.013023965, 0.08931543, -0.21997221, 0.11760443, -0.40943825, 0.28656834) * go_2(0.0, 1.0);
result += mat4(-0.06606179, 0.26007771, 0.033754125, 0.119690455, 0.024669139, -0.06752839, 0.12688096, -0.0063201943, -0.17123021, 0.07548857, -0.14213699, 0.034093797, -0.15632647, -0.123243414, -0.42634043, 0.1715022) * go_2(1.0, -1.0);
result += mat4(-0.046503466, 0.13876389, 0.17973013, -0.25938338, -0.18824704, -0.11876702, 0.31065792, -0.041042212, -0.061369427, 0.2057992, 0.17295738, 0.3836555, -0.21109799, -0.10167118, 0.16577047, 0.113483034) * go_2(1.0, 0.0);
result += mat4(-0.24534856, -0.014482421, 0.22515748, -0.12773542, 0.12794174, -0.02528619, 0.41710484, 0.09154934, -0.17805946, -0.25428918, 0.07294183, 0.047079418, -0.30949152, -0.08919157, 0.17888431, 0.17706038) * go_2(1.0, 1.0);
result += mat4(-0.1741826, 0.046225294, -0.10761791, 0.2619953, 0.007373745, 0.05104337, -0.22309966, 0.34529984, -0.034363825, -0.022187237, -0.08609555, 0.16842419, 0.28136057, 0.17843607, -0.11307746, -0.05668021) * go_3(-1.0, -1.0);
result += mat4(-0.12310616, -0.29661375, -0.10581025, -0.049584012, 0.19651765, 0.08436489, -0.14533581, -0.029874112, -0.15422897, -0.062741704, -0.22694711, -0.15547274, -0.15181333, 0.0286061, 0.022438493, -0.062447168) * go_3(-1.0, 0.0);
result += mat4(0.3497046, -0.09455009, 0.060618952, -0.2134236, 0.054515295, 0.07451165, -0.09267233, -0.010513333, 0.13842636, 0.11563433, -0.054750167, 0.050432, 0.1514256, 0.04284002, -0.2095581, 0.07907657) * go_3(-1.0, 1.0);
result += mat4(-0.11745651, -0.04717057, 0.085377194, -0.065956995, 0.07280491, 0.2730059, 0.11088276, 0.2437957, 0.14018989, 0.1164107, -0.09516929, 0.0022427947, 0.111544006, -0.0680495, 0.09324579, -0.12482022) * go_3(0.0, -1.0);
result += mat4(-0.07995795, -0.03387884, 0.019846136, 0.10231208, -0.07017192, 0.18659039, 0.035161644, 0.101182766, -0.14901665, 0.21307294, 0.063894205, -0.27546507, -0.24792959, -0.067731075, 0.13146006, -0.19333683) * go_3(0.0, 0.0);
result += mat4(0.034206454, 0.1472648, -0.07406727, 0.014654025, 0.18703444, 0.1319857, -0.10610886, 0.08427947, -0.017536618, -0.06487879, -0.12095286, -0.050414838, 0.03260879, 0.1558894, -0.031887084, 0.11840288) * go_3(0.0, 1.0);
result += mat4(0.114811294, -0.14574333, -0.09392587, 0.042283528, 0.08919092, 0.18259068, 0.0980717, 0.21024778, -0.1280008, -0.027260462, -0.1129027, 0.18722472, 0.13733985, 0.047153983, 0.030871978, 0.1998385) * go_3(1.0, -1.0);
result += mat4(-0.06783575, 0.004612595, 0.1153467, -0.11531557, -0.048889533, 0.07673577, -0.02041786, 0.22744459, -0.13092506, 0.13484807, 0.40003043, -0.053706612, -0.16985156, -0.04791236, -0.052443005, -0.08363625) * go_3(1.0, 0.0);
result += mat4(0.18187882, 0.017893985, 0.17856054, 0.005413129, 0.014147176, 0.15102178, 0.12436294, -0.02176765, -0.16727823, -0.0364111, 0.17074408, 0.12899421, 0.31984514, -0.0072070034, 0.031895883, -0.1991405) * go_3(1.0, 1.0);
result += vec4(-0.011865144, 0.11717201, -0.13823777, -0.059450272);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.082203194, 0.021720003, 0.03725474, -0.08048348, 0.2063248, -0.033020593, -0.17585336, 0.06476272, 0.012244563, 0.026554609, 0.014708393, 0.26606125, 0.14248778, 0.12817341, -0.039826933, -0.12751861) * go_0(-1.0, -1.0);
result += mat4(0.24573852, 0.19695967, -0.06257417, -0.04782871, 0.3511875, -0.018083302, -0.077342674, 0.15247667, 0.20321761, -0.07479984, -0.09548503, 0.08109568, -0.23808748, 0.07246303, -0.004242619, 0.16162953) * go_0(-1.0, 0.0);
result += mat4(0.13296306, 0.19495387, 0.009222276, 0.033592198, 0.20443891, 0.16063854, -0.2581601, -0.016132578, -0.2296461, -0.23647323, -0.15407176, -0.18265317, 0.2343241, -0.049697313, -0.09398783, 0.41931856) * go_0(-1.0, 1.0);
result += mat4(-0.10866088, -0.40605694, -0.0042648134, 0.07943803, 0.26914695, 0.14816476, 0.037706107, -0.123223364, -0.19962949, -0.053534556, -0.08397409, -0.04244924, -0.075791344, 0.29629225, 0.2311928, 0.099177904) * go_0(0.0, -1.0);
result += mat4(-0.1748319, -0.2003186, -0.32659066, -0.21007413, 0.20122464, 0.032196607, -0.026299698, 0.33395135, 0.11411664, 0.05971959, 0.09001304, -0.15936212, 0.012322024, 0.19936106, -0.411186, -0.08319479) * go_0(0.0, 0.0);
result += mat4(-0.07349218, 0.006184436, 0.096199185, -0.050186496, 0.064047046, -0.03813128, -0.057007037, -0.025550695, -0.2863145, -0.008512981, -0.20615962, 0.18009211, 0.008298396, 0.22452813, 0.010843521, 0.20169461) * go_0(0.0, 1.0);
result += mat4(0.2691149, 0.059546687, 0.08922005, 0.2252196, 0.30341956, -0.024489028, 0.087045394, -0.03856442, -0.14083561, -0.17683443, 0.14137806, 0.15520614, 0.2073925, -0.19525874, 0.23661858, 0.3098405) * go_0(1.0, -1.0);
result += mat4(0.006530723, 0.04180736, -0.04762067, -0.064395495, 0.02396811, -0.13332283, 0.0037775645, 0.026309434, 0.0033065109, -0.08315753, 0.02917419, 0.12330464, 0.22819455, -0.07489677, 0.12829056, -0.097994626) * go_0(1.0, 0.0);
result += mat4(-0.09983759, 0.032783493, 0.11085758, 0.08993078, -0.057110567, -0.018973934, -0.14946178, -0.03921629, 0.039757587, 0.015860094, 0.04989561, -0.19634786, 0.04351146, 0.019315343, 0.25972188, 0.17989321) * go_0(1.0, 1.0);
result += mat4(-0.04111906, -0.165601, 0.0003682197, -0.056232415, -0.32716644, -0.24015541, -0.057547837, 0.05966729, 0.06854747, 0.03599213, -0.18798864, 0.1183447, 0.014268468, -0.1310834, 0.06415977, -0.19414157) * go_1(-1.0, -1.0);
result += mat4(-0.00070661673, 0.17671427, 0.10584568, -0.060910843, -0.104282066, -0.22676118, -0.01907062, 0.24882245, -0.043454725, 0.07691623, -0.48371696, 0.013537671, -0.025488405, 0.061228953, 0.18548754, 0.028671112) * go_1(-1.0, 0.0);
result += mat4(-0.0121596735, 0.09595702, -0.08244918, -0.1176173, 0.26773354, -0.021729136, 0.075465776, -0.0928876, 0.12461298, 0.16830076, -0.15302569, 0.113850676, 0.09811088, 0.13006307, 0.24999009, 0.10261325) * go_1(-1.0, 1.0);
result += mat4(-0.032246377, 0.038265374, -0.26476422, -0.1442876, -0.19866082, 0.08649541, 0.041478764, 0.11155026, 0.21576422, -0.09572912, -0.11174068, -0.19722937, -0.15801935, 0.29604745, -0.08606268, -0.15532136) * go_1(0.0, -1.0);
result += mat4(-0.06315591, 0.16151646, -0.009230362, -0.04341246, 0.09085519, 0.21924476, 0.38044852, 0.193819, 0.16622902, 0.0025134624, -0.22688466, -0.025276015, 0.07714917, 0.16302192, -0.11767101, -0.11086476) * go_1(0.0, 0.0);
result += mat4(-0.04170153, 0.001859292, -0.26352355, 0.10982333, -0.031867817, 0.15773517, -0.060263418, 0.11117763, -0.017359972, 0.0127261225, 0.0782802, -0.16908924, 0.080516845, -0.05691526, -0.07530135, -0.14553802) * go_1(0.0, 1.0);
result += mat4(0.06112685, -0.032287434, 0.17445667, -0.044935808, -0.11449107, -0.051394563, -0.029589338, -0.14555557, 0.03440661, 0.11035615, -0.17175, -0.14851089, 0.037362, -0.18740481, 0.17278154, 0.18073405) * go_1(1.0, -1.0);
result += mat4(-0.27670652, 0.19484822, 0.2609349, 0.1455016, 0.04438468, 0.1449185, 0.11185832, -0.18598269, -0.019846648, 0.11886126, -0.098498635, 0.15737785, 0.011406795, -0.18860829, -0.13705735, 0.17535745) * go_1(1.0, 0.0);
result += mat4(-0.30244905, -0.28695273, 0.1146976, 0.21144345, -0.037980128, -0.027679864, -0.13992494, -0.04884521, -0.032023884, -0.07921183, -0.16042095, -0.06935386, -0.06570237, -0.1107404, -0.018163798, 0.22625941) * go_1(1.0, 1.0);
result += mat4(-0.07292955, -0.07321777, -0.045146503, -0.33291966, -0.096732594, -0.07203495, 0.33692798, 0.2870733, 0.122160144, -0.076574564, 0.042844944, 0.26448342, 0.07672146, -0.028775277, -0.12088313, 0.15583947) * go_2(-1.0, -1.0);
result += mat4(0.21589327, 0.05258274, 0.09705794, -0.024653846, -0.039402515, 0.28485695, 0.14711736, -0.10556087, -0.15140481, 0.09039498, 0.017308712, 0.11862922, 0.08230978, 0.21678248, -0.043815188, -0.226433) * go_2(-1.0, 0.0);
result += mat4(-0.029258793, 0.26618922, 0.02564014, -0.23189862, -0.24074338, -0.18556763, 0.25973624, 0.04746873, 0.0137007125, -0.22239363, -0.12414957, 0.048228756, -0.22406264, 0.282667, -0.021001073, -0.17465611) * go_2(-1.0, 1.0);
result += mat4(0.32401654, -0.1495363, -0.20869227, 0.04271639, -0.0087802755, 0.031325378, 0.23834595, 0.039336167, 0.17265107, 0.20947595, 0.28737286, 0.0028783784, -0.057340365, -0.050347418, -0.11915604, -0.1831807) * go_2(0.0, -1.0);
result += mat4(0.1811338, 0.07732653, 0.20975596, -0.47129005, 0.07121942, 0.08410583, 0.44170937, -0.19524159, -0.17807977, 0.12837476, 0.20816846, -0.1741958, -0.04411918, 0.06024972, 0.18159702, -0.052485272) * go_2(0.0, 0.0);
result += mat4(-0.15229738, 0.27513, 0.28150418, -0.19543962, -0.02045864, -0.07207227, 0.09589587, 0.09110817, 0.061413247, 0.0046052113, 0.11619411, -0.2988938, 0.065739445, 0.10205611, 0.12847126, -0.028355654) * go_2(0.0, 1.0);
result += mat4(0.0657154, -0.047568597, -0.16148911, 0.16392621, -0.25281775, -0.061153214, 0.017480455, -0.026288848, 0.20319715, 0.04763355, 0.010444491, -0.26671803, -0.25821987, 0.32863674, -0.30734694, -0.18190521) * go_2(1.0, -1.0);
result += mat4(-0.042703815, 0.06633036, -0.048434302, -0.17176376, -0.12699759, -0.1124558, 0.083266065, 0.03354623, -0.13468939, 0.12706263, 0.053659134, -0.06930602, 0.008196115, 0.2034998, -0.06351442, -0.039730288) * go_2(1.0, 0.0);
result += mat4(0.09614661, 0.22500272, 0.088511504, -0.16960482, 0.15364788, -0.18854137, -0.13163191, -0.07503735, -0.23177068, -0.0053305267, -0.041978605, 0.0971947, -0.049034655, 0.04486706, 0.09076307, -0.02310868) * go_2(1.0, 1.0);
result += mat4(-0.1304683, 0.17743458, -0.09817326, -0.0646786, 0.07886976, 0.20109388, -0.034114968, -0.2029261, -0.03348398, 0.029337432, -0.07302782, -0.02240758, 0.030242773, -0.30032325, 0.02085572, -0.027314361) * go_3(-1.0, -1.0);
result += mat4(-0.037377544, 0.026350772, -0.07430488, -0.114671774, -0.126935, -0.046512567, -0.033628833, -0.19018382, -0.041053895, -0.031206857, 0.08562848, -0.01875709, 0.21099389, -0.092511, 0.0073047103, -0.009811013) * go_3(-1.0, 0.0);
result += mat4(0.11358029, 0.17468451, -0.12739041, -0.14332245, -0.22230148, 0.16862972, -0.04462456, 0.2469604, -0.008622369, 0.0081848325, -0.17032363, -0.16024362, 0.21178265, 0.037127133, 0.08559072, 0.11584694) * go_3(-1.0, 1.0);
result += mat4(0.008993893, -0.08037705, 0.4426555, 0.15593371, 0.15273719, -0.03249998, 0.055109, -0.1512612, -0.037183985, 0.20825677, -0.08516227, -0.06664223, -0.10011001, -0.3505215, -0.17941694, 0.052089088) * go_3(0.0, -1.0);
result += mat4(-0.109703645, -0.13505603, 0.1336451, 0.13118869, 0.010915504, 0.12748592, 0.21201555, -0.40841985, -0.11059143, 0.033772044, -0.039282143, 0.03095394, 0.10394723, -0.21343367, -0.10699851, -0.028351074) * go_3(0.0, 0.0);
result += mat4(0.019704714, 0.06243651, 0.09896519, -0.17492259, 0.012675787, -0.004239029, 0.21319824, 0.069183126, -0.0071114586, 0.123431124, -0.24479835, 0.00723795, -0.045293927, 0.014101029, 0.15746681, 0.042405806) * go_3(0.0, 1.0);
result += mat4(0.023828225, -0.0015190929, 0.1194638, 0.082163885, 0.10532113, 0.042044062, 0.02528007, 0.015175004, 0.026613194, 0.33525538, -0.1627064, -0.29887968, -0.197707, 0.038967777, -0.15811683, -0.106895216) * go_3(1.0, -1.0);
result += mat4(0.044362027, -0.04946742, -0.14815849, -0.17660522, -0.034201477, -0.012243106, -0.050183997, 0.06407372, 0.039822515, 0.15880872, -0.0672721, -0.4081093, 0.019489579, -0.060278706, -0.015096743, -0.07799167) * go_3(1.0, 0.0);
result += mat4(0.11861756, 0.27113584, -0.14107186, -0.10246008, -0.124051, -0.1627854, 0.10698585, 0.2846401, -0.061731786, 0.1724438, -0.12428688, -0.09986041, -0.034171514, -0.07100923, 0.041739646, -0.11308375) * go_3(1.0, 1.0);
result += vec4(-0.02981662, -0.26338395, -0.011632586, 0.15063232);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf1
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.17082009, 0.031344634, -0.06131912, 0.00887183, -0.01528174, 0.12943709, 0.24537678, 0.008178781, -0.312396, -0.023583878, 0.07827866, -0.1231261, 0.15081584, -0.18161978, -0.25179705, -0.036934935) * go_0(-1.0, -1.0);
result += mat4(-0.05768411, 0.16785417, -0.1788644, -0.0067257965, 0.021445744, 0.10066516, -0.23864186, 0.1450302, 0.12892793, 0.19856106, -0.24444748, 0.16531628, -0.044425935, -0.02775357, 0.009059946, -0.12958384) * go_0(-1.0, 0.0);
result += mat4(-0.025798557, -0.17238182, -0.34056288, -0.20921059, -0.03576266, 0.1476854, -0.06264234, 0.14452787, -0.04130045, -0.07275762, 0.034578666, 0.2914669, 0.20879944, 0.21359251, -0.048695553, 0.2638088) * go_0(-1.0, 1.0);
result += mat4(-0.022791177, 0.4204545, 0.116855636, 0.20241925, -0.010444933, -0.14462502, 0.022550104, -0.24423064, -0.09417524, 0.045358784, -0.11405829, 0.035979558, -0.2283092, -0.06670842, -0.23852053, -0.22417003) * go_0(0.0, -1.0);
result += mat4(-0.14526704, 0.040880535, 0.14076385, 0.07795045, -0.059177604, -0.13056375, -0.3373641, -0.19344307, -0.29891858, -0.32578763, -0.29061425, 0.1562214, -0.13578376, 0.36586633, 0.24936736, 0.054629393) * go_0(0.0, 0.0);
result += mat4(-0.025790233, -0.13020341, -0.10084969, 0.15767297, -0.09738769, 0.04034404, 0.0038675873, 0.043515608, 0.16899958, -0.29117966, 0.03420067, 0.14432564, -0.10473084, 0.21014084, 0.07775908, -0.09303797) * go_0(0.0, 1.0);
result += mat4(-0.07443987, -0.16225167, 0.036251917, 0.028432872, 0.03759333, 0.004027401, -0.033941846, 0.0019474924, 0.02357054, 0.30748722, 0.1652115, -0.17361522, 0.16905582, 0.08048018, -0.23639561, -0.029408466) * go_0(1.0, -1.0);
result += mat4(0.0461233, -0.09346199, -0.07063276, -0.19447634, -0.049339604, -0.0032855074, -0.22661209, -0.0543389, 0.11924857, -0.21691081, -0.1645725, -0.0075736847, 0.018572787, -0.06552861, -0.01777661, -0.11651732) * go_0(1.0, 0.0);
result += mat4(-0.06425901, 0.123392984, -0.16395192, -0.093448035, -0.029316641, 0.0986573, -0.23135012, 0.011170849, 0.00023920486, 0.15296175, 0.35453254, -0.05189021, 0.20708887, -0.103900835, 0.081992395, -0.21829562) * go_0(1.0, 1.0);
result += mat4(-0.019074136, -0.1572586, 0.27919227, 0.09119617, 0.035954695, 0.2941489, 0.18262725, -0.055522963, -0.21364328, -0.1573611, 0.104966134, 0.08228523, 0.19945285, -0.0039229114, -0.1565048, 0.028975379) * go_1(-1.0, -1.0);
result += mat4(-0.18501253, 0.006473006, 0.06637501, 0.04295065, 0.06411007, 0.1166344, -0.10060226, 0.46296063, -0.08600344, -0.03560105, 0.012215349, 0.017885283, 0.061346993, 0.17336361, 0.01935021, 0.20198092) * go_1(-1.0, 0.0);
result += mat4(-0.04451627, -0.10372061, -0.13968691, 0.14479733, 0.1660607, 0.19334625, 0.0085214665, 0.28863636, -0.07600901, -0.014777084, 0.13209191, -0.09045013, 0.104893915, -0.04776884, -0.007936376, 0.104568765) * go_1(-1.0, 1.0);
result += mat4(0.023751335, -0.108048, -0.050531313, 0.15916029, 0.13246661, 0.04644228, -0.09586482, -0.17222965, -0.22898191, -0.033484615, 0.078883134, -0.052609313, -0.2721741, 0.045986425, 0.13972299, -0.28923607) * go_1(0.0, -1.0);
result += mat4(-0.23364568, -0.008875902, -0.40894926, 0.060443908, -0.2839635, -0.5270991, -0.2500865, 0.002020195, -0.24488612, -0.04982319, -0.009110353, -0.018023955, 0.06647274, -0.25225738, 0.26154432, -0.033934146) * go_1(0.0, 0.0);
result += mat4(-0.1535129, -0.21257545, -0.16553773, 0.17471452, -0.06203719, 0.15238857, 0.18702018, 0.18572305, 0.07740396, -0.074217625, -0.072156586, -0.2183728, 0.00403749, 0.13750519, 0.30362993, 0.06550286) * go_1(0.0, 1.0);
result += mat4(0.37164542, -0.1980723, -0.15659203, 0.19498909, 0.01748114, 0.011807152, -0.05424202, 0.11926474, 0.050406165, -0.12925303, -0.020280985, 0.08429331, 0.14769496, -0.077555746, -0.15216178, -0.27070466) * go_1(1.0, -1.0);
result += mat4(0.35804263, 0.08539285, -0.14785156, -0.13532467, 0.058254432, 0.20448379, -0.006173341, 0.058168225, -0.21714899, -0.13472849, -0.09392532, -0.12753737, -0.097461835, -0.11419082, 0.09384189, 0.06414768) * go_1(1.0, 0.0);
result += mat4(0.023494452, -0.22187226, -0.16694295, 0.0204334, -0.26720086, 0.15916729, 0.3098874, -0.10292057, 0.008854983, 0.13375004, -0.04409455, 0.09286524, 0.095829524, 0.12427317, -0.048659876, 0.18300754) * go_1(1.0, 1.0);
result += mat4(-0.119153984, 0.10163183, 0.025017537, -0.40096784, 0.026778705, 0.15821172, -0.19947284, -0.33337715, 0.2952563, 0.16820388, -0.057061996, -0.029319009, -0.12184868, 0.09031512, 0.12028806, 0.021044692) * go_2(-1.0, -1.0);
result += mat4(0.086744264, -0.046958666, 0.2130253, -0.46672252, 0.07135636, 0.0100029735, -0.13828261, -0.012365689, -0.11374441, 0.21084632, -0.059631422, -0.013799735, -0.037889663, -0.10701892, -0.09493782, 0.15516634) * go_2(-1.0, 0.0);
result += mat4(0.031181194, -0.01535001, 0.029270316, 0.13128386, 0.11838377, -0.17051528, 0.12228499, -0.04841128, 0.33350074, -0.006144013, -0.09055018, 0.27470216, -0.26665646, -0.08703671, -0.01719071, -0.23449609) * go_2(-1.0, 1.0);
result += mat4(-0.12856458, 0.005562174, -0.19517267, 0.13270985, 0.2776414, 0.032003902, -0.15778573, 0.15344355, 0.26930434, -0.13459459, 0.035019353, 0.08896612, 0.12847935, -0.122637205, 0.001815178, 0.08290523) * go_2(0.0, -1.0);
result += mat4(0.33805037, -0.15318587, -0.20955376, -0.26121393, -0.026022578, -0.1617741, 0.1336867, 0.026223289, 0.012059392, -0.17295446, -0.060811974, 0.14027825, -0.21134059, -0.08408573, -0.23773228, 0.110836074) * go_2(0.0, 0.0);
result += mat4(0.16176093, 0.15307428, -0.07711325, -0.3458805, 0.061291527, 0.023916256, 0.21370678, 0.0015756418, 0.10642374, 0.24807373, 0.11164451, 0.10780487, 0.087194376, -0.2718231, -0.008457387, 0.054078236) * go_2(0.0, 1.0);
result += mat4(-0.03259038, -0.20923306, 0.165477, 0.098864526, -0.02734457, 0.08871225, -0.01552188, 0.047712058, 0.055032052, -0.13044262, -0.2899521, 0.22230095, -0.029343741, -0.16427459, -0.005436118, -0.05111821) * go_2(1.0, -1.0);
result += mat4(0.20065974, -0.1556366, -0.12620135, 0.44572976, -0.020925352, 0.12025185, 0.20588058, 0.06391864, 0.046870507, 0.16942503, -0.049370963, 0.008779016, 0.04954915, 0.090298936, -0.16466027, 0.011152038) * go_2(1.0, 0.0);
result += mat4(0.13587528, 0.047841422, 0.19804007, -0.1672396, -0.072491, 0.04543739, 0.25287256, 0.015226213, 0.02007356, -0.049578942, -0.08796175, 0.1714897, -0.07819061, 0.1509537, 0.093094915, 0.031139288) * go_2(1.0, 1.0);
result += mat4(-0.013774682, 0.118201815, -0.009592314, -0.10837201, -0.0686881, -0.083380274, 0.107689425, 0.046642892, 0.119898744, -0.05502989, -0.19719897, 0.0005697584, -0.0921928, 0.032281205, 0.2568853, 0.2325449) * go_3(-1.0, -1.0);
result += mat4(0.02991112, -0.09898633, 0.06076172, -0.20906185, 0.0026118348, 0.06130956, 0.06760944, -0.16662054, 0.065741204, -0.13144116, 0.011419801, 0.22552124, 0.1465757, -0.07417319, -0.10788749, -0.24952699) * go_3(-1.0, 0.0);
result += mat4(-0.19238451, -0.024058497, 0.19580396, -0.067399554, -0.18832864, -0.11752747, -0.078949094, -0.23762032, -0.04141864, 0.022530237, -0.02222157, 0.0054874527, 0.057746816, -0.34854797, 0.028730657, -0.08976777) * go_3(-1.0, 1.0);
result += mat4(0.16888975, 0.19949849, -0.08456147, -0.03619044, -0.019596824, 0.11214634, 0.13971676, 0.22926724, 0.03219445, -0.04566354, -0.14948955, -0.22817011, -0.08714846, -0.19684613, 0.15479128, 0.2433362) * go_3(0.0, -1.0);
result += mat4(0.16050309, -0.102841675, 0.20855242, -0.011171905, -0.10309409, 0.22455123, 0.15892951, -0.06582373, 0.010079549, -0.2055006, -0.09385158, 0.006519388, 0.11838815, 0.37134558, -0.165772, 0.12704434) * go_3(0.0, 0.0);
result += mat4(0.11643292, 0.03294274, -0.09800525, -0.13601723, -0.081318736, -0.059975546, -0.039105035, -0.2893635, -0.13024913, -0.058016162, -0.09961072, 0.10532414, 0.24250132, -0.35546342, -0.092634924, 0.093994915) * go_3(0.0, 1.0);
result += mat4(-0.18799333, 0.25611782, 0.014645917, -0.063751906, 0.06498416, 0.16619027, -0.14411639, 0.3914421, -0.07343631, -0.116468735, -0.10941946, -0.2553544, -0.37774643, -0.0018441634, 0.06827239, -0.0122299045) * go_3(1.0, -1.0);
result += mat4(-0.11884597, -0.2477297, 0.048488285, -0.06438257, -0.124703035, 0.25932777, 0.0650111, -0.0930877, 0.06463341, -0.000544085, 0.0147504965, -0.170097, -0.13241997, 0.20983136, -0.15956205, 0.03424298) * go_3(1.0, 0.0);
result += mat4(-0.034574904, 0.06755256, 0.09508443, -0.17162292, 0.046379335, 0.2178781, 0.08699012, -0.055380464, -0.2237568, -0.07427848, -0.028395249, -0.3225617, -0.084454566, -0.24776657, 0.254169, 0.13229847) * go_3(1.0, 1.0);
result += vec4(0.18765923, -0.07697714, 0.028134674, -0.060966115);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_7_tf
//!WIDTH conv2d_6_tf.w
//!HEIGHT conv2d_6_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.21919365, 0.36627784, 0.12603314, 0.24306288, 0.06447028, 0.06472204, -0.05997039, -0.15651788, 0.017059859, -0.006497198, -0.4189735, 0.021636713, -0.23887977, -0.014220949, 0.031113686, -0.17342716) * go_0(-1.0, -1.0);
result += mat4(-0.10818789, -0.03273837, 0.33918005, -0.19290088, 0.0955361, -0.34107623, -0.054906327, -0.18083344, -0.060723677, 0.24395694, 0.112975016, -0.07254578, -0.14389384, 0.13235968, -0.15054801, -0.26216486) * go_0(-1.0, 0.0);
result += mat4(-0.23442148, -0.07857079, 0.022283873, -0.2656417, 0.037092753, -0.037313666, -0.5057047, 0.042533103, -0.120424, 0.00021930189, -0.0044566668, -0.45536995, 0.00040759926, 0.14597592, -0.094990164, -0.036161344) * go_0(-1.0, 1.0);
result += mat4(0.15024352, 0.19903262, -0.0734784, 0.092836305, -0.025753846, 0.024750374, -0.07550193, 0.035420835, 0.11084378, 0.26119822, -0.08443512, -0.0047807065, -0.042685136, 0.24889739, 0.098650105, 0.2088369) * go_0(0.0, -1.0);
result += mat4(-0.25551823, 0.14455976, 0.19886157, -0.23465924, 0.20711218, -0.20875362, -0.11320392, -0.30852005, -0.06795657, 0.008670962, 0.30601278, 0.6929064, 0.17079145, 0.15744895, 0.06441601, 0.06514001) * go_0(0.0, 0.0);
result += mat4(0.03142604, -0.006410137, -0.023654792, -0.05708553, 0.062985405, -0.077010594, 0.078804865, 0.050882503, 0.010274228, -0.15558401, 0.09490256, 0.14964707, -0.11966925, -0.36176664, 0.27809814, -0.18862294) * go_0(0.0, 1.0);
result += mat4(0.05609992, 0.0041612233, -0.08498908, 0.04479823, -0.080117956, -0.17423204, -0.22858045, 0.054569032, -0.050866384, -0.020000307, 0.027000953, -0.67724514, 0.16240878, -0.04641204, 0.0648367, -0.20613132) * go_0(1.0, -1.0);
result += mat4(0.08542306, -0.08254248, -0.11090553, -0.14140448, -0.10788511, -0.13011602, -0.29319742, -0.26007155, 0.11033401, -0.31966573, 0.32668245, 0.19542319, 0.06329418, 0.20904626, 0.2724067, -0.009155685) * go_0(1.0, 0.0);
result += mat4(-0.007403411, 0.0012836396, -0.23446666, -0.03017208, 0.062420018, -0.13611084, -0.2975928, 0.13173148, -0.03679939, 0.13743873, -0.10121899, 0.074514665, 0.1497629, -0.09523838, 0.39018926, 0.37807035) * go_0(1.0, 1.0);
result += mat4(0.11441487, -0.19565523, -0.25757137, -0.16148767, 0.15575317, -0.12657928, 0.10479676, 0.062919036, 0.010544159, 0.22931573, 0.20360178, 0.4637635, -0.3395036, -0.52467215, 0.08759308, 0.028030418) * go_1(-1.0, -1.0);
result += mat4(0.2699195, -0.34218305, 0.15259695, 0.03139074, -0.024053533, -0.029567484, 0.28480124, 0.20525953, 0.15452823, -0.217713, 0.15861876, -0.012275699, 0.21408023, 0.097508304, -0.57126766, -0.14679857) * go_1(-1.0, 0.0);
result += mat4(-0.0755847, -0.09751562, -0.29480466, -0.22285318, 0.14196442, 0.114573136, -0.22294767, 0.12463806, 0.3322209, -0.04631724, -0.11097061, -0.27986854, -0.16099304, -0.060079545, 0.00299308, 0.120776065) * go_1(-1.0, 1.0);
result += mat4(0.050933484, -0.13776319, -0.18809728, 0.24035202, -0.32528606, -0.41684148, -0.029342847, 0.28642926, -0.07963454, -0.12905268, 0.07606093, 0.24670005, -0.08815598, -0.23320907, -0.008099349, 0.21512873) * go_1(0.0, -1.0);
result += mat4(0.19247563, 0.18083979, -0.09719762, 0.15314941, -0.22350982, 0.46515045, -0.3571128, 0.35953265, 0.06921985, -0.4482386, -0.18732521, -0.5043983, 0.35159567, -0.33315298, -0.21884166, -0.16283798) * go_1(0.0, 0.0);
result += mat4(-0.021124054, -0.007966742, 0.0052493825, 0.022550896, 0.030403977, 0.3377868, -0.47602004, -0.077664234, -0.07222509, -0.07486097, -0.37971064, -0.5107857, -0.06299477, 0.04930232, -0.3330487, 0.29845512) * go_1(0.0, 1.0);
result += mat4(-0.063705474, -0.07917637, -0.02026607, -0.05142568, 0.021577014, -0.07379867, 0.033937998, 0.08148773, -0.02717838, -0.03233838, 0.098000035, 0.036476444, -0.13366953, 0.014477577, 0.24064232, 0.39313284) * go_1(1.0, -1.0);
result += mat4(-0.16046515, -0.094624564, 0.35435164, 0.09942324, -0.07137174, -0.27999225, 0.124644354, -0.0062176553, 0.015016751, -0.05500243, -0.23249559, -0.4508382, 0.1860433, 0.10671491, -0.033345353, -0.06611453) * go_1(1.0, 0.0);
result += mat4(0.21614046, -0.01307525, -0.18941112, -0.20533535, -0.14481686, -0.47801897, 0.22605121, -0.20298961, -0.06744227, -0.20377496, -0.11926173, 0.15645133, -0.31570885, -0.3495616, -0.024666889, 0.040965475) * go_1(1.0, 1.0);
result += mat4(-0.11748018, -0.039976366, -0.00084064255, -0.028653437, -0.16216733, -0.036768105, 0.018064514, -0.0928936, 0.14008482, -0.064511225, 0.24329947, -0.0268608, 0.050330248, 0.08540601, -0.07272679, -0.01187671) * go_2(-1.0, -1.0);
result += mat4(-0.09459936, -0.011723822, -0.06952858, -0.07808506, -0.065588176, 0.332501, -0.0120042395, 0.07668016, 0.14735217, -0.14856043, -0.06702449, -0.020953184, -0.023006834, 0.06135422, 0.1491448, -0.028061569) * go_2(-1.0, 0.0);
result += mat4(0.25136968, 0.25146323, -0.108277924, -0.20407207, -0.0013780294, 0.16108194, 0.25143847, 0.06672421, -0.033905584, -0.021144686, -0.019152718, 0.34619498, 0.14560962, 0.034437314, 0.024790365, -0.049976267) * go_2(-1.0, 1.0);
result += mat4(-0.24928351, 0.12637813, 0.23609994, 0.12722939, -0.036997862, -0.16554876, 0.11144095, -0.10040036, -0.020359103, -0.080701865, -0.3142192, 0.27257237, 0.13546956, -0.14416885, 0.028196262, -0.2886465) * go_2(0.0, -1.0);
result += mat4(0.28524777, -0.4236231, 0.27420738, -0.21095508, 0.23475651, 0.115876295, -0.18837357, -0.0260708, 0.030670704, -0.11516913, -0.11365572, -0.2203149, -0.018612983, -0.10719593, -0.031727783, 0.1403327) * go_2(0.0, 0.0);
result += mat4(0.07240512, 0.03139215, 0.12328737, -0.021201206, -0.13971715, 0.072742075, -0.0011289873, 0.0053133667, 0.035639685, -0.04322272, -0.19288473, -0.15812221, -0.19126481, 0.0698514, 0.17619178, -0.035605464) * go_2(0.0, 1.0);
result += mat4(-0.18552057, 0.07259671, 0.011667668, -0.15630563, 0.11414356, 0.14482655, -0.04021029, 0.18495587, -0.11386139, -0.09058561, -0.011265998, 0.23358451, 0.0521358, 0.12495261, 0.021644838, -0.048094347) * go_2(1.0, -1.0);
result += mat4(-0.09222373, 0.0533347, 0.055820454, 0.22382596, 0.18713981, 0.2668916, -0.019384036, 0.012698582, 0.13325234, 0.20361474, -0.33106443, -0.08571572, -0.21243028, -0.10996386, 0.123459645, 0.1534967) * go_2(1.0, 0.0);
result += mat4(0.18133277, 0.18108074, -0.05638664, 0.29533157, -0.2108019, -0.033636626, 0.5015888, -0.15116066, -0.041320793, -0.14764231, 0.07314567, -0.18865979, 0.10276937, 0.094240844, -0.1364283, 0.27812913) * go_2(1.0, 1.0);
result += mat4(0.06040915, 0.23753685, 0.19019844, 0.23948252, -0.07535012, 0.11848904, 0.14389765, 0.050067905, 0.16150077, -0.030053454, 0.12478255, 0.26020208, 0.111198805, 0.06787492, -0.12771018, 0.006687384) * go_3(-1.0, -1.0);
result += mat4(-0.5421617, 0.10414128, -0.21526064, -0.08883624, 0.13145073, -0.29695904, 0.57386386, 0.073361695, -0.09538372, 0.27593842, 0.070922814, 0.21769938, 0.06214975, 0.11847816, 0.10033405, 0.29360098) * go_3(-1.0, 0.0);
result += mat4(-0.16294672, -0.014815565, 0.22046989, 0.16858687, 0.058917344, 0.21384977, 0.18803519, 0.105688855, 0.0355118, 0.20571202, -0.07341922, 0.26624045, -0.0415102, 0.050942056, 0.19727907, 0.20122413) * go_3(-1.0, 1.0);
result += mat4(-0.020470422, 0.15815964, -0.13437317, -0.1967045, 0.074902646, 0.08356444, 0.055913117, -0.12837863, -0.18647918, 0.07002247, 0.038864706, -0.07288784, 0.04135125, -0.016055549, -0.1340297, -0.15578008) * go_3(0.0, -1.0);
result += mat4(-0.07685624, 0.00079105416, -0.068755336, 0.110282525, -0.014170752, 0.041282844, -0.17035173, 0.19439398, -0.3036256, 0.024148455, -0.19566648, -0.06736254, 0.14203559, -0.13016985, -0.32845357, -0.14266774) * go_3(0.0, 0.0);
result += mat4(0.0087252045, 0.098839566, -0.08770506, -0.08499465, 0.015245115, -0.110854514, 0.054458305, -0.018121868, -0.09666134, -0.08316006, 0.24617113, -0.17195955, 0.2574254, 0.06734342, -0.13792352, -0.07306126) * go_3(0.0, 1.0);
result += mat4(-0.0073954533, -0.20126835, -0.22545357, -0.29462856, 0.057408337, 0.11939119, -0.01846476, 0.12534486, 0.15751605, -0.14282645, -0.14219986, 0.14283386, 0.14090413, 0.10500912, 0.03039335, 0.17448832) * go_3(1.0, -1.0);
result += mat4(0.043910783, -0.09140025, -0.21666165, 0.07616939, 0.104454786, 0.309926, -0.12906921, 0.1140117, 0.09372434, 0.049547072, -0.086615674, -0.034449168, 0.096705064, 0.26001686, 0.027063297, 0.12422948) * go_3(1.0, 0.0);
result += mat4(0.1365422, 0.2679611, 0.12037257, 0.43346113, 0.08223084, -0.016788265, 0.13570398, -0.017974345, -0.17922844, -0.09475725, 0.073539585, -0.106947675, 0.08998511, 0.04133868, 0.16586913, -0.26291734) * go_3(1.0, 1.0);
result += vec4(-0.19233678, 0.016725872, -0.008011114, -0.1977463);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_7_tf1
//!WIDTH conv2d_6_tf.w
//!HEIGHT conv2d_6_tf.h
//!COMPONENTS 4
#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.36016628, 0.019064043, 0.3073228, 0.16891135, 0.026739368, 0.31136194, 0.11260383, -0.26918694, 0.0419928, -0.3365078, 0.20189743, -0.04136312, 0.039564647, 0.033199426, 0.18768296, -0.017119858) * go_0(-1.0, -1.0);
result += mat4(0.28663483, -0.41716507, 0.059281543, 0.043736435, 0.0028875466, 0.13817391, -0.12543318, -0.2794053, -0.023528943, 0.10610115, 0.09100278, 0.040132936, -0.21949205, -0.027810011, -0.0301218, 0.084047124) * go_0(-1.0, 0.0);
result += mat4(0.39674807, -0.0040878756, -0.038235947, 0.11880838, 0.009898328, 0.19107847, -0.009313831, -0.1554276, -0.047341663, 0.18049581, -0.029317195, 0.0708909, 0.0708316, -0.110617444, 0.14584038, -0.022261223) * go_0(-1.0, 1.0);
result += mat4(-0.20400241, 0.0896492, -0.010386381, -0.052133385, 0.005023956, -0.06628705, -0.16436209, -0.25345984, -0.05285192, 0.09706557, -0.03778914, -0.152546, 0.17023252, 0.063713826, 0.00743037, 0.056634087) * go_0(0.0, -1.0);
result += mat4(-0.080793336, 0.4204207, 0.19098237, 0.20028038, -0.054076545, 0.22064368, -0.25853387, -0.3643562, 0.2085573, -0.023731, -0.06727709, -0.18683033, -0.18032159, -0.06388348, 0.304463, -0.2517781) * go_0(0.0, 0.0);
result += mat4(0.11940941, 0.10624008, 0.16120581, 0.2369602, 0.3321827, 0.4272075, -0.10403669, -0.31388018, -0.006372124, -0.00653671, 0.109810196, 0.2277172, 0.005771998, 0.086026914, -0.08934813, -0.094941735) * go_0(0.0, 1.0);
result += mat4(-0.13233568, 0.24112508, -0.0068006413, 0.12466225, 0.11396591, -0.07249253, -0.29090378, -0.12828146, -0.22001141, -0.08532405, -0.11932601, 0.29452974, 0.09572195, 0.017603843, 0.12454017, 0.16321751) * go_0(1.0, -1.0);
result += mat4(0.042107448, -0.00807216, 0.06580674, -0.1289527, 0.13977426, -0.037159685, -0.21001346, -0.08698161, 0.22370502, -0.29170328, 0.2179206, 0.36621302, 0.0825477, -0.016513655, -0.11157249, 0.12861598) * go_0(1.0, 0.0);
result += mat4(0.2246826, -0.13262233, 0.12131653, -0.15522355, 0.38104856, 0.030237729, 0.1286289, -0.19770473, -0.16175011, -0.13688888, 0.23505463, 0.21333031, 0.76352316, -0.17949077, -0.13124311, 0.1613879) * go_0(1.0, 1.0);
result += mat4(-0.050607495, 0.0846705, -0.06136092, -0.033436477, 0.41138348, 0.037043408, -0.02676336, -0.37771952, 0.22147503, 0.06490757, -0.04266158, -0.22606373, 0.045775007, -0.054498192, -0.21495876, -0.036050417) * go_1(-1.0, -1.0);
result += mat4(-0.06242522, 0.2700824, -0.05602621, -0.12361551, 0.14477442, 0.19403581, 0.23505251, -0.072234035, -0.15831544, 0.4640447, -0.104754634, -0.004539681, -0.20246096, 0.23216484, -0.35886365, 0.11360777) * go_1(-1.0, 0.0);
result += mat4(0.14777757, 0.18951412, 0.027219458, 0.11216015, 0.02997997, -0.13466355, -0.0010830094, 0.021302953, 0.23441231, -0.14529245, 0.08068729, 0.10044398, 0.3972878, 0.26570204, 0.0046810666, -0.2863261) * go_1(-1.0, 1.0);
result += mat4(-0.10385485, 0.1053724, 0.16961229, 0.20727012, -0.025148917, -0.011365095, 0.03899919, -0.030950211, 0.079080455, -0.32767853, 0.064670205, -0.035771385, 0.16833797, -0.21567492, 0.30871257, -0.19965471) * go_1(0.0, -1.0);
result += mat4(-0.23420888, -0.004894698, -0.18162623, -0.31107524, 0.11976508, 0.14924951, -0.08723316, 0.21401922, -0.58200324, -0.01177345, -0.049033508, 0.19593577, -0.21139073, 0.13016601, 0.08734843, 0.4158892) * go_1(0.0, 0.0);
result += mat4(0.0009789813, 0.33274913, 0.017405733, -0.042906318, -0.26410276, -0.09291333, 0.019387102, 0.105381854, -0.009176527, 0.09483514, -0.28462934, -0.03644404, 0.285194, -0.4260311, 0.14902237, -0.115670316) * go_1(0.0, 1.0);
result += mat4(-0.09344311, 0.4463103, 0.19984834, -0.09733857, -0.118717775, -0.0708026, 0.24919955, -0.11234634, 0.1246395, -0.052909933, 0.1525815, 0.07724016, 0.0070534665, -0.06404165, -0.18149726, -0.014058336) * go_1(1.0, -1.0);
result += mat4(-0.17353044, 0.15376104, 0.004588994, -0.13554202, -0.19920237, -0.18918681, 0.11327512, -0.117296435, -0.0785251, 0.013677155, -0.2103214, 0.06843426, -0.27790928, 0.09837545, -0.00019213746, 0.09132539) * go_1(1.0, 0.0);
result += mat4(-0.01586651, 0.014929441, 0.2426186, -0.1889374, -0.0865462, -0.07454513, -0.20797268, -0.22366855, 0.19704159, 0.0048206006, -0.16707218, -0.14162683, 0.036798395, -0.1663155, -0.12009389, 0.09603803) * go_1(1.0, 1.0);
result += mat4(-0.041532192, 0.05753804, 0.17927068, -0.042112097, 0.12080969, -0.15052572, -0.34855765, -0.07356988, -0.28199884, -0.18958664, 0.15879883, 0.08511588, 0.0034213227, -0.05338495, -0.37285298, 0.06626709) * go_2(-1.0, -1.0);
result += mat4(-0.20219134, 0.22150375, -0.29405454, 0.06597703, -0.018885285, -0.010551704, -0.010774283, 0.08758955, -0.2015349, -0.17006227, -0.24321876, -0.06864207, -0.118437864, -0.043977212, -0.029736811, 0.14040919) * go_2(-1.0, 0.0);
result += mat4(-0.18709077, -0.09723938, 0.12783436, -0.15167634, 0.29039705, -0.11009911, 0.018371418, -0.060096707, -0.07256923, -0.25799567, -0.06276934, -0.035992302, -0.06729111, -0.059956793, -0.024079734, 0.011838878) * go_2(-1.0, 1.0);
result += mat4(0.010449175, -0.08212451, 0.1409803, 0.11861122, -0.18035835, 0.051930565, 0.01049551, -0.09447962, 0.12029649, 0.040604513, -0.059971705, -0.0044667358, -0.22080486, -0.11187681, 0.124374695, -0.004155485) * go_2(0.0, -1.0);
result += mat4(-0.28584236, -0.38480133, -0.13987814, -0.4463469, -0.3890419, -0.022498172, 0.17334452, 0.21895568, -0.15450422, -0.10905497, 0.15111905, -0.22554915, 0.106121585, -0.029144369, 0.36059046, 0.22140682) * go_2(0.0, 0.0);
result += mat4(-0.23780307, -0.023033705, 0.068205886, -0.110635854, -0.26720005, -0.1608183, 0.19523881, 0.07972837, -0.018495852, -0.2793956, 0.17668398, -0.12020479, -0.079556085, -0.02284952, 0.031480275, 0.31818348) * go_2(0.0, 1.0);
result += mat4(0.22501226, -0.00829407, 0.059581667, 0.16512989, 0.18711442, 0.1200968, 0.11812652, -0.16091056, 0.15733972, 0.045156084, 0.20640492, -0.16852027, -0.11217177, 0.06746273, -0.050218176, 0.08643783) * go_2(1.0, -1.0);
result += mat4(0.20715691, -0.1082907, 0.027892975, 0.19515261, -0.17838904, 0.1532257, -0.108409844, -0.06632365, -0.13805026, 0.23020233, 0.12416581, -0.14861803, 0.16650471, 0.08158386, -0.09051303, -0.06981649) * go_2(1.0, 0.0);
result += mat4(-0.04617126, 0.06579221, 0.25964734, 0.28500968, 0.07641255, -0.090885855, -0.0972522, 0.18298368, -0.06393334, 0.103463, -0.23062052, -0.15270731, 0.13633437, 0.074707486, 0.15065335, -0.024602572) * go_2(1.0, 1.0);
result += mat4(0.118319295, 0.010410938, 0.044655934, -0.104725905, 0.030477569, 0.12867387, 0.039075315, 0.18922117, 0.13301082, -0.1601557, 0.038168408, -0.07372259, -0.09522213, -0.095107146, -0.16679631, 0.044673234) * go_3(-1.0, -1.0);
result += mat4(0.46229, -0.30780822, -0.09081465, 0.1433387, -0.0315039, 0.059409115, -0.24948491, -0.17146957, 0.060843736, -0.041989822, 0.054005735, 0.22835566, 0.12036598, -0.0070898845, 0.17276852, -0.17754094) * go_3(-1.0, 0.0);
result += mat4(-0.35119572, 0.020034311, 0.08751943, 0.08193488, 0.041884877, 0.22649358, -0.07447533, 0.20845473, -0.04859846, -0.16206735, 0.06819576, -0.053000778, 0.18146423, 0.04694148, 0.045293212, 0.06783575) * go_3(-1.0, 1.0);
result += mat4(0.280914, -0.14998704, -0.23485807, -0.015608296, 0.1549556, -0.11992663, -0.094974115, 0.05887284, 0.053392075, 0.10322464, -0.075066686, 0.068358354, -0.18663338, 0.009901499, -0.123370335, -0.12502703) * go_3(0.0, -1.0);
result += mat4(0.7748568, -0.17870626, -0.20770052, 0.024692526, -0.056430295, -0.06324113, -0.03660047, 0.29629672, -0.51896983, -0.027231261, 0.05903762, 0.077677645, -0.061675485, -0.20277846, 0.10352223, -0.08198446) * go_3(0.0, 0.0);
result += mat4(-0.06347568, 0.21643166, -0.09718546, 0.0372257, -0.029537952, -0.0357135, -0.09548363, 0.18225233, -0.29609334, -0.3496132, 0.18245913, -0.10162589, -0.18189451, -0.09077887, 0.117313184, -0.06863874) * go_3(0.0, 1.0);
result += mat4(-0.047373574, -0.020289376, -0.25748715, -0.13568166, 0.15656634, -0.06841899, 0.012100781, -0.13611819, 0.0016357322, -0.23870537, 0.14035743, -0.14700134, 0.2535575, -0.13697346, -0.13693139, -0.10365287) * go_3(1.0, -1.0);
result += mat4(0.4283934, -0.316192, -0.012617617, 0.018468965, 0.21436644, 0.18408814, -0.42651537, 0.12504087, -0.13894933, 0.091662176, -0.20096369, -0.080727175, -0.005487846, 0.17046383, 0.1383948, -0.0054956395) * go_3(1.0, 0.0);
result += mat4(0.20014295, -0.027282396, -0.06317007, 0.04452042, 0.064600386, 0.072222926, -0.33409226, 0.08063831, -0.022607977, 0.1308856, -0.39691743, -0.094889864, -0.1810531, 0.011367248, -0.2531222, -0.22468317) * go_3(1.0, 1.0);
result += vec4(0.26886886, 0.05874665, 0.10268232, 0.05833081);
return result;
}
//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-3x1x1x112
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!BIND conv2d_7_tf
//!BIND conv2d_7_tf1
//!SAVE MAIN
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
#define g_0 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_1 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_8 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_9 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_12 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_13 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_16 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_17 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_20 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_21 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_24 (max((conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))
#define g_25 (max((conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.09689336, 0.06046458, 0.072598994, 0.0, 0.11994565, 0.104477674, 0.09302802, 0.0, -0.05718302, 0.050438102, 0.08814741, 0.0, 0.0308889, 0.0033925986, -0.01715605, 0.0) * g_0;
result += mat4(-0.028314235, 0.06597744, 0.0966897, 0.0, 0.035656154, 0.07770106, 0.075551905, 0.0, 0.0001793458, -0.000479495, -0.00297406, 0.0, -0.053916585, -0.016807461, -0.0057141334, 0.0) * g_1;
result += mat4(-0.047189303, -0.0207, -0.020910334, 0.0, -0.07933196, -0.06961211, -0.086069845, 0.0, 0.0943727, 0.008463375, 0.010755166, 0.0, 0.062410597, 0.022625161, 0.04068433, 0.0) * g_2;
result += mat4(0.10270994, -0.019080428, 0.0050091282, 0.0, -0.004672948, -0.013966742, -0.0063746064, 0.0, -2.5856789e-05, 0.03151499, -0.0023983798, 0.0, 0.113539025, 0.12381699, 0.100360274, 0.0) * g_3;
result += mat4(0.07868885, -0.030913834, -0.009213676, 0.0, 0.04870991, 0.021467991, 0.038739506, 0.0, -0.042969644, -0.07122453, -0.08798675, 0.0, -0.09784122, 0.021434791, 0.02510374, 0.0) * g_4;
result += mat4(0.050420716, 0.0729716, 0.076532185, 0.0, -0.019112485, -0.01037939, -0.026948035, 0.0, -0.02591423, 0.008927897, -0.00042541025, 0.0, 0.1043701, -0.0071186824, -0.041817162, 0.0) * g_5;
result += mat4(-0.16143242, -0.0009298223, -0.01228508, 0.0, 0.07744052, -0.018313263, -0.0488145, 0.0, 0.09241393, 0.07128674, 0.055164956, 0.0, 0.054884013, -0.04834418, -0.06281626, 0.0) * g_6;
result += mat4(-0.049036566, -0.05979936, -0.05594288, 0.0, -0.014564307, 0.031926468, 0.037857566, 0.0, 0.015474487, -0.11385003, -0.11527764, 0.0, -0.07076006, 0.057038613, 0.095983796, 0.0) * g_7;
result += mat4(0.03094887, -0.008734403, 0.00042712069, 0.0, 0.053891554, 0.05837673, 0.06200635, 0.0, 0.09071558, -0.04202184, -0.046172567, 0.0, -0.0425916, 0.04905093, 0.020835675, 0.0) * g_8;
result += mat4(0.096628904, -0.037792254, -0.043241944, 0.0, -0.011923947, -0.025950424, -0.031381752, 0.0, -0.060941868, -0.07859433, -0.07535451, 0.0, -0.026777223, 0.08604982, 0.07829908, 0.0) * g_9;
result += mat4(-0.06435972, 0.0036599538, 0.00786578, 0.0, -0.061972067, -0.05681472, -0.06667608, 0.0, -0.106890626, 0.007406496, 0.029977169, 0.0, -0.20519382, -0.044860814, 0.0021225857, 0.0) * g_10;
result += mat4(-0.16876474, 0.012789643, 0.026692612, 0.0, 0.017817136, 0.026935097, 0.02227043, 0.0, 0.01690181, 0.07716103, 0.086527, 0.0, 0.07923805, -0.10443151, -0.10859543, 0.0) * g_11;
result += mat4(0.003730466, -0.024648283, -0.022169832, 0.0, -0.0062762927, 0.022062732, 0.032966793, 0.0, 0.016349113, 0.017197203, 0.020952817, 0.0, -0.1763789, 0.035497356, 0.053835396, 0.0) * g_12;
result += mat4(0.020886675, -0.07054202, -0.079142675, 0.0, 0.06664387, 0.044960167, 0.042230908, 0.0, -0.095019594, 0.012421141, 0.0142890485, 0.0, 0.056814816, -0.012751135, -0.014684506, 0.0) * g_13;
result += mat4(0.011765893, 0.0008920681, -0.0018258415, 0.0, -0.010473814, -0.023085753, -0.028783914, 0.0, -0.023034256, -0.0024786016, -0.0052162083, 0.0, 0.1643386, -0.06132718, -0.09289065, 0.0) * g_14;
result += mat4(0.016597198, 0.09389637, 0.10833379, 0.0, -0.043163072, -0.04714812, -0.035274632, 0.0, 0.09634976, -0.009292612, -0.022424143, 0.0, -0.08765172, 0.0051558353, 0.010900356, 0.0) * g_15;
result += mat4(0.030815786, 0.021069322, 0.01812191, 0.0, 0.084839165, -0.0080813095, -0.029270556, 0.0, -0.10456346, 0.062386703, 0.0665605, 0.0, 0.11926609, -0.1104228, -0.13291118, 0.0) * g_16;
result += mat4(-0.07159541, -0.007267032, -0.010134558, 0.0, 0.008234213, 0.045609634, 0.040295456, 0.0, 0.018416971, 0.01308482, 0.014649557, 0.0, 0.035107512, -0.02140815, -0.030279048, 0.0) * g_17;
result += mat4(0.01918586, 0.03875863, 0.03229402, 0.0, -0.07917104, 0.041135103, 0.057182517, 0.0, 0.08609541, 0.0079662455, 0.004327576, 0.0, -0.14332893, 0.03120354, 0.056732506, 0.0) * g_18;
result += mat4(0.03200192, -0.0035752193, -0.0031064528, 0.0, -0.010902813, 0.014607456, 0.019431474, 0.0, -0.016461229, -0.004938204, -0.004655488, 0.0, -0.033470232, 0.0026075812, 0.005896968, 0.0) * g_19;
result += mat4(0.037410006, 0.048742272, 0.04348088, 0.0, 0.037719514, 0.030768529, 0.03127472, 0.0, 0.056426726, 0.03066893, 0.016440205, 0.0, -0.010599352, 0.022832409, 0.023211194, 0.0) * g_20;
result += mat4(-0.005733291, 0.06365659, 0.06663611, 0.0, -0.041917093, -0.016493445, -0.020438088, 0.0, -0.0014357592, -0.0022506563, -0.0045095007, 0.0, 0.029893145, -0.009129354, -0.015173116, 0.0) * g_21;
result += mat4(0.013052085, 0.005108175, 0.0025906067, 0.0, -0.021950055, -0.036447693, -0.036141638, 0.0, -0.036296472, 0.0068928464, 0.013102313, 0.0, 0.0060471976, -0.024798103, -0.023548538, 0.0) * g_22;
result += mat4(0.0067743887, -0.06191211, -0.062355213, 0.0, 0.0016080744, -0.020445071, -0.016840393, 0.0, 0.028264903, 0.01852915, 0.015891539, 0.0, -0.023877412, -0.013271666, -0.008158679, 0.0) * g_23;
result += mat4(-0.04317466, -0.018953001, -0.020452993, 0.0, -0.009322576, -0.03022352, -0.030970376, 0.0, 0.05653658, 0.05430553, 0.046692245, 0.0, 0.05615359, 0.059338935, 0.056018773, 0.0) * g_24;
result += mat4(0.022878079, 0.03392234, 0.033057988, 0.0, -0.017554542, -0.0141542535, -0.014122613, 0.0, -0.048634093, -0.05316463, -0.047988772, 0.0, -0.058002178, -0.040221967, -0.034025013, 0.0) * g_25;
result += mat4(-0.018253656, -0.04197674, -0.040467236, 0.0, -0.04358929, -0.028309818, -0.025425073, 0.0, -0.008488672, -0.001727991, 0.00035808363, 0.0, -0.0011709273, 0.0052514165, 0.0059479307, 0.0) * g_26;
result += mat4(-0.08333935, -0.09818201, -0.09476284, 0.0, -0.033692095, -0.046259012, -0.045797516, 0.0, -0.007577072, 0.0022402718, 0.0016200038, 0.0, 0.0029786075, -0.020728534, -0.018938033, 0.0) * g_27;
result += vec4(0.047567394, -0.02504617, -0.028163986, 0.0);
return result + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,300 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.010995803, 0.077095956, -0.043992598, 0.06048717, 0.1164834, -0.11689607, 0.072985925, -0.078805886, 0.01182932, 0.054985743, -0.09018186, 0.044907484, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.1813623, -0.14752422, 0.025720436, -0.17639883, 0.15697388, 0.10445984, -0.1843076, 0.5264643, 0.047516696, -0.097305484, 0.09740847, -0.29619336, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.014534763, 0.09486465, 0.046173926, 0.039391946, 0.09609376, -0.060574662, 0.042200956, -0.3269777, 0.051006425, 0.059818447, 0.04366627, 0.17699827, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.04268535, -0.08152529, 0.10577459, -0.036936995, -0.051562306, 0.054872766, 0.09194519, 0.0025066638, -0.01073954, 0.00064474024, 0.10038221, 0.02131141, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.51751363, -0.40028602, 0.3469574, 0.5933738, -0.91357684, -0.67692596, 0.57815677, 0.39809322, -0.16341521, -0.27169713, 0.12232366, 0.4318641, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.12601124, -0.06263236, -0.45907676, -0.41514075, 0.3330334, -0.1929565, -0.6333532, -0.6552794, -0.045809917, 0.046351526, -0.26173338, -0.30252662, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.0030332592, 0.012103107, 0.010537323, -0.02038607, 0.095558085, 0.097704545, 0.083433494, 0.026790185, 0.01943357, -0.061712462, -0.00015703632, -0.032268334, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.016870102, 0.5215812, -0.11525501, 0.027527615, -0.09045733, 0.61310345, -0.1575268, 0.1905386, 0.020172214, 0.3503187, -0.08209157, -0.051328037, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(0.005494087, -0.010656317, 0.07682753, -0.08116042, -0.03934524, 0.16589017, 0.101483546, -0.066603065, 0.03494657, -0.07885597, 0.074227594, 0.0016264897, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.014463938, -0.0031906287, 0.007015422, -0.003888468);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.08532478, -0.14302494, -0.017921071, -0.0032664281, -0.09841952, 0.024187077, 0.10701477, 0.14110753, -0.05714981, -0.10897174, 0.073803626, 0.103992954, 0.07914382, 0.032193683, -0.18346278, -0.09723936) * go_0(-1.0, -1.0);
result += mat4(-0.034482613, -0.10742312, -0.047286414, -0.08641124, -0.33896688, -0.036533825, -0.48337597, 0.034040943, -0.13598205, -0.080917805, 0.08540263, -0.012667689, -0.009171425, -0.120026454, -0.20536867, -0.032149274) * go_0(-1.0, 0.0);
result += mat4(0.18687321, 0.066278316, 0.024327392, 0.08816582, -0.08017908, 0.09488853, 0.26018232, -0.101504356, 0.17487666, 0.31057635, 0.14785016, -0.09622089, -0.07537452, -0.13844088, -0.05810814, 0.09907489) * go_0(-1.0, 1.0);
result += mat4(-0.04183032, 0.15207712, 0.005002397, 0.32277516, -0.16169126, -0.119836345, -0.04068436, -0.096728764, 0.11943901, 0.1789597, -0.20412198, 0.19009817, 0.36630696, 0.06946421, -0.5254373, -0.11896399) * go_0(0.0, -1.0);
result += mat4(-0.31916487, -0.98911583, 1.0728644, -0.39280394, 0.33458877, -0.17325239, -0.645045, -0.28524077, -0.14512783, 0.24996442, -0.09837877, 0.05468934, 0.31559715, -0.020504637, -0.026724018, 0.24507573) * go_0(0.0, 0.0);
result += mat4(-0.23759829, -0.08530173, -0.16665787, -0.22463752, 0.109896734, 0.13446991, -0.049552456, -0.02385489, -0.01245375, 0.3833208, 0.05758832, 0.1528937, 0.0501858, -0.19651426, 0.0076587177, -0.03297025) * go_0(0.0, 1.0);
result += mat4(0.14554465, -0.01826686, 0.10284085, -0.19152659, -0.017585073, -0.05511482, 0.06362406, 0.023924058, -0.0018977845, -0.103172876, 0.03287086, -0.20085956, 0.36062446, 0.10749464, -0.20984372, 0.018256644) * go_0(1.0, -1.0);
result += mat4(-0.005534592, 0.3709197, -0.18287498, 0.1720451, 0.030155553, -0.023265475, 0.0058617783, -0.031765483, 0.037328955, -0.2730994, 0.35090837, -0.3269043, -0.028477207, 0.32756507, -0.15989502, 0.12158258) * go_0(1.0, 0.0);
result += mat4(0.10873739, 0.19583772, 0.060394943, 0.09410379, -0.04739245, 0.026561242, 0.022990001, 0.1093272, -0.01071349, -0.022938967, -0.046423864, 0.2385325, -0.0319821, 0.046962265, 0.09081178, -0.11001857) * go_0(1.0, 1.0);
result += mat4(0.13012704, 0.112289295, 0.030790284, -0.050499484, 0.11784853, 0.08107028, -0.07556717, -0.15643, 0.015249331, 0.015299608, 0.07748125, 0.054485757, 0.044857923, 0.12161275, -0.048292994, -0.033995003) * go_1(-1.0, -1.0);
result += mat4(0.12931514, 0.15114146, 0.070513315, 0.11246343, 0.4142387, 0.213479, -0.5439916, 0.07776645, 0.13109331, 0.2021147, 0.25932786, -0.22157331, 0.02377734, -0.014970623, -0.1943276, 0.18440372) * go_1(-1.0, 0.0);
result += mat4(-0.22365458, -0.19829084, -0.06881161, -0.06468993, 0.17202774, 0.0048758537, -0.09235021, 0.18941896, 0.064125344, -0.09067088, 0.09748182, 0.13561936, -0.05876288, -0.0122420965, -0.054380875, -0.17743628) * go_1(-1.0, 1.0);
result += mat4(0.18582906, -0.09263032, -0.08210888, -0.20515606, 0.11484005, 0.08557595, 0.0009253741, -0.051202174, -0.18535301, -0.1529345, -0.13092944, 0.03770747, -0.020947013, 0.19187425, -0.15494856, -0.048979875) * go_1(0.0, -1.0);
result += mat4(-0.38131633, 0.4278787, 0.19763695, 0.27655518, -0.08711912, 0.07374453, -0.064803004, 0.5983854, 0.2361923, -0.057221692, -0.37138999, -0.24259573, 0.13890724, 0.25706333, -0.54021406, 0.08095518) * go_1(0.0, 0.0);
result += mat4(0.0991328, -0.022651536, -0.029148921, -0.009812537, -0.09523686, -0.15704902, 0.052389514, 0.21561539, 0.1950314, -0.08572602, 0.0016523858, 0.14125621, -0.030999828, 0.12009709, 0.0373512, -0.105043754) * go_1(0.0, 1.0);
result += mat4(-0.11251988, 0.12106985, 0.011923068, 0.3662747, 0.004800994, 0.017972551, 0.004761366, -0.07934206, -0.13755941, -0.022852683, 0.1502225, 0.009758547, -0.16964264, 0.00984782, 0.07855833, 0.035730787) * go_1(1.0, -1.0);
result += mat4(0.01964957, -0.27226487, 0.033933397, -0.117632054, -0.009058229, 0.047830686, -0.01125145, 0.136628, 0.0056388285, 0.3028781, -0.12286517, 0.23498532, -0.009319075, -0.444048, 0.16174883, -0.06367683) * go_1(1.0, 0.0);
result += mat4(0.02343933, -0.010915871, -0.058680378, -0.21886891, -0.010750894, -0.06671997, 0.0602906, -0.07903071, 0.066891186, 0.06650588, 0.14362891, -0.101870626, 0.02264628, -0.06940821, -0.077616625, 0.110911585) * go_1(1.0, 1.0);
result += vec4(0.032014452, -0.020821465, 0.0826416, -0.002838458);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.06963679, -0.07560548, -0.069522075, 0.0038078027, -0.08002613, 0.13671301, 0.084461786, -0.039376218, 0.19136548, -0.123174496, 0.26566333, -0.16583005, -0.18664864, -0.023539122, -0.21928434, -0.026818147) * go_0(-1.0, -1.0);
result += mat4(0.16660932, -0.18558703, 0.37230486, 0.118128106, -0.14098641, 0.14659132, -0.22217897, 0.12952235, -0.4139033, -0.04308319, 0.12885277, -0.17986743, -0.23556231, -0.08351981, -0.43240538, 0.019033253) * go_0(-1.0, 0.0);
result += mat4(-0.18008037, -0.04448665, 0.011906908, -0.023056917, 0.18136618, -0.04723555, -0.0050158803, -0.14823224, -0.2105281, 0.023047728, -0.14040631, -0.03178526, -0.13477588, -0.01820428, 0.058358394, 0.23792502) * go_0(-1.0, 1.0);
result += mat4(0.07363309, -0.061728477, 0.03573137, -0.0050971056, -0.012813505, -0.17236637, 0.1697835, 0.055788577, -0.22263195, 0.10324512, 0.58971673, -0.4872246, -0.1555681, 0.032747746, -0.096495196, 0.070196226) * go_0(0.0, -1.0);
result += mat4(0.14174286, 0.099460006, -0.088765986, 0.58350676, -0.025177564, -0.46004987, 0.37007022, -0.11437029, -0.5164534, -0.60465246, 0.38859612, -0.32846406, 0.050266482, -0.20334712, 0.18316261, -0.19327633) * go_0(0.0, 0.0);
result += mat4(-0.09377763, -0.0012762006, -0.028991895, -0.26523829, 0.20173682, 0.037923716, -0.03174243, 0.07103378, -0.10764164, -0.30752546, 0.20556998, -0.1892279, 0.08115748, -0.023550175, -0.07627362, 0.11746628) * go_0(0.0, 1.0);
result += mat4(-0.06998859, -0.017997518, 0.069938794, -0.14943017, -0.14179112, 0.16643842, -0.110231474, 0.08895815, -0.24074875, 0.3277253, -0.07435203, -0.23452802, 0.039962552, -0.07145652, -0.022511544, -0.04571222) * go_0(1.0, -1.0);
result += mat4(-0.059785757, -0.23771374, -0.030571314, 0.25222278, 0.106601834, 0.34398326, 0.14511436, -0.03867526, -0.38982397, -0.11944689, 0.12997924, -0.13079585, 0.005729482, 0.012653905, -0.063693404, 0.09632285) * go_0(1.0, 0.0);
result += mat4(-0.04933823, 0.0547175, 0.050636575, -0.10060694, 0.1344485, 0.19752938, -0.100068115, -0.028829506, -0.14096203, -0.079092234, 0.092109434, 0.011606209, -0.04052607, -0.008347507, 0.06956573, -0.028109524) * go_0(1.0, 1.0);
result += mat4(0.21918017, -0.11115073, 0.2262453, -0.06889667, -0.11256312, -0.07438075, -0.088454485, 0.13672407, -0.06905764, 0.08128395, 0.016103368, 0.050190717, 0.09691516, 0.05845721, 0.4886816, 0.041121427) * go_1(-1.0, -1.0);
result += mat4(-0.3449472, 0.09711974, -0.13881907, -0.018265123, 0.27855873, -0.07030004, 0.29545054, 0.37216932, 0.08657718, 0.099066615, -0.10574013, -0.17667885, -0.14855732, -0.11351448, 0.66945946, 0.11312157) * go_1(-1.0, 0.0);
result += mat4(0.2526151, -0.04594331, -0.06606611, 0.09104881, 0.06857995, -0.075284235, -0.17664689, 0.21578754, 0.0696524, 0.09142951, 0.080997564, -0.0682772, -0.0011445724, -0.11736295, 0.2519232, -0.101926275) * go_1(-1.0, 1.0);
result += mat4(-0.12913518, 0.058357026, 0.195421, -0.15651494, 0.2877076, 0.0033844314, -0.07831594, 0.052855384, -0.031295884, 0.03301088, -0.18408822, 0.06732994, 0.23742151, -0.12568143, 0.22810535, -0.11545694) * go_1(0.0, -1.0);
result += mat4(-0.49203303, -0.22656603, 0.1723193, -0.51250046, -0.09742038, 0.758559, -0.3387505, -0.6193586, 0.14136684, 0.27679884, -0.050113205, 0.31041816, -0.36475047, -0.48746544, 0.3233227, 0.4579754) * go_1(0.0, 0.0);
result += mat4(0.46636763, 0.1507748, -0.2581362, 0.15413165, -0.17160143, 0.14256273, -0.074575804, -0.099299066, -0.0017214464, 0.13778336, -0.07378213, -0.15489665, -0.10533715, -0.0011083825, 0.39584312, 0.0023906573) * go_1(0.0, 1.0);
result += mat4(0.026959421, -0.06391859, 0.0034752619, 0.14521928, -0.0010877338, -0.032619733, 0.005375293, -0.018952755, 0.03381545, -0.007652831, 0.034141563, 0.046016496, 0.11219674, 0.030913852, 0.077403754, 0.17192438) * go_1(1.0, -1.0);
result += mat4(0.040326044, 0.17290725, -0.1220239, -0.09594783, -0.025229257, 0.17913155, -0.26623353, -0.033396784, -0.03075146, 0.009143897, -0.0136083895, -0.13886899, 0.075683735, -0.11584183, 0.22182357, 0.19350322) * go_1(1.0, 0.0);
result += mat4(0.15726025, -0.10215694, -0.060057458, 0.26487043, -0.04075552, -0.016496127, 0.0015382086, 0.108562306, 0.026795091, 0.0441233, -0.08754318, -0.0460157, 0.048422016, 0.14107347, 0.07986661, 0.1047697) * go_1(1.0, 1.0);
result += vec4(0.0766796, 0.08115133, -0.05703058, 0.14025708);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_2_tf
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.18038331, 0.21830973, -0.10019419, -0.022745568, -0.14944611, -0.15669158, 0.46361133, -0.07289843, 0.02976627, -0.09000817, 0.113060996, 0.05635241, 0.012762965, -0.022688959, 0.01629751, 0.061114635) * go_0(-1.0, -1.0);
result += mat4(0.024338024, -0.10004009, -0.13709056, -0.0851965, 0.23927099, -0.024349794, -0.16574804, 0.084686354, -0.047885604, 0.09688507, -0.12733915, 0.06980246, 0.11480734, 0.014669346, -0.07505829, 0.04676309) * go_0(-1.0, 0.0);
result += mat4(0.054203495, 0.011881634, -0.036115017, -0.0686298, -0.13682245, -0.15678032, 0.057050128, -0.03368558, 0.13011025, 0.033391044, -0.09841339, -0.027057761, -0.18701133, 0.20852546, -0.13660902, 0.0005817616) * go_0(-1.0, 1.0);
result += mat4(-0.08077834, 0.35952288, -0.07647382, -0.0033230998, 0.13929126, -0.09155619, 0.14128102, 0.16005981, 0.18161216, -0.09485738, 0.0029118075, 0.052682754, 0.03242074, 0.08299826, 0.073796146, -0.06446532) * go_0(0.0, -1.0);
result += mat4(-0.36655015, 0.4606936, 0.19073649, 0.31655258, -0.006838053, -0.579939, 0.089126326, -0.14021218, -0.3437716, 0.16714323, 0.17705944, -0.22418492, -0.3883696, -0.2302651, 0.2581861, 0.21983066) * go_0(0.0, 0.0);
result += mat4(0.0992383, -0.014257871, -0.023896435, 0.19868234, 0.0408007, 0.07995299, 0.16102871, -0.11668251, 0.22458278, -0.05587917, 0.19373615, -0.016202094, -0.25106144, 0.15634494, 0.11624891, -0.2930768) * go_0(0.0, 1.0);
result += mat4(0.024616942, 0.36248252, -0.14779098, -0.019894283, -0.007111256, 0.010641561, -0.09541178, 0.21236233, 0.009501827, 0.08132797, -0.13983901, 0.027207611, 0.038444366, -0.013995817, -0.16242191, 0.03294123) * go_0(1.0, -1.0);
result += mat4(0.0131698875, -0.18124102, -0.13503514, -0.06099072, 0.07422735, -0.20906176, -0.049005672, 0.08739405, -0.031758767, -0.1978915, 0.23094437, 0.54512614, 0.21338555, -0.011205669, -0.23727885, -0.29533875) * go_0(1.0, 0.0);
result += mat4(-0.0010255767, -0.07168225, -0.033568826, 0.22161655, -0.087293416, 0.11350447, 0.13653576, 0.061226424, -0.13074352, 0.058425818, 0.038460605, 0.2749964, -0.012814839, 0.085885845, -0.038151987, -0.17960808) * go_0(1.0, 1.0);
result += mat4(0.19728905, -0.040724937, -0.18270236, 0.046735186, 0.03507326, 0.119867206, -0.12691991, 0.18119748, -0.052895024, 0.11348764, -0.043787055, 0.004703516, 0.006752757, -0.06939761, -0.009801806, -0.075640485) * go_1(-1.0, -1.0);
result += mat4(0.051735226, 0.1732299, -0.10672899, 0.0320877, -0.4913656, 0.2102274, 0.43920282, 0.059108034, 0.08349019, -0.16517872, 0.15436842, -0.1075667, 0.022741623, -0.26693836, 0.3645307, 0.017874828) * go_1(-1.0, 0.0);
result += mat4(0.034464058, 0.014929155, 0.054227423, 0.14167373, -0.0023630706, -0.08904212, 0.11918041, -0.034539603, 0.06048089, -0.06807333, 0.14447778, 0.035260547, 0.09979546, -0.1924939, 0.14596114, -0.12069667) * go_1(-1.0, 1.0);
result += mat4(-0.04427228, -0.23673469, 0.010357103, -0.2907043, -0.06845721, -0.078984015, 0.06867713, -0.058163825, -0.12154615, 0.08430951, 0.1922373, 0.030108064, -0.43081748, -0.38715646, -0.022240646, -0.15403675) * go_1(0.0, -1.0);
result += mat4(0.46885306, -0.33421394, -0.6695223, -0.41841158, 0.30317923, 0.24244753, -0.1047785, -0.18656285, 0.06261881, -0.4405616, 0.24233986, 0.40070608, 0.81440526, 0.11305212, -0.8826317, -0.023478031) * go_1(0.0, 0.0);
result += mat4(-0.07879348, -0.024378026, -0.041883785, -0.17030984, 0.23229122, -0.011237109, 0.12058088, 0.20766267, -0.36519575, 0.09599417, -0.1271098, 0.06990154, 0.21161246, 0.041002538, -0.36046275, 0.007304667) * go_1(0.0, 1.0);
result += mat4(0.10873893, 0.003872542, -0.13476561, -0.036068805, -0.054637462, 0.02304618, 0.04707738, -0.2856381, 0.07124422, 0.010866545, 0.20484549, -0.008342406, -0.43660247, -0.041055538, 0.33536008, -0.060022205) * go_1(1.0, -1.0);
result += mat4(0.1966458, 0.0016302796, -0.25712642, -0.09639119, -0.006955351, 0.10882133, 0.1107341, 0.062697805, -0.1074494, 0.17361663, 0.6429869, -0.39846307, -0.26302996, 0.048710946, 0.40387508, 0.4299715) * go_1(1.0, 0.0);
result += mat4(0.18948616, 0.24086732, -0.064474985, -0.11069709, 0.1279659, -0.13438123, -0.028438117, 0.125883, 0.018153818, -0.21942288, 0.020390838, -0.22797634, -0.10821287, -0.17175092, 0.122016855, 0.20699544) * go_1(1.0, 1.0);
result += vec4(-0.05101961, -0.060740646, -0.024465766, 0.058471628);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_3_tf
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.14533128, 0.07266841, 0.13238011, -0.23328504, 0.031516243, 0.058471266, -0.06394412, 0.090752736, -0.0042359144, 0.12357294, -0.04377495, 0.0011743477, 0.05412243, -0.08146249, 0.04002749, -0.032876283) * go_0(-1.0, -1.0);
result += mat4(-0.036972385, -0.15238069, -0.3453321, -0.36025128, 0.07597202, -0.02368151, -0.3889606, 0.34607083, 0.3133179, -0.21712309, -0.4210954, 0.21450534, 0.15226828, 0.25326282, 0.45327064, -0.3350824) * go_0(-1.0, 0.0);
result += mat4(0.019018406, -0.33060563, -0.092601225, 0.14970545, 0.1441509, -0.19228427, -0.032771986, 0.26331595, 0.052981265, -0.06627376, -0.08634131, 0.038706224, 0.13403937, -4.4842476e-05, 0.049002815, -0.12719193) * go_0(-1.0, 1.0);
result += mat4(0.17527401, -0.0035254909, -0.047959115, -0.4526988, -0.07510284, 0.0013256798, -0.07539148, 0.24220634, -0.08708839, -0.14494033, -0.17085724, -0.099797316, 0.0068515535, -0.08918779, 0.27164719, -0.1702649) * go_0(0.0, -1.0);
result += mat4(0.31848368, 0.48983255, -0.44140294, -0.65174145, -0.004199057, 0.19494705, 0.5196497, -0.027118586, 0.032509074, -0.23900363, -0.14489244, 0.36314297, -0.23168536, -0.20960593, 0.61471456, 0.12401275) * go_0(0.0, 0.0);
result += mat4(-0.24317405, 0.21560913, 0.15564032, 0.11606844, -0.15039803, -0.59578896, 0.14100945, -0.026194477, 0.37237462, -0.49472088, -0.15215331, -0.38820064, -0.25089455, -0.29643852, -0.09513793, 0.019779462) * go_0(0.0, 1.0);
result += mat4(0.12498539, 0.0710632, -0.25012368, -0.2272255, -0.08647026, 0.12277892, 0.011025097, -0.12168395, -0.13489573, 0.016708186, -0.15583871, -0.057124946, 0.1216943, 0.019803725, 0.06952334, -0.032985855) * go_0(1.0, -1.0);
result += mat4(0.28794885, 0.33783793, -0.14469545, -0.081780486, -0.50320613, -0.067601606, -0.06847453, -0.021648854, -0.34295765, 0.15071863, -0.06619896, -0.084465064, 0.31909832, 0.015414661, 0.14930317, -0.11295768) * go_0(1.0, 0.0);
result += mat4(0.24530606, 0.25526014, 0.09971985, -0.07749641, -0.2361951, -0.07997673, 0.03617294, 0.02959561, -0.4498983, -0.014073485, -0.20587012, 0.06396779, 0.1262825, 0.027433183, 0.14469334, 0.011538011) * go_0(1.0, 1.0);
result += mat4(-0.038572453, -0.023108613, -0.039481267, -0.012160024, -0.004521989, -0.028665857, 0.04295255, 0.10580258, 0.05439479, -0.072261885, 0.11030243, 0.08934696, 0.09133867, 0.017547369, 0.097613186, 0.05491059) * go_1(-1.0, -1.0);
result += mat4(-0.09972817, 0.057730395, 0.12665828, 0.32861367, -0.16186063, 0.0745509, 0.2394045, -0.08687853, -0.034404907, -0.05843572, 0.0684561, -0.1355754, 0.19248672, -0.60372186, 0.12583947, 0.4388962) * go_1(-1.0, 0.0);
result += mat4(0.10341107, 0.061113223, 0.08773817, -0.082504354, -0.16612078, 0.2681751, 0.019737698, -0.17122322, -0.135949, 0.3048101, 0.087803006, 0.11373851, 0.013192192, -0.27022064, 0.35529897, -0.15321451) * go_1(-1.0, 1.0);
result += mat4(-0.032835662, 0.11123062, -0.11322452, -0.17300649, 0.04680824, 0.12849288, 0.17269878, -0.048671383, 0.05189037, -0.009078046, 0.22105052, 0.013008137, -0.009738674, 0.15391739, 0.20969556, 0.14189166) * go_1(0.0, -1.0);
result += mat4(-0.47377753, 0.3038031, 0.18604809, 0.1931698, -0.2964668, -0.12287907, -0.7107761, 0.26619422, -0.33923018, 0.19200724, 0.013786281, -0.17496964, 0.079325035, -0.3694445, 0.0054486147, -0.33018264) * go_1(0.0, 0.0);
result += mat4(0.14903802, -0.028043179, 1.5238678e-05, 0.021232028, 0.16025065, 0.14746875, -0.22831628, -0.12177345, 0.038778774, 0.32188168, -0.042017702, 0.27155936, 0.17920609, 0.04099755, 0.28527525, 0.074623376) * go_1(0.0, 1.0);
result += mat4(0.057019282, -0.112741895, 0.030361209, 0.14567861, 0.056265317, -0.01573537, -0.06707608, 0.016657263, 0.09829025, -0.026795063, 0.023042196, 0.09438241, -0.025483066, -0.052787006, 0.19730279, 0.021218104) * go_1(1.0, -1.0);
result += mat4(0.19868211, -0.01531125, 0.108596824, -0.035456363, 0.0033609823, 0.057961613, -0.013726211, 0.101742364, 0.33357215, 0.14468077, 0.29711527, -0.24662566, -0.119014986, -0.1899639, 0.11246697, -0.0035374009) * go_1(1.0, 0.0);
result += mat4(-0.05602109, -0.15539522, 0.010730943, 0.057116497, -0.02037749, 0.084210664, -0.028235348, 0.10574697, 0.056925274, 0.07922333, -0.090088, 0.1615985, -0.0044301567, -0.089945644, 0.024176618, -0.041844133) * go_1(1.0, 1.0);
result += vec4(0.0015292584, -0.043625206, -0.09429898, -0.06280405);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_4_tf
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.06051604, -0.028152643, -0.21418124, 0.13032125, 0.42565975, -0.09571944, -0.34494513, 0.30004, -0.073245734, -0.028659137, 0.0032105136, -0.05009555, -0.048971225, 0.04814533, 0.002843805, -0.046224426) * go_0(-1.0, -1.0);
result += mat4(-0.07495975, 0.018714864, 0.21229684, -0.13614887, 0.79988647, -0.0697328, 0.38232988, 0.24165109, 0.25947478, -0.0009418982, -0.17369923, 0.10007766, 0.024117598, 0.028611807, 0.15090801, -0.06344829) * go_0(-1.0, 0.0);
result += mat4(-0.07982219, 0.0900347, 0.007609254, -0.0034791247, 0.013611781, -0.13560618, 0.09685799, 0.06276075, 0.134693, -0.14370437, -0.25175703, -0.0016138123, -0.0075672898, -0.13325731, -0.061100446, 0.0059743375) * go_0(-1.0, 1.0);
result += mat4(-0.039018434, -0.19668463, -0.43018532, 0.31886247, 0.4965479, 0.114569925, 0.19110382, 0.27343535, 0.0707728, -0.11877004, -0.25827697, 0.37012872, 0.1474777, 0.07056952, -0.14965728, 0.061595406) * go_0(0.0, -1.0);
result += mat4(0.506543, -0.16268773, 0.455319, -0.0702646, 0.70102173, -0.14041683, 0.70184857, 0.4817842, -0.3389246, -0.14463086, 0.13763213, -1.1259074, 0.47722015, 0.38352612, -0.04293366, -0.5604627) * go_0(0.0, 0.0);
result += mat4(0.17606944, 0.15897374, 0.13499324, 0.29241478, -0.032824475, 0.11128662, -0.22204424, -0.051803727, 0.013195331, -0.42040786, -0.3950585, 0.70745844, 0.38646924, -0.19080774, -0.15171832, -0.10742828) * go_0(0.0, 1.0);
result += mat4(-0.039278325, 0.18421806, -0.044948544, 0.07902063, -0.2149251, 0.09913459, -0.09743655, -0.26899317, -0.002695496, -0.07554527, -0.22373366, 0.17830558, -0.047994815, -0.06789183, -0.06755918, -0.104452066) * go_0(1.0, -1.0);
result += mat4(-0.0493473, -0.30411786, -0.056439694, -0.06582185, -0.21309847, 0.100670904, -0.22966193, -0.045954112, 0.12728062, -0.25081897, -0.094699375, -0.4036555, 0.060854495, -0.64373237, -0.21522263, -0.6683476) * go_0(1.0, 0.0);
result += mat4(0.063481025, 0.11744312, -0.043330096, 0.33817932, -0.06679828, -0.23207302, -0.10188898, -0.10590511, 0.058780864, 0.047292337, -0.11834696, 0.10076128, -0.036641665, 0.30200714, -0.0002892557, -0.10303763) * go_0(1.0, 1.0);
result += mat4(-0.10842604, 0.042055763, 0.29702973, -0.07409644, -0.030164458, -0.012098744, -0.06396587, -0.08787527, 0.051854923, 0.12997511, 0.11468497, 0.15022379, 0.007814715, 0.014517445, 0.025484756, 0.01078619) * go_1(-1.0, -1.0);
result += mat4(-0.29229385, 0.040265664, -0.15376821, 0.075579196, -0.05593569, -0.045405343, 0.12099204, 0.1571252, 0.17841713, 0.04673325, 0.14550509, 0.08603346, -0.049786013, 0.06121843, -0.16273825, -0.13857752) * go_1(-1.0, 0.0);
result += mat4(0.06903744, 0.2628764, -0.13582836, -0.35678583, -0.13821034, -0.019381443, -0.19570538, -0.09298511, 0.08965436, 0.09745909, 0.20055099, 0.024967568, 0.08144204, 0.004633625, 0.12809834, -0.009431525) * go_1(-1.0, 1.0);
result += mat4(0.09784006, 0.010729353, 0.046643205, -0.110926524, -0.21556224, 0.00016300633, 0.122175336, 0.15004392, 0.013864355, 0.24767809, 0.13865592, 0.0155424485, -0.1450483, -0.15688781, -0.06195043, -0.13745981) * go_1(0.0, -1.0);
result += mat4(0.018991318, 0.55401963, 0.11709872, -0.028442185, -0.46035343, -0.10215539, -0.60193926, 0.47882316, -0.23346989, 0.037200127, 0.22814943, -0.08231696, -0.36430013, -0.011152757, 0.48752213, 0.29796222) * go_1(0.0, 0.0);
result += mat4(-0.07258066, -0.023222538, 0.23230423, -0.30317304, 0.03942911, -0.06899803, 0.23778579, 0.07418621, -0.17443737, 0.33387753, 0.007354842, -0.123447575, -0.1745315, 0.11071779, -0.11949625, -0.22832453) * go_1(0.0, 1.0);
result += mat4(-0.024909232, -0.0308135, 0.12170621, -0.13298757, 0.045828197, -0.1532345, -0.06633672, 0.23591088, 0.04964077, 0.14091493, 0.038343724, -0.029780807, 0.05762822, -0.048930667, -0.02434709, 0.07109019) * go_1(1.0, -1.0);
result += mat4(-0.16039175, 0.3004474, -0.17278233, 0.13677922, 0.18838613, 0.15054552, 0.32901475, -0.1288333, 0.26378244, -0.05119892, 0.34533516, 0.25180495, 0.19452183, 0.0843233, -0.08029368, 0.39877903) * go_1(1.0, 0.0);
result += mat4(-0.07097129, -0.26492423, -0.055032317, -0.093516104, -0.11795062, 0.04086253, -0.07989471, 0.059686553, 0.09378249, 0.45851848, 0.2510942, 0.19599153, 0.019765077, -0.02920918, -0.04125142, -0.13859107) * go_1(1.0, 1.0);
result += vec4(0.04400571, -0.04015565, 0.0140529545, 0.05474095);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_5_tf
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.014236042, -0.0031431736, -0.1551387, 0.12515116, -0.28528872, 0.36161992, 0.15750743, -0.17111474, 0.13792591, -0.0657419, -0.17471549, 0.14650472, 0.034169197, -0.019157575, 0.23520657, -0.20358163) * go_0(-1.0, -1.0);
result += mat4(0.02015035, 0.12993371, 0.11199667, -0.09854378, 0.5001741, 0.03462961, 0.24919736, 0.08505297, -0.20902094, -0.24141377, -0.15360375, 0.049974803, -0.037157424, -0.048510186, 0.20106035, -0.118480384) * go_0(-1.0, 0.0);
result += mat4(0.086798504, -0.009607818, 0.034812123, -0.005187592, 0.0351509, 0.021755, -0.04996161, -0.041231696, 0.0020545553, 0.015730752, -0.07507172, 0.018597523, -0.02393343, 0.07624775, 0.03892451, -0.0025574185) * go_0(-1.0, 1.0);
result += mat4(0.035725456, 0.06809103, 0.51926994, -0.39983147, -0.16402833, -0.1243394, -0.25922915, 0.28285915, 0.15959994, -0.2351732, 0.2650535, -0.30193794, -0.11468332, 0.050777763, -0.51894253, 0.4408367) * go_0(0.0, -1.0);
result += mat4(-0.27042082, 0.22243942, 0.14902467, 0.38428563, 0.46612173, 0.5169912, -0.22330502, -0.11300288, -0.36141354, 0.0668681, 0.2984152, 0.1275798, -0.24121419, 0.2952039, -0.45109174, -0.3822957) * go_0(0.0, 0.0);
result += mat4(0.26543504, -0.05742226, -0.052103903, -0.013124308, -0.14358385, -0.04024543, 0.07665455, -0.012301872, -0.18752757, -0.03913891, 0.038205814, -0.006583095, -0.25550908, -0.25725332, -0.12454206, -0.0058936924) * go_0(0.0, 1.0);
result += mat4(-0.0018946569, 0.019746022, -0.13080788, 0.11450627, -0.013743845, -0.027179785, -0.14425103, 0.07109661, 0.023703793, 0.086905524, 0.03151253, 0.0132474145, 0.041018624, 0.04548913, 0.2718715, -0.20008296) * go_0(1.0, -1.0);
result += mat4(-0.076830454, 0.11652955, 0.5068201, -0.3082819, 0.058615055, -0.006765798, -0.057522714, 0.049981344, -0.006897243, -0.21763432, 0.16896053, -0.21176189, -0.061227098, 0.03566485, 0.08901554, -0.050980624) * go_0(1.0, 0.0);
result += mat4(0.02327798, 0.07662976, 0.034811985, -0.03238033, -0.0021881019, -0.030997375, -0.069672935, 0.04040273, -0.1217442, 0.104173124, 0.09862539, 0.020557549, -0.022286594, 0.10287763, -0.021694934, 0.07542515) * go_0(1.0, 1.0);
result += mat4(0.124069154, -0.08579466, -0.07816314, 0.11332851, -0.034682628, -0.11038275, 0.04750615, -0.096100725, 0.039588403, -0.15149672, -0.05529172, 0.034304325, -0.022520235, -0.05023852, -0.2674731, 0.21886522) * go_1(-1.0, -1.0);
result += mat4(-0.1948599, -0.14946899, -0.39548838, 0.18042913, -0.007919619, 0.19826505, 0.23789087, 0.009140256, 0.11857748, 0.18215668, 0.13606293, -0.09209675, -0.080678545, -0.020431137, -0.07728839, -0.051353537) * go_1(-1.0, 0.0);
result += mat4(-0.07616472, -0.0032800382, -0.045657665, -0.039144326, -0.37786487, -0.08877774, 0.053579114, -0.070886396, 0.011311804, 0.107276045, 0.013236154, 0.009832061, 0.08292063, 0.12258811, 0.0005569043, -0.009806432) * go_1(-1.0, 1.0);
result += mat4(-0.28062925, 0.15946878, -0.1021801, -0.06471589, -0.26999477, 0.21230288, -0.14243907, 0.2555922, -0.09608517, 0.26339412, 0.20891234, -0.23538485, 0.33958244, -0.12569186, 0.43289876, -0.33462036) * go_1(0.0, -1.0);
result += mat4(0.16265294, 0.2625464, -0.34452894, 0.2233622, 0.13850005, -0.42999864, -0.5385177, -0.11035979, 0.51662, -0.78238726, -0.09422375, 0.83759475, 0.44468537, 0.14301361, 0.108906105, 1.1596143) * go_1(0.0, 0.0);
result += mat4(-0.73757625, -0.12369605, 0.23523071, 0.006587637, -0.15445381, 0.22757277, 0.052819528, 0.10183905, -0.07912228, -0.16998893, -0.13360223, 0.014348178, -0.17778571, -0.41047302, 0.10241381, -0.08526306) * go_1(0.0, 1.0);
result += mat4(0.14712952, 0.048995696, 0.05299946, -0.06817572, 0.1498064, -0.079825334, 0.40354064, -0.31789717, -0.1998377, 0.00955295, -0.32318407, 0.30898204, -0.039571725, -0.026203401, -0.16292085, 0.08574385) * go_1(1.0, -1.0);
result += mat4(-0.6353329, -0.56000775, -0.17279743, 0.18198174, -0.19555812, 0.056538377, 0.34365895, -0.07799055, 0.19011354, -0.13952748, 0.029196098, -0.19596763, -0.069196045, -0.17402656, 0.07948411, -0.016226962) * go_1(1.0, 0.0);
result += mat4(0.25592864, 0.083498634, -0.28515807, 0.10789751, 0.0043962947, 0.07085363, 0.048724182, -0.025131436, -0.0049440865, -0.033094388, -0.032935806, 0.04266025, 0.20026933, 0.0927841, -0.006839351, -0.013012285) * go_1(1.0, 1.0);
result += vec4(0.02021373, 0.0014037411, 0.0012718709, 0.017278494);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x1x1x56
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_1_tf
//!BIND conv2d_2_tf
//!BIND conv2d_3_tf
//!BIND conv2d_4_tf
//!BIND conv2d_5_tf
//!BIND conv2d_6_tf
//!SAVE conv2d_last_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.0067711817, 0.08160003, 0.0247279, 0.03084815, -0.026977416, -0.02120602, -0.025078611, -0.029852165, -0.011627478, -0.012742972, 0.022736797, -0.0028815821, -0.007515677, 0.0172887, -0.023259213, 0.009608947) * g_0;
result += mat4(-0.028660107, -0.014015208, -0.027838672, -0.013171922, 0.0029435428, 0.027047642, -0.017478354, 0.022834882, -0.037572853, -0.0034044068, -0.0149029335, -0.013362301, 0.009827443, -0.015742151, -0.0074795415, -0.0022266617) * g_1;
result += mat4(-0.07579662, -0.039754186, -0.066026606, -0.046816852, 0.1099032, 0.043956704, 0.073109835, 0.04680284, -0.06896613, -0.008838632, -0.044584926, -0.01319039, -0.0021152915, -0.04503326, 0.027061926, -0.028334105) * g_2;
result += mat4(0.15458213, 0.059769996, 0.09327123, -0.028782733, 0.023459995, -0.15390377, -0.13432898, -0.1127775, 0.072764635, -0.0020463336, 0.034736466, -0.0012086042, -0.05847183, -0.029952323, 0.052969377, 0.09590908) * g_3;
result += mat4(-0.07476772, -0.016574614, 0.04131183, 0.017335678, 0.009654406, 0.072183535, -0.002266456, 0.086873695, 9.310129e-05, 0.0056416965, -0.004188391, 0.023132093, -0.05183336, -0.025825873, -0.03684392, -0.0075729224) * g_4;
result += mat4(0.00878842, 0.03869637, -0.035759524, 0.003345386, -0.064184256, -0.034568302, -0.06672922, -0.0686381, -0.06794392, -0.10685906, 0.04679947, -0.012535639, 0.006932529, -0.007783515, 0.109123886, 0.13804391) * g_5;
result += mat4(-0.03160699, 0.050473, -0.09030729, 0.0649397, 0.11466501, 0.17912874, -0.0081851315, 0.052244574, 0.051632743, 0.061941486, 0.06546816, 0.12174249, -0.05104755, -0.018193979, -0.032196652, -0.035292786) * g_6;
result += mat4(0.013612735, -0.0024100312, -0.068611205, -0.07369285, -0.019647537, -0.066944756, -0.010012875, -0.06785739, -0.062246565, -0.087313406, -0.044278186, -0.09368995, 0.052555013, 0.13604961, 0.05645059, 0.08763303) * g_7;
result += mat4(0.04218486, -0.05028401, 0.059086576, -0.03545452, 0.027737848, 0.0043074046, 0.0011001764, -0.073026665, -0.04094988, 0.044061556, -0.009812515, 0.06841999, -0.06612581, 0.037223976, -0.07759491, -0.04356598) * g_8;
result += mat4(-0.027558247, 0.014248466, -0.019813016, -0.058107473, -0.016717663, -0.020424338, 0.0053625097, -0.009917319, 0.013678771, 0.0113340765, 0.0061787106, -0.036083996, -0.020179711, -0.011310535, 0.054827053, -0.0008278952) * g_9;
result += mat4(0.028690035, -0.012079616, 0.11931408, -0.048533775, 0.069336995, 0.0049852817, 0.013774468, 0.035233382, -0.07384821, 0.0003354423, -0.0059171803, -0.04503906, 0.08727279, 0.005138857, -0.17724465, 0.055782065) * g_10;
result += mat4(-0.20744391, 0.24348328, -0.3145766, 0.17026486, -0.022870807, -0.01648648, -0.05912279, -0.012555373, -0.066004686, 0.03182394, 0.16285324, -0.1221846, -0.31816196, 0.007928748, 0.43180224, -0.015949022) * g_11;
result += mat4(0.16363169, 0.14781676, -0.2377973, -0.1571377, -0.09038187, 0.0046504294, 0.033955004, -0.051421452, 0.046735536, 0.006827522, -0.121338, 0.12671822, 0.15833299, -0.1858712, -0.1942371, 0.17336044) * g_12;
result += mat4(-0.018145572, -0.015550516, 0.044410378, 0.046016492, 0.084021375, 0.05327457, -0.008270992, -0.045435544, 0.07185879, -0.131923, 0.26721445, -0.26745328, -0.07093472, 0.042701527, 0.13793674, -0.095621444) * g_13;
result += vec4(0.016836504, 0.010161949, 0.021351453, 0.01278978);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Depth-to-Space
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_last_tf
//!SAVE MAIN
//!WIDTH conv2d_last_tf.w 2 *
//!HEIGHT conv2d_last_tf.h 2 *
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
vec4 hook() {
vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);
ivec2 i0 = ivec2(f0 * vec2(2.0));
float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];
float c1 = c0;
float c2 = c1;
float c3 = c2;
return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,158 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.0057322932, 0.12928207, -0.056848746, 0.18680117, -0.0306273, 0.25602463, 0.053723164, 0.20419341, 0.0018709862, 0.022848232, -0.04105527, 0.10169034, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.009471417, -0.12957802, 0.096014425, 0.21836184, 0.00021601951, -0.22997683, 0.23666254, 0.41192335, 0.021762101, 0.0047863554, 0.008233427, 0.108514786, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.01156376, -0.18988979, 0.04614705, -0.044767227, 0.01050636, -0.26426336, 0.23741047, 0.0027636609, -0.027718676, -0.14202335, -0.016650287, -0.06637125, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.057809234, -0.11033858, 0.056533534, -0.06292466, 0.13880666, -0.18710336, 0.2441031, -0.25326246, 0.0032683122, -0.026437074, 0.0023248852, 7.640766e-05, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.49110603, 0.4429004, -0.44015464, -0.41174838, -0.87738293, 0.7808468, -1.0929365, -0.59699076, -0.18409836, 0.185138, -0.11773224, -0.17097276, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.10580959, -0.055947904, -0.03431237, -0.080236495, 0.14862584, -0.15393938, -0.18872876, -0.3170681, 0.03559387, -0.003990826, 0.021298569, 0.012844483, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.040715586, -0.25781113, 0.08896714, -0.1225879, -0.15790503, -0.54010904, 0.29588607, 0.10401059, 0.003413123, -0.108357325, 0.0112870345, -0.11888622, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.0049315444, 0.02376202, -0.08224771, 0.121118225, -0.041512914, -0.027994309, -0.585988, -0.069672115, -0.017247835, 0.0056576864, 0.04319012, 0.055003505, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(0.37521392, 0.15916082, 0.059708964, 0.19046007, 0.8120325, 0.38343868, 0.3436578, 0.5287958, 0.16570656, 0.06957687, 0.014022592, 0.074799836, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(-0.01050964, -0.00939481, 0.17684458, 0.027366742);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.011029496, 0.05866063, -0.09460646, -0.017664742, -0.022488879, 0.18384217, -0.00397663, -0.064733066, 0.08466802, 0.10667488, 8.0212536e-05, 0.0908869, 0.13580276, 0.00097438256, 0.12176522, -0.08218466) * go_0(-1.0, -1.0);
result += mat4(0.16062798, -0.10190268, 0.03280682, 0.05621916, -0.009684231, -0.08464307, 0.17058301, -0.096469186, 0.1967505, -0.1450099, 0.093607284, -0.28240147, -0.21377413, 0.10079291, -0.1741522, 0.17330575) * go_0(-1.0, 0.0);
result += mat4(-0.060160473, 0.06316997, 0.0046929033, -0.049405966, 0.13851729, 0.06830702, -0.0586872, -0.040827133, 0.007052838, -0.03576886, -0.111261636, 0.039155316, -0.07380389, -0.09369825, 0.04471156, 0.09678487) * go_0(-1.0, 1.0);
result += mat4(-0.36683616, -0.035950605, -0.24414362, -0.009159744, 0.19335322, -0.099253505, 0.075083904, -0.00076695543, 0.65291303, -0.25599423, 0.19827642, 0.065899536, -0.07423247, -0.068967685, 0.0050554527, -0.060272824) * go_0(0.0, -1.0);
result += mat4(-0.020688485, -0.83178276, 0.11104878, 0.26454413, 0.13655476, 0.37675047, -0.22219229, -0.01751935, 0.44552696, 0.92510307, 0.16063261, -0.62011045, 0.19366647, -0.06996067, -0.2504841, 0.00803723) * go_0(0.0, 0.0);
result += mat4(0.0051537007, -0.057168536, -0.16110587, 0.25232598, -0.04447099, 0.11997351, 0.14808103, -0.34443566, -0.26212573, -0.21970181, 0.2724405, 0.21050811, -0.07949061, -0.064808235, -0.21208277, -0.0042361654) * go_0(0.0, 1.0);
result += mat4(-0.0888952, -0.20169449, 0.19144905, -0.016882861, -0.013283103, 0.07552998, -0.24686803, 0.012453213, -0.065454446, -0.016123284, -0.47316182, 0.070926026, 0.09219782, 0.13118166, 0.074736096, 0.0077910526) * go_0(1.0, -1.0);
result += mat4(0.5832154, 0.1138069, -0.039765622, 0.3182784, -0.25497997, 0.0013993139, 0.39285088, -0.48511526, -0.39891505, -0.19094779, -0.082146175, -0.20826934, 0.020590555, -0.0012490178, -0.4398621, 0.14377014) * go_0(1.0, 0.0);
result += mat4(0.21917395, 3.4314657e-05, 0.25734863, -0.3433305, 0.015720673, 0.2676127, -0.06807297, 0.15040149, -0.23638041, -0.0050233034, -0.13666134, 0.4542111, -0.033572577, -0.08450588, -0.23341487, 0.053490847) * go_0(1.0, 1.0);
result += mat4(-0.17482175, 0.057647135, 0.33135444, 0.0850751, -0.1718849, -0.0854123, 0.036795795, -0.13874969, -0.10903869, -0.19007301, -0.06064334, -0.03786032, -0.036696054, 0.07844446, 0.012523185, -0.01562906) * go_1(-1.0, -1.0);
result += mat4(-0.04411997, -0.10331819, 0.10050193, 0.12406485, 0.07431592, 0.30109692, -0.17511666, -0.13263564, -0.10192587, 0.07821255, -0.22415096, 0.25552443, 0.17881326, -0.13914281, 0.109979235, -0.0016463579) * go_1(-1.0, 0.0);
result += mat4(-0.01911644, -0.15412527, 0.028903123, 0.20831817, 0.00375175, 0.08110953, 0.074919395, -0.17581624, -0.015677985, 0.06504228, 0.08817818, -0.12518327, -0.09537373, 0.028905088, -0.051288474, 0.054334078) * go_1(-1.0, 1.0);
result += mat4(0.2852779, -0.28924024, 0.36805123, 0.21079305, -0.28336474, 0.1679663, -0.08641141, -0.10699407, -0.16090055, 0.1287612, -0.15910125, 0.05734755, 0.15883245, 0.0053026294, 0.080674745, 0.0505137) * go_1(0.0, -1.0);
result += mat4(0.17639062, 0.3790122, -0.19588692, -0.020314282, 0.26197383, 0.09014768, 0.19696823, -0.41025418, -0.08308115, -0.33279485, -0.22528782, 0.06172439, -0.1365661, -0.13094363, -0.005086559, 0.089024484) * go_1(0.0, 0.0);
result += mat4(0.05262993, 0.0006296959, 0.1657725, -0.32591924, 0.12126701, 0.061543245, -0.10526848, 0.041583937, 0.094976954, 0.09416157, -0.22019257, -0.058390073, -0.2073888, 0.057273377, 0.19558284, 0.004208022) * go_1(0.0, 1.0);
result += mat4(0.30005738, 0.18478931, -0.23342943, 0.22455733, -0.016488122, 0.099634305, 0.31620836, -0.15731157, 0.09595808, 0.0013774688, 0.48273298, -0.07027936, -0.18764344, -0.26194447, -0.11794225, -0.012173601) * go_1(1.0, -1.0);
result += mat4(0.117986746, -0.13846518, -0.019614812, -0.3011192, 0.5501164, 0.3408611, -0.40090847, 0.15706886, 0.13050972, 0.051776595, 0.20792943, 0.23389706, -0.22965533, -0.053367328, 0.3911586, -0.032988597) * go_1(1.0, 0.0);
result += mat4(0.054753624, -0.008485731, -0.2451672, 0.17528129, 0.13657846, 0.010480436, 0.07651423, -0.43316832, 0.12736236, 0.13804524, 0.12529011, -0.30946237, -0.14423579, 0.08403089, 0.24335162, 0.057288036) * go_1(1.0, 1.0);
result += vec4(0.012077211, 0.013045883, 0.0380778, -0.02908858);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.036115196, -0.06971895, -0.07508942, 0.016036168, 0.12120111, 0.24536026, 0.044755507, -0.20663576, 0.029635755, -0.15427187, 0.027148994, -0.20795093, 0.10170582, 0.077919215, 0.66063017, -0.4632968) * go_0(-1.0, -1.0);
result += mat4(-0.0052889925, -0.019060908, -0.08660142, -0.022095207, -0.08097976, -0.015142803, -0.18552722, -0.078493506, -0.16293915, -0.20099808, -0.08370822, 0.3701389, 0.09094984, 0.2487225, 0.24338846, 0.044003833) * go_0(-1.0, 0.0);
result += mat4(-0.061406493, -0.017232792, -0.10917424, 0.11203319, 0.040699825, -0.019294346, 0.084953666, -0.018133596, 0.07209552, 0.016069936, 0.17805555, -0.089537814, 0.15809004, 0.1027023, 0.15044671, -0.15530108) * go_0(-1.0, 1.0);
result += mat4(0.0948676, -0.040305693, -0.005591629, -0.048048403, -0.07547777, 0.056606572, 0.021390207, 0.32600567, -0.20805131, -0.099587254, 0.029613169, 0.0092129605, -0.29429698, -0.09898621, 0.44470885, -0.89487344) * go_0(0.0, -1.0);
result += mat4(-0.122259885, 0.11445877, 0.06666907, 0.1869428, -0.1553992, -0.1658741, 0.2988138, -0.57746625, -0.34609964, 0.11169158, -0.41877756, 0.38075635, 0.21293911, 0.09640372, -0.12754214, -0.08026104) * go_0(0.0, 0.0);
result += mat4(0.15128808, 0.050087795, 0.09219755, -0.18080945, 0.0044571217, -0.046019405, -0.1289922, 0.20305426, 0.19601224, 0.04667917, 0.17465587, 0.027672665, 0.18441725, 0.06845396, 0.11288585, -0.23283863) * go_0(0.0, 1.0);
result += mat4(-0.072962, -0.06639447, 0.049347494, -0.1386401, 0.10396071, 0.08187777, -0.04280746, 0.07390891, 0.06628344, 0.037797406, 0.021885803, -0.013147403, 0.22376558, 0.36243078, 0.12874891, -0.0023783944) * go_0(1.0, -1.0);
result += mat4(0.074945286, 0.16045591, -0.11798349, 0.12910712, 0.054760084, -0.095626175, -0.047832094, 0.03493912, 0.11817307, 0.037452437, -0.14301221, -0.027356789, -0.052390423, 0.11373512, 0.07686775, 0.010008694) * go_0(1.0, 0.0);
result += mat4(-0.023999173, -0.091900624, 0.02388157, 0.03173873, 0.0065633506, -0.033716757, -0.1198324, 0.12057766, 0.026465805, -0.07517131, -0.07760598, 0.060463097, 0.07345541, 0.046037503, 0.21101558, -0.26785463) * go_0(1.0, 1.0);
result += mat4(0.15544604, -0.03902825, 0.04630384, -0.25173616, -0.0691359, 0.07476507, 0.009071253, 0.089964196, -0.26539803, -0.3958477, -0.22155671, 0.20735882, -0.105860494, -0.003996804, -0.044815883, 0.39544627) * go_1(-1.0, -1.0);
result += mat4(0.6169709, 0.23717614, -0.37884676, -0.7484867, 0.020169826, -0.30718836, 1.0965588, -0.20711036, -0.39149985, -0.06843563, -0.06522909, 0.103805855, 0.03265825, -0.15137726, 0.12837899, -0.01294922) * go_1(-1.0, 0.0);
result += mat4(-0.23638196, -0.4560866, -0.11948684, -0.1464144, 0.10690008, 0.007835961, 0.11864342, -0.13101323, -0.16509797, 0.075027354, 0.08122998, 0.13451207, 0.0011890623, 0.052157886, 0.08372405, -0.07085038) * go_1(-1.0, 1.0);
result += mat4(-0.21997726, -0.16488647, -0.0291317, 0.17997476, 0.1493211, 0.027494298, 0.0034613227, -0.3207727, 0.18699001, 0.14728633, -0.042895135, -0.07612043, 0.125076, -0.14714554, -0.03480009, -0.22753975) * go_1(0.0, -1.0);
result += mat4(-0.5342686, -0.7426105, -0.38294584, 0.42549992, 0.46053204, 0.7867879, 0.106234804, -0.041163098, 0.5198579, -0.5219404, 0.14809476, -0.41802374, 0.06810794, -0.15122683, -0.047409, 0.13178343) * go_1(0.0, 0.0);
result += mat4(-0.50428164, 0.18220626, 0.35510704, -0.081787474, 0.03155813, 0.019284263, 0.0032388573, -0.20513348, -0.05385551, 0.17803182, -0.26206362, 0.2870375, 0.008557827, 0.08401449, -0.027598893, -0.010791235) * go_1(0.0, 1.0);
result += mat4(0.16657415, 0.067647465, 0.093076974, -0.14438486, -0.10017002, 0.0022367141, 0.03250936, -0.052794546, -0.009178676, -0.019673595, -0.0016697067, -0.15424626, -0.112123474, -0.11079971, 0.011987111, -0.11747758) * go_1(1.0, -1.0);
result += mat4(-0.023021797, -0.058703423, -0.037978355, -0.062433913, -0.13130441, 0.048656322, 0.056839373, 0.109036915, -0.07823158, 0.14785293, 0.058555078, -0.11679035, -0.14002073, 0.07395252, 0.098268874, -0.06710464) * go_1(1.0, 0.0);
result += mat4(0.14906375, 0.030001195, -0.10338215, 0.0662968, -0.161953, -0.13682815, 0.09563142, 0.009514228, -0.009491218, 0.06737101, -0.1393389, 0.15231515, -0.073147796, 0.00767062, 0.028675212, 0.014213088) * go_1(1.0, 1.0);
result += vec4(0.018736731, -0.0026039074, 0.050130025, -0.055364225);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_2_tf
//!SAVE conv2d_last_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.019100675, -0.014241565, 0.004667036, -0.03865062, 0.106731094, 0.026099661, 0.014594411, -0.011881356, 0.0040967264, -0.004626336, 0.006469508, 0.010875305, -0.033909045, -0.085905954, 0.07861378, 0.019452631) * go_0(-1.0, -1.0);
result += mat4(0.20777655, -0.060354974, 0.0023840065, -0.064121604, -0.17397617, 0.019293457, -0.09707183, 0.080641985, 0.01025124, -0.017382381, 0.008661793, -0.010995665, 0.21943407, -0.115574986, 0.14471593, -0.068836235) * go_0(-1.0, 0.0);
result += mat4(0.057942886, -0.06311754, 0.2253396, -0.04159292, -0.020731755, 0.007877151, 0.041525815, 0.025278691, 0.03041967, -0.025137542, 0.024364179, -0.024543528, 0.029438615, -0.015506873, 0.081686, -0.07812221) * go_0(-1.0, 1.0);
result += mat4(0.054237515, 0.0676094, -0.0047708177, 0.0043467237, -0.10032304, -0.020498628, 0.04240586, 0.07272254, 0.0784221, 0.017945962, -0.022310399, -0.013134622, 0.015638694, -0.10001543, 0.1043031, 0.05898838) * go_0(0.0, -1.0);
result += mat4(-0.021652509, 0.35796642, 0.059497777, 0.23948468, 0.15454951, -0.10017235, -0.19072174, -0.44812536, -0.03974552, 0.04529369, 0.22207436, 0.026222564, -0.09705454, 0.5623026, -0.3354105, -0.017278556) * go_0(0.0, 0.0);
result += mat4(-0.053682446, -0.03411237, -0.09399936, 0.15128824, -0.07463, -0.042020727, 0.0031783928, 0.13481957, -0.07731454, 0.044114403, -0.23085599, 0.060444202, -0.15015422, 0.0018040676, -0.18684982, 0.2812511) * go_0(0.0, 1.0);
result += mat4(0.0029329916, 0.001596018, 0.0007512241, 0.016544111, -0.04876942, -0.05272409, 0.037884697, 0.049948208, 0.015518177, 0.11368592, -0.03815777, -0.013149978, -0.027638039, 0.107719295, -0.04115787, 0.02745414) * go_0(1.0, -1.0);
result += mat4(0.016691081, 0.010204119, 0.04078854, 0.01613337, 0.03325829, 0.0114824055, -0.017286912, -0.07284126, -0.110984206, -0.21041764, 0.0089543555, 0.18986733, 0.01537506, -0.2059135, 0.029074017, 0.013117443) * go_0(1.0, 0.0);
result += mat4(0.013965926, 0.029871881, 0.0034499036, -0.011343668, 0.022120327, -0.0068748263, 0.009324342, -0.039081004, 0.08032371, 0.050809264, 0.035050742, -0.2032847, 0.06305391, -0.021958945, 0.038569167, -0.22465245) * go_0(1.0, 1.0);
result += mat4(0.046307724, -0.012419472, 0.007673863, -0.042344846, 0.011042414, 0.016994251, -0.018166406, -0.016955731, -0.13240299, 0.01768431, -0.027607648, 0.0699927, -0.02840628, 0.004414203, 0.0049618417, 0.011084679) * go_1(-1.0, -1.0);
result += mat4(-0.119954154, -0.007455482, -0.031108133, -0.009946449, 0.0077065965, 0.01660345, 0.032943666, 0.016376585, 0.10273124, 0.1556573, -0.24643841, 0.107307844, -0.068235755, 0.0561896, -0.0104672015, 0.042693343) * go_1(-1.0, 0.0);
result += mat4(-0.01634601, 0.04195375, -0.10401894, 0.047641944, -0.034602515, -0.0034419263, -0.010457858, 0.015194475, -0.03962551, -0.030031368, 0.16036317, 0.019283568, -0.05877721, 0.016504882, -0.15523468, 0.018161612) * go_1(-1.0, 1.0);
result += mat4(-0.08083991, 0.0024665035, -0.049373373, 0.030371357, 0.0113322195, -0.014676956, 0.011646689, -0.01142667, 0.124930486, 0.06625774, -0.045840867, -0.009693036, -0.012649251, -0.07388084, 0.008790075, 0.0013844534) * go_1(0.0, -1.0);
result += mat4(-0.33941835, -0.2763476, -0.118311435, -0.063535266, 0.20936015, 0.13731301, 0.13443594, 0.07464433, 0.059650812, -0.36973104, 0.16444235, -0.37082872, 0.06432777, -0.18283032, -0.044489607, -0.13895285) * go_1(0.0, 0.0);
result += mat4(0.13533665, 0.08268915, -0.03675727, -0.14348659, 0.0186255, -0.05051692, 0.056702953, 0.0061717895, 0.047663026, -0.088188455, 0.23254345, -0.014015464, 0.08400204, -0.0073777726, 0.2202068, -0.12366078) * go_1(0.0, 1.0);
result += mat4(0.04361004, 0.046543695, 0.0064863074, -0.03358146, -0.022602187, 0.018138997, -0.011071864, 0.010244091, -0.019814799, -0.17250171, 0.040823266, -0.040131986, 0.010125854, 0.020660749, 0.0020435036, -0.010819304) * go_1(1.0, -1.0);
result += mat4(-0.004810193, -0.11286074, 0.051985834, 0.04788631, -0.023950428, 0.036145125, -0.038203828, 0.052401308, 0.022986965, 0.26420745, -0.06076917, -0.09252999, 0.03164547, 0.15652153, -0.037934, -0.0035418556) * go_1(1.0, 0.0);
result += mat4(0.03358366, -0.005219482, 0.007060882, -0.06569114, -0.02941682, 0.00966056, -0.0153679885, 0.019905418, -0.107232265, -0.03405676, -0.044340115, 0.26892832, -0.04723829, -0.02589829, 0.004563232, 0.19318114) * go_1(1.0, 1.0);
result += vec4(-0.00346731, -0.0046263863, -0.004627155, -0.0057769152);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Depth-to-Space
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_last_tf
//!SAVE MAIN
//!WIDTH conv2d_last_tf.w 2 *
//!HEIGHT conv2d_last_tf.h 2 *
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
vec4 hook() {
vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);
ivec2 i0 = ivec2(f0 * vec2(2.0));
float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];
float c1 = c0;
float c2 = c1;
float c3 = c2;
return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,969 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(0.3053028, -0.037464816, 0.113983095, 0.12537485, -0.18630321, 0.084269725, -0.01351514, -0.20190673, -0.12298384, -0.037622184, -0.070214555, -0.19367279, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.41849324, 0.099702746, -0.04276645, -0.047299717, 0.20074473, 0.14217933, 0.15571699, 0.19553481, 0.21868695, -0.053848714, 0.016413521, 0.14117444, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.030540446, -0.052293833, 0.0715466, -0.31160545, 0.07808315, -0.16860045, 0.032828577, -0.2955024, -0.110374965, 0.04043687, -0.014024628, 0.058699366, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.10727635, 0.054200135, 0.20853694, 0.21086875, 0.122690216, -0.091823794, 0.310609, -0.01738923, -0.0013488946, 0.10835534, -0.077265196, 0.086751856, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.77150255, 0.40530515, -0.41257596, -0.14367618, 0.46888494, 0.2650122, -0.934199, 0.40476102, 0.32293493, 0.20251967, 0.19891106, -0.29698747, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.12505147, -0.41904053, -0.065798186, 0.34075752, 0.026240354, -0.2977496, 0.032647505, -0.003566783, 0.10290523, -0.23417123, -0.06014203, 0.094735645, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.11207838, -0.04062474, 0.023897955, 0.08605987, -0.020888371, 0.045541205, -0.07231824, -0.25884083, -0.11796847, -0.002691391, 0.0050435597, 0.02756291, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.4615728, 0.041790638, 0.08971143, 0.20213957, -0.38537467, 0.19938901, 0.08594364, -0.08621994, -0.08163473, -0.133266, -0.09561729, -0.014209637, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(0.0787417, -0.0483673, 0.07621572, -0.060169693, -0.013465177, -0.17152289, 0.02515561, 0.17675288, -0.05173998, 0.10768042, -0.029858522, -0.013957215, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.0072128535, -0.05658625, 0.052939568, -0.1760861);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf1
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.112743355, 0.0422517, 0.21350034, -0.0967133, 0.16265953, 0.0022497, 0.015078242, 0.08204187, 0.035236806, -0.0468228, -0.09464228, -0.001864949, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.25631642, -0.41485596, -0.16662048, 0.13201024, 0.057921384, 0.2240005, -0.30038536, -0.08305622, 0.2228756, 0.32263795, 0.10608189, -0.18616734, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.08997524, 0.11516871, 0.19212262, -0.035154644, 0.11612274, -0.04056247, 0.14974374, 0.029173585, -0.07629641, -0.14353512, 0.041081246, 0.20230265, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.2262286, 0.055954933, -0.14499907, 0.17314723, 0.16590612, -0.06688698, -0.11118816, -0.012938116, -0.043101817, 0.026133137, 0.2958395, 0.06543993, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.07311521, -0.3041244, -0.47978505, -0.6350967, -0.17432262, 0.34965977, 0.25399777, -0.16590433, -0.49957857, 0.0549526, -0.40869385, -0.08780993, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.3014447, -0.00021343959, -0.14953177, 0.028001398, -0.14931908, -0.14910097, -0.13287953, -0.45026535, 0.17378895, 0.024704922, -0.027308129, -0.10292025, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.06732655, -0.13119644, 0.066014715, 0.081011154, -0.15154321, 0.2407805, 0.07733481, 0.12312706, 0.1741804, 0.008495716, -0.14125362, -0.043644864, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.11465958, 0.42001364, 0.011069392, 0.3203028, -0.058801666, -0.37830314, -0.030540617, 0.2245139, -0.11310525, -0.14845212, 0.19957744, 0.25789997, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.16037206, 0.21326372, 0.020099448, 0.018666709, 0.122083254, -0.16033986, -0.10725163, 0.2556128, 0.1650688, -0.10475823, 0.048623525, -0.103755645, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.007717166, -0.027800834, 0.0795002, 0.0053199283);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.0056740534, -0.21186607, -0.18014967, 0.118979976, -0.0015611284, -0.07708486, 0.060131397, 0.11653345, 0.027150517, 0.10837246, 0.08583816, -0.14032431, 0.017552888, 0.0035846964, 0.03980114, 0.064649396) * go_0(-1.0, -1.0);
result += mat4(-0.03289318, -0.12004539, 0.26514888, -0.15079662, 0.04214227, -0.027273783, -0.027950313, 0.19614808, 0.18510003, -0.10346252, -0.029836183, 0.09174428, -0.0088710375, -0.18273513, 0.06601674, 0.009983851) * go_0(-1.0, 0.0);
result += mat4(0.08476211, 0.043996535, 0.056711517, 0.009976895, 0.07039107, -0.024862664, -0.059921104, 0.046850603, 0.04983447, 0.04863198, 0.21777405, -0.0576961, 0.045321796, -0.0060038245, 0.096396215, -0.10842004) * go_0(-1.0, 1.0);
result += mat4(-0.15746164, 0.041757874, 0.035169285, -0.1734288, -0.24219254, -0.13318908, 0.2272079, -0.02902605, 0.07750601, -0.1467191, -0.12296749, -0.07533314, -0.07073083, 0.17909113, 0.04789308, 0.17245363) * go_0(0.0, -1.0);
result += mat4(0.057547905, 0.1464685, -0.33115456, -0.26956198, -0.26298407, -0.059824817, 0.022509675, -0.09251868, 0.36277944, -0.2072429, 0.21095088, -0.45492023, 0.07428653, 0.1593302, -0.2945834, 0.12825087) * go_0(0.0, 0.0);
result += mat4(-0.1318458, 0.27804148, 0.037600737, 0.12047866, 0.0065036337, 0.0017241207, 0.060497303, -0.14786585, -0.15149063, 0.02731698, 0.048886403, -0.0025970868, -0.026979815, 0.07348884, 0.015636757, -0.107966796) * go_0(0.0, 1.0);
result += mat4(-0.079988025, -0.01626299, 0.06517438, 0.086406484, -0.1484504, 0.070595, 0.20620634, 0.09713373, -0.13620836, 0.012067949, -0.00068703433, -0.038030174, 0.22300471, -0.0012400965, -0.014827909, -0.08927486) * go_0(1.0, -1.0);
result += mat4(0.15634936, 0.052028038, 0.038081627, 0.12720168, 0.07342066, -0.04318368, -0.0065998454, 0.12109317, -0.45398173, 0.03666754, -0.17773737, 0.038516667, -0.13009632, -0.007457001, -0.013938809, 0.09776142) * go_0(1.0, 0.0);
result += mat4(0.029636936, 0.12864171, 0.11347291, -0.11812842, -0.0870342, 0.035678383, 0.050338242, 0.045754932, -0.07072752, 0.010447726, 0.039642975, -0.08795004, -0.1191525, 0.00967509, 0.13485421, -0.053204738) * go_0(1.0, 1.0);
result += mat4(-0.011072695, -0.09613245, -0.09094804, 0.028029291, -0.04031162, 0.15690295, 0.25094184, -0.21776834, 0.06524669, 0.06412185, -0.052852992, -0.08097702, -0.039127756, 0.036357917, 0.104585476, 0.25095442) * go_1(-1.0, -1.0);
result += mat4(-0.08328618, -0.006246033, 0.099708706, -0.014916097, 0.17727195, 0.4369228, 0.14760216, 0.06707674, 0.025167737, -0.022487842, -0.038962565, 0.15380669, 0.08125089, 0.09844594, 0.33538374, -0.003161368) * go_1(-1.0, 0.0);
result += mat4(-0.0128195705, -0.05475118, -0.037705053, -0.0012077648, -0.17425515, 0.091487505, -0.12909423, 0.0074876705, 0.13438368, 5.778033e-05, 0.04563314, -0.12185897, -0.053612474, -0.049824294, -0.12851205, 0.12856449) * go_1(-1.0, 1.0);
result += mat4(-0.025741795, 0.01867236, -0.00027440622, 0.10502768, 0.27042285, -0.14947751, 0.11143123, 0.2575913, -0.07414089, -0.33919522, -0.13194235, -0.20088726, 0.23121537, -0.08197353, 0.06693911, 0.015411386) * go_1(0.0, -1.0);
result += mat4(0.09143717, 0.22842278, 0.06501074, -0.20009698, -0.042117566, -0.23452093, -0.074082755, -0.10612558, 0.077631965, 0.08343657, -0.07657599, -0.43297377, 0.7092466, -0.16272525, 0.17222248, -0.056038965) * go_1(0.0, 0.0);
result += mat4(0.081200436, 0.046752565, 0.028254949, 0.18820632, 0.096592255, 0.05896745, 0.14845169, 0.034777895, 0.07195204, -0.1908046, -0.015341971, 0.02606145, -0.010377239, 0.0755547, -0.15285216, 0.047916733) * go_1(0.0, 1.0);
result += mat4(-0.06825636, -0.049540907, -0.024328846, 0.03506251, 0.2060094, 0.054119263, -0.06671269, 0.052428722, 0.055792283, -0.14336903, -0.03180757, 0.013760968, -0.037398104, -0.06880077, -0.023608573, 0.0360965) * go_1(1.0, -1.0);
result += mat4(-0.16937497, -0.30156836, 0.0021435453, 0.025772978, -0.17990975, 0.046133514, -0.32447076, -0.083382785, -0.081322014, -0.022132374, -0.05319431, 0.11794733, 0.08943906, 0.12927428, 0.105764806, -0.051034793) * go_1(1.0, 0.0);
result += mat4(-0.011012306, 0.047636557, 0.050260928, 0.051847618, 0.010985655, -0.13752967, 0.023869954, 0.07011459, -0.18244945, 0.07239806, -0.013638856, -0.026982805, 0.11395993, -0.031304818, -0.08714153, 0.077115685) * go_1(1.0, 1.0);
result += mat4(0.08707592, 0.2265186, 0.13363098, -0.039588258, -0.029561255, 0.019238092, 0.024606103, -0.0019022018, -0.062285982, -0.0629511, -0.03753033, 0.109805316, 0.016018672, -0.08284564, -0.04092752, -0.030386891) * go_2(-1.0, -1.0);
result += mat4(0.0016500859, 0.01616536, -0.099148355, 0.24161765, 0.028064307, -0.028680569, 0.054400917, -0.1978921, -0.08584302, -0.096797146, -0.06546965, -0.09342837, 0.030265866, 0.07057579, -0.02080932, 0.053178705) * go_2(-1.0, 0.0);
result += mat4(-0.030304352, 0.047440585, -0.04248429, 0.08568772, -0.051317703, 0.036739342, 0.00865767, -0.018183297, -0.07335176, 0.025001721, -0.068509035, 0.1814819, -0.09756565, -0.024179723, -0.05959287, 0.0352454) * go_2(-1.0, 1.0);
result += mat4(0.023015196, -0.022870664, -0.12028372, -0.111095205, 0.11065281, -0.19900022, -0.24012049, -0.017028643, -0.13484617, 0.050107025, 0.10741765, 0.037951697, 0.013090438, -0.0010045726, -0.029447839, -0.1859787) * go_2(0.0, -1.0);
result += mat4(0.17922719, -0.24138594, -0.44595388, -0.032014426, 0.06897096, 0.07125395, 0.1944457, -0.035794795, -0.24022278, -0.13230884, -0.1277025, 0.21229011, -0.12249393, 0.06141907, 0.2687936, -0.26896995) * go_2(0.0, 0.0);
result += mat4(0.0397242, -0.30710965, 0.28815824, -0.06642567, -0.07588877, -0.019552408, 0.0057806037, 0.11465521, 0.03560534, -0.10640553, 0.023589289, -0.16667193, 0.02066607, -0.01026633, -0.02655378, 0.082493655) * go_2(0.0, 1.0);
result += mat4(-0.007902949, -0.08501038, -0.029395591, -0.07072227, -0.01800967, -0.14564751, -0.08372804, -0.049974415, 0.1756957, -0.02042449, -0.04413007, -0.016873527, -0.2385717, -0.001741017, 0.08298281, -0.019873247) * go_2(1.0, -1.0);
result += mat4(-0.01803727, 0.0642893, 0.21513617, 0.066888265, -0.042107955, -0.123470366, 0.045296013, -0.11958806, 0.48208967, -0.027188249, 0.12136116, 0.05246265, 0.13522038, -0.016297493, 0.028486907, -0.059840377) * go_2(1.0, 0.0);
result += mat4(-0.1373251, -0.11281026, -0.06418318, 0.08444032, 0.062874556, -0.009133875, -0.049571835, -0.042995855, 0.12483249, -0.025967957, -0.11202483, 0.09862257, 0.099986054, 0.009230306, -0.09042664, 0.046612263) * go_2(1.0, 1.0);
result += mat4(0.03203309, 0.106030256, 0.045741174, -0.020529225, -0.028610658, -0.055219248, -0.21404657, 0.07746393, -0.059359375, 0.0033258004, -0.0054513607, 0.06856653, 0.18043655, -0.119936846, -0.05639265, -0.10240379) * go_3(-1.0, -1.0);
result += mat4(-0.0004331875, 0.10426754, -0.008130048, 0.012795991, -0.14372933, -0.40797862, 0.105197415, -0.0041354536, -0.079792455, 0.0914027, 0.012418237, -0.11449173, 0.020261409, -0.14681602, -0.13355242, 0.18290488) * go_3(-1.0, 0.0);
result += mat4(0.052306626, 0.010864275, -0.072627716, -0.009773121, 0.09484167, -0.09631301, 0.14896165, -0.21220942, -0.11994051, -0.002957136, -0.118194886, 0.08661347, 0.10005298, -0.029620873, 0.101668894, 0.0242806) * go_3(-1.0, 1.0);
result += mat4(-0.055188183, -0.06322889, 0.12994595, 0.03140751, -0.092755616, 0.04239107, 0.18460171, 0.08471877, 0.014203371, 0.13608724, 0.035351243, -0.07883493, -0.10067456, 0.14417742, 0.0054235114, 0.100745104) * go_3(0.0, -1.0);
result += mat4(-0.043811034, -0.16055201, -0.11927185, 0.20517266, 0.16734722, 0.27720267, 0.1205665, 0.045803893, -0.07874647, 0.06764307, -0.11157022, 0.080770165, -0.044105835, -0.03276538, -0.10945451, 0.100562036) * go_3(0.0, 0.0);
result += mat4(-0.044731796, -0.12854387, -0.061937924, -0.21604767, -0.036132332, -0.024353411, -0.16718283, 0.14903957, -0.11620588, 0.14563644, 0.23363836, 0.08400659, 0.15248756, -0.1424437, 0.112882614, -0.04096889) * go_3(0.0, 1.0);
result += mat4(-0.0486021, -0.05714939, 0.042517707, -0.06106919, -0.12970918, -0.071898215, -0.044727243, -0.026308542, 0.05687118, -0.0394057, -0.109454155, -0.0021216893, 0.018588595, 0.08061093, 0.0500373, -0.0034918839) * go_3(1.0, -1.0);
result += mat4(0.11269324, -0.17924047, -0.12965205, -0.07287767, -0.015830642, -0.044497102, 0.20014328, -0.14054494, 0.1232692, 0.2395109, 0.14093149, 0.03518561, -0.14088139, -0.09045081, -0.07283352, 0.053434785) * go_3(1.0, 0.0);
result += mat4(0.020512339, 0.026349569, -0.06666101, 0.05554806, -0.03044066, 0.26656216, 0.019155584, -0.12118906, 0.087923005, -0.1716557, 0.050843164, 0.037432503, -0.030232614, 0.030457936, 0.04232163, -0.066400655) * go_3(1.0, 1.0);
result += vec4(-0.0216415, 0.09015036, -0.030761974, -0.26541537);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.04688368, 0.13853125, 0.1714716, -0.03034447, -0.08090605, 0.1225867, 0.17535992, 0.012508419, -0.0010665918, -0.07481546, -0.15541986, 0.0671128, -0.029307734, -0.076674186, 0.03925896, -0.07140553) * go_0(-1.0, -1.0);
result += mat4(-0.13273083, 0.062933214, 0.04200143, -0.0080243945, -0.120439716, -0.090192355, -0.022639645, 0.00020024918, -0.11211478, -0.12949537, 0.025783822, 0.009155746, 0.01004339, -0.0661901, 0.10630156, 0.053137038) * go_0(-1.0, 0.0);
result += mat4(0.07113487, -0.16011865, -0.10838903, -0.0034704183, 0.110606894, -0.14915739, 0.036511585, -0.003103608, -0.0551775, -0.13140677, 0.05270299, 0.12139221, 0.02226174, 0.008415268, -0.06647426, 0.118130066) * go_0(-1.0, 1.0);
result += mat4(-0.045172617, -0.0020388453, -0.27287582, 0.002428232, -0.2833772, 0.13788106, 0.073339015, 0.10666715, 0.08455194, 0.16499293, 0.089058325, 0.008815447, 0.034657538, -0.109856166, -0.11499077, -0.02918854) * go_0(0.0, -1.0);
result += mat4(0.07910854, -0.26334837, -0.3246593, -0.08246522, 0.09211476, 0.40793833, -0.09658794, -0.14430091, -0.50632644, 0.087234974, 0.26298127, 0.3687086, 0.06492316, 0.23082961, 0.18233871, -0.09283792) * go_0(0.0, 0.0);
result += mat4(-0.022744032, 0.21690565, 0.2694824, -0.12230013, -0.07969618, 0.21595429, -0.034979805, 0.008938489, 0.21289209, -0.446482, -0.042927746, -0.13587558, -0.032581557, -0.07182814, -0.054092336, -0.009542036) * go_0(0.0, 1.0);
result += mat4(-0.0034912943, -0.080354184, -0.08577375, -0.1521193, 0.09809233, 0.034529503, -0.100664355, 0.008191219, -0.014303411, -0.02862216, -0.18669915, -0.12384598, 0.046499267, 0.093707144, 0.10661308, 0.15079576) * go_0(1.0, -1.0);
result += mat4(-0.031025652, -0.0384342, 0.14258307, 0.25531343, 0.0075049917, -0.03966595, 0.062381975, 0.19593526, -0.2868182, 0.03162008, -0.4391041, -0.524017, -0.034463473, -0.0066741486, -0.24586639, 0.10521736) * go_0(1.0, 0.0);
result += mat4(-0.07452321, -0.0227877, -0.025402244, 0.115727395, -0.039511252, -0.07785703, -0.013689458, 0.0066024344, -0.052957747, 0.011206241, -0.0021671024, 0.077190824, -0.11709912, 0.046635598, 0.123751156, -0.03712064) * go_0(1.0, 1.0);
result += mat4(0.055411004, -0.0020031065, 0.06685547, -0.018829947, -0.06378933, -0.18389674, -0.0023551763, 0.0670314, 0.13038594, 0.0601923, -0.03035789, -0.019537423, -0.014483204, -0.056800704, 0.08663347, -0.106859975) * go_1(-1.0, -1.0);
result += mat4(-0.06603686, 0.07360526, -0.0072026253, -0.06778907, -0.039178446, 0.012397263, -0.13482279, 0.05745685, -0.055182382, -0.10545766, 0.003857615, 0.041947857, -0.15239377, 0.041826613, 0.058879383, -0.0042669442) * go_1(-1.0, 0.0);
result += mat4(-0.0697229, -0.010702144, -0.032265816, 0.013317131, 0.105028264, 0.21032134, 0.06845646, -0.018358687, 0.064568676, 0.08437135, -0.000723181, 0.1324007, 0.05527932, -0.049871888, -0.10125047, -0.005040889) * go_1(-1.0, 1.0);
result += mat4(-0.006467578, -0.05120533, -0.011780779, -0.011742203, -0.34242442, -0.020819988, 0.17381702, -0.059836414, -0.028882682, 0.23210457, 0.16579404, -0.03708216, -0.23541835, -0.03290251, 0.029319672, 0.26189178) * go_1(0.0, -1.0);
result += mat4(-0.30955994, -0.06408282, -0.16872866, 0.10767772, -0.041430887, 0.051697977, 0.12523535, -0.060389146, 0.026289431, 0.06359533, 0.13526368, 0.2479901, -0.3263977, 0.10216362, -0.0030894123, 0.046437826) * go_1(0.0, 0.0);
result += mat4(0.10061438, -0.17047118, -0.21593021, -0.023389054, -0.17507865, -0.30822313, -0.22044766, 0.16078933, 0.07099252, -0.11573018, 0.24712858, -0.0659458, -0.037504572, -0.12297423, 0.03342632, -0.058119852) * go_1(0.0, 1.0);
result += mat4(-0.020957774, -0.0224927, 0.04069268, -0.07911167, 0.074009344, 0.065916434, 0.008222278, 0.11625076, -0.25299504, 0.03357169, -0.021988, 0.015821831, -0.0021187372, -0.030700417, -0.004374924, 0.027358979) * go_1(1.0, -1.0);
result += mat4(0.06549052, -0.048067164, 0.05489091, -0.28851983, 0.13378961, 0.026875904, -0.09877994, -0.19947459, -0.1274035, -0.022928834, -0.26344195, -0.025870804, 0.022505255, 0.0070861108, 0.121051334, -0.025964163) * go_1(1.0, 0.0);
result += mat4(0.059426542, -0.0327433, 0.2313695, -0.07046268, 0.20479666, 0.027021704, 0.2564928, -0.11689885, -0.07407976, -0.019611249, 0.093463086, -0.121553615, 0.035009407, -0.008135333, -0.075931996, 0.047803063) * go_1(1.0, 1.0);
result += mat4(-0.059434246, -0.1652242, -0.124611154, 0.04743711, 0.10530296, -0.13869187, -0.036534663, -0.035206333, 0.06067593, 0.06126907, 0.120151915, -0.06722673, 0.008103894, 0.037225723, -0.007520425, 0.065720856) * go_2(-1.0, -1.0);
result += mat4(-3.6759695e-05, -0.036789574, 0.013370567, -0.037871476, -0.013454664, 0.15086569, 0.10164699, 0.057703357, -0.12871023, 0.12827681, -0.055057358, -0.040753044, -0.0142621, 0.08563361, -0.04615499, -0.03130452) * go_2(-1.0, 0.0);
result += mat4(-0.117965914, 0.09056485, 0.07272314, 0.009695964, -0.11331058, 0.07467256, -0.08291521, 0.00937355, -0.04097737, 0.07752905, -0.017335521, -0.12539999, 0.039462104, -0.0007037007, 0.06034812, -0.09497377) * go_2(-1.0, 1.0);
result += mat4(0.20828065, 0.0400099, 0.047638226, -0.046423353, -0.026133502, 0.098207295, 0.056742374, 0.017029466, -0.058164768, -0.046973787, -0.17328712, -0.0012984811, 0.050085854, 0.11296557, 0.12639083, 0.058543045) * go_2(0.0, -1.0);
result += mat4(-0.098907426, 0.22031747, 0.101559944, 0.06616554, 0.026110496, 0.56487054, 0.23754556, -0.07540935, 0.31768414, -0.47653618, 0.015073956, -0.33731326, 0.087285936, -0.24593173, -0.26141426, 0.15003823) * go_2(0.0, 0.0);
result += mat4(0.046026446, -0.13767281, 0.064847544, 0.07717139, 0.08544123, -0.11092969, 0.072325274, 0.010849038, -0.3055905, 0.66436774, 0.1434729, 0.0494463, 0.07115603, 0.083811216, 0.020431712, 0.06537088) * go_2(0.0, 1.0);
result += mat4(-0.15532711, 0.030139687, 0.040853374, 0.11089222, -0.08150315, -0.015851755, -0.06787692, 0.096075505, -0.011956207, -0.0017758606, 0.1277494, 0.16156575, -0.038588695, -0.0626418, -0.041797023, -0.19467135) * go_2(1.0, -1.0);
result += mat4(0.12917455, 0.017410474, -0.20125067, -0.08040003, -0.13494664, 0.17789102, -0.19909395, 0.08441434, 0.078570575, -0.06330619, 0.23767303, 0.5442659, -0.009227878, -0.021818208, 0.14318731, -0.09042824) * go_2(1.0, 0.0);
result += mat4(0.097801, 0.09345441, 0.17846581, -0.14773296, 0.06536365, 0.07642184, -0.011880635, 0.02086135, 0.013336972, -0.053295113, -0.13410404, 0.027241753, 0.087728985, -0.044033397, -0.13098569, 0.009423933) * go_2(1.0, 1.0);
result += mat4(-0.02488427, 0.0134966355, -0.0075000813, 0.07272353, 0.015842725, 0.13765687, 0.028079558, -0.08384948, -0.06666623, -0.023220664, 0.025091043, -0.055167805, -0.18826278, 0.04423603, 0.13499942, 0.059128854) * go_3(-1.0, -1.0);
result += mat4(0.01935146, -0.030980906, -0.031569187, -0.0036869382, 0.036753897, 0.118464164, 0.15871695, -0.09842428, 0.023324292, 0.071796335, -0.07869346, -0.10751301, -0.2588698, 0.064011686, 0.17386378, -0.039197855) * go_3(-1.0, 0.0);
result += mat4(0.08590827, 0.005497696, -0.026512025, 0.015661815, 0.1102415, -0.08268483, -0.0032903247, 0.10049029, -0.008157236, -0.035823178, -0.017570151, -0.081716835, -0.3531045, 0.010005245, 0.017141227, -0.016376914) * go_3(-1.0, 1.0);
result += mat4(-0.16617337, -0.007689783, 0.00954665, 0.07117733, -0.001669262, -0.012331606, 0.051613946, 0.062780835, 0.06123557, -0.20243123, -0.19181818, 0.032895602, 0.19760677, 0.004464939, 0.12754539, -0.27360034) * go_3(0.0, -1.0);
result += mat4(0.15006685, -0.083587274, -0.03215495, -0.16992462, -0.011944293, 0.058361508, -0.088097006, 0.023880545, -0.04168166, -0.06960282, -0.092672385, -0.057278465, 0.23540072, -0.1721208, -0.018213503, -0.23494521) * go_3(0.0, 0.0);
result += mat4(-0.124885194, 0.1905868, 0.11108704, 0.03163991, 0.11383064, 0.101223364, 0.069428995, -0.14298953, -0.07609092, 0.13704266, -0.07749446, -0.0005389336, -0.04617235, 0.18011934, 0.08350316, 0.09416366) * go_3(0.0, 1.0);
result += mat4(0.073356606, 0.067966126, -0.21285574, 0.0782625, -0.0034364646, -0.032581426, -0.05538558, -0.1317288, 0.14552782, -0.1132393, 0.13063973, -0.00833602, 0.0026844777, 0.028135289, -0.02536825, -0.028372496) * go_3(1.0, -1.0);
result += mat4(-0.318728, 0.07862527, -0.12176221, 0.35010242, -0.029198067, 0.016302662, 0.17667587, 0.12605923, 0.1556697, -0.06061443, 0.05843511, 0.10891248, 0.01267106, -0.018492714, -0.15945031, -0.050723754) * go_3(1.0, 0.0);
result += mat4(-0.21555941, -0.016813517, -0.084676236, -0.07545412, -0.14518794, -0.014592766, -0.2446481, 0.0530632, 0.0847341, 0.12342537, -0.028644923, 0.083479315, -0.04179012, 0.0025225023, 0.16006976, -0.026940256) * go_3(1.0, 1.0);
result += vec4(-0.060742114, -0.037577342, 0.055704296, 0.03134311);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.13129333, -0.022117995, -0.009753253, 0.020439912, 0.044090994, -0.0916335, 0.0036765633, -0.11719207, -0.06413809, 0.04079378, -0.00085516454, -0.06306388, -0.12660664, -0.054126263, -0.005513979, 0.06364538) * go_0(-1.0, -1.0);
result += mat4(-0.028422508, 0.23270117, -0.28674677, -0.10820166, 0.024321957, -0.0811145, -0.07290707, -0.02125165, -0.064260505, 0.052076746, -0.009654081, 0.08363882, -0.02037171, 0.15006389, 0.121593125, -0.011237004) * go_0(-1.0, 0.0);
result += mat4(-0.14672333, 0.015381624, 0.1028172, -0.041823238, 0.0072677187, -0.042953942, 0.06426537, -0.0938381, -0.05990813, -0.04599802, -0.11264726, -0.027826328, -0.058160868, 0.10747306, -0.07327458, 0.07998872) * go_0(-1.0, 1.0);
result += mat4(-0.08702181, -0.03750975, -0.045659006, 0.04488332, 0.09102003, 0.066556975, -0.04353586, 0.08994567, -0.13561495, -0.10653702, 0.006989605, 0.028230097, 0.07177144, 0.2938447, -0.00943923, 0.022120917) * go_0(0.0, -1.0);
result += mat4(-0.1801194, -0.11119162, 0.1977298, -0.247902, -0.16654298, -0.07423158, 0.114130594, 0.0014401592, 0.006954727, -0.09810646, -0.051310766, 0.19487657, 0.2545855, -0.06328558, -0.04617056, 0.09444692) * go_0(0.0, 0.0);
result += mat4(0.011378825, 0.16044368, 0.017211074, 0.14472178, 0.032992378, -0.008925819, 0.035120245, -0.012409223, 0.074333005, 0.1178002, -0.128956, -0.13624239, -0.2791275, 0.21457297, -0.1476131, 0.04874687) * go_0(0.0, 1.0);
result += mat4(-0.03491764, -0.061763793, 0.05779039, 0.0054837577, -0.023937583, 0.08281698, 0.032306053, -0.014566218, 0.12738499, -0.0132100545, -0.051833414, 0.0057818824, 0.012158851, -0.20231532, -0.0043795826, 0.10285843) * go_0(1.0, -1.0);
result += mat4(-0.22269921, -0.15135509, -0.039143335, 0.033390045, 0.06770212, -0.14538582, -0.08011057, 0.03796648, -0.025913516, 0.13925864, 0.18309896, 0.012709204, -0.24912506, 0.3217706, 0.0394195, 0.017977878) * go_0(1.0, 0.0);
result += mat4(0.00080196525, 0.059145816, 0.05720508, 0.0056548906, 0.005168018, 0.09938438, 0.0200503, -0.05516137, 0.061309986, -0.019621318, -0.1541441, 0.019540716, 0.030571707, -0.09054893, 0.032851614, -0.27210873) * go_0(1.0, 1.0);
result += mat4(0.27061436, -0.114008114, -0.0020118617, -0.1656827, 0.09770587, 0.029897455, -0.03307522, -0.04661818, 0.033011347, 0.18498488, -0.05162084, 0.087471776, -0.24665618, -0.12538423, -0.08123797, -0.010210389) * go_1(-1.0, -1.0);
result += mat4(0.075188264, 0.0020608555, 0.18558815, 0.041179713, 0.11232638, 0.05507779, -0.19599183, 0.027942855, 0.06199144, 0.22141005, -0.06121163, 0.014993597, 0.24105869, -0.019737717, -0.112485714, 0.0157406) * go_1(-1.0, 0.0);
result += mat4(0.09425698, 0.0207658, 0.12074599, 0.009430481, 0.11889248, -0.025782838, 0.0034711843, 0.05113582, 0.012531833, -0.0018606635, -0.09137569, 0.018120576, 0.4051155, 0.02222076, -0.16001017, 0.10981527) * go_1(-1.0, 1.0);
result += mat4(-0.03582557, 0.014994796, -6.4688604e-05, 0.24618183, -0.11697727, 0.24388117, 0.038502026, -0.3511993, 0.101741396, -0.10748137, 0.035059888, -0.017535849, 0.09450039, 0.06541661, 0.12149035, 0.28798738) * go_1(0.0, -1.0);
result += mat4(-0.27143848, 0.017990451, -0.69144464, 0.037944376, -0.04551905, 0.09263134, 0.4259611, -0.14107811, -0.10641847, 0.23065196, 0.040813655, -0.07789163, 0.3087666, 0.08190437, 0.16409059, -0.06455426) * go_1(0.0, 0.0);
result += mat4(-0.08290655, -0.35286915, -0.18082355, -0.32229406, 0.1608227, 0.030915622, 0.09207708, 0.02655054, 0.039464593, 0.026095424, 0.052584656, 0.033881903, -0.01751319, -0.0011676399, 0.04002607, 0.1630013) * go_1(0.0, 1.0);
result += mat4(-0.012021132, 0.12163766, -0.07410629, -0.06879096, 0.017859738, -0.039261997, -0.028677614, -0.23610398, -0.15963873, -0.0006119958, 0.11275506, 0.0082659265, 0.05677582, 0.08676638, -0.08669759, -0.10475464) * go_1(1.0, -1.0);
result += mat4(0.12792721, 0.06888765, 0.31803077, 0.26002547, -0.067599155, -0.011822328, -0.2589909, -0.30024147, 0.11076704, 0.15200609, -0.018180368, -0.19146141, 0.22298847, 0.059484895, 0.034478076, 0.15610938) * go_1(1.0, 0.0);
result += mat4(0.0870121, -0.016420847, -0.011579898, 0.097182855, -0.120095566, -0.06843338, -0.043460473, -0.060684606, -0.027540063, -0.008499213, 0.033570655, -0.06866259, 0.01429712, -0.07424434, 0.0009466247, 0.09142678) * go_1(1.0, 1.0);
result += mat4(-0.03781424, 0.04587032, 0.03744051, 0.02712279, -0.051038064, 0.0669144, -0.02640278, 0.12384894, -0.0022533627, -0.010022036, 0.07536463, -0.030489929, 0.09418577, 0.155089, -0.011290433, -0.02102941) * go_2(-1.0, -1.0);
result += mat4(-0.0053278613, -0.07160643, 0.039028414, 0.04123311, -0.10693177, -0.1170874, 0.07230816, -0.033255517, -0.119176835, 0.0786526, -0.11880206, -0.11354601, -0.037539184, 0.14404313, 0.069760695, 0.024738638) * go_2(-1.0, 0.0);
result += mat4(0.03413808, -0.006487654, 0.10006853, 0.22228058, -0.13796462, -0.14042488, 0.04017443, -0.031790894, -0.06673143, 0.009888688, 0.08831443, -0.0045771743, -0.028375361, -0.04704813, 0.07128581, -0.07012518) * go_2(-1.0, 1.0);
result += mat4(-0.06954315, -0.23728988, -0.14192343, -0.08236467, -0.2552115, 0.04102959, -0.06355397, -0.08340241, 0.17617856, 0.20281969, -0.16249381, 0.10843737, -0.04392261, -0.08587206, 0.053069845, -0.15482199) * go_2(0.0, -1.0);
result += mat4(0.124981806, 0.12828638, -0.061472785, -0.20108232, -0.14905351, -0.40766275, -0.35427195, -0.13183996, 0.09307428, -0.07697028, 0.06702549, -0.22656697, 0.019868268, -0.19361132, 0.08784669, 0.20249842) * go_2(0.0, 0.0);
result += mat4(-0.004661343, -0.09333453, -0.24876262, -0.07906779, 0.110697776, -0.37069768, -0.042212646, -0.0046135853, -0.2254257, -0.023392014, 0.031476703, -0.045574382, -0.12675518, -0.076056994, -0.08228006, -0.040303517) * go_2(0.0, 1.0);
result += mat4(0.16182694, 0.0512523, 0.051189836, 0.048962783, -0.05156489, -0.17987493, -0.012037288, 0.06953726, -0.09458492, 0.1610021, -0.004063283, -0.032922342, 0.08995396, 0.1939926, -0.018710036, -0.08153231) * go_2(1.0, -1.0);
result += mat4(-0.064830944, 0.06121252, -0.18886387, -0.12976822, -0.031117212, 0.12219633, 0.19070715, 0.12495262, -0.11994464, -0.24687837, -0.08425294, -0.016920334, -0.13286817, -0.3260188, -0.11776061, 0.1651019) * go_2(1.0, 0.0);
result += mat4(-0.17652592, 0.002499805, -0.030541016, -0.01393431, 0.031418208, 0.08209422, 0.12430871, 0.4387016, -0.108871914, -0.09041422, 0.031226631, -0.1638517, 0.20756467, 0.014476537, -0.012701195, -0.03440563) * go_2(1.0, 1.0);
result += mat4(0.005320072, -0.0032291536, -0.017209187, 0.031944863, -0.2479921, -0.24433962, -0.13832912, 0.07835928, -0.17707248, 0.028202811, -0.19121435, 0.164587, 0.123152815, 0.0050288937, 0.084104605, -0.0380019) * go_3(-1.0, -1.0);
result += mat4(0.16008669, -0.018608516, -0.013778938, 0.033447385, -0.01242472, -0.070916265, 0.026909694, -0.07318777, 0.15158044, 0.12047607, -0.1709358, 0.2031767, 0.0025611701, -0.21457459, 0.2791286, 0.10159932) * go_3(-1.0, 0.0);
result += mat4(0.14320926, 0.020023825, -0.0484187, 0.011563084, -0.2640472, -0.013056275, 0.004234292, -0.095376395, 0.28363484, -0.0058227647, -0.0777649, 0.05238444, 0.41757923, -0.07081097, 0.012567031, -0.13029522) * go_3(-1.0, 1.0);
result += mat4(0.07266207, 0.042793367, -0.08212271, -0.23401663, -0.19457819, 0.4191269, -0.03095442, 0.15339781, -0.28451788, 0.09316364, 0.10231693, -0.22844811, 0.111623526, 0.120017685, 0.18777381, 0.014420896) * go_3(0.0, -1.0);
result += mat4(0.15037206, -0.29763284, 0.2601235, 0.0193363, 0.13686465, 0.009907918, -0.37781665, 0.04916627, 0.14114739, 0.5043813, 0.0447959, -0.029427614, 0.041768756, 0.27211213, 0.14163221, 0.086162075) * go_3(0.0, 0.0);
result += mat4(0.19159287, 0.21363218, 0.15053211, 0.08992885, 0.100828275, 0.09379921, 0.030783929, 0.11664482, -0.059145752, -0.19400764, -0.09351283, -0.016430443, -0.12910964, -0.067078374, 0.11760082, 0.121194765) * go_3(0.0, 1.0);
result += mat4(-0.055059325, 0.09299572, 0.06848913, 0.06334532, -0.1476285, 0.111801244, -0.033960916, 0.06474366, -0.04952303, 0.27885208, -0.052447475, 0.09226763, -0.15024844, -0.0033919013, 0.013498364, 0.09135676) * go_3(1.0, -1.0);
result += mat4(-0.017010042, -0.122343406, -0.19097193, -0.27957183, -0.18206005, 0.102321096, 0.22794476, 0.0439245, -0.23710132, -0.08070259, 0.17377135, 0.23811814, 0.17799385, 0.049567625, 0.1470908, 0.07329385) * go_3(1.0, 0.0);
result += mat4(0.0038071256, 0.19454515, -0.01222965, -0.07390379, -0.0532754, 0.03942833, 0.123840906, 0.023459576, -0.0658742, -0.023957543, -0.14682837, 0.1221027, -0.010986398, -0.066184506, 0.03026491, -0.0638446) * go_3(1.0, 1.0);
result += vec4(-0.06427697, -0.00039365015, 0.011889719, 0.060232002);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf1
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.012110923, 0.07818654, 0.07964548, 0.11885079, -0.07694473, -0.01378252, 0.006632789, -0.12876098, 0.0069211307, 0.022278586, 0.069553085, 0.16569804, -0.11123615, 0.06125189, -0.11232848, 0.1559266) * go_0(-1.0, -1.0);
result += mat4(-0.3261174, -0.25586754, 0.21129315, 0.3135101, 0.1509055, 0.0044283345, 0.024674175, -0.08000473, 0.01213029, 0.09093019, 0.04942677, 0.09806723, -0.16454464, -0.14433062, -0.058094524, -0.060819894) * go_0(-1.0, 0.0);
result += mat4(0.023174008, 0.02858724, 0.07685972, 0.036857616, -0.10415571, 0.10241035, -0.01893166, 0.02065923, 0.058356714, 0.096426114, -0.03772327, -0.1529002, 0.13740575, -0.048291504, -0.06152548, -0.15199897) * go_0(-1.0, 1.0);
result += mat4(0.029300174, -0.13222043, 0.0139825605, -0.02274408, 0.062944874, 0.028447356, 0.05960515, 0.034447193, 0.03133432, -0.019283533, -0.024591971, -0.0043914663, 0.15245225, 0.006851478, -0.051783554, 0.17453748) * go_0(0.0, -1.0);
result += mat4(-0.09125915, 0.081739366, 0.01196335, 0.23130219, -0.22557035, -0.13537665, 0.0022028848, -0.043430023, 0.22759882, 0.07920754, -0.027986467, -0.14051494, -0.19557038, -0.03585936, -0.4258294, -0.03856216) * go_0(0.0, 0.0);
result += mat4(0.18511422, -0.09368415, 0.1551229, 0.04322566, -0.023400841, -0.02261204, 0.15129441, -0.007954805, -0.10739125, 0.019459398, 0.013128325, 0.018073296, 0.20886365, -0.20662378, -0.03814699, -0.09272838) * go_0(0.0, 1.0);
result += mat4(-0.027352437, -0.039882626, 0.12598103, -0.093930446, 0.030846786, -0.09325075, -0.009084744, -0.024584265, 0.07159868, 0.14162529, 0.19019091, 0.058855128, -0.09880401, -0.01843218, 0.14753596, -0.2449532) * go_0(1.0, -1.0);
result += mat4(0.06565521, 0.09150168, -0.08654865, 0.0829788, -0.07596146, -0.01815166, -0.08786775, -0.03477514, 0.20538878, -0.012766377, 0.020719538, 0.088188395, -0.034300096, 0.29972988, -0.20005241, 0.018425167) * go_0(1.0, 0.0);
result += mat4(0.11713916, 0.024167519, 0.05167596, -0.0027117804, -0.016994188, 0.048177514, -0.012556207, 0.010979094, 0.09098878, 0.028514355, 0.06063336, -0.06624107, 0.012754856, 0.013208708, -0.061374772, -0.0025992664) * go_0(1.0, 1.0);
result += mat4(-0.09053513, 0.03183455, 0.017340872, 0.12934409, -0.022161964, -0.0015361432, -0.049972344, -0.12763855, 0.12779881, -0.04697911, 0.018968226, -0.119873665, 0.05462772, -0.13919477, -0.10226718, -0.2540179) * go_1(-1.0, -1.0);
result += mat4(-0.29912186, -0.09291771, 0.050926663, 0.49361777, 0.21372582, 0.076717265, -0.058968987, -0.1572678, 0.3194591, -0.120582424, 0.03942037, 0.023128232, 0.24321598, 0.07046334, -0.21204855, -0.648296) * go_1(-1.0, 0.0);
result += mat4(0.05366883, -0.020366706, 0.020979457, -0.06893884, 0.04837168, 0.017253762, 0.008874203, -0.020785445, -0.20425391, 0.060179923, 0.046167206, 0.09863377, -0.14381303, 0.038928367, -0.06590863, -0.18408588) * go_1(-1.0, 1.0);
result += mat4(0.07099762, 0.2029403, -0.033945918, 0.15202214, 0.0901113, -0.27336198, -0.17693861, -0.16206753, -0.17642029, 0.09400492, -0.11165698, -0.07863893, -0.16306102, -0.056210615, 0.22173557, 0.013508989) * go_1(0.0, -1.0);
result += mat4(0.08541511, -0.27093616, -0.35273993, -0.48919773, 0.038383547, -0.16013749, 0.012996215, -0.03434873, 0.07024113, -0.28971404, 0.10623425, -0.0019642068, -0.062374946, 0.3291145, 0.22468035, -0.42971882) * go_1(0.0, 0.0);
result += mat4(0.020427933, 0.15062793, 0.08308975, -0.025095072, 0.030093266, -0.09649862, -0.03382388, -0.0016017791, 0.105402954, 0.020693144, -0.051065, 0.07704679, 0.02864139, -0.00135146, 0.03762216, 0.029277142) * go_1(0.0, 1.0);
result += mat4(0.01700994, 0.12214317, 0.06749582, 0.07354159, -0.093085855, -0.065021954, 0.010773045, -0.00095128635, -0.045384295, -0.072611265, -0.043900184, 0.049471326, 0.029131187, 0.03180158, -0.13313527, 0.05280797) * go_1(1.0, -1.0);
result += mat4(0.14751251, -0.15087761, 0.09932281, -0.099232934, -0.062390897, 0.112391844, -0.09159478, 0.15856399, 0.034708973, 0.01819943, -0.02730164, -0.13562973, -0.05687333, -0.0114601655, 0.07025971, 0.02496533) * go_1(1.0, 0.0);
result += mat4(-0.0117268525, -0.026162883, 0.07481553, 0.13420302, 0.029870516, 0.07405776, -0.06379041, 0.09631234, -0.07754842, 0.035888605, 0.0034764851, -0.040771756, -0.092022054, -0.034230903, -0.02281844, -0.0028173258) * go_1(1.0, 1.0);
result += mat4(-0.059846643, 0.016772347, -0.02287152, 0.07036337, -0.024946844, 0.09826078, -0.068491876, 0.20852126, 0.073890835, -0.058288682, 0.013093785, -0.05776076, 0.0516503, 0.052794468, 0.10837015, 0.038539834) * go_2(-1.0, -1.0);
result += mat4(-0.16391893, -0.008062687, -0.35022175, 0.2510062, -0.15820411, 0.048403125, 0.024878092, 0.037888516, -0.035924178, -0.068953894, -0.025386479, 0.24405715, -0.018495679, -0.051277515, 0.14754932, -0.031538483) * go_2(-1.0, 0.0);
result += mat4(-0.038429607, -0.047140498, -0.018157095, -0.029318782, -0.04094171, -0.11870087, 0.11214255, 0.07142628, 0.021007229, -0.005681072, 0.1662777, 0.10829575, 0.112268396, 0.03567479, -0.06738845, 0.0032037434) * go_2(-1.0, 1.0);
result += mat4(-0.032217573, 0.2102397, -0.20617546, -0.07920811, 0.12918773, 0.054486286, -0.13656865, 0.05806265, 0.01963165, 0.049910642, 0.15538268, 0.10724465, -0.09697837, -0.03070673, -0.0071386313, -0.11899626) * go_2(0.0, -1.0);
result += mat4(0.130827, 0.0051715383, -0.07212691, 0.45726067, 0.2773031, 0.2973666, 0.3951691, 0.01333662, -0.14561643, 0.04508669, 0.121690124, 0.13326228, -0.22579186, 0.058161184, 0.09281702, -0.00079749606) * go_2(0.0, 0.0);
result += mat4(-0.00771113, 0.09912341, -0.41895548, -0.06705759, 0.029148718, 0.052991726, 0.18665347, -0.031787418, 0.23053595, 0.09444956, 0.10691037, -0.06325714, -0.05335701, 0.1917427, -0.0065284846, 0.032622546) * go_2(0.0, 1.0);
result += mat4(-0.056801565, -0.019131258, -0.0939022, -0.08130343, -0.11051993, 0.0035269214, -0.047361933, -0.0543875, 0.10854369, 0.06445185, 0.016828364, -0.022595318, 0.1450623, 0.033027507, -0.020425137, 0.16169788) * go_2(1.0, -1.0);
result += mat4(-0.08747717, 0.07770065, 0.018155783, 0.07160794, 0.09860347, -0.04329888, -0.0043579484, -0.2014418, -0.060260013, 0.0036374568, -0.17566042, -0.2268221, 0.001273691, -0.2609373, -0.19417606, -0.04102927) * go_2(1.0, 0.0);
result += mat4(-0.086845055, -0.114253804, -0.13433142, -0.025941795, -0.0155711295, -0.13578776, 0.12059696, -0.08760523, -0.0057348222, 0.12164273, 0.07270617, -0.06352636, 0.08894258, 0.04140841, 0.1230304, -0.030357126) * go_2(1.0, 1.0);
result += mat4(0.03320213, 0.015911903, -0.06288296, -0.121976145, 0.2713457, 0.13913193, -0.092420585, 0.105714336, 0.10294281, -0.04591945, -0.11767934, 0.032249406, -0.06506192, -0.04639334, 0.08137017, -0.031746846) * go_3(-1.0, -1.0);
result += mat4(0.13717805, 0.0071242675, -0.077256985, -0.14974317, -0.08467893, -0.20126395, -0.06240603, 0.09554399, -0.075844854, 0.28380412, 0.046030026, 0.053188596, 0.50943077, 0.1179795, 0.32203588, -0.06712207) * go_3(-1.0, 0.0);
result += mat4(-0.18528835, 0.0016975187, -0.0041140947, 0.11234392, -0.34049067, -0.056880493, -0.04325441, 0.09905571, 0.10978758, 0.009608353, -0.10801905, -0.04071131, -0.09096832, -0.12350487, 0.011801418, 0.22521795) * go_3(-1.0, 1.0);
result += mat4(0.040283076, -0.034117915, -0.026142653, -0.06058959, 0.12511659, 0.4131219, 0.59190845, 0.39758852, 0.16032091, -0.5975032, -0.14516282, 0.115154505, 0.03874097, 0.18462797, 0.22934213, 0.05285643) * go_3(0.0, -1.0);
result += mat4(-0.17804009, 0.33769128, -0.14572927, -0.029545018, 0.3897, -0.055615567, 0.15232995, 0.48788264, -0.21422523, 0.03397293, 0.0337794, -0.19830915, -0.022457365, -0.35096076, 0.42616987, -0.19268763) * go_3(0.0, 0.0);
result += mat4(-0.13191561, -0.18337126, 0.017879983, -0.070472844, -0.09409196, -0.025770849, -0.060219247, 0.10869267, -0.17341033, -0.09199785, -0.0667796, -0.093538545, -0.21300837, 0.030474098, -0.04540468, 0.041321553) * go_3(0.0, 1.0);
result += mat4(-0.0998177, -0.08669185, -0.0090886615, 0.0021083376, 0.08900095, 0.5062186, 0.45537788, 0.029077586, -0.1001008, -0.0077697043, -0.0096318, 0.11706454, 0.07401959, -0.00650215, 0.06092762, 0.037442297) * go_3(1.0, -1.0);
result += mat4(-0.18500404, 0.0024998419, -0.11761331, -0.026825588, 0.27255726, 0.093010515, 0.3281413, -0.051473666, -0.050259475, -0.17258662, -0.23394547, 0.104795866, 0.035074063, -0.061560635, 0.05975411, -0.094255395) * go_3(1.0, 0.0);
result += mat4(-0.023440497, -0.021479638, 0.0036277648, 0.004972212, 0.02416659, -0.09856867, -0.03971455, -0.27094853, 0.026615402, -0.0047890246, -0.13755885, 0.16591635, -0.0016293586, 0.133207, 0.047790572, 0.029041538) * go_3(1.0, 1.0);
result += vec4(-0.0063728676, -0.029053684, -0.052831043, 0.006475641);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.0431447, 0.047972627, 0.09522898, 0.19048582, 0.0015511789, 0.1182684, -0.065335006, 0.061233886, -0.02451869, 0.065670215, -0.015341636, 0.06836347, 0.10215459, 0.17516296, 0.0857072, 0.072732896) * go_0(-1.0, -1.0);
result += mat4(0.10117189, 0.049022958, -0.016017418, -0.12119866, 0.089112304, 0.016286526, -0.025251161, 0.03239003, -0.0783818, -0.086096615, -0.13673106, -0.15934734, -0.51308054, -0.061430074, -0.16208844, 0.2227776) * go_0(-1.0, 0.0);
result += mat4(-0.011567444, 0.025550444, -0.018439503, -0.015003767, 0.11606929, -0.11613111, -0.040906087, -0.015202219, 0.03932618, -0.1106059, 0.03703376, 0.018548314, -0.12761284, -0.038109995, -0.23577367, 0.20272344) * go_0(-1.0, 1.0);
result += mat4(0.025444161, -0.075270735, 0.10999789, 0.16305386, 0.016178958, -0.074034974, 0.1177035, -0.077481024, -0.047774278, -0.029782977, 0.23137823, -0.2389453, 0.033015423, -0.10381626, -0.16437943, 0.20906886) * go_0(0.0, -1.0);
result += mat4(-0.098473966, 0.11013442, -0.18486807, 0.1907086, -0.17564997, -0.08509439, -0.42472756, -0.17446618, 0.3440862, 0.12719585, -0.12213955, -0.02246555, 0.18982963, 0.20809166, -0.36067408, 0.51116616) * go_0(0.0, 0.0);
result += mat4(-0.019805575, 0.07812505, 0.061653323, -0.08379226, 0.026396899, 0.009063019, -0.10845824, 0.0827647, 0.045301896, -0.07748021, -0.07435832, 0.14860612, -0.077515624, 0.010588131, -0.22704287, 0.26849246) * go_0(0.0, 1.0);
result += mat4(-0.02884339, -0.09512523, -0.038564682, 0.08862835, 0.041666254, -0.10532901, 0.040582962, -0.10063983, -0.15736029, -0.03644334, -0.005061672, 0.04302295, -0.046482194, -0.05262547, 0.05110866, 0.03204655) * go_0(1.0, -1.0);
result += mat4(-0.005932702, 0.033263832, 0.0044865874, -0.02328917, 0.056534443, -0.14084046, 0.022353357, 0.015087431, -0.2734596, -0.026544483, 0.06297078, 0.11277746, 0.06127936, 0.02466357, -0.04970561, 0.02098484) * go_0(1.0, 0.0);
result += mat4(0.013603583, 0.036264602, 0.10985147, 0.01532773, -0.09012781, 0.1132652, -0.17016481, 0.025332611, -0.077462606, 0.02990799, -0.10627784, -0.006231141, -0.089164406, -0.051507175, -0.043900985, 0.09049239) * go_0(1.0, 1.0);
result += mat4(-0.15391691, 0.1915742, 0.014101639, -0.022153432, 0.06291936, -0.017871676, -0.016763045, -0.14741553, -0.011252563, -0.20720159, -0.030648025, -0.0142307645, 0.010291614, -0.09243969, -0.052940153, 0.0061574522) * go_1(-1.0, -1.0);
result += mat4(0.032283742, 0.030768922, 0.1070225, -0.027818602, 0.10032608, 0.0061178426, -0.03561339, -0.26687133, 0.14369439, -0.11362691, -0.08980895, 0.066520914, 0.33414948, 0.006998835, 0.09193012, -0.2857383) * go_1(-1.0, 0.0);
result += mat4(-0.059588976, -0.02046844, -0.042585023, 0.031939838, 0.12796514, -0.06155685, 0.03540324, 0.009929082, -0.0039611827, 0.10790477, 0.049435645, -0.083034374, 0.23874004, -0.07460337, -0.020173345, -0.2006587) * go_1(-1.0, 1.0);
result += mat4(-0.13217632, 0.052319963, -0.026713084, -0.0051368694, -0.10380872, -0.28659084, 0.0044393227, 0.005174543, -0.05092618, -0.07092548, -0.027397033, -0.01609789, 0.13699281, -0.14706929, 0.17737861, -0.23746766) * go_1(0.0, -1.0);
result += mat4(0.19268502, 0.14133929, -0.1305119, -0.4034132, 0.057504695, -0.24550998, -0.081932545, 0.45489246, -0.29331785, 0.19625074, 0.063166246, 0.15158689, 0.6715147, -0.4610189, 0.08921431, 0.17761138) * go_1(0.0, 0.0);
result += mat4(0.044718128, -0.011809122, 0.024131307, -0.30093196, -0.05607289, 0.047759805, 0.004210022, 0.098192796, 0.030430846, 0.008207501, 0.12266905, -0.10549182, 0.11584339, -0.091016166, -0.08635591, -0.13889709) * go_1(0.0, 1.0);
result += mat4(-0.19226642, 0.07147627, -0.14759602, 0.4041079, 0.0744628, -0.19612685, 0.1498252, -0.06273549, 0.017959936, 0.10858338, -0.14985329, 0.062042814, -0.13240446, -0.24362786, 0.113626175, -0.15332204) * go_1(1.0, -1.0);
result += mat4(0.08383099, -0.13935047, -0.25981048, 0.16491203, 0.07513876, -0.28346774, 0.19722275, -0.044425573, 0.020889329, -0.22140723, 0.025403097, -0.09183192, 0.014202567, -0.18666178, 0.062913105, -0.047674105) * go_1(1.0, 0.0);
result += mat4(-0.1862771, 0.25878942, -0.043018065, 0.22144824, 0.016088247, 0.12113542, -0.11965952, -0.01587184, 0.07830932, -0.16069177, 0.13421321, 0.018718706, 0.09548377, 0.018543294, 0.013614677, -0.1054485) * go_1(1.0, 1.0);
result += mat4(-0.2121733, -0.015635416, 0.027564054, -0.085904464, 0.064805664, -0.070543915, 0.08966146, -0.06359783, 0.01131311, 0.046913184, -0.09809833, -0.092063695, -0.087217696, 0.012411829, 0.0045399712, 0.027389864) * go_2(-1.0, -1.0);
result += mat4(-0.19307798, 0.09449126, 0.084036835, 0.30262446, 0.011706106, 0.029800637, 0.04612629, 0.006186647, 0.11228541, 0.055147965, 0.17659879, -0.023410015, 0.19965266, -0.06684007, -0.081968054, -0.052410994) * go_2(-1.0, 0.0);
result += mat4(-0.058564443, 0.08252549, 0.058217794, 0.0864448, -0.25663558, 0.080260284, -0.0010294432, 0.05830051, -0.07684524, 0.1820709, 0.04438993, 0.019178499, -0.12425012, -0.04596089, -0.010032888, -0.0012803525) * go_2(-1.0, 1.0);
result += mat4(-0.43352658, 0.15262963, 0.25620222, 0.22428556, 0.09667152, 0.0037820593, -0.07951691, -0.11553085, 0.12982155, 0.17988266, -0.14283511, 0.074744284, 0.03604327, 0.00452661, -0.12865154, -0.020020623) * go_2(0.0, -1.0);
result += mat4(0.06850602, -0.18057181, 0.2093389, -0.07333886, 0.28406742, -0.048766967, 0.18114483, 0.47292945, -0.2340266, -0.06862712, 0.28263155, 0.3150323, -0.054724697, -0.16958356, 0.27928987, -0.19666018) * go_2(0.0, 0.0);
result += mat4(0.03281329, 0.0038649621, -0.07108877, 0.10791149, 0.15235375, -0.3083721, 0.168294, 0.10379698, 0.029038485, 0.16282903, 0.04483725, -0.018684763, 0.108186625, 0.027885616, -0.019351846, 0.1623065) * go_2(0.0, 1.0);
result += mat4(-0.110499054, 0.31347123, 0.030852, 0.01631416, -0.1466389, 0.080429435, -0.18689284, 0.10667815, 0.20645237, -0.18004708, -0.10570413, -0.15435064, -0.019000605, -3.126077e-06, 0.037761535, -0.015040956) * go_2(1.0, -1.0);
result += mat4(-0.023364332, -0.023399066, 0.2712722, 0.049637552, -0.10222765, -0.2698945, 0.20991959, 0.04921932, 0.21510898, -0.0751939, -0.19781734, -0.28162366, -0.041881047, 0.0065111094, -0.04102195, 0.0982682) * go_2(1.0, 0.0);
result += mat4(-0.032176614, 0.019144032, -0.08985387, 0.091637276, 0.1012352, 0.0003583357, 0.07897295, -0.09531175, -0.001155058, 0.074372366, -0.026186578, 0.07283374, 0.06052053, 0.009307753, -0.03874333, -0.06228009) * go_2(1.0, 1.0);
result += mat4(-0.022224072, -0.15717922, -0.1406057, -0.05941157, -0.028769474, -0.21226564, -0.036570027, 0.22266355, 0.14120889, 0.014577123, 0.10216447, 0.018429281, 0.056729726, -0.055834044, 0.058146577, -0.11999068) * go_3(-1.0, -1.0);
result += mat4(0.009995364, -0.020045493, -0.0057422677, 0.0643022, 0.016475432, -0.030856136, 0.042140726, 0.15077904, -0.32955253, 0.0694449, 0.17931722, 0.3439302, -0.12484157, -0.10958869, -0.15755124, -0.09755644) * go_3(-1.0, 0.0);
result += mat4(-0.008314924, 0.07704758, 0.043228816, -0.08110893, 0.099286236, -0.053224478, 0.22877018, -0.189486, -0.00798416, 0.018341504, 0.10734141, 0.0752633, -0.042524844, -0.086395286, 0.14299925, 0.026488977) * go_3(-1.0, 1.0);
result += mat4(-0.052531082, 0.19139186, 0.12205995, -0.2573172, 0.15157184, 0.0073150825, 0.089774385, 0.06604469, -0.16528498, -0.002511137, 0.14287429, -0.07819732, 0.025014274, 0.15338829, 0.0761692, -0.02803716) * go_3(0.0, -1.0);
result += mat4(-0.21000335, 0.15277153, 0.08546171, 0.2816124, -0.16559112, -0.11068559, 0.47053605, -0.009787771, -0.0013089112, -0.06985127, 0.44743782, 0.25142467, -0.32670796, 0.044035822, -0.12545367, -0.2996084) * go_3(0.0, 0.0);
result += mat4(-0.11526387, 0.15654811, 0.099616654, 0.15473685, 0.21278231, 0.046207245, 0.117993094, -0.26825273, -0.12539764, 0.14013724, 0.17357737, -0.05387817, 0.076738276, -0.13339446, 0.15005626, -0.2108176) * go_3(0.0, 1.0);
result += mat4(-0.0008846504, -0.05998622, -0.028892396, 0.04784136, 0.0104263965, 0.10899508, -0.073364735, 0.077516064, -0.074248806, -0.21749993, -0.26203, 0.041161157, 0.09366407, -0.026498007, 0.0122177545, 0.03892727) * go_3(1.0, -1.0);
result += mat4(0.04349908, 0.13671173, 0.2242545, -0.028021423, -0.03802222, 0.0052366396, -0.010709643, 0.031290106, 0.06291333, -0.024909683, -0.15439379, -0.04502091, 0.2062182, -0.5983536, -0.09670497, -0.38446042) * go_3(1.0, 0.0);
result += mat4(-0.008962513, 0.13044207, 0.04964221, 0.012250417, 0.012129821, 0.019985713, -0.06421885, 0.009168735, -0.044516414, 0.071368866, -0.006634213, 0.06497366, 0.08578495, -0.10586125, 0.06628038, -0.14006054) * go_3(1.0, 1.0);
result += vec4(0.056541316, 0.041788545, -0.036094554, -0.021763096);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf1
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.0647927, 0.053666476, -0.14723225, 0.027874574, -0.0003166473, 0.07337155, -0.061972085, -0.012667777, -0.17071614, 0.091927536, -0.051160213, 0.21336353, 0.13854574, 0.09582817, 0.032316446, 0.13838023) * go_0(-1.0, -1.0);
result += mat4(-0.0398984, 0.108049214, 0.093780346, -0.022015186, -0.15188989, -0.1381083, 0.2998843, 0.21623154, -0.08862326, 0.025862623, 0.06895634, 0.13529755, 0.06957801, -0.0011681129, 0.105972745, -0.04722446) * go_0(-1.0, 0.0);
result += mat4(-0.026321493, -0.04828038, -0.012545767, -0.005490858, -0.054038163, 0.075943105, -0.11526662, 0.022242405, -0.03543104, -0.12451852, -0.14911178, 0.013503498, 0.08773292, 0.09695139, -0.013498657, -0.27424073) * go_0(-1.0, 1.0);
result += mat4(0.018575635, -0.11321618, -0.07853153, 0.04104883, 0.0018416744, 0.11579002, 0.03685964, -0.031546146, -0.1755398, 0.23517849, -0.08095411, 0.031999595, -0.18542038, -0.26171613, -0.20567231, -0.05683613) * go_0(0.0, -1.0);
result += mat4(0.1538556, 0.21723682, 0.12131733, -0.15308167, 0.103326, -0.006956118, 0.043583486, -0.23811384, -0.103285454, 0.05543916, -0.37894246, 0.32072112, 0.22651967, 0.03516268, 0.34612176, 0.23688535) * go_0(0.0, 0.0);
result += mat4(0.040021293, 0.0029912095, 0.04885362, 0.061496444, 0.016926387, -0.118446946, 0.038948335, -0.0934512, -0.25194243, -0.054018084, -0.07149527, 0.017903058, 0.0845516, 0.33802906, 0.11953944, -0.081294954) * go_0(0.0, 1.0);
result += mat4(-0.09558082, -0.36974236, -0.07524102, 0.11131445, 0.047626104, 0.12854609, -0.10264962, -0.044669047, -0.05572307, 0.34475142, -0.16806377, -0.0037204176, 0.03400533, -0.04047774, 0.024379745, 0.09056291) * go_0(1.0, -1.0);
result += mat4(-0.039392482, 0.2553437, 0.11705501, 0.03219211, 0.073977776, -0.16610906, -0.032796364, -0.054669864, -0.07123178, 0.00079619256, -0.36920992, -0.029054813, 0.12830003, 0.004987549, 0.08724278, -0.029499404) * go_0(1.0, 0.0);
result += mat4(0.021272454, -0.063295126, 0.011779576, 0.103093, -0.011095461, 0.027948728, -0.014605259, -0.04723974, -0.05334346, -0.044831257, -0.07296399, -0.03314197, -0.01687865, -0.09261895, -0.06128567, 0.092708185) * go_0(1.0, 1.0);
result += mat4(0.0077418387, 0.00871427, 0.060824487, 0.1093608, -0.021077013, -0.057341542, -0.04769576, -0.08144089, 0.0212823, -0.06731425, -0.04134463, -0.0016761447, -0.03402026, 0.036424547, 0.11689576, -0.14946719) * go_1(-1.0, -1.0);
result += mat4(0.18536687, 0.020073935, 0.17041959, 0.024790209, 0.08397728, -0.13884324, 0.013950321, -0.055075396, -0.09317963, -0.05723721, -0.060491834, 0.0017911601, -0.109154835, 0.010338362, -0.1982491, -0.21752335) * go_1(-1.0, 0.0);
result += mat4(0.031852514, 0.031424347, 0.07817056, 0.07770759, 0.019805199, -0.091223724, 0.11914662, 0.1673029, -0.018734453, 0.16275099, 0.23245652, 0.36139074, -0.1396047, -0.14774057, 0.13756078, -0.123794965) * go_1(-1.0, 1.0);
result += mat4(-0.034937833, 0.20777488, 0.10104809, -0.035140667, 0.2536575, 0.010970045, 0.16896339, -0.081219964, -0.062478427, -0.0010431948, -0.027980985, 0.11446318, -0.127309, 0.21002083, 0.044436257, -0.16986957) * go_1(0.0, -1.0);
result += mat4(0.06309646, -0.042341243, 0.36642808, 0.18653205, 0.06973023, 0.06315932, -0.323688, 0.25672218, 0.042820994, 0.13792914, -0.12892757, -0.09220378, -0.18939693, 0.03862022, -0.17376114, -0.24673308) * go_1(0.0, 0.0);
result += mat4(-0.02130602, -0.35428852, -0.011634983, -3.9823462e-05, 0.110818714, -0.2981158, 0.060209107, 0.012538829, -0.0744833, -0.050204318, -0.12676497, -0.031484153, -0.28799182, 0.22338839, -0.070876874, -0.02102363) * go_1(0.0, 1.0);
result += mat4(-0.07929991, 0.014598492, 0.23034762, 0.024872296, 0.07480494, -0.17139243, -0.014421178, 0.056448363, -0.028626937, -0.022152562, 0.044871796, -0.048653606, 0.009350802, 0.019022083, -0.08554845, -0.0922645) * go_1(1.0, -1.0);
result += mat4(-0.027405115, 0.1831188, 0.28516722, 0.19882526, 0.27299204, -0.06910511, 0.03244419, -0.0031333128, 0.061055277, -0.114398144, 0.03729459, -0.07840815, -0.37776002, -0.24129418, -0.54815483, -0.2702045) * go_1(1.0, 0.0);
result += mat4(0.053723935, 0.13472083, 0.09563273, 0.19009806, -0.18722993, -0.25939655, -0.016197463, -0.067061596, 0.1647598, 0.061905228, 0.06191816, -0.018582113, -0.07218153, 0.11278394, 0.05478068, -0.104871586) * go_1(1.0, 1.0);
result += mat4(0.0036616288, -0.045782693, -0.226954, -0.05043515, -0.078096785, -0.036197383, 0.09269631, 0.016823346, -0.0060579977, -0.041455746, 0.09032774, -0.09217121, 0.058089796, 0.060311552, 0.033079024, 0.022586476) * go_2(-1.0, -1.0);
result += mat4(0.0436363, -0.079482526, 0.0027447809, 0.039558932, 0.13275702, 6.898711e-05, -0.21961488, -0.11315821, 0.0076181027, -0.025279062, -0.15829584, -0.063141204, 0.062049046, 0.13117202, -0.02435016, 0.109555416) * go_2(-1.0, 0.0);
result += mat4(-0.010148116, 0.056620967, -0.015910713, -0.07370375, 0.1529919, 0.005792597, 0.02771225, -0.17027487, 0.096740395, 0.063347995, 0.17823112, 0.054105148, 0.04995114, -0.28613812, 0.06369567, 0.15978208) * go_2(-1.0, 1.0);
result += mat4(-0.13688345, 0.16967694, -0.061759472, 0.013682004, -0.1290496, 0.07167547, -0.065592445, -0.17897636, 0.057080988, 0.035630587, 0.09140394, -0.08695068, 0.16807681, 0.014749346, 0.07875138, 0.034913708) * go_2(0.0, -1.0);
result += mat4(-0.098915346, -0.31459075, -0.10892429, 0.1557498, -0.19764107, -0.26881596, -0.03589311, 0.45288458, -0.34171388, 0.12675741, 0.18415868, -0.19770056, 0.29025507, -0.15812592, 0.09685835, 0.0027761247) * go_2(0.0, 0.0);
result += mat4(0.06425249, -0.01169722, 0.06379363, 0.053835012, -0.07356561, -0.06367294, 0.108630784, -0.14137438, 0.08536725, -0.03209748, 0.07250959, -0.014214082, 0.07170588, -0.25647813, 0.1092683, 0.18791042) * go_2(0.0, 1.0);
result += mat4(-0.023783233, 0.14261739, 0.102011986, -0.03633555, -0.05032627, 0.09378387, 0.11764051, 0.1353335, 0.032817088, -0.1352964, -0.00667997, -0.13388929, 0.022861317, 0.0037358075, 0.018605746, -0.0009892831) * go_2(1.0, -1.0);
result += mat4(0.22419162, -0.23105696, -0.09900454, -0.15831396, 0.12398773, 0.097933106, -0.13189293, 0.1330756, -0.19673057, -0.037342317, -0.13462654, -0.08974021, 0.030326528, -0.0815862, -0.118352115, 0.009187904) * go_2(1.0, 0.0);
result += mat4(-0.012130391, -0.06408448, 0.13710785, -0.06678414, -0.09970725, -0.14895032, -0.02366641, 0.029581001, -0.07101809, 0.09414698, 0.018300869, 0.009139046, -0.0027311493, -0.2359952, -0.011602826, -0.007582444) * go_2(1.0, 1.0);
result += mat4(-0.15473361, -0.06868751, -0.030721204, -0.08650113, 0.071349874, -0.08177769, 0.1611948, 0.18305337, -0.0144878505, 0.10975452, -0.026968453, -0.04909913, -0.059665974, 0.056036238, -0.11623168, -0.10584912) * go_3(-1.0, -1.0);
result += mat4(-0.096973225, 0.054132458, -0.010600018, 0.089397885, -0.0031138035, 0.037452973, 0.041115325, 0.1924831, 0.14759748, 0.032560788, -0.082884625, 0.0324635, -0.083511285, -0.050381303, 0.025589975, -0.0981257) * go_3(-1.0, 0.0);
result += mat4(-0.09183111, 0.034952193, -0.048511654, 0.020719057, 0.1863456, 0.01902738, 0.14455654, -0.008500172, 0.16385981, -0.07806569, -0.031216217, -0.17002788, -0.08882952, 0.07335293, -0.2223089, 0.01706056) * go_3(-1.0, 1.0);
result += mat4(-0.08361569, 0.046698716, -0.016646344, 0.09351987, 0.0054158634, -0.13641126, -0.12396605, 0.011380122, 0.040951792, -0.11222528, -0.0031548145, -0.0022303525, 0.0350846, -0.03280425, -0.09972476, -0.113325305) * go_3(0.0, -1.0);
result += mat4(-0.19961461, -0.27561286, -0.12783135, -0.062596925, 0.005870981, -0.24796526, 0.18717633, -0.16945636, -0.076396205, -0.08411448, 0.13751988, 0.21014418, -0.008655945, -0.09848541, -0.14536901, -0.2132181) * go_3(0.0, 0.0);
result += mat4(0.14118621, 0.20831147, -0.020545695, 0.008340737, 0.016840864, -0.16912372, -0.121718146, 0.15108089, -0.19803092, -0.07827729, -0.047639225, -0.12277847, 0.04974115, -0.09349339, -0.2756667, -0.19581003) * go_3(0.0, 1.0);
result += mat4(-0.0036992705, 0.16539848, 0.022026122, 0.07740234, -0.035687633, -0.004568715, 0.017408118, -0.09757294, -0.094941914, -0.3381112, -0.12724453, 0.025583982, -0.18571027, 0.047607586, -0.0704089, -0.055323426) * go_3(1.0, -1.0);
result += mat4(0.13821335, 0.028168043, 0.09990671, -0.032266147, -0.067236245, 0.11512147, -0.112986445, -0.10818019, -0.10062181, 0.21276556, 0.01681818, 0.069806606, 0.09628121, 0.06456379, 0.10394843, -0.02343886) * go_3(1.0, 0.0);
result += mat4(0.041937463, 0.072631165, 0.045366894, -0.0046993676, 0.03946691, 0.121010706, -0.030089365, -0.007266469, 0.0092267515, 0.14853416, -0.033248078, -0.027284347, -0.10031526, 0.15864117, -0.16782752, -0.18466589) * go_3(1.0, 1.0);
result += vec4(0.07722432, -0.025165567, 0.034291282, -0.09902708);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.004729794, -0.0124398535, -0.08538641, -0.058604605, 0.008671952, 0.25604513, 0.020800482, 0.24144122, -0.028920606, -0.04705229, 0.030192787, 0.0010597534, 0.017666103, 0.0041322373, 0.20027764, 0.08919112) * go_0(-1.0, -1.0);
result += mat4(0.0001626656, 0.05816014, -0.0060765734, 0.08811165, 0.35835367, -0.016291425, -0.56892496, 0.083845764, 0.15026698, -0.15916558, 0.08069463, -0.3931291, -0.0123534845, -0.111639686, -0.14637001, -0.08171439) * go_0(-1.0, 0.0);
result += mat4(-0.114976816, 0.023376396, 0.13855027, 0.07438716, -0.069991484, 0.20377779, 0.23929878, -0.040769435, 0.018832395, 0.005638609, -0.091848075, 0.027843866, 0.023744943, -0.06620523, -0.11678267, 0.0844119) * go_0(-1.0, 1.0);
result += mat4(0.0035854098, -0.08432094, -0.17799544, -0.10041983, 0.25605857, 0.021009786, 0.030499447, -0.09928291, 0.052178737, -0.08286175, -0.057888374, 0.024606042, 0.046342995, 0.13875343, 0.11279266, 0.19826262) * go_0(0.0, -1.0);
result += mat4(-0.016232021, -0.21539623, 0.0936961, 0.021143785, 0.094262615, 0.049040064, 0.40978724, 0.15347758, 0.08884813, -0.24887115, -0.14756748, -0.5020875, 0.112477, 0.1466549, -0.33418837, 0.5769466) * go_0(0.0, 0.0);
result += mat4(-0.16832942, -0.07354198, -0.12081261, -0.055348314, 0.39716053, 0.25583258, 0.09870877, 0.2151021, -0.025700683, -0.1801462, -0.04616654, -0.02782245, -0.054461803, -0.00042802413, -0.00163228, -0.004240747) * go_0(0.0, 1.0);
result += mat4(-0.05193433, -0.0018198475, -0.17647028, -0.19462106, 0.1538165, 0.054894235, 0.12183955, 0.07340974, -0.0019901982, 0.0357373, -0.07597063, -0.06681543, -0.00090057997, -0.053894397, -0.010301875, -0.16553953) * go_0(1.0, -1.0);
result += mat4(-0.30873474, -0.2836045, 0.057037193, -0.5016378, 0.11952749, 0.102353275, 0.2351629, -0.14635189, -0.019398788, -0.08776502, 0.021669978, -0.089918956, -0.2187901, -0.1180891, -0.049789533, -0.16109149) * go_0(1.0, 0.0);
result += mat4(-0.078335494, -0.08867304, 0.03349591, -0.1000293, -0.20235832, 0.22917585, -0.09905303, 0.08381748, 0.014350217, -0.14478815, -0.027479894, -0.026432173, -0.10309177, -0.09860884, -0.019177807, -0.06963025) * go_0(1.0, 1.0);
result += mat4(0.008169383, 0.12532842, -0.23369955, 0.077973194, 0.09076616, -0.021277165, 0.1721421, -0.26914293, -0.014729218, -0.023279984, -0.057670787, 0.003598546, -0.015225789, -0.0115396585, -0.26196182, -0.10724508) * go_1(-1.0, -1.0);
result += mat4(0.16542235, 0.06589374, 0.07410237, 0.26753154, -0.3356288, 0.3096256, 0.07112498, -0.0992165, 0.15020338, -0.11021673, 0.18803611, 0.12918204, 0.109007336, -0.031968266, 0.057093572, 0.035949256) * go_1(-1.0, 0.0);
result += mat4(0.065006174, 0.031055925, 0.0390232, -0.01678507, -0.21553491, 0.14171642, -0.19541772, -0.033691674, -0.06241631, 0.07497651, 0.024557155, 0.056778047, -0.060191352, -0.0261998, 0.07493729, -0.0699132) * go_1(-1.0, 1.0);
result += mat4(-0.008541382, 0.020270415, -0.027760057, -0.040962905, -0.26732433, 0.34379438, -0.23012447, 0.0051356517, -0.04059567, 0.0972959, 0.039965224, -0.14796777, -0.0016924662, -0.116963714, -0.026353523, -0.29799464) * go_1(0.0, -1.0);
result += mat4(0.03329303, -0.12663862, -0.0004959157, -0.11162377, 0.26238343, 0.43260252, -0.16504994, 0.10727678, -0.22505566, 0.43474057, 0.43304008, 0.05143919, 0.40494493, 0.08689636, -0.035733614, 0.25727916) * go_1(0.0, 0.0);
result += mat4(0.12175736, -0.014467151, -0.17461288, -0.18480565, -0.26439998, 0.307935, -0.058916792, -0.014292711, -0.0569471, 0.10751278, -0.04134206, 0.1847734, -0.07519831, -0.033909313, -0.05001451, -0.136606) * go_1(0.0, 1.0);
result += mat4(0.1424893, -0.026820501, 0.19645774, -0.0011315406, -0.14680974, 0.07662838, 0.21108222, 0.13260938, 0.17923595, -0.085527614, 0.08217639, 0.06579479, 0.05985784, -0.09016323, 0.11172888, 0.111903176) * go_1(1.0, -1.0);
result += mat4(0.19842595, 0.0093640275, 0.10433465, 0.13341904, -0.082806975, 0.22555825, -0.1315717, 0.11907785, 0.24012424, 0.47776055, 0.1835734, 0.17483878, 0.079803735, 0.01155073, -0.21146573, -0.16484722) * go_1(1.0, 0.0);
result += mat4(0.15064004, 0.021381427, 0.18301587, 0.21225913, 0.054995645, 0.03212186, 0.052798916, -0.048424408, 0.03609021, 0.0964704, -0.059469886, -0.05133066, -0.08157349, 0.051145166, -0.09107608, -0.1362262) * go_1(1.0, 1.0);
result += mat4(0.090521574, -0.014747857, -0.081675015, -0.118686825, 0.04848682, -0.033071827, 0.008534588, 0.023765508, 0.16849907, -0.21797262, -0.17049783, -0.07824179, -0.033794608, 0.052612655, 0.095820345, -0.07262317) * go_2(-1.0, -1.0);
result += mat4(0.22816367, -0.13772108, -0.036353834, -0.47638395, -0.0530902, 0.14089061, 0.076203234, 0.18006112, 0.121814854, -0.20750527, 0.08266107, -0.28634354, 0.14301859, -0.13458411, 0.00501663, -0.039783802) * go_2(-1.0, 0.0);
result += mat4(-0.103384845, -0.14389835, 0.08275834, -0.068423435, 0.22643796, -0.02966374, -0.2847584, 0.037081387, 0.02349005, -0.19353923, -0.00095957273, -0.13623689, -0.073120415, 0.03941467, 0.21864155, -0.014019576) * go_2(-1.0, 1.0);
result += mat4(-0.082576886, 0.17085212, 0.08971252, -0.04213377, -0.032548156, 0.022137715, 0.08399252, -0.0011743539, -0.09410863, -0.41728264, -0.20709297, -0.18933547, 0.027059928, 0.09743364, 0.2504647, -0.041173562) * go_2(0.0, -1.0);
result += mat4(-0.20924084, 0.291118, 0.029851688, 0.16953468, 0.02936709, 0.12213576, 0.22944322, 0.108747594, 0.0001881129, -0.27398208, -0.009702691, 0.15449248, -0.9472944, -0.26114875, -0.28161275, -0.3495961) * go_2(0.0, 0.0);
result += mat4(-0.12994622, -0.2758638, -0.1091727, -0.0968308, -0.14323105, 0.035175014, -0.08023811, 0.006023802, -0.031529594, -0.1486306, -0.3398172, -0.23240276, -0.29163983, 0.173475, 0.18809283, 0.22197202) * go_2(0.0, 1.0);
result += mat4(0.048254848, -0.083444916, -0.014334202, 0.060992356, -0.023099286, -0.09492961, 0.05592045, 0.0026059286, 0.08998117, -0.108810075, -0.053304546, 0.045926623, 0.068255246, 0.099023566, 0.01595483, 0.1336309) * go_2(1.0, -1.0);
result += mat4(0.21916585, 0.2837387, 0.14624594, 0.18843961, -0.06747584, 0.054924384, -0.082568415, 0.05011459, 0.014297759, -0.3884833, -0.054417178, -0.18970548, 0.088336475, -0.030646667, -0.2980552, -0.030035203) * go_2(1.0, 0.0);
result += mat4(-0.02748568, -0.011897529, -0.2370837, -0.016740574, -0.0282112, 0.050353892, -0.10761107, -0.00036999505, 0.037646662, -0.17742962, 0.06489219, -0.158852, -0.08016933, 0.07808515, -0.105895035, 0.079869986) * go_2(1.0, 1.0);
result += mat4(-0.0058994526, -0.037170693, 0.2574696, 0.06199102, -0.04497728, -0.10667442, -0.15183865, 0.0212881, -0.030842574, 0.073473394, 0.010764398, -0.00084518327, -0.03893014, -0.009649613, 0.07443129, 0.15108284) * go_3(-1.0, -1.0);
result += mat4(0.11325495, -0.096435815, -0.097331434, -0.049700152, -0.17231967, 0.047090057, -0.019111065, 0.104790315, -0.15004838, 0.13950798, 0.055996202, -0.070548095, 0.047154237, -0.007650949, -0.053611025, -0.012242293) * go_3(-1.0, 0.0);
result += mat4(0.12787002, -0.04958212, 0.053988468, 0.0017896162, 0.049493514, -0.009475431, -0.0022641935, 0.03933694, -0.005174597, 0.043754533, -0.1432976, 0.037084177, -0.04601288, -0.032077815, -0.059897035, 0.12584484) * go_3(-1.0, 1.0);
result += mat4(0.019409029, 0.10492923, 0.268368, 0.12597778, -0.17733063, -0.0085961, -0.27136415, -0.049664587, 0.012515404, -0.21444482, -0.39275557, -0.12297177, 0.06800057, 0.19228315, 0.06245887, 0.35772634) * go_3(0.0, -1.0);
result += mat4(-0.16317715, 0.2288402, -0.23235172, 0.22230752, -0.1646375, 0.13366091, 0.16681044, -0.17399235, 0.33997267, -0.3179832, -0.34756508, 0.39843196, -0.10748536, 0.322923, 0.23339489, 0.08684083) * go_3(0.0, 0.0);
result += mat4(0.02835275, 0.12314228, 0.24030593, 0.30856124, 0.055735108, -0.044914473, 0.0031432225, 0.07469899, 0.1778018, 0.107083894, -0.023706734, -0.15501897, 0.0943098, -0.034707237, -0.18622099, 0.05257965) * go_3(0.0, 1.0);
result += mat4(0.042839274, 0.12597966, 0.08979042, -0.0647561, -0.050434645, 0.049438696, -0.20008127, -0.05572608, 0.046238814, 0.12622325, -0.019017145, -0.13960391, -0.040050175, 0.14298008, -0.20270552, 0.13391526) * go_3(1.0, -1.0);
result += mat4(-0.0073277587, 0.10606624, -0.08940439, -0.09656414, 0.12387374, -0.0013147948, 0.23607181, -0.00037969893, 0.050353236, -0.17266603, 0.27796733, -0.09877832, 0.02711225, 0.096394345, 0.07457944, 0.21541388) * go_3(1.0, 0.0);
result += mat4(-0.18612787, -0.00027517386, -0.17136407, -0.06413671, 0.025629476, -0.04570916, 0.0008431566, -0.03419168, 0.08123608, 0.09465922, 0.11975521, 0.1269741, 0.08413221, 0.12125001, 0.04727287, 0.072378494) * go_3(1.0, 1.0);
result += vec4(0.04244928, -0.014280219, 0.017129054, -0.08807801);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf1
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.01973856, -0.05053795, 0.015545361, 0.10867395, 0.33441806, 0.14731607, 0.6793983, -0.21394718, -0.00846322, 0.09146322, -0.07427475, -0.078477465, -0.090998545, 0.133366, 0.105515696, -0.13784988) * go_0(-1.0, -1.0);
result += mat4(-0.05404873, 0.09784018, -0.1337389, -0.18082313, 0.13461179, -0.3816801, 0.12209786, 0.08176651, 0.10461896, -0.43315184, 0.017470734, 0.20423968, -0.03941875, -0.101959296, -0.09440259, 0.09154717) * go_0(-1.0, 0.0);
result += mat4(0.17229515, -0.06907825, -0.008382803, -0.16671611, -0.01576541, 0.03985307, 0.08209482, -0.11707446, -0.11793074, 0.13702396, -0.02013158, 0.07302033, -0.022301994, -0.11464677, 0.036753565, -0.093276784) * go_0(-1.0, 1.0);
result += mat4(-0.017650167, 0.009475923, -0.17856382, 0.15925962, 0.06434641, -0.15568036, 0.038135886, 0.18855911, -0.04427734, 0.1878215, 0.10856261, 0.0041275816, -0.12046199, 0.13610138, 0.3741596, -0.12934728) * go_0(0.0, -1.0);
result += mat4(-0.24631616, 0.0169485, -0.035534818, 0.37795424, -0.08546174, 0.07817259, 0.42897213, -0.47965595, -0.0146556785, -0.20510523, -0.18889453, 0.06476019, 0.1021008, -0.35398817, -0.031071864, -0.21416448) * go_0(0.0, 0.0);
result += mat4(0.32810766, 0.050585747, -0.17658374, -0.13881154, 0.16417882, -0.21286008, -0.106835455, -0.1722344, -0.14151084, 0.08962986, 0.057395387, -0.01623662, 0.02570415, 0.15626897, -0.12687978, 0.080729105) * go_0(0.0, 1.0);
result += mat4(-0.050597478, -0.018753758, -0.036346875, -0.017908493, 0.058593344, 0.008303028, 0.05254987, -0.06635018, -0.022532012, 0.029511122, 0.026682215, -0.054647952, 0.069466785, -0.08892492, 0.025351115, -0.023130694) * go_0(1.0, -1.0);
result += mat4(0.2412473, -0.16138165, -0.15117447, 0.11851003, -0.096868426, 0.082690425, 0.27923304, 0.11590443, 0.19363573, -0.15770023, -0.066793665, 0.011681678, 0.14037277, -0.112065665, -0.048159517, 0.009453693) * go_0(1.0, 0.0);
result += mat4(0.1580054, -0.0060506654, 0.05267837, -0.09178131, -0.09107123, 0.23191126, 0.21108283, -0.070422985, 0.024321035, 0.06131459, 0.066626504, 0.032481454, 0.044402298, 0.1390604, -0.14432502, 0.040869843) * go_0(1.0, 1.0);
result += mat4(0.10264861, 0.013504324, 0.012482852, -0.1781206, -0.12799414, -0.27026084, -0.123830505, 0.098105, -0.039127555, 0.09367889, 0.122323096, 0.1416734, 0.044763107, -0.21801683, -0.14018978, 0.17646866) * go_1(-1.0, -1.0);
result += mat4(0.017453065, 0.11498537, -0.10998983, -0.3116098, -0.3099762, 0.5024706, 0.051817298, 0.03170681, -0.18937826, 0.07946567, -0.11978771, -0.09523745, -0.0033551592, -0.11768945, 0.08932359, -0.06689581) * go_1(-1.0, 0.0);
result += mat4(0.1507582, -0.013266159, -0.073085934, -0.07252967, -0.06301927, -0.13218755, 0.12984878, -0.13678701, 0.023422396, 0.082123175, 0.006906731, -0.004018426, -0.15813835, 0.13711788, 0.016018609, 0.13443229) * go_1(-1.0, 1.0);
result += mat4(-0.06960673, 0.16156524, -0.1374069, -0.05803206, -0.077960715, -0.10676749, 0.26282015, 0.03521529, 0.058099385, -0.014738148, 0.0011174522, 0.24279532, -0.023991548, -0.108812414, -0.08886019, 0.20584475) * go_1(0.0, -1.0);
result += mat4(-0.08043308, 0.063343, 0.055290066, -0.15991378, -0.08096304, -0.23888679, 0.019161629, 0.38381267, 0.3672934, -0.119608454, -0.43623593, -0.46014485, -0.5323366, 0.1318621, 0.087373205, -0.05535459) * go_1(0.0, 0.0);
result += mat4(0.20640239, -0.1369444, -0.21677823, 0.08202178, 0.10515278, 0.06810837, 0.073207974, 0.23623931, 0.102422275, -0.05016664, -0.0039228587, -0.1810343, -0.2235563, -0.1246854, 0.1428113, -0.10609135) * go_1(0.0, 1.0);
result += mat4(-0.031941894, -0.08905056, 0.21501167, 0.11244667, -0.011811734, 0.21630247, 0.07589472, -0.040489636, -0.11824066, -0.11520391, -0.10075633, -0.035642453, 0.062144946, 0.0073282206, 0.14119269, -0.060479023) * go_1(1.0, -1.0);
result += mat4(-0.29382935, -0.056808118, 0.051812876, -0.061358813, -0.08344258, 0.124203674, 0.037964176, -0.01961274, -0.000951725, 0.50005037, -0.24176972, 0.06487161, -0.15469861, 0.04336187, 0.17826353, 0.040010225) * go_1(1.0, 0.0);
result += mat4(0.02044482, -0.0879271, -0.01053958, -0.31148303, 0.07497373, -0.11548258, -0.1666126, 0.02369657, -0.058044076, 0.010801491, -0.005933901, -0.08910467, 0.007953008, 0.03761974, -0.029501524, 0.16816042) * go_1(1.0, 1.0);
result += mat4(0.1779597, -0.10213089, 0.29942423, -0.016642543, -0.015537001, -0.04676146, 0.09585872, -0.0055750017, -0.014361908, -0.20667697, -0.11348746, 0.13081487, -0.10437329, 0.14328459, 0.11648822, -0.09163837) * go_2(-1.0, -1.0);
result += mat4(0.019033967, -0.12420627, -0.07748253, 0.43203858, -0.109799065, 0.07605535, 0.060791396, -0.24517195, -0.15674245, 0.21267459, 0.10665515, -0.073150024, -0.1358355, 0.0054066703, -0.16434059, -0.06031853) * go_2(-1.0, 0.0);
result += mat4(-0.18834068, 0.26840356, -0.12937617, 0.16103932, -0.0062331813, -0.13630053, -0.013911821, 0.022389365, -0.044232946, -0.056454606, 0.022426741, 0.18010215, 0.041900013, 0.03375041, -0.11376866, -0.010313381) * go_2(-1.0, 1.0);
result += mat4(0.12497669, -0.31161824, 0.097568035, 0.19443443, -0.05056519, -0.0031457904, 0.1055554, -0.083650924, 0.07630523, -0.34177595, -0.093093194, 0.20701368, -0.030962149, -0.054470222, -0.23853977, 0.004326528) * go_2(0.0, -1.0);
result += mat4(0.34370202, 0.085750066, -0.16071722, -0.54335934, -0.35595295, -0.050744478, -0.17405547, 0.008628697, -0.007086256, 0.23164117, 0.340156, 0.5475976, -0.15292351, 0.28019544, 0.038059216, 0.0044727) * go_2(0.0, 0.0);
result += mat4(-0.08231968, -0.0052294536, 0.07451547, 0.22278999, -0.3305531, 0.0017458396, 0.10818422, -0.21325395, -0.08807993, -0.110342845, 0.10082142, -0.051594347, 0.24192205, -0.18042035, -0.0095462985, -0.08757798) * go_2(0.0, 1.0);
result += mat4(0.096379586, 0.021887815, -0.05097233, -0.06797989, -0.026171045, 0.022944937, -0.015915364, 0.037667938, 0.17216732, -0.014889412, 0.07343887, 0.028236505, 0.0015047621, 0.1355103, -0.09918284, -0.07673695) * go_2(1.0, -1.0);
result += mat4(-0.25385055, 0.15163356, 0.0030003798, 0.18464413, 0.05611221, 0.099498056, -0.07128191, 0.042955168, 0.027493173, 0.07440157, 0.07814497, 0.096160784, 0.13571084, 0.056412842, -0.031997006, -0.16073681) * go_2(1.0, 0.0);
result += mat4(-0.21634746, 0.025153082, -0.064477116, 0.0005679147, -0.0029436245, 0.12794618, 0.024849026, 0.03018052, 0.11723976, 0.059955597, -0.013594654, 0.09091745, 0.04775348, 0.21260159, -0.07463213, -0.06727042) * go_2(1.0, 1.0);
result += mat4(-0.12166018, 0.024545137, 0.08611618, -0.17627168, 0.09042604, -0.14157623, -0.22147785, 0.09100581, 0.11078359, 0.031410985, -0.17170976, 0.09532806, -0.059569277, 0.09392676, 0.11784347, -0.21471368) * go_3(-1.0, -1.0);
result += mat4(0.1483187, -0.2217563, 0.12032977, 0.14932398, 0.27428308, -0.04568031, 0.12670338, 0.09586169, 0.06700745, 0.005126449, 0.0027694793, -0.033667028, 0.06447861, -0.08585174, -0.05509812, -0.11358761) * go_3(-1.0, 0.0);
result += mat4(-0.22750492, 0.032906335, -0.029479047, 0.11580199, -0.05812372, -0.032269973, 0.05219915, 0.041658226, 0.010897959, 0.065550454, 0.0076911976, -0.045743827, 0.11614996, -0.10393113, -0.0012606392, -0.034367524) * go_3(-1.0, 1.0);
result += mat4(0.09350742, 0.09561609, 0.3735968, 0.031685118, -0.042026598, 0.17006761, -0.3910107, 0.16984761, 0.25679177, 0.036610503, -0.13772772, 0.11101589, -0.1137049, 0.07211461, 0.18065079, -0.12324793) * go_3(0.0, -1.0);
result += mat4(-0.020749722, 0.14413361, -0.061903823, -0.21550268, 0.31306142, -0.11532895, 0.029482557, 0.03282164, -0.09800627, -0.20765196, 0.33030233, 0.075725295, 0.49252015, 0.042455837, -0.07264194, -0.10401895) * go_3(0.0, 0.0);
result += mat4(-0.22697076, -0.15738785, 0.09740376, -0.072098814, -0.06638972, 0.12336611, 0.0073687397, 0.048267826, 0.06717852, -0.027047804, -0.123397194, 0.17829034, 0.04215185, 0.066311836, -0.061742183, -0.046373066) * go_3(0.0, 1.0);
result += mat4(0.041311592, 0.2813485, 0.055084586, -0.01823069, 0.08105147, -0.087944716, -0.10135052, -0.02653456, 0.063169874, -0.1351186, 0.06722432, -0.016406318, 0.08666922, 0.0555909, 0.12086502, -0.17224412) * go_3(1.0, -1.0);
result += mat4(0.26026788, -0.18303715, 0.029279215, -0.12858874, 0.027197823, 0.0919464, 0.00849638, 0.10547888, -0.12952055, -0.14414985, 0.1903315, 0.05004528, -0.12657289, 0.038008716, -0.036606666, -0.054025438) * go_3(1.0, 0.0);
result += mat4(0.069167465, 0.2699947, -0.11137602, -0.05888806, -0.107324794, -0.07598601, 0.06042177, 0.0064530694, -0.039780665, -0.076666445, -0.00846108, -0.06165907, -0.06978219, -0.19108103, -0.040026028, -0.120319635) * go_3(1.0, 1.0);
result += vec4(-0.14375664, -0.0056876075, 0.052177623, 0.07152566);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.15667982, -0.31441393, 0.29112124, -0.15737213, 0.022372838, 0.10690639, -0.12019085, -0.051941186, -0.30367845, 0.02612279, 0.2372532, 0.2021648, -0.20481086, -0.003770439, 0.14981231, 0.066780254) * go_0(-1.0, -1.0);
result += mat4(0.03270688, -0.42270073, 0.044317324, 0.15907793, 0.14681059, -0.2934784, 0.24933252, -0.067273855, 0.07752533, -0.23194817, 0.0686707, 0.08999225, 0.121678345, -0.12916678, 0.012397381, 0.012315053) * go_0(-1.0, 0.0);
result += mat4(-0.10090412, -0.20792678, 0.11076032, -0.02938975, -0.1944187, -0.2003259, 0.04438032, 0.36946484, -0.019868722, -0.15830222, 0.042811528, 0.015641417, 0.113098525, 0.080257006, 0.011135628, -0.2877629) * go_0(-1.0, 1.0);
result += mat4(0.15482685, 0.06579119, 0.28301102, 0.23729764, 0.15990537, 0.4529694, 0.107880585, 0.10668121, -0.42430598, -0.2631025, 0.10513542, -0.036242936, -0.09827965, -0.0069260495, -0.11689201, -0.041436482) * go_0(0.0, -1.0);
result += mat4(0.08472191, -0.13051608, 0.047930017, 0.36831668, 0.1164478, 0.21384816, 0.22062506, 0.2094167, 0.48668453, 0.32302913, 0.36268055, -0.091801375, -0.079141125, -0.26613805, -0.16608004, 0.03810683) * go_0(0.0, 0.0);
result += mat4(-0.13474251, -0.04824603, 0.23303726, -0.116136365, 0.0056330245, 0.15829784, 0.0012259148, 0.12648389, 0.038680512, 0.05131116, 0.024099711, 0.4555406, 0.0035716395, 0.11633299, 0.094744846, -0.2457627) * go_0(0.0, 1.0);
result += mat4(-0.0576871, -0.04037522, 0.16857862, 0.0031084458, -0.027274646, -0.18154246, 0.13337846, 0.035422433, -0.0030749738, -0.17288287, 0.019983152, -0.31871706, -0.03280405, 0.06825421, -0.1563798, 0.05031885) * go_0(1.0, -1.0);
result += mat4(-0.066631876, 0.012560506, 0.1690693, -0.018248236, 0.0450104, 0.016296914, -0.14910112, -0.16191053, 0.5078224, -0.017615631, 0.15226597, -0.13373777, 0.20148668, 0.060258996, 0.13215344, 0.18430072) * go_0(1.0, 0.0);
result += mat4(0.12976126, -0.072738245, 0.053067926, 0.09752956, -0.04716214, 0.04136464, 0.014162617, -0.06621296, -0.09617736, 0.057469178, 0.01280261, -0.042976785, -0.12570308, 0.006027807, 0.031038594, 0.06569918) * go_0(1.0, 1.0);
result += mat4(-0.12655424, -0.41563693, -0.030971345, -0.06357555, -0.14121394, -0.15667427, 0.14398985, 0.05995984, 0.0821605, 0.12462943, 0.007492498, -0.0030187522, -0.22804567, -0.10487421, 0.13180672, -0.13978589) * go_1(-1.0, -1.0);
result += mat4(-0.075991526, 0.12352044, -0.17844258, 0.010614991, -0.18293494, 0.25009897, -0.080779895, 0.21548378, 0.22215544, 0.048670914, -0.057372037, 0.078176, 0.17490411, 0.004919551, 0.059619516, 0.12660357) * go_1(-1.0, 0.0);
result += mat4(-0.06282951, 0.10929357, 0.026720649, -0.15939257, 0.17107709, -0.04334904, -0.03047162, -0.101681694, 0.03118431, 0.19994627, 0.025729552, 0.035035726, -0.0012207883, -0.08618888, 0.061205562, 0.009940555) * go_1(-1.0, 1.0);
result += mat4(-0.23581573, 0.08002133, -0.15170844, 0.08872338, -0.25767094, -0.09273545, 0.18153891, 0.2544269, -0.084598936, -0.089766875, -0.14610913, 0.002247754, 0.1802837, -0.019625561, 0.30239686, -0.032793984) * go_1(0.0, -1.0);
result += mat4(0.5223286, 0.10347663, 0.4000593, 0.25440502, -0.07646958, -0.31940606, 0.053407036, -0.09356492, 0.2738851, 0.23945184, -0.2907089, -0.45822915, 0.13415676, 0.17187089, 0.08731114, -0.27670014) * go_1(0.0, 0.0);
result += mat4(0.059273496, -0.107137166, 0.12087539, 0.179237, -0.021209063, -0.02548005, 0.061256204, 0.033822674, 0.54491127, -0.2475085, 0.08055858, -0.4071213, -0.045093834, 0.07161349, 0.08219979, -0.31735933) * go_1(0.0, 1.0);
result += mat4(-0.29527053, 0.021469543, 0.07202354, -0.07103959, 0.03990857, 0.2490762, -0.19419849, -0.13916986, -0.05325315, 0.12922864, -0.041463424, -0.031249814, 0.073991664, -0.09723187, 0.35132217, 0.024760868) * go_1(1.0, -1.0);
result += mat4(0.09606787, -0.0951808, -0.0059865676, -0.052033573, -0.3118038, 0.4432636, -0.12943317, 0.09484738, 0.10621756, -0.10550469, 0.11264014, 0.1402276, -0.012679125, -0.08809835, 0.029994955, -0.15121669) * go_1(1.0, 0.0);
result += mat4(0.123397775, 0.048338536, -0.00975707, -0.103767075, -0.041053303, -0.07228534, 0.046792876, 0.0668788, 0.29554394, 0.012451002, 0.19568972, 0.112091154, 0.10882395, -0.0995439, 0.051324263, 0.24967718) * go_1(1.0, 1.0);
result += mat4(0.2699648, 0.17300771, -0.16056584, 0.1099392, 0.11674778, -0.19811755, 0.111880325, -0.06075038, -0.095849104, -0.04510651, -0.04180761, -0.0052786698, 0.11037549, -0.24115366, 0.018509468, -0.07819484) * go_2(-1.0, -1.0);
result += mat4(0.10981622, 0.044488225, 0.050722387, -0.3146652, -0.0013019707, -0.24084032, -0.10475088, 0.026944289, 0.1592903, 0.33087498, 0.061839584, -0.043863457, -0.06904603, -0.08635262, 0.088630445, -0.15485142) * go_2(-1.0, 0.0);
result += mat4(-0.06810522, 0.19927117, -0.08130387, 0.11612667, -0.015104349, -7.738651e-05, -0.06419643, -0.14813533, 0.026650215, 0.015038833, 0.08161237, 0.058321163, 0.015005185, -0.16189656, 0.024501886, 0.1927279) * go_2(-1.0, 1.0);
result += mat4(0.31858218, 0.11962043, -0.20560326, -0.13190113, 0.02138715, -0.057066392, -0.085771754, -0.124566585, 0.044749223, 0.13687828, 0.1195792, 0.14021616, 0.26204133, 0.05119197, -0.13980037, 0.050747477) * go_2(0.0, -1.0);
result += mat4(-0.21238558, -0.0734057, -0.2036023, -0.34308743, -0.29370925, 0.2393742, -0.37877437, 0.036869828, -0.17053255, -0.26900926, -0.23330869, 0.32902205, -0.4882585, 0.27430108, -0.033711653, 0.15501487) * go_2(0.0, 0.0);
result += mat4(0.23487025, 0.085289046, -0.14281847, 0.12543266, 0.15871634, -0.13858907, 0.14810285, -0.0239261, 0.1286852, 0.07754033, 0.01072327, -0.14313328, 0.05480442, -0.12195059, 0.11341822, 0.08224607) * go_2(0.0, 1.0);
result += mat4(0.19490337, 0.023521842, -0.24548791, 0.0035114093, -0.07937166, -0.07674376, 0.08365873, -0.003286068, 0.023862893, 0.009626835, 0.032829892, 0.0078141205, 0.053484406, -0.08297165, 0.09303188, 0.004273738) * go_2(1.0, -1.0);
result += mat4(-0.0032906602, 0.13636959, 0.027821168, 0.06270053, 0.024775786, -0.077529594, 0.03799126, 0.030000908, 0.031749167, 0.04360487, 0.004448846, -0.17835903, -0.30834544, 0.013150946, -0.13758293, -0.03296242) * go_2(1.0, 0.0);
result += mat4(-0.14166978, 0.034131095, 0.049779188, 0.09453289, -0.011406557, -0.07020709, -0.0031981543, -0.03443845, -0.00010218944, 0.0855161, -0.10951453, 0.042758763, 0.1718446, -0.1577923, 0.0410027, -0.04992991) * go_2(1.0, 1.0);
result += mat4(0.1219178, 0.105126485, -0.041097324, -0.08110963, -0.04857337, -0.11544925, -0.14572923, 0.092435546, 0.091857366, 0.15425235, -0.020324683, -0.05764375, -0.020458939, -0.10527823, -0.085554086, 0.16358297) * go_3(-1.0, -1.0);
result += mat4(-0.12372687, -0.009976829, 0.14252265, -0.1321053, -0.05965866, -0.1393898, -0.017603246, -0.02714342, -0.16824952, -0.23083204, -0.012299022, -0.06689838, -0.015830487, 0.21299921, -0.11637202, 0.0074968333) * go_3(-1.0, 0.0);
result += mat4(-0.01979935, -0.182785, -0.015397454, 0.14175794, -0.011465284, 0.11285164, -0.036115747, 0.07150463, -0.083641894, -0.10221778, -0.13871445, 0.099696055, 0.04603662, -0.06463785, -0.007984529, -0.0032940735) * go_3(-1.0, 1.0);
result += mat4(0.072830334, -0.057334073, 0.09086239, 0.13039105, 0.06350303, 0.17130788, -0.2181585, -0.09137403, -0.31397742, -0.019071499, -0.017274613, 0.13762084, 0.10195637, -0.021455176, 0.04011394, -0.08029658) * go_3(0.0, -1.0);
result += mat4(-0.26982597, -0.40265098, -0.4151411, 0.038557775, -0.095602125, 0.3503172, -0.029988842, -0.03484708, 0.095536314, -0.0030311556, 0.31589827, 0.52763534, -0.12629713, -0.24356791, 0.0059487303, 0.42298427) * go_3(0.0, 0.0);
result += mat4(0.054166105, 0.18827972, -0.081673265, -0.06720384, 0.09375001, 0.22173035, -0.14050071, 0.108400136, -0.15553835, -0.08716729, -0.037366748, 0.10971073, -0.02560103, -0.26702073, -0.05201882, 0.2432563) * go_3(0.0, 1.0);
result += mat4(0.16196893, 0.0889265, -0.09887943, -0.042956755, -0.054403376, -0.123823255, 0.045847844, 0.017027669, 0.00539936, -0.112265736, 0.050549984, -0.104931094, -0.06883012, -0.25745714, 0.11155538, -0.15363649) * go_3(1.0, -1.0);
result += mat4(-0.22157209, 0.18200903, -0.13290548, 0.026721261, -0.06066069, -0.18150693, 0.08768983, 0.037362453, -0.1073367, -0.070236765, -0.41223463, -0.168915, -0.15517351, -0.13949952, -0.13307643, -0.15935421) * go_3(1.0, 0.0);
result += mat4(-0.026589906, 0.0930502, 0.05195435, 0.06301585, -0.01107014, -0.019382332, 0.027223695, -0.004045145, -0.15238355, -0.0345132, 0.06355168, 0.0011230056, 0.16690113, 0.0017829507, -0.0023939044, -0.09471834) * go_3(1.0, 1.0);
result += vec4(0.024455175, 0.01669877, -0.066231176, 0.036848705);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf1
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.01763509, -0.17156707, -0.06841296, -0.026132878, -0.10600523, 0.11245994, 0.121395074, -0.09331501, 0.12764473, 0.0428028, -0.11837395, 0.2092563, -0.04357652, -0.0490096, 0.024701532, 0.10518723) * go_0(-1.0, -1.0);
result += mat4(-0.17130826, -0.31987694, -0.07639005, 0.21362033, 0.058639023, 0.066175915, -0.25344703, -0.07923442, -0.14766373, 0.040518284, -0.031103026, -0.040075514, -0.051108997, -0.28214613, -0.18504949, 0.27544948) * go_0(-1.0, 0.0);
result += mat4(0.030991005, -0.011353306, 0.15237464, 0.15458584, 0.1250524, 0.19959912, 0.14049476, 0.38410887, 0.07378578, -0.017728366, 0.0963528, -0.043756213, -0.039577194, -0.11800575, -0.08392266, -0.07599512) * go_0(-1.0, 1.0);
result += mat4(0.022089608, -0.027317125, 0.051330008, -0.0075439885, 0.021650828, -0.0009390209, -0.12043464, 0.049332134, -0.055557396, -0.053297505, -0.0918705, -0.13089466, -0.10994107, 0.072746456, 0.11496739, -0.05225977) * go_0(0.0, -1.0);
result += mat4(0.29730305, 0.26317745, 0.052159555, -0.32006654, 0.48288685, -0.049926184, -0.08091092, -0.13825637, -0.1485706, -0.288657, -0.41443697, 0.06856032, -0.23809211, -0.12953928, 0.4783034, -0.47557938) * go_0(0.0, 0.0);
result += mat4(0.026139118, -0.23031352, 0.04861487, 0.033556074, 0.2702056, 0.22802536, -0.15385233, 0.1664119, 0.18749923, 0.36927548, -0.011473684, -0.11771165, -0.16859052, -0.4513202, 0.12863952, 0.02482837) * go_0(0.0, 1.0);
result += mat4(0.0073229345, -0.061915245, 0.06710329, 0.0062416573, -0.00555983, 0.14592186, 0.11201052, -0.123630054, 0.32611257, -0.11279885, -0.059449438, 0.2891043, -0.10519016, 0.040108994, -0.012468261, 0.02083298) * go_0(1.0, -1.0);
result += mat4(-0.057483062, 0.08454755, -0.15529329, -0.12572923, 0.2600099, -0.02319978, -0.04037675, 0.11496361, 0.07728194, -0.12908956, -0.025529336, 0.112581626, 0.02971823, 0.11659056, -0.01298622, 0.017061908) * go_0(1.0, 0.0);
result += mat4(0.22417091, -0.00222947, 0.04980858, 0.12260437, -0.025507605, 0.042577885, 0.120813504, -0.048522256, -0.038494784, -0.0072195013, -0.23012944, -0.020850847, -0.078296244, -0.014830018, 0.19759563, -0.10000253) * go_0(1.0, 1.0);
result += mat4(-0.032090195, 0.023757193, -0.08989734, 0.14419042, 0.0112194475, -0.093776144, -0.020197887, 0.29295877, 0.06872183, 0.09511462, -0.03245769, -0.06504889, 0.05132126, 0.00399527, 0.075911656, 0.250893) * go_1(-1.0, -1.0);
result += mat4(-0.3418496, 0.25525784, 0.0018161442, 0.028484365, -0.17573346, -0.12457501, 0.18466166, 0.20209278, 0.10282706, 0.16353399, 0.025052028, -0.059714165, -0.055806916, -0.28651386, 0.112798095, 0.11624314) * go_1(-1.0, 0.0);
result += mat4(-0.018793896, 0.07500149, -0.01728254, -0.1726998, -0.13333, 0.09590344, -0.036537904, -0.11522523, 0.19445558, 0.22680458, 0.12061006, -0.06225618, 0.1127748, 0.28380096, -0.07099846, -0.007440302) * go_1(-1.0, 1.0);
result += mat4(-0.43887648, -0.10018577, -0.29267642, 0.12149727, -0.14333835, 0.04161915, 0.19442867, 0.16506511, 0.09655387, -0.0014398015, 0.13189743, -0.14068556, 0.049408, 0.0829072, 0.2950336, 0.36965907) * go_1(0.0, -1.0);
result += mat4(0.41486958, -0.023498302, -0.37900022, -0.31752598, 0.13758768, -0.18782206, -0.31358528, 0.3330786, -0.4039293, -0.06539036, 0.032599606, 0.10663507, -0.26369813, -0.17365438, 0.20723309, 0.1801556) * go_1(0.0, 0.0);
result += mat4(0.004117444, -0.14894462, 0.14915143, -0.047375835, -0.2609916, -0.10172324, -0.14925237, -0.33830285, 0.12131607, -0.18156646, -0.42382464, -0.052582145, 0.2329045, -0.4576963, 0.13756892, 0.055571318) * go_1(0.0, 1.0);
result += mat4(-0.31689477, 0.017058033, -0.01904924, -0.016893756, -0.011479519, 0.07316262, -0.07086077, 0.08923511, -0.08190091, -0.025866933, -0.06909204, -0.028601022, 0.023224542, 0.03082087, 0.2230426, -0.16713654) * go_1(1.0, -1.0);
result += mat4(0.13457374, 0.110913865, -0.1130815, -0.031438913, -0.55201167, 0.04831016, 0.25107765, -0.014003224, 0.19532952, 0.02062346, 0.04839241, 0.088673405, 0.30325848, -0.20222804, -0.085780576, 0.22512968) * go_1(1.0, 0.0);
result += mat4(0.076354, 0.021940092, -0.16170324, 0.0025543426, -0.0032400405, -0.0046705627, 0.06241069, -0.031247333, 0.098353796, 0.03723474, 0.22971998, -0.017877292, 0.119858086, 0.008041448, 0.2140585, 0.10343376) * go_1(1.0, 1.0);
result += mat4(0.08627595, 0.04532834, 0.027579082, -0.16222088, 0.15583228, -0.14371829, -0.07243855, -0.111895435, -0.14438897, -0.10250594, 0.0034202964, -0.066547595, -0.034390844, -0.021545287, 0.014540157, -0.10215731) * go_2(-1.0, -1.0);
result += mat4(0.19720152, 0.21534947, 0.1130938, -0.011730973, 0.013247983, -0.10344174, -0.1906514, -0.015767017, -0.020093633, -0.26487067, -0.005960781, -0.057149183, 0.030110173, 0.047692046, -0.19308545, -0.25292158) * go_2(-1.0, 0.0);
result += mat4(0.039498243, 0.053682897, -0.01844695, -0.017540915, 0.039454967, -0.27696076, 0.09503274, -0.038958035, 0.17321438, -0.036311295, 0.03123055, 0.02310311, 0.040591653, 0.0054627894, -0.03520426, -0.026101988) * go_2(-1.0, 1.0);
result += mat4(0.055991564, 0.06512919, -0.12532505, 0.024075158, -0.04926237, -0.11701171, 0.026792146, 0.013033238, -0.052847516, -0.01550091, -0.008442071, -0.077945165, -0.033220004, -0.13678443, -0.07040586, 0.121846326) * go_2(0.0, -1.0);
result += mat4(-0.19537796, -0.016634773, 0.10707109, -0.024361614, -0.16002733, -0.44066608, 0.16488662, 0.013152995, 0.22407806, 0.12854017, 0.19028598, -0.08379244, -0.05594235, -0.15909895, 0.511962, 0.39027596) * go_2(0.0, 0.0);
result += mat4(-0.032652248, 0.06004893, 0.011166194, 0.102761306, -0.035113614, -0.29961765, -0.013817978, 0.20938557, 0.08488225, -0.1118558, -0.0375328, -0.035511103, 0.0046933405, 0.20203683, -0.13552529, -0.12685429) * go_2(0.0, 1.0);
result += mat4(0.03054923, 0.08224908, -0.059128158, -0.02583655, -0.02133876, 0.0048713544, 0.10848829, 0.06324404, 0.028332822, -0.011002306, -0.027557913, -0.06072362, 0.1019048, -0.02587316, 0.08563405, -0.08119947) * go_2(1.0, -1.0);
result += mat4(-0.10568117, 0.1075248, 0.19379964, -0.14337265, 0.019374132, -0.0907804, -0.13827625, -0.03628561, 0.014735499, -0.026882607, -0.25948793, 0.034926686, -0.05988073, -0.22735636, 0.053511668, 0.04765336) * go_2(1.0, 0.0);
result += mat4(-0.029848114, 0.09183966, 0.084713496, 0.09422864, 0.069713995, -0.10584984, -0.020899031, 0.059645247, -0.075805016, -0.01828552, 0.06689195, -0.13804196, -0.023465823, -0.034038994, -0.12946706, 0.058709413) * go_2(1.0, 1.0);
result += mat4(0.061918218, 0.038984764, 0.013660938, -0.19340219, -0.014949839, 0.12946278, 0.12725051, 0.13429146, 0.05993008, -0.015394284, 0.011232483, 0.0344157, 0.022161875, -0.023923954, 0.061736204, 0.025963215) * go_3(-1.0, -1.0);
result += mat4(0.048136763, 0.03162042, -0.01967249, 0.06374493, 0.034645267, 0.22403605, 0.036197048, -0.06903216, -0.1024706, -0.0005459356, 0.049185563, 0.16309108, 0.07394778, 0.10351343, 0.28430694, -0.13531347) * go_3(-1.0, 0.0);
result += mat4(-0.14705071, -0.09458433, 0.03063114, 0.07901115, -0.11911086, -0.06428132, -0.013549552, -0.041342866, -0.20770676, -0.15104479, 0.054365363, -0.11652907, 0.05639815, 0.070518605, 0.0017846811, -0.00056205114) * go_3(-1.0, 1.0);
result += mat4(0.27148908, 0.07358356, 0.13644488, -0.13824654, 0.0112991175, -0.021521023, -0.10197379, 0.007816017, -0.13314332, 0.12318473, -0.043214846, -0.15759036, -0.19744353, -0.10267182, -0.28249928, 0.11233295) * go_3(0.0, -1.0);
result += mat4(-0.096474804, 0.17893109, 0.014679829, -0.21218887, -0.24170275, 0.10603527, 0.05375366, -0.059315052, 0.17087384, 0.13633691, -0.37958893, 0.43264794, 0.17829923, 0.06485103, -0.37551817, -0.22082718) * go_3(0.0, 0.0);
result += mat4(-0.30536333, -0.033212308, -0.25232, 0.11730442, -0.11176368, 0.26223183, -0.049025323, -0.01375941, -0.29028055, 0.16842811, -0.035684332, -0.4180911, -0.1611732, 0.07683385, -0.14263596, 0.17508087) * go_3(0.0, 1.0);
result += mat4(0.23580009, 0.025621435, -0.15757325, 0.008123166, -0.021905439, -0.02162503, -0.059497356, -0.01636353, 0.047654126, -0.084423855, -0.033733923, 0.0127116265, -0.059593942, -0.053935718, -0.050729543, 0.013887048) * go_3(1.0, -1.0);
result += mat4(-0.19232626, 0.07915767, -0.05909752, 0.007695347, 0.058876406, 0.057521783, -0.080253534, 0.2011056, -0.27965516, -0.08033169, -0.13025513, 0.12854645, 0.053400308, -0.18445957, -0.18463044, 0.27920377) * go_3(1.0, 0.0);
result += mat4(-0.061806213, -0.020037206, 0.003183183, -0.029844081, -0.039553937, 0.028905323, -0.11367984, -0.097321615, -0.10112643, 0.0039709485, -0.06020118, -0.23871279, -0.077974856, 0.05806996, -0.21440302, 0.11898043) * go_3(1.0, 1.0);
result += vec4(-0.023832673, 0.03702965, -0.04749135, -0.10982549);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.030931145, 0.013683292, -0.0650242, -0.028732346, 0.120067924, -0.029404473, 0.0038229884, -0.14631765, 0.041900825, -0.076596744, -0.11096378, -0.27100095, 0.0052598766, -0.05929686, -0.06816563, -0.086864315) * go_0(-1.0, -1.0);
result += mat4(-0.043620087, -0.16360405, 0.006527374, 0.15706524, 0.08338088, -0.19027525, 0.22595987, -0.054963548, 0.01825031, -0.03149212, 0.025471251, 0.06429379, -0.011633275, -0.079389006, -0.0030728737, 0.17345747) * go_0(-1.0, 0.0);
result += mat4(-0.011275288, -0.10668036, 0.05718997, 0.010336089, 0.33393976, -0.2029354, 0.075444475, -0.092244044, 0.07605498, 0.20125951, 0.10493973, -0.12306946, 0.03658231, 0.08233366, -0.12205888, -0.116969004) * go_0(-1.0, 1.0);
result += mat4(-0.0070305974, 0.105127215, 0.006041873, 0.26743913, 0.028119443, 0.14823505, -0.28344348, 0.12362866, -0.1215781, 0.08104382, 0.102011785, 0.085380934, 0.061244503, -0.06230063, -0.05353345, 0.1166729) * go_0(0.0, -1.0);
result += mat4(0.08945733, 0.4101902, -0.06404005, 0.040728435, 0.13076581, -0.20805469, -0.10897316, -0.14924604, 0.10090762, 0.015475414, 0.26346552, 0.12096677, -0.20199244, 0.2780031, 0.18515368, 0.35105625) * go_0(0.0, 0.0);
result += mat4(0.07463155, 0.26932517, -0.06768551, 0.10470878, -0.1423996, 0.013550665, -0.06167201, -0.1022994, -0.3107166, -0.15609552, 0.1695213, -0.1277181, 0.12582655, -0.1596128, 0.015612055, -0.19826376) * go_0(0.0, 1.0);
result += mat4(0.011745468, 0.006471601, 0.008110513, 0.025831396, 0.1272883, -0.221959, 0.11993834, -0.007903633, 0.009993582, -0.10170755, 0.026594637, -0.027883623, 0.030666083, -0.036415886, 0.007469573, 0.0674783) * go_0(1.0, -1.0);
result += mat4(-0.022760388, -0.10911659, -0.012589904, -0.046462692, 0.36987287, 0.71668935, -0.04466556, 0.12082762, 0.0026539841, 0.07070946, -0.00020439121, -0.13925348, 0.08672072, 0.20075354, -0.066352285, 0.14655356) * go_0(1.0, 0.0);
result += mat4(-0.081081845, -0.21956222, 0.06781787, -0.106362104, -0.03016425, -0.010460211, -0.009725996, -0.009805538, 0.07037355, 0.19254607, 0.038890257, 0.29580075, -0.10355764, 0.12613009, 0.02485986, -0.031927988) * go_0(1.0, 1.0);
result += mat4(-0.13882205, 0.21770848, 0.015392157, 0.010310204, 0.008225721, 0.07457836, 0.09984027, -0.25452816, 0.2193511, -0.22262146, -0.12950355, 0.026151875, 0.022114651, -0.030566849, 0.034688126, 0.03047327) * go_1(-1.0, -1.0);
result += mat4(0.0363441, 0.19290726, -0.1143055, 0.30871987, -0.05780708, 0.082128406, -0.115280904, 0.07636388, 0.48947453, -0.29715258, 0.146737, -0.3275992, -0.055972476, -0.09991753, 0.17435446, 0.10917291) * go_1(-1.0, 0.0);
result += mat4(0.026389305, 0.054523308, -0.028950177, 0.06913328, -0.18626037, 0.08829993, 0.10407121, 0.001246911, 0.103938825, -0.3117343, -0.045564886, 0.07316613, 0.0027089121, 0.099437356, -0.046500806, -0.0927284) * go_1(-1.0, 1.0);
result += mat4(0.051037624, -0.2068234, 0.061572235, -0.3345198, 0.16960172, -0.30289862, -0.002583443, 0.39312238, 0.08246557, 0.16374862, -0.31902805, -0.13205275, -0.032050006, 0.01670186, 0.13852347, 0.120012194) * go_1(0.0, -1.0);
result += mat4(-0.67096996, -0.06274476, 0.18575665, 0.80282855, 0.23201196, -0.0054729837, 0.050396994, -0.42014772, 0.34904522, 0.26281372, 0.24697208, 0.55475426, 0.49850988, -0.06581312, -0.0068906257, -0.15741143) * go_1(0.0, 0.0);
result += mat4(-0.04252036, -0.28224963, 0.009723064, 0.116357096, 0.2992567, -0.26702902, -0.05648925, 0.12729199, -0.37574205, 0.54211813, -0.25248805, -0.13023548, 0.18903324, -0.5182459, 0.0141203115, -0.19444294) * go_1(0.0, 1.0);
result += mat4(-0.0017735233, -0.010132458, -0.040924776, -0.13767008, 0.20757031, -0.06509882, -0.09756446, 0.018974079, 0.090851985, -0.010158765, -0.03999607, -0.12055641, 0.03629025, -0.018645551, -0.05506811, -0.014202848) * go_1(1.0, -1.0);
result += mat4(0.16203491, 0.011118734, -0.18486023, -0.024290733, -0.3673846, -0.20295864, 0.23055002, -0.1555852, -0.02706522, 0.03262891, 0.008724611, -0.03760652, -0.20946771, -0.01951837, 0.16955496, 0.11690098) * go_1(1.0, 0.0);
result += mat4(0.0783421, 0.22656651, -0.15715368, -0.024174158, 0.020260733, 0.032390315, -0.029133298, 0.086601086, 0.13871798, -0.12525433, 0.16097449, 0.058946393, 0.029865682, 0.08508385, 0.040569812, -0.09402932) * go_1(1.0, 1.0);
result += mat4(-0.05063873, 0.11269313, -0.057484943, -0.13579641, 0.047973365, -0.07103839, -0.07838756, -0.0028928046, -0.019466015, 0.018428024, 0.010016324, -0.057396665, -0.19495595, 0.034307264, -0.022888038, 0.08112259) * go_2(-1.0, -1.0);
result += mat4(-0.09790086, 0.10613111, 0.06611674, 0.19356097, -0.00073371036, -0.019078335, 0.076719105, -0.016212497, -0.3283475, -0.07547389, -0.08140701, 0.3185625, -0.25060275, 0.16820994, -0.123497784, 0.43272668) * go_2(-1.0, 0.0);
result += mat4(-0.06365342, 0.11186735, -0.17493224, -0.04207358, 0.0003117533, 0.034089327, -3.067692e-05, -0.03422754, 0.16267666, 0.054771993, 0.048384454, -0.041866794, 0.0036008756, 0.0021496525, 0.20258942, -0.06297619) * go_2(-1.0, 1.0);
result += mat4(0.03578836, 0.08763908, -0.22370125, -0.32465744, 0.019142643, 0.011316954, 0.17920344, 0.031633645, 0.03766343, -0.116487674, -0.05281752, -0.018965483, 0.049297336, -0.34511214, 0.42598158, 0.051361635) * go_2(0.0, -1.0);
result += mat4(0.26638633, -0.33628765, 0.04437907, 0.09616201, -0.020049393, 0.2560829, -0.027108455, 0.255752, 0.3666511, 0.052277412, -0.46667686, 0.48482272, 0.51302284, -0.06941614, -0.17967525, -0.07889891) * go_2(0.0, 0.0);
result += mat4(0.18503937, 0.088710256, 0.2083147, -0.20758459, -0.036416974, 0.018303726, 0.03729963, -0.035969947, -0.2685231, -0.42169708, -0.039593916, -0.02642618, 0.29050872, -0.25723743, -0.111259766, 0.15001127) * go_2(0.0, 1.0);
result += mat4(-0.026473878, -0.07241443, 0.022400148, -0.03214132, 0.0859297, -0.0036677981, -0.07039137, 0.03703108, 0.042322673, -0.01222808, -0.08151938, 0.033109214, -0.048737407, 0.25929528, -0.40535828, -0.123594694) * go_2(1.0, -1.0);
result += mat4(0.10233285, 0.22455986, -0.13368733, 0.033236265, -0.052114893, -0.11709317, 0.009709581, 0.19201641, -0.02973698, 0.032114245, -0.09771862, 0.085680574, 0.15827927, -0.15042172, 0.21833214, -0.13262676) * go_2(1.0, 0.0);
result += mat4(-0.08460587, -0.09473209, 0.019323658, -0.057233352, 0.0019434267, -0.14437936, 0.034232683, 0.0030602294, -0.023598112, 0.10692026, -0.09960999, 0.005887181, 0.014738836, -0.32473162, -0.10886747, -0.08365826) * go_2(1.0, 1.0);
result += mat4(0.10900178, 0.00080280803, -0.14009437, -0.053074867, -0.07811151, -0.03456029, -0.104943685, 0.016918905, -0.11335709, 0.079421654, 0.13481963, 0.037818357, -0.027339859, 0.05856774, -0.044562265, 0.03908084) * go_3(-1.0, -1.0);
result += mat4(0.07628258, -0.23815769, 0.2840278, -0.3541637, -0.044292126, -0.09310441, -0.1335055, -0.031899665, -0.11981227, 0.24012394, -0.041896038, -0.10168982, 0.20248915, -0.10036763, -0.044115108, 0.08520525) * go_3(-1.0, 0.0);
result += mat4(0.07234102, -0.119480744, -0.01401321, -0.025182616, -0.031284854, -0.050089385, 0.014808948, 0.038662236, -0.18539418, 0.017342187, 0.023812262, 0.13428104, 0.020824855, -0.07433546, 0.054307282, 0.08511016) * go_3(-1.0, 1.0);
result += mat4(-0.11046813, -0.04663274, 0.33497185, 0.023273284, -0.24681108, 0.116665915, 0.12045893, 0.13306482, -0.039098527, 0.04747061, 0.042796664, 0.053514794, 0.011861975, -0.048702, 0.008408589, -0.09497112) * go_3(0.0, -1.0);
result += mat4(0.34634927, 0.37973458, -0.79267627, -0.7362719, 0.35489878, -0.07635863, 0.24082923, -0.27480397, -0.3236968, -0.25523046, 0.05118527, -0.040529836, -0.6000509, 0.39020586, 0.27632973, 0.5141453) * go_3(0.0, 0.0);
result += mat4(0.16761221, -0.033125393, 0.00561569, 0.083019435, -0.101278506, 0.07810264, 0.12060661, 0.16048536, 0.14257826, -0.15996903, 0.018831912, -0.094429865, -0.22227801, 0.426937, -0.054677445, 0.05067348) * go_3(0.0, 1.0);
result += mat4(0.02233958, 0.02608942, -0.045318656, 0.06509929, 0.035911568, 0.025316885, 0.0840986, 0.08326237, 0.048455603, -0.13630742, 0.07230253, -0.047261715, -0.092630014, 0.04786565, 0.10354939, -0.07094341) * go_3(1.0, -1.0);
result += mat4(-0.1463382, -0.14900577, 0.2835977, -0.106733374, -0.11554754, -0.168429, -0.1411373, -0.20654152, -0.06388508, 0.039648015, 0.08543832, -0.13253337, 0.017264463, -0.06346233, -0.10823598, 0.067361064) * go_3(1.0, 0.0);
result += mat4(0.04419582, 0.039152585, 0.06222691, 0.05757103, 0.012084537, 0.051425997, -0.061130576, 0.16752882, 0.07497411, 0.13495837, -0.15585983, -0.02050144, -0.08555421, -0.09147339, 0.025115604, 0.05948922) * go_3(1.0, 1.0);
result += vec4(0.00590038, 0.03082865, 0.002111702, -0.03330112);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf1
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.009029573, 0.029218858, 0.029705316, -0.019268971, -0.0023235187, -0.072589695, 0.1424836, 0.09049359, 0.04342995, 0.18134294, 0.018145641, 0.14789368, 0.050923645, 0.06524081, 0.036812488, 0.11108108) * go_0(-1.0, -1.0);
result += mat4(-0.026506428, 0.016968496, 0.015961196, 0.010030791, -0.3141888, -0.06769598, -0.23920257, -0.031002127, -0.07351358, -0.19290134, -0.24282931, -0.18831016, -0.0928966, 0.075177215, -0.19699521, -0.05810917) * go_0(-1.0, 0.0);
result += mat4(-0.017991852, -0.079427645, 0.035970494, -0.017095685, -0.27197137, -0.20046075, 0.2616644, 0.021876303, -0.077394076, -0.04978692, 0.20363241, -0.013741705, -0.032103598, 0.14403099, 0.01442474, 0.048115995) * go_0(-1.0, 1.0);
result += mat4(-0.16939245, -0.001777, 0.026244136, -0.14122388, -0.056853324, 0.54357284, -0.19769607, -0.03187079, 0.04559263, -0.16048127, 0.12830622, 0.1442168, 0.006611398, -0.01618195, 0.012860053, -0.16539487) * go_0(0.0, -1.0);
result += mat4(0.13116026, -0.006161343, 0.7209969, 0.18338475, 0.3099777, 0.6500026, 0.3883795, -0.021434233, 0.31667513, 0.008917659, 0.14124091, -0.22335114, 0.12198921, -0.16449445, 0.08773425, 0.30054978) * go_0(0.0, 0.0);
result += mat4(-0.10413989, -0.10316161, 0.04342709, -0.021252686, 0.120892406, 0.37798002, -0.35963747, 0.021069285, 0.37587845, -0.08159587, 0.011139747, 0.2501104, -0.094568014, 0.037900843, -0.025109999, -0.030106556) * go_0(0.0, 1.0);
result += mat4(0.09680291, -0.040868275, 0.051731605, 0.089064725, -0.56098557, -0.38148618, -0.017037416, 0.08508287, -0.019247344, 0.019857002, -0.03512887, 0.031057188, -0.09648583, -0.04474188, 0.028748507, -0.11880965) * go_0(1.0, -1.0);
result += mat4(-0.010236943, 0.04257042, -0.08202597, -0.004203426, -0.26801527, -0.11716526, -0.017402772, -0.05819106, -0.13394608, 0.0234606, -0.15404865, -0.06801164, -0.0047627664, -0.1975249, 0.09420144, 0.23249897) * go_0(1.0, 0.0);
result += mat4(0.107361935, 0.07373787, 0.06242962, 0.05236332, -0.028867323, 0.025924044, -0.042526353, -0.0015729597, -0.1323144, -0.4040712, 0.023919407, -0.09535502, 0.049100045, 0.081110805, 0.08946112, 0.058505684) * go_0(1.0, 1.0);
result += mat4(0.13236825, -0.04468476, -0.04426802, 0.031087106, -0.09093992, -0.07470971, -0.01591504, 0.05924266, -0.21910913, 0.065537, -0.18358919, -0.02533145, -0.1512009, -0.04953928, 0.015540006, -0.0043442883) * go_1(-1.0, -1.0);
result += mat4(-0.14016777, -0.1086958, 0.16316028, 0.050777458, 0.23148167, 0.04944809, -0.10599886, -0.10447021, -0.40729257, -0.10926556, 0.069055155, 0.110635415, 0.108922414, -0.1716362, 0.10743909, -0.102534756) * go_1(-1.0, 0.0);
result += mat4(0.017795928, -0.066930935, 0.09396082, 0.092585504, 0.14223933, 0.059458215, 0.072033696, -0.04507726, -0.19956456, 0.1251282, -0.31733638, -0.10465904, 0.08546377, 0.048638333, 0.031372465, -0.08720661) * go_1(-1.0, 1.0);
result += mat4(0.108719654, -0.092161916, -0.014724377, 0.20068261, -0.24350016, 0.2113636, -0.07483714, -0.45665312, -0.25134233, 0.2753893, -0.11324696, -0.04472, 0.1576102, -0.045395147, 0.06013951, -0.12507361) * go_1(0.0, -1.0);
result += mat4(0.546225, -0.281897, 0.19477816, -0.116612464, -0.3145171, -0.41660902, 0.333625, 0.35902345, 0.48333502, 0.4662005, 0.10222491, -0.15314859, -0.3036888, 0.22849742, 0.20740797, 0.41399437) * go_1(0.0, 0.0);
result += mat4(0.007284074, 0.0393942, -0.31192186, -0.15687793, -0.289214, -0.015956698, -0.24718472, -0.1637855, -0.00765037, 0.26677555, 0.20215511, 0.37790874, -0.22096673, 0.25287116, -0.2446764, -0.13610223) * go_1(0.0, 1.0);
result += mat4(-0.16734968, 0.16721225, -0.053508647, -0.041097626, 0.062356673, 0.07812319, -0.263546, -0.39739034, 0.003389846, 0.12676363, -0.13175991, -0.19019242, -0.011847587, -0.007580052, -0.023946386, 0.046034034) * go_1(1.0, -1.0);
result += mat4(-0.17047611, 0.13298693, -0.07506747, -0.045542978, 0.33571973, 0.20192616, 0.30674616, 0.25668672, -0.24134545, 0.031693842, -0.009647641, 0.040534843, 0.03159419, -0.1100516, 0.11371316, 0.06098735) * go_1(1.0, 0.0);
result += mat4(-0.05518961, 0.19402988, -0.09646874, -0.059196774, -0.0073436056, -0.1381309, 0.06868669, 0.061328378, -0.1480867, -0.15774113, -0.022572191, 0.122521356, -0.04067007, -0.10145177, 0.13006335, -0.099452734) * go_1(1.0, 1.0);
result += mat4(0.06962972, 0.07768411, 0.021085173, 0.108355984, -0.03132525, 0.10220273, -0.11626593, -0.14104277, 0.018778645, -0.024237925, 0.048783034, 0.09074447, 0.4120426, -0.01948466, 0.073218934, 0.055681944) * go_2(-1.0, -1.0);
result += mat4(-0.22553118, -0.12923603, -0.22068842, -0.35037905, 0.005709937, -0.09528472, 0.08718399, 0.13200706, 0.17220478, 0.096844435, -0.30439013, -0.14122063, 0.15733318, -0.1014675, 0.33836862, 0.042193163) * go_2(-1.0, 0.0);
result += mat4(0.15826897, -0.034870047, 0.09295099, -0.17674965, -0.042326324, 0.06680338, -0.074267656, -0.0631393, -0.11267909, -0.19795708, 0.22005288, 0.35703793, 0.033995766, -0.12663686, -0.02449896, -0.123250045) * go_2(-1.0, 1.0);
result += mat4(0.021434195, 0.058398597, 0.04828315, -0.0016824572, -0.04291545, -0.0744907, -0.07698706, -0.15937585, -0.18852457, -0.17966963, 0.023800725, 0.025979731, -0.51412296, -0.018316887, -0.23076254, -0.12298674) * go_2(0.0, -1.0);
result += mat4(0.16054317, -0.0002730893, -0.54173076, -0.62443435, 0.04300197, -0.08529622, 0.15392275, 0.15742144, 0.025834514, -0.2800517, -0.17600477, 0.0020806703, -0.3010582, 0.45233512, 0.25595665, 0.103661336) * go_2(0.0, 0.0);
result += mat4(-0.024034392, -0.43800178, 0.28606912, -0.20908915, 0.078471914, -0.030501373, -0.059055753, 0.050494444, 0.063274644, -0.025071034, 0.17561312, -0.100698635, -0.25631955, 0.039981876, -0.18506624, 0.08366402) * go_2(0.0, 1.0);
result += mat4(-0.1413656, 0.03589635, -0.020917566, 0.017598262, 0.020156413, -0.018854238, 0.027228508, -0.03806087, -0.021715842, 0.071974196, -0.040065665, 0.08459291, -0.23530225, 0.16599682, -0.2772327, 0.10041177) * go_2(1.0, -1.0);
result += mat4(-0.055056706, 0.1286236, -0.11890451, -0.1790546, 0.16517544, -0.040448934, 0.12548013, 0.017075695, 0.07185459, -0.13236302, 0.19354409, 0.12767012, 0.31120765, 0.16378082, -0.036915366, -0.19724306) * go_2(1.0, 0.0);
result += mat4(-0.02225051, 0.033263147, 0.003279449, 0.08826271, -0.047833472, 6.574577e-05, 0.13721916, 0.04801998, -0.014958419, 0.08791209, -0.08076282, 0.024002168, -0.18028922, 0.23835851, -0.23309888, -0.119310364) * go_2(1.0, 1.0);
result += mat4(0.044960875, 0.18821983, 0.027640678, 0.013462449, 0.19011214, 0.21559924, -0.03329638, 0.07234414, 0.030880248, -0.11273214, 0.102028474, 0.12203351, 0.035855662, 0.008828778, 0.007218363, -0.012421797) * go_3(-1.0, -1.0);
result += mat4(-0.09450626, 0.025191775, -0.10738468, 0.16237053, 0.073676676, 0.12488881, -0.048748355, 0.007877263, 0.3572506, -0.07911043, 0.14684045, 0.0015310893, -0.33411503, -0.1151223, 0.004201752, 0.017775744) * go_3(-1.0, 0.0);
result += mat4(-0.10607509, -0.008143826, -0.08448629, -0.27557802, 0.0046665915, 0.008158659, 0.030826218, 0.020516023, 0.2333065, -0.017463414, -0.041772116, -0.03027809, -0.028166672, -0.080471426, 0.048199337, 0.08341059) * go_3(-1.0, 1.0);
result += mat4(-0.14640257, -0.18334304, -0.061674733, 0.0008892598, -0.2374775, -0.2721524, -0.040371176, 0.26362613, 0.19872928, -0.11246391, 0.0842288, 0.11188515, 0.0045209546, -0.04250933, -0.0738212, -0.069005966) * go_3(0.0, -1.0);
result += mat4(-0.08760266, 0.4816288, -0.21241407, 0.22734411, -0.1783721, -0.26842996, 0.099888, -0.2867675, 0.085521065, -0.3780281, -0.018543908, -0.039699722, 0.75688565, -0.5333645, 0.47567275, 0.09518891) * go_3(0.0, 0.0);
result += mat4(-0.04072665, 0.05998423, -0.48314768, -0.29495844, 0.10358383, -0.09816629, 0.028586809, -0.047708735, 0.008320228, 0.04089551, -0.18359782, -0.27615002, 0.12414414, -0.072417594, 0.25932562, 0.30268723) * go_3(0.0, 1.0);
result += mat4(0.14481631, 0.06484443, -0.09898657, -0.06553556, 0.25750044, -0.07265585, 0.12903488, -0.022347894, -0.04693863, -0.000107379274, 0.030295763, -0.0325354, 0.086214684, -0.021326948, 0.039682828, -0.034843277) * go_3(1.0, -1.0);
result += mat4(-0.031971477, -0.25145087, 0.03931631, 0.14262606, -0.06044626, 0.22820354, -0.10506207, 0.18064679, 0.0069641788, 0.01477993, -0.003626875, 0.118767865, 0.109416224, -0.002998205, 0.035680585, 0.07843882) * go_3(1.0, 0.0);
result += mat4(0.03375426, -0.059815384, 0.11632834, -0.12411481, 0.022583738, 0.02544465, -0.054889992, -0.07031964, -0.10140042, 0.16750422, -0.1448294, -0.09316004, 0.035582513, -0.026138382, -0.031955894, 0.040148776) * go_3(1.0, 1.0);
result += vec4(-0.03573331, 0.032919675, 0.011109369, 0.008329268);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.11498094, -0.053904895, -0.11520678, -0.05479549, 0.028396055, 0.032767884, 0.052479446, 0.05257866, -0.25706592, -0.3454966, -0.24713765, -0.2854201, -0.10287636, 0.0023146886, -0.09190338, -0.011193905) * g_0;
result += mat4(-0.05461422, 0.008780496, -0.07738697, -0.032230727, -0.047554165, -0.025061952, -0.051897213, -0.009545297, -0.14548294, -0.15184018, -0.01313442, -0.015299784, -0.0007883845, -0.12866738, -0.15260352, -0.27081275) * g_1;
result += mat4(0.11007706, 0.035344437, 0.11020841, 0.0425353, 0.1613199, 0.18417408, 0.09274313, 0.11943135, 0.106862, 0.079875536, 0.0937752, 0.068030775, 0.029093558, -0.06441164, 0.06467169, -0.021989612) * g_2;
result += mat4(0.049548414, -0.012455486, 0.07185561, 0.021865537, 0.020969186, -0.03374196, -0.024260623, -0.07739141, 0.07164591, 0.12741035, 0.0379913, 0.076403245, 0.07049977, 0.0744538, 0.0062989634, 0.01818882) * g_3;
result += mat4(-0.12511204, -0.010836819, 0.13709816, 0.22472954, 0.21280868, -0.006484726, 0.17554289, -0.009977173, 0.078398876, 0.20698707, 0.13432744, 0.29740283, -0.24750128, -0.32757792, -0.19807857, -0.2537023) * g_4;
result += mat4(-0.27207088, -0.1385644, -0.2166476, -0.07687419, -0.20300622, -0.29678395, -0.13135734, -0.20851587, 0.0361364, 0.011243289, -0.06845459, -0.11796941, 0.11575868, 0.070215136, -0.10295678, -0.12281369) * g_5;
result += mat4(0.13619795, -0.0019436983, -0.12701888, -0.25933513, -0.20134166, 0.00062823144, -0.076756015, 0.11002947, 0.0059049693, -0.18756741, -0.0718802, -0.2589954, 0.23413423, 0.30107784, 0.14445266, 0.18920745) * g_6;
result += mat4(0.1494216, 0.0587532, 0.05478662, -0.039123338, 0.23322394, 0.29950607, 0.24384268, 0.27843767, -0.16094431, -0.04705998, -0.016345032, 0.028868208, -0.102872886, -0.04659664, 0.104105346, 0.14305067) * g_7;
result += mat4(-0.001037014, 0.010001526, -0.0052278573, 0.024779709, 0.06857274, 0.067640975, 0.085439384, 0.09242789, -0.066597246, -0.055928994, 0.0015658981, 0.016131008, -0.03524695, -0.018364554, -0.047754433, -0.014295886) * g_8;
result += mat4(-0.042207, 0.02835915, -0.1404656, -0.08563323, -0.030979915, -0.0673764, 0.10733943, 0.057902794, 0.00022424995, -0.0023634837, -0.10778953, -0.10202357, -0.020368295, -0.019088887, -0.06875738, -0.08504131) * g_9;
result += mat4(-0.00043458896, 0.00045652856, -0.02016843, -0.020062413, -0.08740103, -0.042085808, -0.10644177, -0.09226477, 0.11212161, -0.00048174805, 0.021872435, -0.05868698, 0.0333954, 0.058184672, 0.05532576, 0.07621587) * g_10;
result += mat4(0.054245148, 0.001020329, 0.09106849, 0.05303779, 0.009889632, 0.01309413, -0.09187347, -0.08618193, -0.011621187, 0.016222361, 0.061095525, 0.060885344, 0.078050986, 0.0111776795, 0.08829944, 0.032022282) * g_11;
result += mat4(0.01643529, 0.02285545, -0.03498564, 0.00769657, -0.0042474116, 0.015836312, -0.025771018, -0.0016368, -0.008897948, -0.012588166, -0.01416411, -0.003578984, 0.025991246, 0.021237152, 0.017450012, 0.025172485) * g_12;
result += mat4(0.014568868, 0.017796224, -0.036679734, -0.03138748, 0.019457601, -0.027607411, -0.004529679, -0.038048342, -0.054055385, -0.03876025, 0.041948095, 0.005869784, 0.02439633, 0.05177997, 0.016000897, 0.0057169925) * g_13;
result += mat4(-0.03021866, 0.017678728, -0.01371109, 0.013548159, -0.0038099394, -0.014066414, 0.028093752, 0.0027308422, -0.010615999, 0.012673458, -0.03028171, -0.016818244, -0.06530097, -0.018845048, -0.0072947564, -0.0038243714) * g_14;
result += mat4(-0.019006258, -0.007847591, 0.03690709, 0.06714211, 0.0073993434, -0.009766907, -0.0021441753, -0.01308625, 0.06658726, 0.06701995, -0.027305668, -0.016032105, -0.028976806, -0.0036668575, -0.0027825525, 0.0105632655) * g_15;
result += mat4(0.028945107, -0.0014701135, 0.048950657, -0.01923516, -0.0014054152, 0.002650635, -0.005300331, 0.004860559, 0.011158468, 0.005940625, -0.012095051, 0.0041518128, -0.020433836, -0.025870577, -0.0007547932, -0.026509356) * g_16;
result += mat4(-0.004545374, 0.04264545, 0.021741537, 0.029115127, 0.04225599, -0.0055392785, 0.026570829, -0.031795148, -0.008307126, 0.020176455, 0.010904648, 0.017765503, -0.10806103, -0.01776947, 0.00070428237, -0.06356262) * g_17;
result += mat4(-0.05663172, 0.05908046, -0.03837452, 0.06636983, -0.007960516, -0.06384041, 0.023125881, -0.030108837, 0.0038054318, -0.023263922, 0.020264054, -0.0062937695, 0.031630237, 0.020909082, 0.03594235, 0.035879835) * g_18;
result += mat4(-0.0050448794, 0.033650696, -0.002830413, 0.035174295, -0.024521282, 0.013054315, -0.020833842, 0.037953895, 0.08249671, 0.024239466, -0.012758333, -0.027316988, 0.051040914, 0.0005025873, 0.039778862, 0.0024668393) * g_19;
result += mat4(0.017232442, 0.022482058, 0.020233413, 0.024337437, 0.07986929, 0.06234036, 0.12662584, -0.05271183, -0.009718745, -0.0046989853, -0.0030333172, -0.04034237, -0.0113442, 0.022746231, -0.035293855, -0.009433693) * g_20;
result += mat4(0.015766997, 0.013647276, -0.029327558, 0.039106004, -0.010398323, -0.032851525, 0.02908329, -0.003789618, 0.12963496, 0.010851003, 0.1126276, -0.049255487, 0.06867432, 0.07970792, 0.017840397, -0.026481882) * g_21;
result += mat4(-0.058729574, -0.07886952, 0.033267397, 0.02755372, -0.0172006, 0.012404398, -0.0230168, -0.015059758, -0.09239916, -0.029533267, -0.043251917, 0.0035152994, 0.022931995, 0.101714484, -0.044946067, 0.094993) * g_22;
result += mat4(-0.04708704, -0.032475296, -0.03228093, -0.08810475, 0.013745045, 0.027828002, -0.031922746, 0.022986397, -0.061620213, -0.03694645, -0.055026993, 0.0031291894, -0.028799903, -0.0025357977, -0.03441407, 0.0028600092) * g_23;
result += mat4(0.058981724, -0.10447273, -0.088705614, 0.16546178, -0.023549391, -0.008831522, -0.018411588, 0.029640056, -0.068086684, -0.05414636, -0.029401174, 0.036180343, -0.031988926, -0.047249753, 0.008162177, 0.00548062) * g_24;
result += mat4(0.05287462, -0.030657746, 0.02821435, 0.037005343, 0.03534311, -0.15614955, 0.07085459, -0.11997641, -0.009156166, -0.021968868, -0.054147746, -0.07307657, -0.006428544, -0.017528288, 0.012614676, 0.037840024) * g_25;
result += mat4(-0.021977803, 0.047799855, 0.02660416, -0.07292106, 0.045195807, -0.0056674764, 0.10824326, -0.112114795, 0.1447127, -0.0119616175, 0.0011661504, -0.04553905, 0.13048342, 0.14574122, -0.105522245, -0.102792375) * g_26;
result += mat4(-0.16397473, 0.15785863, -0.06666504, -0.01682913, 0.06070918, 0.070222184, 0.037701584, 0.026657054, -0.0835267, -0.009457008, 0.13232987, 0.13508691, -0.056414206, -0.06818828, 0.079076104, 0.032249212) * g_27;
result += vec4(-0.10795144, -0.09953324, -0.055413827, -0.03875493);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.024905335, -0.0020974763, 0.02695263, 0.00016802056, -0.024053082, -0.02133723, -0.031614035, -0.031826317, 0.120421864, 0.10555479, 0.08609448, 0.116875134, 0.046175968, 0.04224941, 0.059216674, 0.035143953) * g_0;
result += mat4(0.059397914, 0.016519934, 0.07189327, 0.047407165, 0.04808963, 0.02792908, 0.057017103, 0.034324065, 0.14228246, 0.11275426, 0.088058695, 0.059600517, 0.02063494, 0.052596953, 0.047207687, 0.08789091) * g_1;
result += mat4(-0.013453174, 0.008474715, -0.017593835, 0.009218917, 0.070580654, 0.040542338, 0.08812338, 0.074653216, -0.016356857, 0.015809007, -0.008739107, 0.0097674895, -0.018381525, -0.007775341, -0.040571664, -0.011188163) * g_2;
result += mat4(-0.026196122, -0.034825727, -0.042998232, -0.033436514, -0.01678153, -0.004592797, -0.010311677, 0.0008815291, -0.08899181, -0.10274026, -0.066960976, -0.082430154, -0.057137426, -0.07554528, -0.030993424, -0.050372377) * g_3;
result += mat4(0.022921838, -0.010479244, -0.050794605, -0.073633075, -0.053708922, 0.009594084, -0.071259, -0.01054356, 0.005165821, -0.08024963, -0.049251772, -0.09581235, 0.17995799, 0.09743011, 0.13533138, 0.11643848) * g_4;
result += mat4(0.09727046, 0.07292666, 0.06820908, 0.041535784, -0.0049705, 0.0048759184, -0.035702795, -0.015944308, -0.010730028, 0.018847652, 0.06466244, 0.086318985, -0.05661574, -0.040698618, 0.010839972, 0.0027009705) * g_5;
result += mat4(-0.04628466, 0.010060396, 0.02609333, 0.08664702, 0.057045907, 0.033591177, 0.02186063, -0.024303377, 0.006569828, 0.08025825, 0.016128821, 0.10180713, -0.12228169, -0.112990454, -0.078443415, -0.09126021) * g_6;
result += mat4(-0.12733299, -0.087755, -0.07374111, -0.044979006, -0.025347412, -0.004083168, 0.023782173, 0.02900392, -0.017815407, -0.041119996, -0.057978686, -0.13521095, 0.08364004, 0.06950181, 0.023554614, 0.008043734) * g_7;
result += mat4(0.009062775, -0.003570175, -0.007378757, -0.0018487388, 0.01145638, 0.05217187, -0.008250244, 0.008433307, -0.056756936, -0.044681005, -0.08096105, -0.08033185, -0.023784965, -0.01859799, 0.013042476, 0.021188647) * g_8;
result += mat4(-0.0071619656, -0.012498299, -0.05144986, -0.078112476, -0.034992415, -0.017038302, -0.04464615, -0.044504963, 0.024249, -0.004297534, 0.03674578, 0.03090718, 0.04698553, 0.008344952, 0.057619847, -0.0338724) * g_9;
result += mat4(-0.011845145, -0.0045043705, -1.6646482e-06, -0.0038495932, -0.01992515, 0.004827126, 0.019493148, 0.00862289, 0.10151322, 0.0021909082, 0.09940764, 0.03728846, 0.027824005, 0.04358071, 0.014909185, 0.036326095) * g_10;
result += mat4(0.022513246, 0.028257169, 0.0102195935, 0.03301329, 0.052253865, -0.0021944977, 0.08247392, 0.03256867, -0.040685873, -0.0052207555, -0.0451257, -0.054165114, 0.01647699, 0.0028809097, -0.015233776, -0.0008741886) * g_11;
result += mat4(0.017371105, 0.01597189, -0.052552313, -0.008554715, -0.0023150423, 0.006076517, -0.012868931, 0.0039361073, -0.007524978, -0.004284313, -0.021520883, -0.010327569, 0.02543678, 0.008725823, -0.0073885336, 0.005528395) * g_12;
result += mat4(0.019192757, 0.016561812, 0.0027538154, 0.0013078215, 0.007916496, -0.042525183, -0.013173432, -0.05265476, -0.062195376, -0.011255499, 0.020898128, 0.021532273, -0.001524097, 0.034835674, -0.004051403, -0.0292426) * g_13;
result += mat4(-0.049191684, -9.43322e-06, -0.009106849, 0.012845289, -0.019482708, -0.011163468, 0.0034011535, -0.007062845, -0.006469714, 0.03177786, -0.033006195, -0.0006813464, -0.053963087, 0.00085209147, 0.02734121, 0.034086403) * g_14;
result += mat4(-0.03232248, -0.004037002, -0.010319106, 0.030889064, 0.019604538, 0.0020888883, 0.010277864, 0.000661223, 0.057915937, 0.030683514, 0.00042533095, -0.013019287, -0.015896408, 0.0038484468, -0.0042103594, 0.02174542) * g_15;
result += mat4(0.032975145, 0.0011456647, 0.04913679, -0.017063798, 0.0117176045, 0.007440557, 0.0020480808, 0.009415731, 0.027573857, 0.015140836, -0.01679426, -0.006124731, -0.03206279, -0.029842237, -0.010428016, -0.028513178) * g_16;
result += mat4(-0.00506859, 0.055869613, 0.010164368, 0.027031485, 0.042289548, -0.0054258504, 0.032214936, -0.029970925, -0.0058315448, 0.022889478, 0.01681123, 0.02985076, -0.111186065, -0.02202099, 0.0030994313, -0.062343158) * g_17;
result += mat4(-0.060951103, 0.06079555, -0.0396464, 0.070911355, -0.011480358, -0.06803282, 0.01637355, -0.043100975, -0.00423709, -0.028337711, 0.021635853, 0.0014857082, 0.030084312, 0.018155476, 0.043694943, 0.038795974) * g_18;
result += mat4(-0.0060662925, 0.029721662, -0.008117774, 0.034551267, -0.024477571, 0.018841071, -0.027095588, 0.034495078, 0.082398005, 0.008998768, -0.016399248, -0.043801688, 0.05936684, 0.006066549, 0.045399766, 3.5319943e-05) * g_19;
result += mat4(0.019259382, 0.02494012, 0.029301709, 0.028329274, 0.09122267, 0.06900443, 0.1412115, -0.043169618, -0.01627418, -0.004989528, -0.0042651827, -0.04556752, -0.023623291, 0.013007996, -0.04483056, -0.015727345) * g_20;
result += mat4(0.016332543, 0.016384754, -0.030676385, 0.045312885, -0.0100853555, -0.032632045, 0.031514473, -0.0070776115, 0.13642761, 0.0023589598, 0.12214136, -0.062155515, 0.08240989, 0.08894205, 0.03325406, -0.016589595) * g_21;
result += mat4(-0.06494277, -0.08158925, 0.030425413, 0.019835634, -0.012624623, 0.013942616, -0.030527417, -0.021668324, -0.09444672, -0.033064254, -0.044167448, 0.0011024752, 0.03210801, 0.12662941, -0.03912534, 0.1112649) * g_22;
result += mat4(-0.04716062, -0.03751481, -0.031030515, -0.09067383, 0.0077815712, 0.02169541, -0.035285182, 0.02290573, -0.0704085, -0.03916127, -0.058103334, 0.004915147, -0.0333844, -0.011548617, -0.031151932, -0.00043817286) * g_23;
result += mat4(0.05976319, -0.107285, -0.097245865, 0.17706421, -0.021453341, -0.0047738464, -0.017621001, 0.033400454, -0.07225561, -0.05599672, -0.027600193, 0.038664024, -0.03762786, -0.052429967, 0.0104017975, 0.007116869) * g_24;
result += mat4(0.06014114, -0.029824806, 0.03209269, 0.04392036, 0.031300627, -0.16249833, 0.06878509, -0.12658615, -0.012383169, -0.025043553, -0.06527381, -0.08149099, -0.014006842, -0.018669648, 0.014510818, 0.042045828) * g_25;
result += mat4(-0.023342922, 0.047104675, 0.029629575, -0.082307704, 0.04035797, -0.0013049254, 0.11085582, -0.11031226, 0.14778149, -0.016699014, -0.00634342, -0.055320874, 0.14306462, 0.15896587, -0.110229075, -0.1069649) * g_26;
result += mat4(-0.17449625, 0.15787153, -0.06711028, -0.023110518, 0.06862914, 0.074063435, 0.042682912, 0.029800726, -0.08768606, -0.009814701, 0.14180017, 0.14780663, -0.05672417, -0.074305914, 0.07873489, 0.028458012) * g_27;
result += vec4(0.06026231, 0.040204916, 0.037672628, 0.023496555);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf2
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.1765669, 0.14268716, 0.19186598, 0.15799578, 0.016374417, 0.018578433, 0.0039475, 0.0046772263, 0.39840183, 0.36909792, 0.35409746, 0.37422222, -0.108508386, -0.1331279, -0.10336035, -0.14776541) * g_0;
result += mat4(-0.057757027, -0.14071062, -0.025283009, -0.09397916, -0.09031894, -0.14219165, -0.08299535, -0.13970287, -0.12259208, -0.14382727, -0.22002274, -0.25016093, -0.048906635, 0.06620249, 0.016965045, 0.1295978) * g_1;
result += mat4(-0.16748372, -0.13718611, -0.18565705, -0.15029612, -0.080749065, -0.09955825, 0.032431383, 0.023855643, -0.2748885, -0.23232168, -0.29121292, -0.26405892, 0.16556135, 0.18657646, 0.1424068, 0.18855052) * g_2;
result += mat4(0.10960496, 0.10851629, 0.095003806, 0.11053746, 0.09885307, 0.14437789, 0.13191165, 0.17365928, 0.16558935, 0.15473324, 0.21136154, 0.19976667, -0.07267957, -0.11469687, -0.029134216, -0.06817615) * g_3;
result += mat4(0.10202856, 0.04216857, -0.03959349, -0.09849683, -0.1576996, -0.049997438, -0.1579918, -0.058789205, 0.029792828, -0.07311781, -0.045432188, -0.11312683, 0.24257647, 0.16204113, 0.17869382, 0.16024388) * g_4;
result += mat4(0.17193612, 0.12692013, 0.13177487, 0.0796725, 0.0797928, 0.08952722, -0.012468046, 0.011071511, -0.068559825, -0.024852324, 0.0526428, 0.07917346, -0.085534215, -0.09591339, 0.04615827, 0.024577664) * g_5;
result += mat4(-0.14653449, -0.067267366, -0.002524394, 0.086243175, 0.13660401, 0.08039592, 0.09179008, 0.022573143, -0.024744196, 0.09120211, 0.017654825, 0.14114714, -0.16093308, -0.14538004, -0.09950235, -0.111152865) * g_6;
result += mat4(-0.188637, -0.12968326, -0.1200479, -0.06537649, -0.12589337, -0.106242515, -0.02788782, -0.025949068, 0.04948153, 0.02222735, -0.025291357, -0.12379292, 0.11074645, 0.11902375, -0.00056989543, -0.0024386419) * g_7;
result += mat4(0.018286629, 0.0072215167, 0.00037828335, 0.0047001047, 0.011478272, 0.041745186, -0.015742473, -0.002282524, -0.03440817, -0.02196847, -0.07838253, -0.07993771, -0.010155526, -0.017590692, 0.027141469, 0.029741213) * g_8;
result += mat4(0.016512005, 0.004950637, -0.0238836, -0.05587327, -0.03164328, -0.009499985, -0.059880238, -0.061794154, 0.023154303, -0.013266373, 0.04701534, 0.0415862, 0.06357814, 0.033057794, 0.08389772, 0.00035060212) * g_9;
result += mat4(-0.016403968, -0.012538788, -0.0015746636, -0.004771009, -0.021361275, -0.009695242, 0.020548422, -0.0024130535, 0.07796766, -0.01516671, 0.09961382, 0.042754963, 0.017363647, 0.03729065, -0.004795824, 0.01550197) * g_10;
result += mat4(-0.0028093113, 0.011869523, -0.02216933, 0.011177349, 0.033342455, -0.021146454, 0.07830085, 0.032490104, -0.03281833, 0.0060484232, -0.04081057, -0.04945058, -0.0056189033, -0.010636801, -0.041949317, -0.025739705) * g_11;
result += mat4(0.012979897, 0.016758928, -0.049062215, -0.0035748442, 0.0085972, 0.0036381132, -0.0055621094, 0.0041307937, -0.0008907763, -0.0034079372, -0.025680453, -0.015531803, 0.012816766, 0.009977763, -0.016416566, 0.0034859509) * g_12;
result += mat4(0.021753248, 0.016452711, 0.009833835, 0.0065052663, 0.0014061348, -0.046160888, -0.0132271005, -0.05051269, -0.05746351, -0.0012690664, 0.017191738, 0.018192926, -0.008879476, 0.026354216, -0.012801991, -0.029587373) * g_13;
result += mat4(-0.04220692, -0.0015560482, -0.0019648245, 0.013402305, -0.018259782, -0.0036008905, 0.0035650074, -0.0019178417, 0.00051580026, 0.027355857, -0.017914988, 0.004937948, -0.046335887, 0.00013612259, 0.030293299, 0.030688645) * g_14;
result += mat4(-0.036683388, -0.0031274238, -0.026074665, 0.021684237, 0.022639066, 0.0022493738, 0.011508554, -0.0006385944, 0.04890418, 0.020119468, 0.004167364, -0.008356099, -0.008598796, 0.0089028, -0.0029575853, 0.016687104) * g_15;
result += mat4(0.027207986, 0.0011099194, 0.042383645, -0.015179333, 0.014744431, 0.006148344, 0.005165422, 0.0070196544, 0.030286826, 0.016620956, -0.01611366, -0.00667594, -0.029524863, -0.024751091, -0.013321004, -0.025199674) * g_16;
result += mat4(0.0027477827, 0.054622147, 0.010154094, 0.025437292, 0.031773083, -0.01055473, 0.022864206, -0.029010754, -0.0029999653, 0.025018329, 0.015316208, 0.027188798, -0.10096525, -0.017268656, 0.0012529213, -0.062078856) * g_17;
result += mat4(-0.053670805, 0.057336535, -0.037418038, 0.06443577, -0.016027879, -0.058168363, 0.007034215, -0.03390141, -0.0019346164, -0.027947908, 0.021723913, -0.0018286633, 0.030507812, 0.018293543, 0.042917266, 0.033528328) * g_18;
result += mat4(-0.004559579, 0.029667616, -0.001870353, 0.0378995, -0.017147437, 0.020192018, -0.021574946, 0.031568103, 0.07487145, 0.0032376775, -0.018893708, -0.041981626, 0.054478757, 0.0061423797, 0.041280247, 0.000878061) * g_19;
result += mat4(0.017076394, 0.023647636, 0.029403262, 0.029923365, 0.08866472, 0.060613394, 0.1314274, -0.04490231, -0.016304834, -0.0062647443, -0.0031828512, -0.03989252, -0.024330825, 0.00741213, -0.04075287, -0.01615817) * g_20;
result += mat4(0.017866978, 0.017720113, -0.02846163, 0.040761847, -0.0063438355, -0.02347501, 0.029564403, -0.0029562064, 0.12505588, -0.0073986333, 0.11250363, -0.06179967, 0.07854423, 0.08546533, 0.034743227, -0.010757377) * g_21;
result += mat4(-0.06416677, -0.08344284, 0.030138884, 0.017635904, -0.012087523, 0.014205202, -0.03221233, -0.023834767, -0.091186255, -0.028958676, -0.04724334, 0.00013161585, 0.027391518, 0.1249978, -0.045047652, 0.10737729) * g_22;
result += mat4(-0.04326348, -0.03543181, -0.029558217, -0.08582413, 0.007812453, 0.014296562, -0.028779754, 0.018517692, -0.063755795, -0.036619596, -0.050809663, 0.005431336, -0.029205568, -0.011827915, -0.031110523, -0.005648626) * g_23;
result += mat4(0.05499293, -0.10000709, -0.0943537, 0.16143042, -0.019952895, -0.0039807972, -0.014841254, 0.0320363, -0.065173544, -0.049425576, -0.023904482, 0.03759679, -0.03207411, -0.047782745, 0.01352581, 0.008140566) * g_24;
result += mat4(0.055923894, -0.025134467, 0.029583648, 0.04096879, 0.027551858, -0.14995384, 0.06467113, -0.11633077, -0.01563784, -0.026909819, -0.06292879, -0.078409635, -0.009081105, -0.015533088, 0.019585673, 0.04334208) * g_25;
result += mat4(-0.021717606, 0.042464726, 0.02743202, -0.07388838, 0.03460472, 0.0038285658, 0.099842004, -0.098247, 0.13276267, -0.020793032, -0.008603039, -0.051913783, 0.12959045, 0.14735717, -0.10888226, -0.10263746) * g_26;
result += mat4(-0.16819532, 0.141579, -0.062480718, -0.021918943, 0.06348125, 0.06849444, 0.03888676, 0.027375204, -0.08194279, -0.012574497, 0.13523251, 0.13739482, -0.047547445, -0.058767617, 0.07009549, 0.028136581) * g_27;
result += vec4(0.069033325, 0.040207114, 0.027286075, 0.0065334598);
return result;
}
//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Depth-to-Space
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_last_tf
//!BIND conv2d_last_tf1
//!BIND conv2d_last_tf2
//!SAVE MAIN
//!WIDTH conv2d_last_tf.w 2 *
//!HEIGHT conv2d_last_tf.h 2 *
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
vec4 hook() {
vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);
ivec2 i0 = ivec2(f0 * vec2(2.0));
float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];
vec2 f1 = fract(conv2d_last_tf1_pos * conv2d_last_tf1_size);
ivec2 i1 = ivec2(f1 * vec2(2.0));
float c1 = conv2d_last_tf1_tex((vec2(0.5) - f1) * conv2d_last_tf1_pt + conv2d_last_tf1_pos)[i1.y * 2 + i1.x];
vec2 f2 = fract(conv2d_last_tf2_pos * conv2d_last_tf2_size);
ivec2 i2 = ivec2(f2 * vec2(2.0));
float c2 = conv2d_last_tf2_tex((vec2(0.5) - f2) * conv2d_last_tf2_pt + conv2d_last_tf2_pos)[i2.y * 2 + i2.x];
float c3 = c2;
return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,300 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.029052526, 0.059789784, -0.024398074, 0.06907132, 0.18920785, -0.12923062, 0.0766382, -0.12048348, -0.017786544, 0.06251133, -0.068393864, 0.056690093, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.14032267, -0.077691495, -0.009036259, -0.13049065, 0.20954624, 0.023231741, -0.2759354, 0.49927905, 0.039609738, -0.092625424, 0.09426452, -0.2246486, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(-0.023119625, 0.046549924, 0.073033765, 0.03727065, 0.04498207, 0.024455868, 0.17602317, -0.3150503, 0.019985953, 0.03670126, 0.0071220254, 0.107966185, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.111121014, -0.084099665, 0.12595509, -0.048271902, -0.007799661, 0.04831373, 0.11868961, 0.11607051, 0.05169621, -0.050569464, 0.120362274, 0.034607537, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.41167754, -0.44940078, 0.35485214, 0.58048695, -1.0151424, -0.70137614, 0.38405335, 0.37337455, -0.096364655, -0.14538667, 0.17917591, 0.32259464, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(0.06378494, -0.040756933, -0.4773648, -0.47702238, 0.1803328, -0.21388084, -0.5509359, -0.6491179, -0.048081584, -0.0025129975, -0.28561604, -0.22229671, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.037024107, 0.016497454, -0.05315267, -0.023392007, 0.1840294, 0.12675077, 0.037417043, -0.022394283, -0.028192522, -0.016344562, -0.07269005, -0.04747136, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.039480202, 0.5577544, -0.117326505, 0.06622856, -0.038784727, 0.65673745, -0.109742545, 0.22294083, 0.00038519394, 0.24552485, -0.07008514, 0.00029412706, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.009279719, -0.031882852, 0.14124188, -0.0759899, -0.024016602, 0.15252088, 0.13614258, -0.09961189, 0.05446014, -0.03827061, 0.11210173, -0.028823104, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.012836382, -0.0062823873, -0.03165346, -0.0017501811);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_tf
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.06215308, -0.054471835, 0.1285146, 0.037585296, -0.14467795, 0.0057610427, 0.006528968, 0.18607244, 0.03762581, -0.121003255, 0.0827445, 0.076479666, 0.07540097, 0.16371846, -0.18786757, -0.12048073) * go_0(-1.0, -1.0);
result += mat4(-0.11520603, -0.22384967, 0.0970881, 0.045122143, -0.40358877, 0.12487416, -0.4489702, 0.04854906, -0.08240888, -0.0058777514, 0.19108902, 0.042189106, -0.07843178, 0.0012592699, -0.11303816, -0.1118517) * go_0(-1.0, 0.0);
result += mat4(0.29387334, 0.08150406, -0.06009834, 0.07054583, -0.146034, 0.04053809, 0.23284695, -0.08233496, 0.0957811, 0.20221621, 0.13570721, -0.081069246, -0.031743366, -0.038912926, -0.059060514, 0.05190416) * go_0(-1.0, 1.0);
result += mat4(0.18404631, -0.034244366, -0.13406059, 0.2242061, -0.23668705, -0.10359684, -0.09689738, -0.04932347, -0.0020525968, 0.15236467, -0.2634303, -0.029878438, 0.3283669, 0.09891668, -0.46656898, -0.03271751) * go_0(0.0, -1.0);
result += mat4(-0.1824976, -1.0545974, 0.93027455, -0.13887188, 0.3128633, -0.2734884, -0.831517, -0.18065166, -0.2119423, 0.19241124, -0.13313763, -0.10523897, 0.2675327, -0.06853148, 0.007885104, 0.26000848) * go_0(0.0, 0.0);
result += mat4(-0.18109167, -0.019768981, -0.14131357, -0.3181756, 0.22158594, 0.1431138, -0.12970252, -0.011459096, -0.03742945, 0.2316056, -0.054623842, -0.09360549, 0.10176328, -0.108150974, 0.122787155, 0.07614884) * go_0(0.0, 1.0);
result += mat4(-0.02370754, -0.10264054, 0.030659392, 0.056694325, 0.034085, -0.0538203, 0.09111551, 0.0063995267, 0.0835243, -0.06623529, 0.20924146, 0.09457414, 0.27925664, 0.049511474, -0.22062886, 0.03312504) * go_0(1.0, -1.0);
result += mat4(0.17009354, 0.34391564, -0.1680695, -0.051450633, -0.044037253, -0.1412577, 0.01097572, -0.049040757, 0.104024716, -0.34571946, 0.5213214, -0.17010914, -0.016452854, 0.3303069, -0.22249438, 0.23866816) * go_0(1.0, 0.0);
result += mat4(0.20491506, 0.20098424, 0.015425732, -0.033999693, -0.0111842435, 0.09945295, -0.025766203, 0.17068656, 0.049262784, 0.0077788536, 0.068585835, 0.19229786, -0.013048818, 0.04877973, 0.024053875, -0.06846659) * go_0(1.0, 1.0);
result += mat4(0.14208305, 0.09790381, -0.008420949, -0.016165754, 0.02445528, 0.08700781, 0.046639573, -0.22105917, -0.08529265, 0.06606378, 0.0947481, 0.08149193, -0.0959293, -0.037756894, -0.008136973, 0.046241503) * go_1(-1.0, -1.0);
result += mat4(0.2577669, 0.13766493, 0.021107635, 0.018978242, 0.452542, 0.25566816, -0.68909633, 0.03804329, 0.06771752, 0.07894156, 0.22501312, -0.047511246, 0.00040517355, -0.0202232, -0.27541754, -0.040150844) * go_1(-1.0, 0.0);
result += mat4(-0.30176973, -0.15739526, -0.038548045, -0.07003333, 0.32053417, 0.025467036, -0.044913124, 0.20454903, 0.12475206, -0.03966162, 0.07139637, 0.12101497, -0.10777517, -0.062379625, 0.06598757, -0.14795317) * go_1(-1.0, 1.0);
result += mat4(0.12068516, 0.0026514034, -0.055378057, -0.0976728, 0.15887645, 0.15590422, 0.076294206, -0.15417404, -0.16548084, -0.18422292, -0.1670212, 0.041155312, -0.11765263, 0.16991171, -0.21535093, 0.01542368) * go_1(0.0, -1.0);
result += mat4(-0.37845853, 0.5732961, 0.114283465, 0.14638355, -0.109194644, 0.087304994, -0.15938401, 0.58242995, -0.025850652, 0.02730721, -0.48582682, -0.2547883, 0.1899583, 0.24296008, -0.8162976, 0.018036429) * go_1(0.0, 0.0);
result += mat4(0.1633212, -0.117295206, 0.021892091, 0.07762347, -0.09726402, -0.1364192, 0.10752197, 0.42020246, 0.06665656, -0.10822656, 0.1337331, 0.0552859, 0.04700212, 0.108017646, -0.2009353, -0.0435288) * go_1(0.0, 1.0);
result += mat4(-0.045603696, 0.05774526, -0.0071174325, 0.24119262, -0.06899063, 0.016012343, 0.009982042, -0.19038968, -0.17796072, -0.12510185, 0.22739507, -0.1805478, -0.100294635, 0.017557602, 0.039842658, 0.13116726) * go_1(1.0, -1.0);
result += mat4(0.0131523665, -0.20472725, 0.121814765, -0.17769355, 0.097669855, 0.09648846, -0.072887406, 0.22992326, -0.019087939, 0.35375193, -0.057155706, 0.17699116, 0.030690158, -0.423475, 0.03703492, -0.03429164) * go_1(1.0, 0.0);
result += mat4(-0.12143413, -0.018402342, 0.04536776, -0.12743106, 0.03355068, -0.09694192, 0.09913357, -0.036602203, 0.11038047, 0.13236065, 0.12966877, -0.10163848, 0.0030612876, -0.116145626, 0.045318183, 0.11492169) * go_1(1.0, 1.0);
result += vec4(0.05657016, -0.04848861, 0.10297782, -0.0076417355);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_1_tf
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.088122115, -0.009916053, -0.124469265, -0.032139737, -0.13709281, 0.09177288, -0.06794775, -0.03962873, 0.17613642, -0.11064388, 0.2531882, -0.3817648, -0.1172188, -0.042132895, -0.098772734, -0.114560865) * go_0(-1.0, -1.0);
result += mat4(0.18876404, -0.14613804, 0.23869626, 0.06580185, -0.13533239, 0.25754455, -0.29734856, 0.028218834, -0.39304733, -0.14716247, 0.19408274, -0.18518063, -0.31482637, -0.1508887, -0.3841371, 0.021975968) * go_0(-1.0, 0.0);
result += mat4(-0.22316615, -0.0923483, 0.16932568, -0.13138154, 0.139829, 0.010975908, -0.0587337, -0.054484393, -0.13758336, -0.030077504, -0.050642505, -0.14933856, -0.040563874, -0.030220931, 0.2867556, 0.17022403) * go_0(-1.0, 1.0);
result += mat4(0.12611523, -0.07087836, 0.08281469, 0.024588918, -0.023549056, -0.13102995, 0.17571726, 0.0740372, -0.3167631, 0.17491543, 0.4459055, -0.4687942, -0.19755729, 0.03723031, -0.06757113, 0.03502462) * go_0(0.0, -1.0);
result += mat4(0.12098187, 0.12341856, -0.061940372, 0.7251308, 0.055730965, -0.5169302, 0.37959704, -0.08753306, -0.45700142, -0.4960699, 0.1690022, -0.40233734, 0.0262836, -0.13345262, 0.11002605, -0.16773209) * go_0(0.0, 0.0);
result += mat4(-0.20428565, 0.117523015, -0.044863444, -0.1770644, 0.22925, 0.029694336, -0.23891294, 0.039587863, -0.11235541, -0.23890465, 0.037618574, -0.039127905, 0.14058869, 0.020599412, -0.074353516, 0.12343045) * go_0(0.0, 1.0);
result += mat4(-0.04680316, -0.049870726, 0.06975308, -0.21486782, -0.08177838, 0.09760846, -0.031408366, 0.13881667, -0.14650045, 0.29182404, -0.080848776, -0.25525567, 0.018876432, -0.015662232, -0.016014043, -0.08435915) * go_0(1.0, -1.0);
result += mat4(-0.100864016, -0.24647528, -0.007994345, 0.13047779, 0.14746517, 0.25517163, 0.054900885, -0.07251866, -0.29500577, -0.03758923, 0.05514366, -0.058372885, -0.03055354, -0.062586576, 0.017739896, 0.08644674) * go_0(1.0, 0.0);
result += mat4(-0.029502464, -0.08905223, 0.0047584837, -0.09646073, 0.044714086, 0.15522493, -0.070930906, -0.026954453, 0.057949875, -0.017211404, -0.00566463, -0.0050975676, 0.0050182147, -0.010722001, 0.011812942, -0.04698445) * go_0(1.0, 1.0);
result += mat4(0.18863353, -0.11575336, 0.26541254, -0.15280409, 0.14376, -0.05783716, 0.08554402, 0.27605456, 0.004611954, 0.074173525, -0.07963756, 0.082979314, 0.099553905, -0.06539344, 0.4330784, 0.07996894) * go_1(-1.0, -1.0);
result += mat4(-0.31001288, 0.035875235, 0.049856357, -0.09614268, 0.23397788, -0.12425775, 0.45108303, 0.27973723, 0.0753222, 0.11388394, -0.043821793, -0.05610102, -0.06536777, 0.009822641, 0.7956708, -0.05798737) * go_1(-1.0, 0.0);
result += mat4(0.19827974, 0.010130333, -0.13153136, 0.11593003, -0.15762039, -0.0040722084, 0.20404483, 0.28999883, 0.08152756, 0.07773477, 0.019730574, 0.0123460535, -0.034676805, -0.19133334, 0.01860159, -0.12945038) * go_1(-1.0, 1.0);
result += mat4(-0.1861255, 0.039945368, 0.28345293, -0.17425321, 0.36748698, 0.03729066, -0.35957313, 0.11234573, -0.07122196, 0.012845119, -0.09049443, 0.10106711, 0.07425845, -0.14626606, 0.46169114, -0.2652126) * go_1(0.0, -1.0);
result += mat4(-0.6222811, -0.08538015, 0.023319554, -0.7206892, -0.33495513, 0.2960924, -0.033479776, -0.29255456, 0.29802337, 0.47570458, 0.012769826, 0.19874385, -0.25652033, -0.4018595, 0.3055839, 0.1881051) * go_1(0.0, 0.0);
result += mat4(0.32152474, 0.0024920676, -0.113435976, 0.14440896, -0.287736, 0.0251382, 0.28160754, 0.02769615, 0.067614004, 0.0832741, -0.095353454, -0.19792004, -0.40394694, -0.24224225, 0.3224996, 0.030300485) * go_1(0.0, 1.0);
result += mat4(0.0045148246, -0.04621849, -0.026470715, 0.0588576, 0.14559188, 0.037437905, -0.13778603, 0.08173416, 0.033548757, -0.015654223, 0.15016593, 0.07761835, -0.016546778, 0.02640291, 0.112717085, 0.020371364) * go_1(1.0, -1.0);
result += mat4(0.114227325, -0.0405595, -0.11662477, 0.014747093, 0.11170598, 0.58740836, -0.27560827, -0.1848705, -0.02491223, -0.15605451, 0.0028677192, -0.11290364, -0.12331832, -0.3191161, 0.3505101, 0.15236251) * go_1(1.0, 0.0);
result += mat4(0.21131381, 0.13965495, -0.103683874, 0.26049778, -0.010777816, 0.015093082, 0.13207617, 0.14064828, -0.007847294, 0.025702007, -0.082993574, -0.04923462, -0.052900862, -0.006775377, 0.1432969, 0.09598549) * go_1(1.0, 1.0);
result += vec4(0.08015534, 0.10264796, -0.031173404, 0.21282151);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_2_tf
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.13262276, 0.18682314, -0.1433667, -0.0061677806, -0.15057871, -0.15389217, 0.40721065, -0.082456455, 0.028175479, -0.06136406, 0.13517159, -0.0066659097, -0.03311807, -0.056219388, 0.066265404, -0.017012158) * go_0(-1.0, -1.0);
result += mat4(0.0652481, -0.02717338, -0.17586891, -0.1458622, 0.37166637, -0.13651049, -0.095090784, 0.1450258, -0.08856753, -0.029000161, -0.11024598, 0.14231622, 0.027118085, 0.060637098, -0.028174674, 0.020973917) * go_0(-1.0, 0.0);
result += mat4(0.17137158, 0.015818363, -0.1761587, -0.07798954, -0.22039492, -0.08250406, 0.15350278, 0.05466543, 0.07231244, 0.124937475, -0.14530692, -0.036220204, -0.20202135, 0.16154502, -0.1472417, 0.045183204) * go_0(-1.0, 1.0);
result += mat4(-0.06751513, 0.3630837, -0.23374555, -0.17641832, 0.23866339, -0.12625019, 0.14955078, 0.3757683, 0.25546572, -0.0009440543, -0.029705383, 0.12500505, 0.039303612, 0.02745342, 0.06280759, -0.027673393) * go_0(0.0, -1.0);
result += mat4(-0.40253955, 0.5532656, 0.15580782, 0.23305601, 0.04307387, -0.37548792, 0.021682428, -0.14554474, -0.44655007, 0.12335231, 0.22693188, -0.19185324, -0.39905196, -0.36661598, 0.34626722, 0.3220371) * go_0(0.0, 0.0);
result += mat4(0.13051705, -0.051269528, 0.027860573, 0.12866034, 0.095374286, 0.0072371624, 0.06641015, -0.040609945, 0.14411138, 0.03813084, 0.024812538, -0.069997884, -0.2398024, 0.16384888, 0.004522481, -0.2734798) * go_0(0.0, 1.0);
result += mat4(-0.048976544, 0.36923414, -0.23769425, -0.02964149, 0.13426293, -0.070416726, -0.036279447, 0.21007125, -0.0062456504, 0.12307804, -0.18920022, 0.016429992, 0.091225415, -0.00714184, -0.079064, 0.050525308) * go_0(1.0, -1.0);
result += mat4(0.007005748, -0.1929285, -0.27960134, -0.014070343, -0.012031938, -0.21320626, 0.22591045, 0.06750757, 0.038049847, -0.08933499, 0.15640227, 0.36653376, 0.11274315, 0.0015512784, -0.14319079, -0.41117874) * go_0(1.0, 0.0);
result += mat4(0.039254356, 0.04123307, -0.14476523, 0.19676228, -0.1746638, 0.068685316, 0.19318552, -0.007086376, -0.08810745, 0.041937724, 0.1393943, 0.27539206, -0.08331265, 0.043064818, -0.02783017, -0.13006629) * go_0(1.0, 1.0);
result += mat4(0.18761271, -0.009960496, -0.18572417, 0.010640895, 0.10240658, 0.036137953, -0.109363064, 0.05820501, -0.04092678, 0.11809751, -0.11843415, 0.11893309, -0.06356792, 0.1509876, -0.12252014, -0.0070098704) * go_1(-1.0, -1.0);
result += mat4(-0.0012312894, 0.038436964, -0.046054237, 0.04859312, -0.4190657, 0.2529927, 0.23133701, -0.00065297337, -0.039581586, 0.00905735, 0.16532114, -0.12568031, 0.17818217, -0.28053075, 0.38509414, -0.03763847) * go_1(-1.0, 0.0);
result += mat4(-0.0897875, 0.063593514, 0.07660054, 0.12268424, 0.21554653, -0.1025501, 0.2557211, 0.04492533, 0.10992355, -0.035215836, -0.009733763, -0.02165148, 0.08618596, -0.19276536, 0.18174514, -0.18021213) * go_1(-1.0, 1.0);
result += mat4(-0.002999377, -0.12630916, -0.030010369, -0.2676409, -0.20229307, 0.15253967, -0.12200155, -0.1552754, -0.16193017, 0.10819683, 0.10696224, -0.1920264, -0.29354608, -0.32021165, 0.08644405, -0.16153689) * go_1(0.0, -1.0);
result += mat4(0.49931613, -0.3669461, -0.49107462, -0.3654748, 0.32047966, 0.03246311, -0.06424334, 0.009108802, 0.2367612, -0.46587244, 0.16957493, 0.3237888, 0.93676794, 0.01834384, -0.9349752, -0.04654371) * go_1(0.0, 0.0);
result += mat4(-0.112562165, 0.006074484, -0.12288025, -0.08560263, 0.25336134, 0.025205871, 0.25063732, 0.12370882, -0.40429187, 0.12992847, -0.2816234, 0.08179623, 0.27197668, 0.066299304, -0.12988937, 0.16257611) * go_1(0.0, 1.0);
result += mat4(0.047864527, -0.05821779, -0.06311128, -0.0065775234, -0.065763995, 0.014864688, 0.09148591, -0.25059348, 0.008846306, -0.22123712, 0.4062609, -0.100248575, -0.50293785, -0.13373566, 0.21480446, -0.0841981) * go_1(1.0, -1.0);
result += mat4(0.19313097, -0.061253734, -0.1801314, -0.20178059, -0.039574936, 0.08167749, 0.010974997, 0.069656976, -0.13193963, 0.35555324, 0.62686867, -0.28656846, -0.27831817, -0.0040086447, 0.4031064, 0.47767937) * go_1(1.0, 0.0);
result += mat4(0.15396428, 0.069321476, -0.15190981, -0.24133344, 0.106151104, -0.11271092, 0.06878746, 0.14279713, -0.02006402, -0.36284852, -0.00926688, -0.39887694, -0.20926239, -0.021860912, 0.07588468, 0.2620174) * go_1(1.0, 1.0);
result += vec4(-0.0073282495, -0.040352557, -0.063710704, 0.07255652);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_3_tf
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.17903937, -0.0014294779, 0.1824805, -0.19555633, -0.0052551827, -0.013796094, 0.06358042, 0.13301018, 0.008874768, 0.06605332, 0.06117636, 0.012946474, 0.048656575, 0.0060409275, -0.0671362, -0.06897735) * go_0(-1.0, -1.0);
result += mat4(-0.16098012, 0.10772552, -0.13175552, -0.5299018, 0.068713695, -0.048258893, -0.49698257, 0.36581638, 0.21755004, -0.12125899, -0.27382872, -0.12268086, 0.014334542, 0.20573758, 0.45879167, -0.29648975) * go_0(-1.0, 0.0);
result += mat4(0.06860283, -0.18047708, 0.024707617, 0.11900479, 0.09474589, -0.16559775, -0.054095846, -0.011377782, -0.008733984, 0.105020404, -0.040116277, -0.0022003972, 0.1453799, -0.032110006, -0.018741792, -0.12511599) * go_0(-1.0, 1.0);
result += mat4(0.20024729, -0.01969923, -0.026999667, -0.39064395, -0.14559332, -0.11634086, -0.13226044, 0.11779975, -0.08838282, -0.0882447, -0.23166943, -0.15760234, 0.030928904, -0.032423917, 0.20324136, -0.19692755) * go_0(0.0, -1.0);
result += mat4(0.49499384, 0.7327846, -0.6173799, -0.53821295, -0.15000962, 0.11169762, 0.6942423, 0.07956513, 0.06913002, -0.19037646, -0.19826908, 0.68080276, -0.2747096, -0.15832238, 0.47366706, 0.090432756) * go_0(0.0, 0.0);
result += mat4(-0.18274948, 0.09204629, 0.16644076, 0.05641037, 0.03328184, -0.6218293, 0.26432592, -0.093742386, 0.33038342, -0.24853565, -0.23683667, -0.37430722, -0.20684583, -0.32283148, -0.07633969, -0.08765815) * go_0(0.0, 1.0);
result += mat4(0.06821987, 0.06395764, -0.14685121, -0.15894371, -0.093540885, 0.057568345, -0.048376244, -0.009256543, -0.26325077, -0.03193119, -0.16857445, -0.02404981, 0.110593356, 0.042911418, 0.06626762, -0.0312436) * go_0(1.0, -1.0);
result += mat4(0.3108626, 0.37123847, -0.082249805, -0.21339422, -0.3756041, -0.08518717, -0.16853802, 0.011641729, -0.30096757, 0.26942274, -0.08990497, -0.19451031, 0.21974437, -0.04231723, 0.26160353, -0.040834647) * go_0(1.0, 0.0);
result += mat4(0.11795158, 0.24436565, 0.043223023, -0.0159957, -0.19689156, 0.13223267, -0.013983249, 0.09437164, -0.47648698, -0.00082660443, -0.085406005, 0.10885898, 0.104696035, -0.053257108, 0.024389362, 0.0282572) * go_0(1.0, 1.0);
result += mat4(0.032890156, 0.0115719065, -0.01898909, -0.03034875, -0.041037276, -0.1026382, 0.03337663, 0.20108728, -0.00023235095, -0.018033072, -0.028535927, 0.07359915, 0.075182244, 0.02959868, 0.15107772, -0.09815672) * go_1(-1.0, -1.0);
result += mat4(-0.004040557, 0.06707476, 0.039022792, 0.52437925, -0.08027356, 0.040488366, 0.035332825, 0.07683081, -0.03521227, -0.081861034, 0.090804815, 0.10580108, 0.20452882, -0.58755285, 0.04303056, 0.41562977) * go_1(-1.0, 0.0);
result += mat4(0.09290062, 0.03495193, 0.02347216, -0.012873525, -0.076936446, 0.1453216, -0.03742271, -0.14174925, -0.058219753, 0.19095406, 0.055627216, 0.09437343, -0.010424211, -0.314692, 0.3314579, -0.053285643) * go_1(-1.0, 1.0);
result += mat4(-0.053961687, 0.1483992, 0.042458896, -0.1966439, 0.13864957, 0.07587672, -0.06519269, 0.09530391, 0.04215073, 0.039545458, 0.21056756, 0.09972659, 0.02987125, -0.08102741, 0.07075036, 0.21867757) * go_1(0.0, -1.0);
result += mat4(-0.5512795, 0.03104814, 0.27901977, 0.122875504, -0.2656715, 0.007895486, -0.6735937, 0.20810314, -0.31432617, 0.07420857, 0.2573659, -0.35361463, 0.19826569, -0.47774056, 0.15816487, -0.29203883) * go_1(0.0, 0.0);
result += mat4(0.35078493, -0.07371588, -0.026663188, -0.20976657, -0.009644347, 0.037428845, -0.33933878, -0.010807704, 0.088060796, 0.16753472, -0.12296045, 0.17563403, 0.1501952, 0.07353703, 0.32531765, 0.11667607) * go_1(0.0, 1.0);
result += mat4(0.096126616, -0.058021486, -0.03439203, 0.06868024, 0.047914367, 0.026945053, 0.04207778, 0.046023168, 0.16024022, 0.07846185, 0.004195093, 0.07272046, -0.10458233, -0.0904536, 0.16049337, 0.015941419) * go_1(1.0, -1.0);
result += mat4(0.032256138, -0.055398785, 0.079738356, 0.113359064, 0.11975066, -0.074372105, 0.102006756, -0.011490042, 0.15155345, 0.0025528704, 0.23328577, -0.059241068, -0.067783386, -0.18220833, 0.0057692174, 0.039900843) * go_1(1.0, 0.0);
result += mat4(-0.06173998, -0.07121991, -0.01118306, -0.063749574, -0.032665797, 0.0014987896, 0.03113169, 0.06916617, 0.0066490914, -0.052818965, -0.050131317, 0.10337558, -0.030870482, -0.14671221, 0.12152145, -0.05003445) * go_1(1.0, 1.0);
result += vec4(-0.010524109, -0.008519857, -0.08958723, -0.07917139);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_4_tf
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.11029161, 0.027180295, -0.115622066, 0.16493714, 0.29633296, -0.11739625, -0.36390316, 0.15221693, -0.009233659, -0.062213745, -0.07184558, 0.07418268, -0.05182182, 0.0066014086, -0.006811494, -0.010030367) * go_0(-1.0, -1.0);
result += mat4(-0.18361749, 0.08565693, 0.24127418, -0.20478591, 0.6198113, -0.17994536, -0.011840256, 0.120292775, 0.2873902, -0.019704796, -0.062267166, 0.0104749305, -0.048370067, -0.028105626, 0.11494511, -0.15941763) * go_0(-1.0, 0.0);
result += mat4(-0.08084502, 0.10195475, -0.03200553, 0.032734055, 0.030348243, -0.028927604, 0.045914374, 0.029237835, 0.07756032, -0.06346545, -0.290196, 0.057043966, 0.13982558, -0.12195619, -0.15895663, -0.10097537) * go_0(-1.0, 1.0);
result += mat4(-0.12018707, -0.320156, -0.4089669, 0.26015735, 0.59622765, -0.05654362, 0.28581724, 0.32069868, -0.0013007161, -0.060870633, -0.2732852, 0.2357145, 0.2137239, 0.0110256495, -0.069258444, 0.113870576) * go_0(0.0, -1.0);
result += mat4(0.54700065, -0.072552234, 0.27267826, -0.26660076, 0.7043544, 0.18192886, 0.80024594, 0.2447395, -0.3289639, -0.2681839, 0.063631415, -1.0118654, 0.45691678, 0.42904988, -0.2301862, -0.6652257) * go_0(0.0, 0.0);
result += mat4(0.19215634, 0.030154131, 0.07679603, 0.50318545, 0.056434657, 0.028623195, -0.14471184, -0.13905096, -0.03254216, -0.1191584, -0.18907212, 0.49208716, 0.5069476, -0.1490824, -0.104480386, -0.06595394) * go_0(0.0, 1.0);
result += mat4(-0.08893682, 0.13113782, 0.023672188, 0.013086517, -0.25986442, 0.038162243, -0.10951209, -0.2027832, -0.013547809, -0.029482972, -0.17670235, 0.13529542, -0.0621569, -0.0979757, -0.10714689, -0.08474307) * go_0(1.0, -1.0);
result += mat4(-0.032828752, 0.00037559783, 0.023968933, -0.047482926, -0.20302027, 0.08830911, -0.20885307, -0.11137413, 0.16585048, -0.076796696, -0.030462325, -0.2020944, 0.048723634, -0.45607433, -0.29950324, -0.5867916) * go_0(1.0, 0.0);
result += mat4(0.008863689, 0.061761267, -0.039097138, 0.24465923, -0.05917457, -0.21383028, -0.085846715, -0.14150433, 0.0988731, -0.0160538, -0.045119412, 0.095252946, -0.057551738, 0.21348421, -0.03480491, -0.26071647) * go_0(1.0, 1.0);
result += mat4(-0.21351442, 0.10038809, 0.34001955, -0.100911774, 0.0208522, -0.028755441, 0.025793588, 0.013080005, 0.03849989, 0.13662058, 0.04311886, 0.17398632, -0.01397261, -0.016415505, -0.0070752064, 0.007656161) * go_1(-1.0, -1.0);
result += mat4(-0.280189, 0.09252764, -0.077729605, 0.12662902, -0.10433321, 0.03644144, -0.06625324, 0.05696802, 0.15468478, 0.08328583, 0.069849946, 0.061947342, -0.05560477, -0.0074776993, -0.15365681, -0.03526299) * go_1(-1.0, 0.0);
result += mat4(0.05886785, 0.15303846, 0.0066637015, -0.19983207, -0.07803175, -0.10772685, -0.12690999, -0.08275092, 0.033436153, 0.08424011, 0.17092863, 0.0043526487, 0.014620474, 0.044702258, 0.1686881, -0.016890949) * go_1(-1.0, 1.0);
result += mat4(0.1833738, 0.14381635, -0.025888365, -0.14182197, -0.25804865, 0.07216123, 0.025790794, 0.14096753, 0.023591481, 0.15610993, 0.026975863, 0.008755717, -0.13039349, -0.063048325, -0.121329494, -0.12331732) * go_1(0.0, -1.0);
result += mat4(0.0005065098, 0.44017914, 0.18493074, 0.13099027, -0.36087477, -0.37567857, -0.48981526, 0.5590752, -0.23918836, 0.19170256, 0.16816153, -0.29986876, -0.44738817, 0.018545123, 0.66217834, 0.31810755) * go_1(0.0, 0.0);
result += mat4(-0.16725904, 0.05753713, 0.058880586, -0.336765, 0.013667228, 0.056172702, 0.13465533, -0.07573556, -0.06313958, 0.06746643, 0.18878669, 0.09404202, -0.21780397, 0.12862128, -0.09476746, -0.34096682) * go_1(0.0, 1.0);
result += mat4(-0.07169524, 0.072302215, 0.052789338, -0.14035568, 0.078670934, -0.22246763, -0.0098074945, 0.024950746, 0.10949147, 0.06182366, 0.021721192, 0.12129548, 0.094007075, 0.06076156, 0.016474832, 0.08092115) * go_1(1.0, -1.0);
result += mat4(-0.10960447, 0.1878152, -0.029822018, 0.10598909, 0.1582181, 0.086522795, 0.093725055, 0.12908185, 0.23202112, -0.28859115, 0.26614165, 0.124523655, 0.19427507, 0.059677128, 0.003624697, 0.44220912) * go_1(1.0, 0.0);
result += mat4(-0.03620583, -0.102766834, 0.025527107, -0.11316131, -0.1507822, 0.0543862, -0.08225627, -0.06438472, 0.04580623, 0.6329729, 0.23854075, 0.35752076, 0.04363613, -0.12580468, -0.0006126687, -0.04995386) * go_1(1.0, 1.0);
result += vec4(0.060475674, -0.042036578, 0.06406282, 0.05569301);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x3x3x8
//!HOOK MAIN
//!BIND conv2d_5_tf
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.008187961, -0.08433309, -0.17281345, 0.1306418, -0.2925821, 0.20668334, 0.14854355, -0.15960559, 0.08599311, -0.096088655, -0.10121403, 0.067429096, 0.049158614, 0.036637552, 0.22137405, -0.17016457) * go_0(-1.0, -1.0);
result += mat4(0.024813082, 0.028489944, 0.06814137, -0.1245949, 0.54239255, 0.08648708, 0.28979865, 0.110916786, -0.1927179, -0.17756873, -0.1878214, 0.05795718, -0.080397904, 0.22125137, 0.1907366, -0.016493658) * go_0(-1.0, 0.0);
result += mat4(0.027259264, 0.01494357, 0.04852894, 0.037580628, 0.031005561, 0.003570554, -0.072993, -0.053475574, 0.031119492, -0.14140029, -0.10386501, -0.015266508, 0.01153506, 0.16006693, 0.088294305, 0.04201491) * go_0(-1.0, 1.0);
result += mat4(0.13026185, 0.097255, 0.49145448, -0.33619553, -0.21144676, 0.019042643, -0.27274492, 0.3033865, 0.14040698, -0.13656893, 0.28211337, -0.26930946, -0.1626638, 0.105105706, -0.50837296, 0.39536825) * go_0(0.0, -1.0);
result += mat4(-0.42495522, 0.14972518, 0.0007564128, 0.37367433, 0.44553527, 0.3338494, -0.26058698, 0.087256804, -0.4324135, 0.20706014, 0.33552194, -0.13375738, -0.13469471, 0.22374928, -0.36969653, -0.34171587) * go_0(0.0, 0.0);
result += mat4(0.26543954, -0.004381978, 0.10609993, -0.09718426, -0.15620759, -0.03287476, 0.093032375, 0.00028344034, -0.11699793, -0.016492033, 0.023340177, 0.0062737763, -0.14305823, -0.2721832, -0.160177, -0.06915171) * go_0(0.0, 1.0);
result += mat4(0.03334679, 0.12436332, -0.13226178, 0.13868971, 0.017779246, -0.012697869, -0.11553709, 0.08638636, 0.0955215, -0.0309646, 0.040856246, -0.03978358, 0.023490254, -0.07178907, 0.23794931, -0.1714287) * go_0(1.0, -1.0);
result += mat4(-0.11820261, 0.116130814, 0.58924234, -0.37785482, 0.016644944, -0.071019046, 0.0076222476, -0.024118654, -0.076183304, -0.14971451, 0.06356606, -0.07225465, -0.17400762, 0.030856986, 0.03957665, -0.0070553776) * go_0(1.0, 0.0);
result += mat4(0.10954708, 0.063694, -0.058218896, 0.0010372304, 0.032423936, 0.006164447, -0.031383317, 0.012955956, -0.17115591, 0.16328962, 0.07279567, 0.06571465, 0.005532307, 0.13575353, 0.04082173, 0.041579492) * go_0(1.0, 1.0);
result += mat4(0.03146011, -0.08227295, -0.03498218, 0.04772092, 0.12055223, -0.12383867, 0.05448358, -0.07948453, -0.019064998, -0.0964146, -0.024651276, 0.041473705, -0.06493721, -0.054806646, -0.21607941, 0.20078054) * go_1(-1.0, -1.0);
result += mat4(-0.25740683, -0.33160943, -0.37422308, 0.12679969, -0.032204475, 0.41485202, 0.4538808, -0.082535125, 0.11784846, 0.10195789, 0.064491615, -0.10170162, -0.09500746, -0.15640756, -0.079364255, -0.12576963) * go_1(-1.0, 0.0);
result += mat4(-0.02532797, -0.014487023, -0.09441118, -0.060885422, -0.41196415, -0.1359501, 0.07101173, -0.053279232, 0.010979353, 0.1914526, 0.054606825, 0.015926225, 0.10410896, 0.010272597, -0.048138764, -0.02698072) * go_1(-1.0, 1.0);
result += mat4(-0.35856235, 0.099759184, -0.11972965, -0.03850837, -0.5143867, 0.3721666, -0.100802526, 0.21814734, -0.11864143, 0.15086797, 0.047075786, -0.14188164, 0.48882273, -0.12767795, 0.4937544, -0.41288656) * go_1(0.0, -1.0);
result += mat4(0.21679138, 0.023770422, -0.5454043, 0.18567741, 0.15965948, -0.84900963, -0.4684333, -0.21884751, 0.5876668, -0.9346244, -0.30144307, 0.97177315, 0.24103107, 0.35953388, 0.2032729, 1.2934744) * go_1(0.0, 0.0);
result += mat4(-0.5121466, -0.123357795, 0.1833694, 0.048652876, -0.20895603, 0.0619325, 0.064119816, 0.072841786, -0.21813762, -0.126957, -0.23441431, -0.009071302, -0.09766064, -0.12546945, 0.086008705, -0.0072638122) * go_1(0.0, 1.0);
result += mat4(0.15312338, -0.051029235, 0.07638347, -0.14028431, 0.10694411, -0.14639509, 0.3193828, -0.22767228, -0.19987194, 0.18207504, -0.19648756, 0.24752761, -0.03402804, -0.04186147, -0.20177092, 0.09467012) * go_1(1.0, -1.0);
result += mat4(-0.3587345, -0.20358992, -0.11016057, 0.21079709, -0.26201126, 0.040362626, 0.3186598, -0.059521858, 0.27564716, -0.041431133, 0.19315968, -0.30228892, 0.01191173, -0.10380854, 0.03030344, 0.026699625) * go_1(1.0, 0.0);
result += mat4(0.13373446, -0.011457521, -0.24851708, 0.06563771, -0.051668253, 0.09096929, -0.013976447, 0.041433394, -0.046981215, -0.00015144625, 0.05696515, 0.024501698, 0.2714476, -0.017434085, 0.025333954, -0.054034695) * go_1(1.0, 1.0);
result += vec4(0.06757453, -0.021112159, -0.015639946, 0.05520713);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Conv-4x1x1x56
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_1_tf
//!BIND conv2d_2_tf
//!BIND conv2d_3_tf
//!BIND conv2d_4_tf
//!BIND conv2d_5_tf
//!BIND conv2d_6_tf
//!SAVE conv2d_last_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.03795613, 0.09572901, 0.019826923, 0.10568741, -0.0030050736, -0.018890928, 0.0095737, 0.00807826, -0.022741016, 0.0046556294, -0.017018225, -0.010523109, -0.017621946, -0.0006488902, -0.009405731, -0.0027796263) * g_0;
result += mat4(-0.046617493, -0.018167915, -0.039274286, -0.027566826, -0.015821747, 0.003789104, -0.0020801623, 0.004032968, -0.05708595, -0.018440764, -0.032891296, 0.004184342, 0.047413353, 0.0034510887, 0.019148773, -0.0035636695) * g_1;
result += mat4(-0.046619494, -0.017274255, -0.03372405, -0.011152855, 0.10981248, 0.036214054, 0.07969624, 0.05590572, -0.031791378, -0.00307391, -0.0032425344, 0.0025762853, 0.0053703627, -0.02076939, -0.00058634114, -0.012593452) * g_2;
result += mat4(0.110471316, 0.031102506, 0.07860556, -0.018570926, -0.05038586, -0.07667239, -0.0819002, -0.08958284, 0.03846167, -0.007570915, 0.008598097, -0.0082979705, -0.03610172, -0.022735123, 0.02343143, 0.030037913) * g_3;
result += mat4(-0.075562544, -0.020187575, -0.020969959, 0.0062222136, 0.019780673, 0.059694994, 0.019240001, 0.05951303, 0.004168261, 0.00041100322, -0.0013793377, 0.002048099, -0.040564027, -0.031818517, -0.015498987, -0.02695407) * g_4;
result += mat4(-0.0016428401, 0.018965026, -0.013192817, -0.008289604, -0.044686675, -0.009061507, -0.049217258, -0.043777503, -0.07308355, -0.063734084, 0.019393511, -0.028853234, 0.057311818, 0.04126226, 0.086301416, 0.11784249) * g_5;
result += mat4(-0.06087458, 0.046508487, -0.10723279, 0.017619802, 0.13637137, 0.2054238, 0.013641375, 0.091581754, 0.03556439, 0.0500333, 0.0696777, 0.0922045, -0.020914901, -0.025425691, -0.050319638, -0.049094327) * g_6;
result += mat4(0.0030941095, -0.008679898, -0.05815756, -0.038728733, -0.062450465, -0.073838525, -0.030359933, -0.08355475, -0.039032117, -0.0689333, -0.04834296, -0.079471886, 0.09694701, 0.17491414, 0.093450785, 0.16742545) * g_7;
result += mat4(0.035618782, -0.027659958, 0.055540156, 0.013073733, 0.12144545, 0.05981087, -0.015131131, -0.0476281, -0.090847984, 0.005347584, 0.015588529, 0.024184622, -0.10743599, -0.01785147, -0.08566232, -0.14611128) * g_8;
result += mat4(-0.03812077, 0.018126076, -0.016625525, -0.06906415, -0.06267368, -0.058914356, 0.0009385371, -0.026746314, 0.048242237, 0.028906677, -0.028120263, -0.004209134, 0.009636235, 0.013206963, 0.07449269, 0.038961377) * g_9;
result += mat4(-0.014510558, -0.021065345, 0.09356215, -0.005815953, 0.08807958, 0.067895725, 0.08723713, 0.057831496, -0.10227873, -0.07699344, -0.06321843, -0.07448854, 0.09820774, 0.007563063, -0.14045772, -0.014161681) * g_10;
result += mat4(-0.18385889, 0.2255883, -0.29741547, 0.14618248, -0.08100661, -0.06860545, -0.112705804, -0.122642964, -0.06736901, 0.06971933, 0.12909706, -0.0418256, -0.32786265, 0.032497127, 0.4390302, 0.032726523) * g_11;
result += mat4(0.10560793, 0.083280005, -0.20369564, -0.14290833, -0.119196005, -0.028741803, 0.020456403, -0.06509816, 0.073811695, 0.02724128, -0.08691891, 0.10240907, 0.16827166, -0.17502932, -0.18295282, 0.15154512) * g_12;
result += mat4(0.0036247042, -0.002368346, 0.049646147, 0.058079436, 0.14403848, 0.07125248, 0.040327612, -0.013934329, 0.03871744, -0.1717596, 0.20666012, -0.24093682, -0.09846371, 0.011563227, 0.11973811, -0.0574434) * g_13;
result += vec4(0.022095086, 0.021079032, 0.030224537, 0.02154015);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(M)-Depth-to-Space
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_last_tf
//!SAVE MAIN
//!WIDTH conv2d_last_tf.w 2 *
//!HEIGHT conv2d_last_tf.h 2 *
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
vec4 hook() {
vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);
ivec2 i0 = ivec2(f0 * vec2(2.0));
float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];
float c1 = c0;
float c2 = c1;
float c3 = c2;
return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,969 @@
// MIT License
// Copyright (c) 2019-2021 bloc97
// All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(0.28296316, -0.020139743, 0.1038232, 0.09352482, -0.16964972, 0.07910997, -0.049914766, -0.10661066, -0.121037185, -0.029087039, -0.02511847, -0.078911744, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(-0.3927183, 0.01805193, -0.031168332, -0.13300525, 0.20814548, 0.118818566, 0.1655351, 0.095023684, 0.17600809, -0.03928444, -0.014350658, 0.08458312, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.079089314, -0.0421829, 0.05452305, -0.22055493, 0.013279097, -0.12875281, 0.02452735, -0.101503745, -0.085946664, 0.05539176, 0.022408713, 0.14837204, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(-0.102643915, -0.011254746, 0.1478563, 0.1030208, 0.12396588, 0.0016621432, 0.2551224, -0.10399001, -0.01068436, 0.07155532, -0.104522154, 0.026937222, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.8789423, 0.35707328, -0.29964274, -0.064913996, 0.4962815, 0.26001287, -0.9511284, 0.49574667, 0.39539725, 0.16308042, 0.119878456, -0.30259115, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.08852938, -0.32612664, -0.006712046, 0.28693515, 0.06320871, -0.3322611, 0.04651086, -0.11020996, 0.01821082, -0.22851005, -0.07803438, 0.021527015, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(0.12295851, -0.011285535, 0.015859747, 0.04005441, -0.018136669, 0.03171969, -0.0406123, -0.10731229, -0.12117574, 0.005033036, 0.047838476, 0.026843475, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(0.4655988, 0.05519082, 0.039515793, 0.28410903, -0.36144528, 0.13039446, 0.11338478, -0.2141387, -0.10026682, -0.07903024, -0.09410254, 0.043833878, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(0.110124744, -0.024725702, 0.028102143, -0.09493807, -0.06455328, -0.15164614, 0.04425987, 0.15483347, -0.045039337, 0.07210396, -0.005390788, -0.03832707, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.007907974, -0.035503313, 0.057224784, -0.19763541);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x3
//!HOOK MAIN
//!BIND MAIN
//!SAVE conv2d_tf1
//!WIDTH MAIN.w
//!HEIGHT MAIN.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
vec4 hook() {
vec4 result = mat4(-0.012326053, 0.050769784, 0.1278702, -0.100782245, 0.14329414, -0.054558773, 0.023473471, 0.056829426, 0.048292916, 0.0046510273, -0.11478287, 0.0011030561, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);
result += mat4(0.29542983, -0.55061895, -0.068554066, 0.1433222, -0.072878316, 0.30201668, -0.2223378, -0.06704077, 0.16955832, 0.3279914, 0.17619601, -0.1276919, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);
result += mat4(0.09623417, 0.30559412, 0.094622105, -0.076706685, 0.07943858, -0.084815115, 0.12472551, 0.079850115, -0.13044213, -0.21300878, -0.095747225, 0.13412355, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);
result += mat4(0.21291664, 0.17195296, -0.20080926, 0.1064855, 0.10228669, -0.09580175, -0.11217631, -0.09740562, -0.0033135475, -0.053094357, 0.2983595, 0.035281878, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);
result += mat4(-0.08955812, -0.45707774, -0.4606922, -0.5754473, -0.11395895, 0.33530128, 0.29705846, -0.18877256, -0.43502945, 0.114171304, -0.3750776, -0.081597246, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);
result += mat4(-0.26109028, 0.02662961, -0.10441071, 0.11199392, -0.12038989, -0.09642296, -0.061320662, -0.33058178, 0.20212512, 0.00840794, 0.14357455, -0.038080238, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);
result += mat4(-0.09533881, -0.13644339, 0.068756215, 0.079305276, -0.053370547, 0.19572955, 0.0682981, 0.14469264, 0.15582883, -0.057183057, -0.13919263, -0.016394936, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);
result += mat4(-0.041189935, 0.39878023, 0.028704925, 0.30194348, -0.04486593, -0.33899093, -0.103968106, 0.21802065, -0.077099144, -0.07389541, 0.18069103, 0.18894517, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);
result += mat4(-0.12399862, 0.19246885, 0.034825478, -0.0044787163, 0.13121822, -0.13573012, -0.030162754, 0.1899518, 0.102326415, -0.061512686, -0.005647928, -0.0937634, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);
result += vec4(0.019286277, -0.033644073, 0.08196311, 0.0054393094);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.04088509, -0.06585775, -0.3094732, 0.12059048, 0.041417453, -0.06144871, -0.06655134, 0.03308842, 0.09287731, 0.010969216, 0.10343026, -0.11185897, 0.05685865, -0.09490512, 0.040908635, 0.03501189) * go_0(-1.0, -1.0);
result += mat4(-0.04854754, -0.098667145, 0.67147833, -0.11299351, -0.022114437, -0.029202767, 0.014179382, 0.26027945, 0.22076549, -0.16490546, -0.0010764733, 0.08405975, 0.11849154, -0.19072372, -0.35719597, -0.059621073) * go_0(-1.0, 0.0);
result += mat4(0.079224996, 0.0669873, -0.1718969, -0.05002573, 0.044926763, -0.02904369, 0.017489236, 0.01144465, 0.059109706, 0.064998455, 0.14725484, -0.23879208, 0.039234288, -0.027365638, 0.26172164, -0.094598554) * go_0(-1.0, 1.0);
result += mat4(-0.07159218, -0.03181544, 0.113837324, -0.053089984, -0.098298974, -0.29500297, 0.1608509, -0.044355504, 0.050882854, -0.19417204, -0.080487266, -0.00879743, 0.0007914453, 0.16640955, 0.21786706, 0.16398212) * go_0(0.0, -1.0);
result += mat4(0.16324541, 0.19753313, -0.46597233, -0.2593132, -0.2781038, -0.21973547, -0.024623038, -0.16348499, 0.3628299, 0.044888914, 0.04054647, -0.63605887, 0.02099492, 0.060544077, -0.49359834, 0.36336297) * go_0(0.0, 0.0);
result += mat4(-0.16885692, 0.31907207, 0.020906802, 0.13290039, -0.037330728, -0.022859452, -0.020451576, -0.113437995, -0.085683934, 0.054102756, -0.044824492, 0.061346747, -0.038684413, 0.098444365, -0.06734984, -0.17084897) * go_0(0.0, 1.0);
result += mat4(-0.015821548, -0.119599186, 0.1614827, 0.08383641, -0.07933593, 0.12528986, -0.06300182, 0.09286327, -0.10199266, 0.02419403, 0.0028411683, -0.09028338, 0.07962534, -0.08676035, -0.19237737, -0.115502626) * go_0(1.0, -1.0);
result += mat4(0.09471972, 0.21153462, -0.14393018, 0.055180002, 0.1817461, 0.016607309, -0.0771979, 0.11181317, -0.5491086, -0.102757886, -0.20952754, 0.022466583, -0.075119644, -0.14725658, 0.38451517, 0.12920731) * go_0(1.0, 0.0);
result += mat4(0.0867803, 0.114654355, 0.21199988, -0.15367955, -0.01803536, 0.056378633, 0.0018388306, 0.024613786, -0.13306658, 0.017211098, 0.073351346, -0.12064064, -0.10484361, -0.067748636, 0.033206712, -0.13061953) * go_0(1.0, 1.0);
result += mat4(-0.002236411, -0.022144757, -0.04586377, 0.101181075, -0.03511624, 0.08440529, 0.18544284, -0.22786349, -0.042184375, 0.015734851, -0.038622506, 0.038529944, -0.09170703, 0.034527462, -0.07817406, 0.10547265) * go_1(-1.0, -1.0);
result += mat4(-0.12135524, -0.07412039, -0.04979351, -0.082267545, 0.13343571, 0.29196215, -0.4364121, -0.10226428, 0.060835477, -0.23307934, -0.018231759, 0.15550235, 0.09095689, 0.18164484, 0.1322021, -0.022567045) * go_1(-1.0, 0.0);
result += mat4(-0.0054531163, -0.039762255, -0.030490747, 0.04779882, -0.15290286, 0.056712102, -0.0776974, 0.04114215, 0.15946816, -0.03882117, 0.16770308, -0.026126247, -0.027203865, -0.064107865, -0.13670811, 0.1556276) * go_1(-1.0, 1.0);
result += mat4(-0.092548385, -0.027285473, 0.084179096, 0.014961629, 0.2564254, 0.07626849, 0.28534448, 0.2588713, -0.018600503, -0.2433456, 0.041392803, -0.045712482, 0.26388907, -0.053502295, 0.14522223, 0.032808404) * go_1(0.0, -1.0);
result += mat4(-0.0013780193, 0.3482449, 0.071003586, -0.30707207, -0.05122194, -0.2833618, 0.07910779, 0.051078696, 0.021535402, 0.13021478, 0.022049015, -0.533547, 0.57265025, -0.12843914, -0.14913581, -0.1433724) * go_1(0.0, 0.0);
result += mat4(0.07382619, -0.12152924, 0.13364957, 0.181974, 0.15804219, -0.10126773, 0.3029618, -0.12874149, 0.13743348, -0.23245592, -0.20119278, 0.029547188, 0.042436857, 0.04213892, -0.07975374, 0.023821082) * go_1(0.0, 1.0);
result += mat4(0.022782229, -0.08359311, -0.060623147, 0.06565042, 0.09828792, 0.044808697, -0.28872305, -0.00092168007, 0.021737702, -0.08698349, 0.1950025, 0.07931995, 0.040952396, -0.07443172, -0.021157127, 0.0056698937) * go_1(1.0, -1.0);
result += mat4(-0.09995892, -0.2047294, 0.1414849, 0.062335726, -0.22492298, 0.05269799, -0.029233055, -0.050517935, -0.12534393, -0.12194023, -0.07035469, -0.070764475, 0.18903446, 0.07691209, 0.06153371, 0.011280912) * go_1(1.0, 0.0);
result += mat4(-0.036189888, -0.07586571, -0.05888163, 0.010425367, -0.028375402, -0.18870986, -0.19146784, 0.19274063, -0.18856238, 0.0064240266, -0.14537223, -0.06971656, 0.0852742, -0.04866623, -0.031686075, 0.031702038) * go_1(1.0, 1.0);
result += mat4(0.0618941, 0.100858234, 0.2628019, -0.048507668, -0.051001363, -0.03195978, 0.035452217, -0.001991919, -0.09649028, -0.047445696, -0.09221298, 0.07602656, -0.02382384, -0.119645916, 0.085616075, -0.07076033) * go_2(-1.0, -1.0);
result += mat4(0.019222878, -0.0491929, -0.4902266, 0.18501294, 0.014529614, -0.077125326, 0.011563931, -0.20236616, -0.101982154, -0.021150962, -0.07537948, -0.1540349, 0.028949164, -0.06827332, 0.0067634755, 0.09582376) * go_2(-1.0, 0.0);
result += mat4(-0.05995539, -0.031138182, 0.01334257, 0.06827176, -0.030762246, 0.006615233, -0.03562788, 0.016249394, -0.14797118, 0.014671043, -0.09325859, 0.25653747, -0.11474991, 0.05436232, 0.031051394, 0.04179694) * go_2(-1.0, 1.0);
result += mat4(0.032279838, -0.030521005, 0.0029688699, 0.005165139, 0.15907808, -0.20421815, -0.07713175, 0.067530625, -0.08619395, 0.026114263, 0.08821273, 0.011591694, 0.018677557, 0.057708874, -0.25859246, -0.18693781) * go_2(0.0, -1.0);
result += mat4(0.10823143, -0.31875235, -0.24394153, -0.0025489891, 0.016761065, -0.19857498, -0.07858479, -0.07811158, -0.38551694, -0.049090322, -0.050053325, 0.23398961, 0.014974165, 0.17498055, 0.29105362, -0.353647) * go_2(0.0, 0.0);
result += mat4(0.05621677, -0.19492444, 0.460332, 0.055917628, -0.06404381, -0.06684098, 0.053624872, 0.057300456, -0.019248677, -0.15110065, 0.032379635, -0.12673225, 0.0068658157, -0.13001235, -0.017716292, 0.064182095) * go_2(0.0, 1.0);
result += mat4(-0.06764552, 0.004707433, -0.13827331, -0.21957871, -0.03789028, -0.04962028, 0.022955444, -0.058468018, 0.13735814, -0.031270552, -0.018490225, 0.0063876202, -0.052979283, -0.030049473, -0.004811771, -0.0044099926) * go_2(1.0, -1.0);
result += mat4(-0.028652798, -0.027029367, 0.62600744, 0.0900562, 0.03869923, -0.20111556, 0.095930666, -0.13164565, 0.5562579, 0.011937122, 0.22882107, 0.030288015, 0.09856272, 0.04736032, -0.077492185, -0.10207275) * go_2(1.0, 0.0);
result += mat4(-0.10581002, -0.16504957, -0.5688921, 0.0414545, 0.04749444, -0.052849945, -0.011017121, -0.025284614, 0.14316759, -0.08547362, -0.09654446, 0.08682504, 0.050776027, 0.0678741, -0.04913651, 0.07527552) * go_2(1.0, 1.0);
result += mat4(0.04126091, 0.0048704315, 0.041699376, -0.05820725, -0.09664279, 0.07648305, -0.17979898, 0.11698985, -0.025436765, 0.023232851, 0.010656572, 0.08157569, 0.19584864, -0.022928072, 0.053339157, 0.0039929505) * go_3(-1.0, -1.0);
result += mat4(0.040733483, 0.12260473, 0.08071146, 0.07257762, -0.016945919, -0.31637576, -0.24281953, -0.0038469466, -0.10203634, 0.13631973, 0.06505259, -0.13119389, -0.09723076, -0.139551, -0.07504509, 0.08645985) * go_3(-1.0, 0.0);
result += mat4(0.017005404, 0.049066268, -0.007544932, -0.04884536, 0.09984347, -0.04447364, 0.4902235, -0.062780835, -0.18389583, 0.07305648, -0.22014385, 0.08004685, 0.0992568, -0.08569604, 0.093966395, -0.07047139) * go_3(-1.0, 1.0);
result += mat4(0.0017705248, 0.020553982, -0.09167042, 0.0036356782, -0.11867446, -0.07055574, 0.40252638, 0.09657129, 0.0888632, 0.1031708, -0.022127641, -0.023769693, -0.0861388, 0.13420185, -0.11774454, 0.038774434) * go_3(0.0, -1.0);
result += mat4(-0.15173717, -0.13590458, -0.0891863, 0.12289548, 0.13942605, 0.22152588, -0.19292432, 0.14169839, 0.010543665, 0.07648361, -0.057333756, 0.09535759, -0.053601623, -0.026824495, 0.09365424, 0.17476946) * go_3(0.0, 0.0);
result += mat4(-0.070416056, -0.061970036, -0.039723337, -0.18874651, -0.07098426, -0.019835865, -0.5612458, 0.060437083, -0.03774378, 0.18536821, 0.28587544, 0.035555754, 0.15771326, -0.13527197, 0.13342534, -0.06564073) * go_3(0.0, 1.0);
result += mat4(-0.10967661, 0.025388904, 0.09003177, -0.04087592, 0.09531671, -0.11809294, -0.41613623, 0.038198076, 0.01019813, -0.018864965, -0.18400626, -0.038704176, 0.0105671035, 0.024449013, -0.008989595, -0.027171193) * go_3(1.0, -1.0);
result += mat4(0.16193569, -0.21445285, -0.20130903, -0.13498883, -0.008031679, 0.050757203, 0.78938776, -0.03749514, 0.11998137, 0.19368882, 0.12328945, 0.0058578993, -0.13852382, -0.033867255, -0.018267661, 0.036348555) * go_3(1.0, 0.0);
result += mat4(-0.06254118, 0.087295115, 0.031116437, 0.0416281, 0.061828617, 0.34479564, -0.15537797, -0.17144552, 0.13989387, -0.13792284, 0.056215156, 0.12714528, -0.0198865, 0.04927947, 0.013614583, -0.041810013) * go_3(1.0, 1.0);
result += vec4(-0.044073943, 0.12072677, -0.0022342638, -0.24414532);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!SAVE conv2d_1_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.07115729, 0.01065505, 0.19167988, -0.02504489, -0.15064801, 0.079008736, 0.05437936, 0.027479589, -0.021383656, 0.032731537, -0.06657876, 0.022649521, -0.06501893, -0.02335689, 0.010445489, -0.05430297) * go_0(-1.0, -1.0);
result += mat4(-0.1178601, 0.07425715, 0.063272275, -0.18308601, -0.13955134, 0.005166404, -0.022591779, -0.016827974, -0.024990188, -0.13372071, -0.056342285, 0.12489847, 0.081861794, -0.07083351, 0.021897513, 0.0629395) * go_0(-1.0, 0.0);
result += mat4(0.051357627, -0.13874975, -0.09887168, -0.011908862, 0.03639772, -0.13195883, -0.05321156, 0.03913229, -0.08160194, -0.07128151, 0.043625016, 0.11966009, 0.03162217, 0.018834392, -0.0625129, 0.10726711) * go_0(-1.0, 1.0);
result += mat4(-0.15922394, -0.043482754, -0.22571066, 0.009280428, -0.3882705, 0.08418719, 0.15329506, -0.028419001, -0.011272379, 0.15897545, 0.041217074, -0.0143014155, 0.09451862, -0.056342427, -0.14568482, 0.05556279) * go_0(0.0, -1.0);
result += mat4(0.13879324, -0.23339099, -0.24573983, -0.09575104, 0.03823306, 0.4752516, -0.1696623, -0.18472373, -0.1510259, 0.23040254, 0.4196143, 0.3462817, 0.035172507, 0.18228662, 0.22475636, -0.19945027) * go_0(0.0, 0.0);
result += mat4(-0.08876766, 0.19567333, 0.25174314, -0.09637879, -0.007957943, 0.13510521, 0.030193076, -0.0018362573, -0.006884444, -0.41804117, -0.1026309, -0.053339038, -0.1283198, -0.03033918, 0.055674326, 0.094377995) * go_0(0.0, 1.0);
result += mat4(0.06780768, -0.07774435, -0.0616546, -0.046531744, -0.11723141, 0.10792474, 0.013314576, -0.031451598, -0.009870351, 0.10215877, -0.13101454, -0.19878799, -0.09712651, 0.10423937, 0.14170039, -0.03359521) * go_0(1.0, -1.0);
result += mat4(-0.020114673, -0.015194169, 0.03657608, 0.17162928, 0.070458665, -0.08041664, 0.14067306, 0.19699603, -0.28763783, -0.033556152, -0.6588468, -0.48221052, -0.123711474, -0.080758795, -0.3187303, 0.121004865) * go_0(1.0, 0.0);
result += mat4(-0.074900605, 0.09297913, -0.08621144, 0.116730206, -0.034766622, -0.10381484, 0.060793545, -0.014790814, -0.123858415, -0.0010626495, 0.20547503, -0.07206306, -0.17324795, 0.023932874, 0.017495958, -0.09924652) * go_0(1.0, 1.0);
result += mat4(-0.015568068, 0.005394868, 0.15463537, 0.06416607, -0.045670815, -0.013540727, -0.12960619, 0.0006581649, 0.09432853, 0.05575682, -0.022219105, 0.022416297, 0.0148129435, -0.067619696, 0.022989385, -0.09695771) * go_1(-1.0, -1.0);
result += mat4(-0.107209, 0.07072438, -0.10235772, -0.12078849, -0.02751833, -0.043195058, -0.17197154, 0.120612316, -0.17310137, -0.09429793, 0.06511165, 0.18072544, -0.21168593, 0.16383737, 0.25012484, -0.089589044) * go_1(-1.0, 0.0);
result += mat4(0.005439779, 0.0028433986, -0.09885586, -0.06572956, -0.0061691296, 0.15485546, -0.23724958, 0.004232802, 0.07794742, -0.012552598, 0.07554404, 0.10843201, -0.013223918, -0.08705092, -0.23228747, 0.03599732) * go_1(-1.0, 1.0);
result += mat4(-0.043396916, -0.10680695, -0.019935586, -0.06703658, -0.30075943, -0.010179525, 0.30197874, 0.04888297, 0.00779067, 0.22583807, 0.2039884, -0.0074303118, -0.19240093, -0.024718538, 0.057117213, 0.19431825) * go_1(0.0, -1.0);
result += mat4(-0.37633005, 0.043971814, -0.21423087, 0.118503235, -0.15058799, 0.115756795, -0.13719647, 0.020510519, 0.1123193, 0.14797291, 0.05467349, 0.2039607, -0.31973588, 0.1667847, -0.017739004, -0.11280262) * go_1(0.0, 0.0);
result += mat4(-0.0084394775, -0.1281101, -0.20841378, 0.01986435, -0.04122467, -0.21089631, -0.08062371, 0.11315133, 0.05693114, -0.23773515, 0.03792205, -0.008872407, 0.04554895, -0.10683658, 0.10683206, 0.06875721) * go_1(0.0, 1.0);
result += mat4(-0.103948504, -0.007483217, -0.12571928, 0.054868475, -0.030646881, -0.010098222, 0.019018777, -0.07072212, -0.10689893, 0.16498323, 0.048089568, -0.10912806, -0.027318537, -0.025491163, 0.012588013, 0.072701246) * go_1(1.0, -1.0);
result += mat4(0.14094622, -0.028118243, 0.016804086, -0.18000692, 0.33351874, 0.14980756, -0.07135749, -0.16573106, -0.17243773, 0.054617904, -0.2933543, -0.12602285, 0.08480712, -0.05704333, 0.22336398, 0.026583148) * go_1(1.0, 0.0);
result += mat4(0.046759557, -0.03100408, 0.40000245, -0.08521555, 0.19592628, -0.15150753, 0.25288078, -0.061794683, -0.047818147, -0.12249124, 0.020410215, -0.11503924, 0.046108168, 0.030459814, -0.14096366, 0.09120256) * go_1(1.0, 1.0);
result += mat4(-0.087491795, -0.024289595, -0.09060237, 0.020922959, 0.09557061, -0.08556962, -0.0503455, -0.010846053, 0.0030694185, -0.008256591, 0.08290225, -0.034981687, 0.07342003, -0.021816112, -0.13905519, -0.06265962) * go_2(-1.0, -1.0);
result += mat4(-0.08126147, -0.05866924, -0.015698025, 0.093630895, -0.02379264, 0.115918085, 0.19431724, 0.041815966, -0.051647816, 0.15277039, -0.03721037, -0.085520886, 0.041766718, 0.104392216, 0.0559556, 0.0049254233) * go_2(-1.0, 0.0);
result += mat4(-0.11176419, 0.112272635, 0.1367475, -0.010482275, -0.06719008, 0.064003386, -0.08132314, 0.015465676, 0.052741583, 0.06779717, 0.038533892, -0.16428822, 0.040990274, 0.002559234, 0.097567044, -0.058192518) * go_2(-1.0, 1.0);
result += mat4(0.17228632, 0.008296625, 0.009418271, 0.037103783, -0.0601486, 0.04531715, 0.19613501, 0.112170085, -0.02256726, -0.093685195, -0.1341531, -0.038480807, 0.109840475, 0.062418167, 0.15140085, 0.050787117) * go_2(0.0, -1.0);
result += mat4(0.15433665, 0.2104034, 0.12395812, 0.13799714, 0.14945604, 0.67457545, 0.27575177, -0.047493283, 0.24992993, -0.5305435, 0.0131732905, -0.36911693, 0.14442082, -0.18583177, -0.2861722, 0.19419897) * go_2(0.0, 0.0);
result += mat4(0.040242445, -0.13234852, 0.10056324, 0.055854917, 0.07447713, -0.023067042, 0.00021051937, -0.0495407, -0.22037992, 0.68047297, 0.05774606, -0.012461005, 0.104557075, 0.04832623, 0.010292581, -0.050617047) * go_2(0.0, 1.0);
result += mat4(-0.060079176, 0.086553656, 0.0060872175, -0.012576339, 0.025149338, -0.07379716, -0.18048704, -0.007130346, 0.007826557, -0.095655076, -0.0032888134, 0.21027069, -0.09868755, -0.1180311, 0.0081250835, -0.05078016) * go_2(1.0, -1.0);
result += mat4(0.19124818, -0.05949092, -0.36762074, -0.08203597, -0.10276991, 0.111005515, -0.2845309, 0.113985784, 0.07206471, -0.026585411, 0.20032002, 0.5691625, -0.0460136, 0.03874166, 0.09858682, -0.15913802) * go_2(1.0, 0.0);
result += mat4(-0.00397842, -0.014763085, 0.080231026, -0.09142265, 0.03637215, 0.064106315, -0.030963007, 0.0557953, 0.04173885, -0.024534896, -0.2092259, 0.06913638, 0.08103145, -0.0033994897, -0.10903093, 0.062850125) * go_2(1.0, 1.0);
result += mat4(0.01206918, 0.024855271, -0.051995132, 0.013999821, -0.021517826, 0.06216198, -0.050853133, -0.064136736, -0.047408275, -0.07858566, 0.074464396, -0.038218755, -0.13216262, 0.008905726, 0.10333, 0.03049554) * go_3(-1.0, -1.0);
result += mat4(-0.027152343, -0.069046065, -0.013017797, 0.0763, -0.08611993, -0.020867927, 0.012807627, -0.11971997, 0.025972975, 0.095127404, -0.070044935, -0.21399231, -0.22536097, -0.028828809, 0.123399965, -0.15967365) * go_3(-1.0, 0.0);
result += mat4(0.038314234, -0.014114242, 0.012115026, 0.05505015, 0.11785298, -0.08772618, 0.034408223, 0.09134674, -0.04727011, 0.020709611, -0.01780165, -0.14374214, -0.30412516, -0.011123043, -0.024216317, -0.007538433) * go_3(-1.0, 1.0);
result += mat4(-0.17673545, 0.077738725, 0.056153737, 0.028693894, 0.05688375, 0.021928595, 0.014585902, 0.019364892, 0.029056642, -0.2072201, -0.17548367, 0.085471265, 0.16439342, -0.0052957633, 0.22321554, -0.19246858) * go_3(0.0, -1.0);
result += mat4(0.1914782, -0.15620962, -0.16686897, -0.04141303, 0.07696967, -0.013115313, -0.057627093, -0.13849305, 0.08699377, -0.07339016, -0.053074118, -0.059418138, 0.19988623, -0.23852244, -0.12574267, -0.29139704) * go_3(0.0, 0.0);
result += mat4(-0.017691063, 0.18901291, 0.16250716, -0.11039392, 0.056900974, 0.036662772, -0.13399602, -0.11378214, -0.10924602, 0.2130181, -0.042094063, -0.012445028, 0.013168919, 0.119448364, -0.014406005, 0.0054324497) * go_3(0.0, 1.0);
result += mat4(0.11552786, 0.090796515, -0.11559005, -0.035706047, -0.044022456, -0.027642358, 0.08824298, 0.035067793, 0.18125483, -0.15502097, 0.094219126, 0.07493505, 0.022493582, 0.038250685, -0.076567575, -0.059311453) * go_3(1.0, -1.0);
result += mat4(-0.08612596, 0.016376335, -0.0023271537, 0.32511148, 0.03789289, 0.13106889, 0.22370385, 0.21145949, 0.1844514, -0.0766592, 0.093758754, 0.13821359, -0.062405586, 0.0028724174, -0.13588348, 0.00024406122) * go_3(1.0, 0.0);
result += mat4(-0.08991004, 0.074423954, -0.020964831, -0.070288494, -0.1192369, -0.015506713, -0.28136373, 0.042911243, 0.08215164, 0.11065419, -0.006201638, 0.057742044, 0.0014476188, -0.01443158, 0.22631277, -0.06787264) * go_3(1.0, 1.0);
result += vec4(-0.07235962, -0.019149294, 0.05072898, 0.03962245);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.14315613, -0.031299837, -0.011195234, 0.0073360316, 0.07264984, -0.110979274, 0.06560588, -0.040463638, 0.28964168, -0.05644335, -0.060729366, -0.15811591, 0.028339373, 0.027486937, 0.0360574, 0.05856459) * go_0(-1.0, -1.0);
result += mat4(0.16211128, 0.20672597, -0.30374205, -0.056202736, -0.10893948, 0.053066984, -0.18297112, 0.028844962, 0.22754766, -0.07141921, 0.07142953, -0.1357581, 0.008053467, 0.04668908, 0.17258649, 0.22506891) * go_0(-1.0, 0.0);
result += mat4(0.07014762, 0.032112304, 0.028849715, 0.09427007, 0.008323501, -0.085777245, 0.083501115, -0.16150802, 0.24127382, -0.1305689, -0.027557204, -0.15057805, 0.09748757, 0.08182083, -0.107643455, 0.020552907) * go_0(-1.0, 1.0);
result += mat4(-0.04630706, -0.056070503, 0.058440026, -0.005662525, 0.08736355, 0.08821088, -0.049539115, 0.08171937, 0.28466523, -0.025859421, -0.0026971614, -0.15181617, -0.022231927, 0.3566104, -0.024887348, 0.12051598) * go_0(0.0, -1.0);
result += mat4(-0.20976813, -0.23778942, 0.28854275, -0.27583683, -0.27604774, -0.15861328, 0.09581984, 0.06572128, 0.092306405, -0.06962751, -0.042226445, 0.035234913, 0.084891975, -0.03846841, -0.1473667, 0.2810354) * go_0(0.0, 0.0);
result += mat4(0.028011162, 0.08945262, 0.15859836, 0.18426442, 0.10649845, -0.0918649, -0.12257575, -0.00914911, 0.23701023, -0.030067213, -0.01938559, -0.11026175, -0.5953985, 0.28875506, -0.035278864, -0.05043055) * go_0(0.0, 1.0);
result += mat4(-0.14445779, -0.06907616, 0.13078876, -0.0089114, -0.110637166, -0.123719245, -0.094949, 0.046267383, 0.4727523, 0.0073195575, -0.014788787, -0.14922102, -0.021974785, -0.10706751, 0.00049029186, 0.09215345) * go_0(1.0, -1.0);
result += mat4(-0.20936993, -0.22377276, -0.07697398, 0.039161056, 0.044213686, 0.037542075, -0.06600642, 0.017124292, 0.3406197, 0.011907687, 0.019732054, -0.22745137, -0.22178015, 0.49051985, -0.03707166, 0.14849792) * go_0(1.0, 0.0);
result += mat4(0.07833466, 0.10888627, 0.16015877, -0.049263358, 0.29002127, -0.010949114, 0.013081097, -0.071674205, 0.3532135, 0.013165473, -0.05282189, -0.16688257, 0.009552089, -0.2740816, 0.04927233, -0.37047002) * go_0(1.0, 1.0);
result += mat4(0.23682123, -0.027914839, 0.02372468, -0.07127212, 0.053436097, 0.057737537, -0.008556659, -0.025973454, 0.06468388, 0.18805866, -0.08180048, 0.058999106, -0.3058321, -0.06642967, -0.092997625, 0.10527466) * go_1(-1.0, -1.0);
result += mat4(-0.1353085, -0.016593851, 0.21518163, -0.10272456, 0.14382689, 0.05056661, -0.27799338, 0.11351653, 0.05838342, 0.28104934, -0.03777824, 0.003435516, 0.057915565, -0.17574134, -0.24437475, 0.13420977) * go_1(-1.0, 0.0);
result += mat4(0.13400255, -0.056437124, 0.11310834, 0.040429913, 0.098928474, -0.020769242, -0.079605736, 0.0494632, 0.0660877, 0.098982334, -0.055884495, -0.046533633, 0.17815505, 0.027310565, -0.24176653, -0.025550256) * go_1(-1.0, 1.0);
result += mat4(0.03637618, -0.012618673, 0.11865397, 0.19804053, -0.03522831, 0.24310908, -0.056454524, -0.44885796, 0.02212509, -0.20253624, 0.038810212, -0.17396528, 0.08970355, 0.005103078, 0.061075203, 0.44292897) * go_1(0.0, -1.0);
result += mat4(-0.25074747, -0.0015575301, -0.685015, 0.07345307, -0.08419402, 0.06640714, 0.43799296, -0.17571151, 0.0049855476, 0.09024738, 0.055744022, 0.018739637, 0.34734032, 0.114896655, 0.0404696, -0.11327049) * go_1(0.0, 0.0);
result += mat4(-0.12284062, -0.31131, -0.14712588, -0.18645866, 0.17581487, 0.1357234, 0.09913364, 0.005298711, -0.056155153, 0.042429443, 0.039454732, -0.04111384, 0.2623163, 0.09701166, 0.022825675, 0.050480727) * go_1(0.0, 1.0);
result += mat4(0.058734808, 0.038528245, -0.042670116, -0.15190329, -0.028179986, -0.05362995, 0.017090468, -0.24449602, -0.08240927, -0.033122182, 0.009938243, -0.0052937623, 0.2171439, 0.06879817, -0.10361997, 0.018995138) * go_1(1.0, -1.0);
result += mat4(0.027555468, 0.016337285, 0.19074728, 0.26690376, -0.088713005, -0.0021182299, -0.23062791, -0.32101163, -0.0040022335, 0.16835448, 0.05424022, -0.02156396, 0.24163729, 0.10243619, -0.04331782, -0.014350939) * go_1(1.0, 0.0);
result += mat4(-0.13836963, 0.053369813, 0.036432605, 0.062288612, -0.06264361, -0.049093347, -0.0315955, -0.11237456, -0.064744405, -0.0151798045, 0.044210885, 0.010166375, -0.038355727, -0.05203739, -0.075036794, 0.1664177) * go_1(1.0, 1.0);
result += mat4(-0.08583114, 0.08268218, -0.05771351, 0.10195048, -0.10128163, 0.10874855, -0.02580701, 0.028834302, 0.1950179, -0.0130183315, 0.0092119705, -0.060479227, 0.117747106, 0.061403573, -0.0028475628, -0.032362986) * go_2(-1.0, -1.0);
result += mat4(-0.05310153, -0.061091065, 0.19438389, -0.10475873, 0.00045120303, -0.24876194, 0.017168125, -0.050173752, 0.012073283, 0.035660096, -0.017562328, -0.110271364, -0.015546384, 0.17965329, 0.10068208, -0.014481325) * go_2(-1.0, 0.0);
result += mat4(0.085558474, -0.0007109211, 0.20868625, 0.150163, -0.19283043, 0.025976779, 0.08384698, 0.031011146, 0.17268184, 0.008871077, -0.04097794, -0.12868725, 0.01336166, -0.038823843, 0.1703644, -0.067780636) * go_2(-1.0, 1.0);
result += mat4(0.06480841, -0.44256654, -0.19949587, -0.030677497, -0.27930573, -0.041867044, -0.15648738, 0.11573067, 0.28664824, 0.009770385, -0.058617204, -0.06607673, -0.038160402, 0.009497089, 0.03303058, -0.079379834) * go_2(0.0, -1.0);
result += mat4(0.17752203, 0.10979527, -0.058749028, -0.30194217, 0.30484176, -0.20980492, -0.05234784, -0.2590473, 0.23003183, 0.21903595, -0.024891363, -0.14337292, -0.02971356, -0.29613075, -0.045642294, 0.23826689) * go_2(0.0, 0.0);
result += mat4(0.018211683, -0.005840598, -0.19021381, -0.096696235, 0.39998052, -0.34746838, -0.039627917, 0.087701194, 0.15526368, -0.008095372, -0.044220537, -0.08634815, -0.121496454, -0.06792033, -0.14959472, 0.078917444) * go_2(0.0, 1.0);
result += mat4(0.33109078, 0.012287281, -0.034155898, -0.04840956, 0.068748444, 0.006142039, 0.06598935, 0.024775596, 0.22379673, 0.056089353, -0.006119644, -0.018509025, 0.10084137, 0.15556572, -0.041211523, -0.21550669) * go_2(1.0, -1.0);
result += mat4(-0.058160853, 0.08899222, -0.17401625, -0.1449813, -0.015872562, -0.03780256, 0.15702572, 0.34013954, 0.1580772, 0.074823864, 0.035488904, -0.01627819, -0.15551315, -0.3638866, -0.09833458, 0.15037175) * go_2(1.0, 0.0);
result += mat4(-0.12707977, -0.19947061, -0.11524648, 0.09216174, -0.07161296, 0.05675567, 0.06843247, 0.2803306, 0.25222927, -0.044076066, 0.053775772, -0.09939824, 0.16903089, 0.11475717, -0.07015584, -0.036021322) * go_2(1.0, 1.0);
result += mat4(-0.12290332, -0.05469477, 0.02696626, 0.051133692, -0.05541504, -0.2811521, -0.13008943, 0.031793896, -0.32529324, -0.01663752, -0.0658181, 0.17300756, 0.22281154, -0.11001508, 0.09578194, -0.055437982) * go_3(-1.0, -1.0);
result += mat4(0.083753526, -0.048933715, -0.13912897, 0.10929772, -0.1789828, -0.1586524, -0.10964165, -0.08210391, -0.11568187, -0.04813496, -0.2417861, 0.24446528, 0.13570863, -0.26869404, 0.3013413, 0.11678686) * go_3(-1.0, 0.0);
result += mat4(0.21105368, 0.15749952, -0.18983693, -0.023642758, -0.1633653, 0.10107988, 0.052329395, -0.080253236, 0.15375629, -0.045091413, 0.05070866, 0.12416106, 0.16600485, -0.10412354, 0.061849747, -0.084013924) * go_3(-1.0, 1.0);
result += mat4(0.03863923, 0.03690167, -0.053106382, -0.07523278, -0.04214836, 0.53898096, 0.15308584, 0.22835171, -0.24771535, 0.1402687, 0.1000896, -0.08719167, 0.0886567, 0.15255097, 0.14695966, -0.06659865) * go_3(0.0, -1.0);
result += mat4(0.110334344, -0.12696493, 0.24256139, 0.02536166, 0.08322421, 0.022147777, -0.35030407, 0.13734557, 0.053133942, 0.43650532, -0.30170345, 0.08751837, 0.012917502, 0.27496436, 0.11422729, 0.15508565) * go_3(0.0, 0.0);
result += mat4(0.16684863, 0.26743406, 0.15951683, 0.033597723, -0.044719726, 0.1127182, 0.007923161, 0.06415458, -0.07269362, -0.07828715, 0.09216738, 0.11528897, -0.13371283, -0.124177165, 0.14804523, 0.14156726) * go_3(0.0, 1.0);
result += mat4(-0.041141883, 0.023617791, 0.11484465, 0.13131519, -0.14753738, 0.17067687, -0.017538434, 0.24042644, -0.058103643, 0.3143255, 0.02476919, -0.0024666793, -0.26759955, -0.06099211, 0.006415725, 0.10394301) * go_3(1.0, -1.0);
result += mat4(-0.04198037, 0.03277123, -0.25069895, -0.21043587, -0.27417016, 0.08047665, 0.29731026, 0.07629813, -0.15695353, -0.14299184, 0.026618432, 0.13265325, 0.07727133, 0.12872085, 0.13887435, 0.1347057) * go_3(1.0, 0.0);
result += mat4(0.039232086, 0.117847264, -0.071643315, -0.040677182, -0.029160816, -0.06968689, 0.12880929, 0.037579957, -0.036671028, -0.022678757, -0.069731854, 0.10590314, 0.028034678, -0.015759282, 0.047180142, -0.16366881) * go_3(1.0, 1.0);
result += vec4(-0.079253934, 0.001511763, 0.100159355, 0.01585197);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!SAVE conv2d_2_tf1
//!WIDTH conv2d_1_tf.w
//!HEIGHT conv2d_1_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.024126908, 0.01737047, 0.04563732, 0.08303721, -0.21339902, 0.00025652428, -0.09666459, -0.07654246, -0.01201168, 0.14373912, 0.22268519, 0.049181588, -0.0751725, 0.006847365, -0.025867194, 0.19233267) * go_0(-1.0, -1.0);
result += mat4(-0.25251204, -0.34213448, -0.0022676045, 0.29270738, 0.08876456, 0.067294724, 0.2865476, -0.009144941, -0.074606106, 0.14566834, 0.14162645, 0.10980335, -0.7958991, -0.15410729, 0.038512416, -0.17033637) * go_0(-1.0, 0.0);
result += mat4(-0.115404196, -0.11004134, 0.13174473, -0.0006875606, 0.0051814034, 0.058522645, -0.0795437, 0.0011465811, -0.019500278, 0.12752724, 0.16985136, -0.054932587, 0.16734739, -0.04686017, -0.072241016, 0.054562975) * go_0(-1.0, 1.0);
result += mat4(-0.07528159, -0.113516726, 0.2081102, 0.009942251, 0.08256535, 0.050133914, 0.012745932, 0.13902397, 0.009369715, 0.083261885, 0.17366019, 0.069754004, 0.030654406, -0.045856245, -0.055254143, 0.16265897) * go_0(0.0, -1.0);
result += mat4(-0.14366727, 0.24948351, 0.12160293, 0.10929859, -0.116071545, -0.11725494, -0.13926856, -0.026759636, 0.12723772, 0.1938045, -0.02745115, -0.0644584, -0.23854719, 0.059308372, -0.446269, -0.06978486) * go_0(0.0, 0.0);
result += mat4(0.21108554, -0.1717225, 0.066633105, 0.15418948, -0.08902029, 0.047925282, 0.15817304, -0.080941506, 0.007364865, 0.10506626, 0.20205018, -0.078695655, 0.14004812, -0.3195092, 0.19157887, -0.12697977) * go_0(0.0, 1.0);
result += mat4(-0.08145032, -0.14292753, 0.066565305, -0.061348185, -0.08738346, 0.011608093, -0.0024047727, -0.024741996, -0.11547277, 0.10013328, 0.21730538, 0.05598899, -0.17741105, 0.075944185, 0.027434295, -0.2550598) * go_0(1.0, -1.0);
result += mat4(-0.026223006, 0.11214396, -0.133987, 0.1303522, 9.32011e-05, -0.14755996, -0.14002979, -0.039624512, 0.045111652, 0.17618611, 0.17764348, 0.104528464, 0.20592515, 0.07240335, -0.27604735, 0.038880046) * go_0(1.0, 0.0);
result += mat4(0.17734227, -0.002935363, 0.07505682, -0.029969893, -0.024536638, 0.11236127, 0.119374484, 0.08002781, -0.003541722, 0.1428466, 0.1729824, 0.055412393, -0.04790376, 0.18020035, 0.05376964, -0.1520942) * go_0(1.0, 1.0);
result += mat4(-0.11352182, -0.019249126, 0.10782615, 0.03079928, 0.020381734, -0.08998433, -0.09211494, -0.054406203, 0.1828849, -0.07692097, 0.004733955, -0.026685018, -0.08044814, -0.071961075, 0.029184176, -0.22562811) * go_1(-1.0, -1.0);
result += mat4(-0.34489468, -0.07447471, 0.026422959, 0.33550653, 0.22130035, 0.059709545, -0.07646962, -0.18386386, 0.33911958, -0.07534871, 0.040870134, 0.051136248, 0.32681262, 0.20612194, -0.1609581, -0.70460784) * go_1(-1.0, 0.0);
result += mat4(0.27617922, 0.09758603, 0.05103887, -0.09281693, -0.007143339, 0.006635712, -0.055270564, -0.022629099, -0.13023081, -0.013819027, -0.038695697, 0.047280338, -0.13964762, 0.09852924, -0.10056262, -0.084967695) * go_1(-1.0, 1.0);
result += mat4(0.1370323, 0.030904075, -0.033860117, 0.08926374, -0.14616281, -0.29926816, -0.23738252, -0.21374625, -0.14039646, 0.11503669, 0.082101606, -0.061717354, 0.021357644, -0.10676707, 0.03214661, 0.029967157) * go_1(0.0, -1.0);
result += mat4(-0.29881296, -0.22195289, -0.3512607, -0.2277441, 0.033705913, -0.23267402, -0.119738854, -0.18925253, 0.068823405, -0.15160555, 0.2585695, 0.10484223, -0.012574211, 0.38808516, 0.2599094, -0.4991424) * go_1(0.0, 0.0);
result += mat4(-0.07474731, 0.22742131, 0.014462262, 0.08409484, 0.09579643, -0.0519534, 0.0007793075, -0.044820115, -0.010144471, -0.040506937, 0.0056340825, 0.057767954, -0.14988829, -0.05099549, 0.007204364, -0.07094934) * go_1(0.0, 1.0);
result += mat4(-0.05736621, 0.12072876, -0.02037183, 0.05012334, -0.1173538, -0.10062993, -0.0033958228, 0.0142556345, -0.011005385, -0.0066177617, -0.058390465, 0.048240293, 0.09835053, 0.17917523, -0.06466951, 0.017518612) * go_1(1.0, -1.0);
result += mat4(0.1413101, -0.30268928, -0.17851736, -0.10797371, -0.01964573, 0.14356858, -0.06759965, 0.17416531, 0.13905385, -0.017476829, 0.06541924, -0.044690568, -0.080723755, -0.08610206, 0.095347285, -0.09233214) * go_1(1.0, 0.0);
result += mat4(-0.07254187, -0.091158785, 0.018472971, 0.03514051, 0.018888336, 0.107934274, -0.018830854, 0.10007211, -0.053966418, -0.035646267, -0.031214178, -0.05980228, -0.13045661, -0.011743741, -0.03325275, 0.071065165) * go_1(1.0, 1.0);
result += mat4(-0.037697386, 0.054388218, -0.010934479, 0.2266702, 0.049999133, 0.017648092, -0.044225454, 0.21611899, -0.03805845, 0.054236397, -0.018563407, -0.060588073, -0.031215845, 0.075081706, 0.07333242, -0.09651128) * go_2(-1.0, -1.0);
result += mat4(-0.32236508, -0.0026381002, -0.30787975, 0.2963127, -0.13276175, 0.1058753, -0.12744896, 0.09749292, -0.02683677, -0.0041124597, 0.006103888, -0.09997201, 0.092101686, -0.08375288, 0.09641652, 0.053333007) * go_2(-1.0, 0.0);
result += mat4(0.027999232, -0.060004722, -0.009207874, -0.0952888, -0.038418446, -0.13316345, 0.099323496, 0.048450433, 0.0443969, 0.056023613, 0.1156147, 0.018980766, 0.040020484, 0.07555044, 0.0039174426, -0.044098593) * go_2(-1.0, 1.0);
result += mat4(-0.101029314, 0.33333415, -0.22052327, -0.035329416, 0.17229559, 0.12564908, -0.07879576, -0.09248896, -0.03239869, 0.022611454, 0.05610472, -0.02181683, -0.06347532, -0.077292696, 0.02005389, -0.078899406) * go_2(0.0, -1.0);
result += mat4(-0.028139396, -0.04349171, -0.019393284, 0.42110333, 0.37065667, 0.5282552, 0.43816927, 0.19155908, 0.051832534, 0.02050813, 0.030795977, 0.023960136, -0.27617985, 0.19165507, -0.005492024, -0.13349663) * go_2(0.0, 0.0);
result += mat4(5.0700226e-05, 0.21293098, -0.39902148, -0.058406413, -0.06766975, 0.1129277, -0.012398328, 0.025031524, 0.03519656, 0.06486415, 0.15710293, 0.014098051, 0.057754945, 0.116186336, -0.14429826, 0.051864166) * go_2(0.0, 1.0);
result += mat4(-0.012280755, 0.043744788, -0.06420968, 0.012739398, 0.043073926, 0.031230433, 0.00036492705, -0.039208546, -0.09329152, 0.06928111, 0.11622664, -0.009106846, 0.111528054, -0.020315262, 0.036427997, 0.15881014) * go_2(1.0, -1.0);
result += mat4(-0.066635534, 0.13901882, 0.0885122, 0.1030835, 0.08539728, -0.015466482, 0.0706688, -0.1611047, 0.02179479, -0.00048529037, 0.08708685, -0.00894464, -0.13046473, -0.21456988, -0.20666413, 0.049039323) * go_2(1.0, 0.0);
result += mat4(-0.100800075, -0.03772198, -0.095183305, -0.15150243, -0.08743059, -0.24299338, -0.019315414, -0.1574107, -0.013610722, 0.064871654, 0.058439128, 0.008972897, 0.10339555, -0.027356634, 0.07666196, 0.048524544) * go_2(1.0, 1.0);
result += mat4(0.046309173, -0.03858991, -0.13260359, 0.0017626585, 0.1453724, 0.1402359, -0.079240486, 0.13017912, 0.0629575, -0.15448172, -0.1856442, -0.044694453, -0.17226808, -0.08065212, -0.008038736, -0.15994963) * go_3(-1.0, -1.0);
result += mat4(0.18369722, 0.03849556, -0.035185467, -0.20205377, 0.03879293, 0.02712859, -0.051278092, 0.14862835, 0.10261192, 0.18085574, -0.025982017, -0.029160796, 0.5301373, 0.09614058, 0.35518438, -0.014906588) * go_3(-1.0, 0.0);
result += mat4(-0.31154996, -0.06868871, -0.012681131, 0.028093819, -0.37321633, -0.14738804, 0.06060776, 0.050054748, 0.013779029, -0.020390315, -0.12487434, -0.0029474346, -0.274524, -0.09142805, 0.0132142445, 0.1577639) * go_3(-1.0, 1.0);
result += mat4(-0.02177336, -0.020817943, -0.0111796055, -0.0046033757, 0.45033064, 0.3573757, 0.55279994, 0.602122, -0.05536106, -0.33642644, -0.1851379, -0.052192084, 0.03683446, 0.13613251, 0.20098919, -0.090587094) * go_3(0.0, -1.0);
result += mat4(0.1520822, 0.37173554, -0.061298244, 0.0019386727, 0.44656134, 0.13406622, 0.39018136, 0.5722051, -0.13074401, 0.012778576, -0.2837446, 0.16098566, 0.100189455, -0.40386122, 0.17464107, -0.17862785) * go_3(0.0, 0.0);
result += mat4(-0.01217905, -0.24295084, 0.08192982, -0.14160301, -0.05936872, -0.003312342, -0.07542139, 0.13488367, -0.21560493, -0.14342502, -0.19195864, -0.09448305, -0.1038431, -0.075766176, 0.03226791, 0.06455397) * go_3(0.0, 1.0);
result += mat4(-0.076916575, -0.10891301, 0.032635316, 0.03848802, 0.15750243, 0.48169684, 0.5410635, 0.017279895, 0.012730932, -0.0059071835, 0.030766146, -0.0225503, -0.030178519, -0.05866621, 0.033593398, -0.00033098995) * go_3(1.0, -1.0);
result += mat4(-0.10757409, 0.2644168, -0.025696747, -0.0077012815, 0.31728277, 0.29771668, 0.2443613, -0.047722775, -0.083712585, -0.12742844, -0.3138776, -0.059888497, 0.12291351, -0.14435866, 0.051414594, -0.11889901) * go_3(1.0, 0.0);
result += mat4(-0.063888945, 0.002844068, -0.06129518, 0.03381495, 0.10176077, -0.11625004, -0.10745763, -0.20636752, -0.03820934, 0.01926402, -0.20310643, 0.09767577, -0.00776684, 0.13453315, -0.036967937, 0.09780335) * go_3(1.0, 1.0);
result += vec4(0.019374544, -0.050425697, -0.005817216, -0.0059976326);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.028328063, 0.038015317, 0.14893384, 0.10103896, 0.028176744, -0.02067147, -0.10970998, 0.015726602, -0.07402682, -0.075281784, -0.012586929, 0.053476278, 0.14823362, 0.20312984, 0.24213, 0.039118115) * go_0(-1.0, -1.0);
result += mat4(0.009731573, -0.019011121, 0.016360838, -0.0073153526, 0.14594506, -0.0427664, -0.094225354, -0.013891855, -0.037061375, 0.024959227, -0.12289382, -0.21792257, -0.33579424, 0.052678566, 0.04346115, 0.07943186) * go_0(-1.0, 0.0);
result += mat4(0.0022269129, 0.013298362, -0.045071404, -0.007918287, 0.010860651, -0.073057, -0.0042394064, 0.03340809, 0.04938919, -0.024218693, -0.008147567, 0.08848061, -0.06840333, 0.10077341, -0.272586, -0.06542769) * go_0(-1.0, 1.0);
result += mat4(0.15929016, -0.1415167, 0.057084452, 0.06830724, 0.0046992986, 0.068573505, 0.22142749, -0.18493174, -0.1006019, -0.11373546, 0.17520057, -0.12888812, 0.05176946, -0.14703397, -0.20610721, 0.16611591) * go_0(0.0, -1.0);
result += mat4(-0.0069309813, 0.22358349, -0.18569049, 0.13456121, -0.21528137, 0.04618922, -0.47261322, -0.09682613, 0.5402922, 0.15818685, 0.05288464, -0.09949312, 0.21833964, 0.06652228, -0.2694682, 0.58216536) * go_0(0.0, 0.0);
result += mat4(0.040808782, 0.023110595, 0.12678777, -0.09057271, 0.03159572, 0.044006016, -0.10090222, 0.09940838, -0.08454473, -0.118349984, -0.053009644, 0.24352531, -0.103818566, 0.12536442, -0.17832974, 0.25161982) * go_0(0.0, 1.0);
result += mat4(-0.026323501, -0.14911288, -0.0073903934, 0.06902844, 0.07188603, -0.05006621, 0.06539817, -0.048083752, -0.08032579, -0.07449341, -0.015944218, 0.032426495, 0.069349505, -0.07441237, 0.055614363, 0.065174624) * go_0(1.0, -1.0);
result += mat4(-0.046432327, -0.051616143, 0.017791865, -0.047294978, 0.025944458, -0.0020909954, 0.083794415, -0.055740435, -0.3720184, 0.06654654, 0.1944378, 0.07806658, 0.00870193, 0.005404396, -0.059417505, -0.06270168) * go_0(1.0, 0.0);
result += mat4(-0.011699918, -0.03260685, 0.016413182, -0.02199741, -0.042898953, -0.018734168, -0.12387174, 0.06405199, -0.050764065, 0.07050078, 0.006969675, 0.05508108, -0.079687595, 0.12154926, 0.071177684, 0.046873443) * go_0(1.0, 1.0);
result += mat4(-0.2158498, 0.03612371, -0.05268691, -0.065594874, 0.06997431, -0.07327132, -0.03323361, -0.23306306, -0.00011140713, -0.1891967, -0.017328357, 0.15796778, -0.061359044, 0.008202449, -0.031317197, -0.020873578) * go_1(-1.0, -1.0);
result += mat4(-0.022816254, -0.014594548, 0.0064240466, 0.07976367, -0.0070318123, -0.07651652, -0.111756384, -0.2788498, 0.16634429, -0.1583179, -0.10245271, 0.10480152, 0.345051, -0.07809675, 0.046080578, -0.32139245) * go_1(-1.0, 0.0);
result += mat4(0.020630263, 0.032152038, 0.0019161701, 0.05435833, 0.078139454, -0.10090956, 0.14244889, 0.017286595, 0.0039871824, -0.026395446, 0.14158171, 0.0010112645, 0.17055373, -0.08093189, -0.049234428, -0.33473247) * go_1(-1.0, 1.0);
result += mat4(-0.10982378, 0.029386539, -0.15483, -0.04345961, -0.16869037, -0.30862433, 0.060743757, -0.032285906, 0.017884266, -0.09846199, -0.090971105, -0.1693697, -0.063690096, -0.08489718, 0.18247683, -0.19921213) * go_1(0.0, -1.0);
result += mat4(0.1898742, 0.22187345, -0.28495324, -0.42578775, 0.12833633, -0.2251503, -0.025917793, 0.6011678, -0.36586264, 0.23302059, -0.072634645, 0.0064221635, 0.56792957, -0.4684677, 0.05015159, 0.30121225) * go_1(0.0, 0.0);
result += mat4(0.10837159, 0.14743729, -0.03639783, -0.34797576, -0.18306817, -0.07957882, -0.111433275, 0.30104128, -0.102763996, -0.01020151, 0.016333267, -0.012390819, 0.11835027, -0.12597388, -0.006298998, 0.0513052) * go_1(0.0, 1.0);
result += mat4(-0.23662986, 0.23325302, -0.046104953, 0.36488137, 0.06990537, -0.06887873, -0.012611426, -0.02618366, -0.05296669, 0.195254, 0.016366778, 0.01693462, -0.08488424, -0.24656284, -0.035283253, -0.15318634) * go_1(1.0, -1.0);
result += mat4(0.061704446, -0.26930714, -0.24598889, 0.27657726, 0.05046488, -0.341884, 0.10704377, -0.15971762, 0.072999336, -0.2005826, -0.05874223, -0.053938035, -0.08284583, -0.22792995, 0.1027033, -0.012932447) * go_1(1.0, 0.0);
result += mat4(-0.029079054, 0.14774945, 0.026151389, 0.12380946, 0.08926635, -0.08387116, -0.17018612, -0.09304499, 0.086990625, -0.27579373, 0.003823722, -0.024723161, 0.08762848, -0.10080674, -0.012214886, -0.30239874) * go_1(1.0, 1.0);
result += mat4(-0.25756493, 0.2537789, 0.21723714, 0.0017929028, -0.014724892, 0.086692676, 0.11934202, -0.025869334, 0.008618976, -0.0046638376, -0.06863114, -0.07598151, -0.17309345, 0.009138105, -0.099874064, 0.07377463) * go_2(-1.0, -1.0);
result += mat4(-0.39971545, 0.16774859, 0.13102596, 0.30735064, -0.060374007, -0.036933452, 0.14408773, 0.06479284, 0.03806265, 0.045560133, 0.043136165, -0.019244662, 0.17573427, -0.11398941, -0.0751098, 0.041702736) * go_2(-1.0, 0.0);
result += mat4(-0.074492976, 0.18349282, -0.009050458, 0.0869807, -0.23123743, -0.015426683, -0.14346392, 0.005445149, -0.05322614, 0.10287576, 0.16083732, -0.09557319, -0.13891783, -0.13752605, -0.023572346, 0.13608918) * go_2(-1.0, 1.0);
result += mat4(-0.31140685, 0.40130782, 0.07704675, 0.27509958, 0.09711739, -0.18293281, -0.14500841, -0.15334702, 0.098314695, 0.22749798, 0.006017282, -0.013669673, 0.07147038, 0.022289474, -0.036797456, -0.0013958871) * go_2(0.0, -1.0);
result += mat4(0.0547557, -0.03036202, 0.65113044, 0.10668893, 0.304707, -0.1456157, 0.27668485, 0.2279428, -0.42439902, -0.0073047588, 0.045635667, 0.271637, -0.19595222, -0.28107607, 0.3905438, -0.29898256) * go_2(0.0, 0.0);
result += mat4(0.076843366, 0.037181348, 0.08652873, 0.1756985, 0.03728033, -0.22783624, 0.16810594, -0.022009399, 0.16058537, 0.24559903, 0.05266939, -0.13929726, 0.15964857, 0.0013167082, 0.015017631, 0.101646364) * go_2(0.0, 1.0);
result += mat4(-0.3022452, 0.20052882, 0.13433233, 0.04250016, -0.15248592, 0.014216527, -0.23489903, 0.13919333, 0.22891816, -0.0053335144, -0.05567782, -0.12769286, -0.05337762, -0.11429989, -0.00882089, -0.030790573) * go_2(1.0, -1.0);
result += mat4(-0.11763547, 0.1073185, 0.15810886, 0.013149736, -0.028268294, -0.24712053, 0.08592036, 0.075742744, 0.19626461, -0.10880887, -0.22599675, -0.37207767, -0.032548983, -0.011045266, -0.035218395, 0.099996395) * go_2(1.0, 0.0);
result += mat4(0.05631665, 0.029538663, 0.043909863, 0.13720988, 0.10980592, -0.047748722, 0.080308706, -0.06828442, 0.1144396, -0.12510885, -0.067976676, 0.030742755, 0.07134681, -0.06652312, -0.0642328, -0.034490924) * go_2(1.0, 1.0);
result += mat4(0.019588284, -0.15197967, -0.16797094, -0.026324488, 0.014429439, -0.028491383, 0.059453625, 0.23443304, 0.02504347, 0.08872467, 0.032782357, -0.085310735, 0.013040259, -0.09837991, 0.073533125, -0.03544458) * go_3(-1.0, -1.0);
result += mat4(0.02198588, -0.09614766, 0.024655875, 0.025384603, 0.012162857, 0.065071434, 0.018112874, 0.19828922, -0.33289856, 0.011323505, 0.13696423, 0.31772846, -0.06587399, -0.05569957, -0.16469179, -0.22545892) * go_3(-1.0, 0.0);
result += mat4(-0.009093827, 0.086783886, 0.060070645, 0.049957857, 0.041628215, 0.082412794, 0.117729135, -0.178277, 0.08326062, -0.07120824, 0.1788718, 0.050748438, -0.08952197, -0.14609487, 0.05515471, 0.14784457) * go_3(-1.0, 1.0);
result += mat4(-0.10823147, -0.05108019, 0.092807196, -0.13899301, 0.19123949, -0.044189975, 0.0030145745, 0.08935499, -0.10338727, 0.01996205, 0.15671325, -0.08229972, 0.05603653, 0.043324884, 0.13562247, -0.11487494) * go_3(0.0, -1.0);
result += mat4(-0.18872134, -0.07302765, 0.030137405, 0.30928415, -0.07689583, 0.045998566, 0.45554903, -0.1653404, 0.14705873, -0.10649596, 0.46833125, 0.17608039, -0.43967086, 0.056812476, -0.17908083, -0.40455228) * go_3(0.0, 0.0);
result += mat4(-0.08093384, 0.032636635, 0.124594346, 0.13655491, 0.16780408, -1.4671803e-05, 0.13044862, -0.397665, -0.013273644, 0.08253894, 0.16302188, -0.052874118, 0.04073075, -0.18063635, -0.00838661, -0.31084144) * go_3(0.0, 1.0);
result += mat4(0.06804371, -0.14755388, -0.12055216, -0.00437858, -0.044694718, 0.22744909, 0.012434794, 0.06245207, 0.00560859, -0.15815294, -0.19711316, 0.07711764, 0.03078979, -0.09560189, 0.10509056, 0.010651465) * go_3(1.0, -1.0);
result += mat4(-0.026342146, 0.13919179, -0.0030414977, 0.06607403, 0.071292974, 0.065464914, -0.027091878, 0.10620255, -0.052090824, 0.06840278, -0.08457357, 0.08867469, 0.2976581, -0.6702739, -0.15472057, -0.3066263) * go_3(1.0, 0.0);
result += mat4(-0.00072869845, 0.046573937, -0.08363707, 0.07867379, 0.038065, 0.01228845, 0.031746328, -0.024448024, -0.065555945, 0.1220454, 0.032151606, -0.022336006, -0.0010816467, -0.026455112, 0.112422734, -0.10285581) * go_3(1.0, 1.0);
result += vec4(0.052450567, 0.10404023, -0.059578225, 0.009724121);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!SAVE conv2d_3_tf1
//!WIDTH conv2d_2_tf.w
//!HEIGHT conv2d_2_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.037506457, -0.06573841, -0.087879084, -0.06359248, -0.0017873603, -0.009097742, 0.010108622, 0.026364084, 0.012306545, 0.12607974, -0.088268295, 0.14034338, 0.24951904, 0.0983314, 0.03635719, -0.047059253) * go_0(-1.0, -1.0);
result += mat4(-0.05570699, 0.11044774, 0.04827364, -0.03185735, -0.032498132, -0.062959515, 0.2933071, 0.22244357, 0.061075654, 0.0064111133, 0.011452209, 0.11576761, 0.13969804, 0.20502032, 0.1114938, 0.022496287) * go_0(-1.0, 0.0);
result += mat4(-0.054194342, 0.000389916, -0.039589155, -0.018707246, -0.036095835, -0.06873059, -0.077109694, 0.028726012, -0.08820959, -0.109247595, -0.05745309, 0.043230128, 0.033671502, 0.16398554, 0.030398889, -0.17000203) * go_0(-1.0, 1.0);
result += mat4(-0.09218165, -0.12813722, -0.040984686, -0.016605416, 0.054269493, 0.12971285, -0.013961638, -0.17803082, -0.014683587, 0.2502267, -0.14249405, -0.025687713, -0.097426265, -0.30111355, -0.21776466, 0.008809217) * go_0(0.0, -1.0);
result += mat4(0.21033873, 0.15221386, 0.18138756, -0.08248389, -0.10091519, -0.06940753, -0.014009188, -0.3009861, -0.02452202, -0.08800422, -0.36376888, 0.18485394, 0.35076657, -0.13293292, 0.24624826, 0.39373755) * go_0(0.0, 0.0);
result += mat4(0.014170062, -0.029623963, 0.057001226, 0.09269898, -0.14630881, -0.16557585, 0.06735037, -0.015008042, -0.27238864, 0.081130914, -0.07869508, 0.098087415, 0.11217335, 0.48223323, 0.18613088, -0.035602476) * go_0(0.0, 1.0);
result += mat4(-0.21623239, -0.1125095, -0.09964635, 0.101452544, 0.11877652, 0.13471957, -0.10402355, 0.0077938605, 0.030518647, 0.22309083, -0.2115206, 0.017967062, -0.042780407, 0.099759325, -0.10465051, -0.033807248) * go_0(1.0, -1.0);
result += mat4(-0.059608232, 0.06684556, 0.00039066386, 0.08542961, 0.097183906, -0.1868667, 0.07778909, -0.06172202, 0.0021662437, -0.05387577, -0.4077133, -0.028940776, 0.110816136, -0.04154161, 0.030078325, 0.072834246) * go_0(1.0, 0.0);
result += mat4(-0.01881586, -0.06384429, -0.054874837, -0.016731417, -0.06570834, -0.13579571, 0.0033891131, -0.059161015, -0.11559389, 0.02149361, -0.08791608, -0.008113861, 0.08313892, -0.07327947, -0.013473171, 0.13254371) * go_0(1.0, 1.0);
result += mat4(-0.11458958, -0.08827364, -0.025030116, 0.12626298, 0.0070429775, 0.0337767, 0.051719055, -0.09654129, -0.04867615, -0.03609001, -0.06522421, -0.044131942, -0.048825134, 0.10652733, -0.015310965, -0.07341175) * go_1(-1.0, -1.0);
result += mat4(0.05782829, 0.014247012, 0.12126171, 0.100055166, 0.24079333, -0.20155986, 0.1640186, -0.12158374, -0.153708, -0.24445893, -0.10536192, 0.12758626, -0.19430119, -0.019024884, -0.080120996, -0.29866305) * go_1(-1.0, 0.0);
result += mat4(-0.017357074, 0.04390695, 0.12889594, 0.11451521, 0.03333342, -0.16417275, 0.10196121, 0.13059081, 0.09948873, 0.15007107, 0.22664218, 0.35449567, -0.089776486, 0.025239054, 0.12463201, -0.13109131) * go_1(-1.0, 1.0);
result += mat4(0.064875744, 0.40551752, 0.11903257, 0.14822967, 0.14993542, -0.12758526, 0.23159283, -0.06080246, -0.084577255, 0.14307548, -0.02186462, 0.05793564, -0.050965074, 0.23895216, -0.07796932, -0.1624384) * go_1(0.0, -1.0);
result += mat4(-0.15942748, 0.07191155, 0.42204422, 0.35219797, 0.23286703, -0.283381, -0.2749432, 0.25922084, 0.10494953, 0.14575887, -0.19649154, -0.14563714, -0.03709703, 0.023375817, -0.05610175, -0.32548484) * go_1(0.0, 0.0);
result += mat4(-0.04872624, -0.3592348, -0.027413938, 0.0836858, 0.046842758, -0.35193914, 0.06154142, 0.05559191, -0.22538327, -0.097689696, -0.21317257, -0.033945527, -0.23628096, -0.016477302, 0.027297588, -0.04105733) * go_1(0.0, 1.0);
result += mat4(0.11543502, -0.043297376, 0.118703, 0.15013209, 0.03191795, 0.014122794, 0.05156918, 0.023102578, 0.0808462, -0.06445798, 0.15860644, -0.062393136, -0.018691704, -0.00032888897, 0.01196705, -0.025045555) * go_1(1.0, -1.0);
result += mat4(0.08301664, 0.12298539, 0.20151077, 0.2993159, 0.16968682, -0.18196446, -0.13322797, -0.13693243, -0.0048389523, -0.057406515, 0.21409932, -0.060822334, -0.08554752, -0.19363636, -0.35241908, -0.32256603) * go_1(1.0, 0.0);
result += mat4(-0.0523748, 0.17082025, 0.08556144, 0.19181536, -0.2445756, -0.3616732, -0.01641404, -0.078599006, 0.23907976, 0.025989126, 0.07574993, -0.06859337, -0.06667767, -0.022847861, -0.037942342, -0.21112117) * go_1(1.0, 1.0);
result += mat4(0.15098672, 0.024212115, -0.19068481, -0.22606348, -0.15221487, -0.032165635, -0.06244531, -0.043535717, -0.07398802, -0.06088507, -0.013834592, -0.10145823, 0.06901983, -0.0862135, -0.05545454, 0.15514566) * go_2(-1.0, -1.0);
result += mat4(0.044767097, -0.07583697, -0.17739761, -0.25538698, 0.0966659, -0.0013492911, -0.23315248, -0.21652249, -0.14381947, 0.017784966, -0.15960035, -0.13297895, 0.009810349, -0.041348267, 0.05443229, 0.17781278) * go_2(-1.0, 0.0);
result += mat4(-0.0052824756, 0.087268956, -0.022167318, -0.09450279, 0.1254372, 0.075806946, 0.028893303, -0.09019378, 0.03488572, 0.046265777, 0.026162563, 0.003914548, -0.0632334, -0.19494742, -0.03602023, 0.113897055) * go_2(-1.0, 1.0);
result += mat4(-0.11311528, 0.2616239, 0.12303548, 0.13427438, -0.26537886, 0.015112677, -0.03641703, -0.014114427, -0.023280613, 0.03626403, 0.12833157, 0.19168468, 0.2119137, -0.02374797, 0.117919676, 0.07794395) * go_2(0.0, -1.0);
result += mat4(-0.13746078, 0.25739196, 0.008431936, -0.053867325, -0.13228695, -0.20661803, 0.026474724, 0.3205188, -0.41819036, 0.42812085, 0.17249924, -0.15810613, 0.39602605, -0.10873597, 0.1457145, -0.060503867) * go_2(0.0, 0.0);
result += mat4(0.03706167, -0.036211733, 0.06519942, -0.2123978, 0.019934088, 0.17494182, -0.017252771, -0.067341134, -0.15416612, -0.114118524, -0.00028491023, -0.08172238, -0.11722721, -0.2647645, 0.13316637, 0.13562322) * go_2(0.0, 1.0);
result += mat4(0.11832847, 0.22822993, 0.020318847, 0.0734738, -0.025950216, -0.072782144, 0.11133989, 0.18845533, -0.004584898, -0.10486471, 0.054522812, -0.14136603, 0.01940983, -0.039433163, 0.008390286, 0.013686628) * go_2(1.0, -1.0);
result += mat4(-0.042335663, 0.0035399816, -0.1813205, -0.25639042, 0.1042524, 0.07707001, -0.04922454, 0.18140413, -0.22322963, 0.030809738, -0.11041754, -0.040288754, 0.09431559, -0.08017892, -0.18317147, -0.019331435) * go_2(1.0, 0.0);
result += mat4(-0.061776266, 0.0069793356, 0.019964112, -0.14504445, -0.00070097746, -0.027107855, 0.030182542, -0.05625612, -0.04958449, 0.123165295, 0.0013953283, 0.017912487, 0.031161329, -0.31798717, 0.018331604, 0.030411277) * go_2(1.0, 1.0);
result += mat4(-0.0530594, -0.07933117, 0.024755973, 0.004785411, 0.045512546, 0.12833083, 0.023195961, -0.018028054, 0.014223835, 0.102213494, 0.052169293, -0.020509718, 0.017905682, 0.021354724, -0.0410789, -0.066523656) * go_3(-1.0, -1.0);
result += mat4(0.017061293, -0.08770806, -0.04889939, 0.01825556, -0.03228951, -0.06838898, -0.09249373, 0.18103507, 0.087000825, 0.04175679, -0.09305919, -0.2792485, 0.03405797, 0.062147446, -0.04757652, -0.021603985) * go_3(-1.0, 0.0);
result += mat4(-0.04115162, 0.02547615, 0.07033616, 0.09814065, 0.2597489, -0.0335038, 0.14097647, 0.047022782, 0.1374654, -0.27390274, 0.02080897, -0.15251215, -0.025431091, 0.08871465, -0.22243279, -0.07792812) * go_3(-1.0, 1.0);
result += mat4(-0.061674852, -0.051326606, -0.04885301, 0.08548189, -0.07100394, 0.044875987, -0.19810183, -0.09841128, -0.06628199, -0.041564234, 0.1111919, -0.044448826, 0.06980301, 0.00046094303, -0.045978926, -0.20736355) * go_3(0.0, -1.0);
result += mat4(-0.18405268, -0.28115878, -0.33536536, 0.0753763, 0.028309148, 0.0014874876, 0.28369543, -0.2133985, 0.16520546, 0.29562506, 0.109781906, 0.028433772, -0.02691105, -0.39038795, -0.12942268, -0.080103286) * go_3(0.0, 0.0);
result += mat4(-0.05387814, -0.04672615, 0.046064686, 0.2791977, 0.11359623, -0.204098, -0.018091407, 0.13550591, 0.04216003, -0.1631328, -0.043013666, -0.045698896, 0.032403514, 0.010206319, -0.25789943, -0.36328712) * go_3(0.0, 1.0);
result += mat4(0.11280466, 0.11671405, -0.02122692, 0.021664057, -0.07836575, 0.014747725, 0.030007286, -0.10128616, -0.13695373, -0.10353946, -0.043571353, 0.05922437, -0.11293257, 0.0828006, -0.07322761, -0.08197273) * go_3(1.0, -1.0);
result += mat4(-0.0010509897, -0.1674067, 0.08191839, 0.056608744, 0.061343428, 0.19574693, 0.05302967, -0.006813754, -0.016064182, 0.22949885, -0.06631832, 0.034382205, 0.12674272, 0.06583508, 0.19319807, 0.011400221) * go_3(1.0, 0.0);
result += mat4(-0.032175347, -0.021227444, -0.027698517, 0.067299634, 0.23929007, 0.20669897, 0.004856941, 0.0009404045, 0.04919408, 0.020296812, 0.012571405, -0.16185577, -0.012276781, 0.16609742, -0.15718406, -0.20344186) * go_3(1.0, 1.0);
result += vec4(0.022815697, 0.012251767, 0.045309987, -0.0879881);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.010501252, -0.046741538, -0.0017120431, -0.04840009, 0.20547974, 0.3366821, -0.10182207, 0.17451541, -0.03404171, -0.15138055, 0.16771653, -0.07168161, 0.102572344, 0.08266354, 0.20205829, 0.13429944) * go_0(-1.0, -1.0);
result += mat4(0.05584234, 0.06844309, 0.025430907, 0.124140054, 0.36385667, 0.12099467, -0.41671994, 0.085477844, 0.19748127, -0.21473993, 0.005037813, -0.3973761, 0.04669592, -0.100342326, -0.09403772, -0.034248166) * go_0(-1.0, 0.0);
result += mat4(-0.17654696, 0.009085064, 0.028360577, 0.033909567, 0.09377573, 0.27896938, 0.103994116, 0.0008595595, 0.064523555, 0.040994007, -0.06337235, 0.05662917, 0.0037455747, 0.017608117, -0.14610702, 1.2175746e-05) * go_0(-1.0, 1.0);
result += mat4(-0.04631749, -0.14251712, -0.16420849, -0.16259338, 0.46187812, 0.17576592, 0.00049142196, 0.029193122, -0.003925961, -0.11218227, 0.007026237, -0.20583045, -0.0010964901, 0.19355829, 0.2221649, 0.1187224) * go_0(0.0, -1.0);
result += mat4(-0.041567978, -0.31510913, 0.01618704, 0.04979329, 0.101294376, 0.16356954, 0.21361789, 0.20735294, 0.1900854, -0.4151726, -0.30471593, -0.59483325, 0.033624128, 0.11495109, -0.15194787, 0.4920959) * go_0(0.0, 0.0);
result += mat4(-0.18910064, -0.06516878, -0.20508374, -0.063928686, 0.7289614, 0.26674315, 0.2929481, 0.4026098, -0.033123735, -0.090371035, -0.029094126, -0.15197921, -0.08723726, -0.060160585, -0.07908409, -0.08826931) * go_0(0.0, 1.0);
result += mat4(-0.08321312, -0.09749648, -0.08783197, -0.23072585, 0.24343425, 0.10888949, 0.17419606, 0.04136083, 0.0066000987, -0.06112787, -0.12176007, -0.20907228, -0.0008522778, -0.054704696, -0.07197735, -0.0877179) * go_0(1.0, -1.0);
result += mat4(-0.40559706, -0.3801705, 0.05970925, -0.6157092, 0.28944594, 0.1252121, 0.403247, -0.122819394, -0.096336536, -0.2324694, 0.05980106, -0.19970767, -0.16646989, -0.10164633, -0.09282806, -0.08897996) * go_0(1.0, 0.0);
result += mat4(-0.14336498, -0.12967408, -0.016268672, -0.021431219, -0.0850116, 0.37105832, -0.04093888, 0.08540873, 0.035717323, -0.07282701, -0.009123291, -0.0036565473, -0.02508944, -0.087611906, 0.03604423, -0.00089080486) * go_0(1.0, 1.0);
result += mat4(0.1373875, 0.05283984, -0.11992707, 0.102294855, 0.3305128, 0.044920854, 0.31622922, -0.04711731, 0.001336024, 0.022799017, -0.062343203, 0.017140022, -0.07556853, -0.12864219, -0.25721326, -0.20741239) * go_1(-1.0, -1.0);
result += mat4(0.22062224, 0.09266222, 0.22466063, 0.18527372, -0.06940306, 0.1317168, 0.019784274, -0.07422301, 0.04061616, 0.0022494853, 0.21723995, 0.24732308, 0.14088804, 0.0116154915, 0.102064446, 0.020701224) * go_1(-1.0, 0.0);
result += mat4(-0.025154127, 0.045180723, -0.05877639, -0.099235624, 0.13630918, 0.24653725, -0.05723323, -0.022995364, -0.10826078, 0.049667366, 0.12618053, 0.1557369, 0.037487056, -0.22215757, 0.005912914, -0.20549043) * go_1(-1.0, 1.0);
result += mat4(0.09641055, 0.098845296, -0.08192096, -0.03691394, -0.18450394, 0.29955688, -0.082493715, -0.06268039, -0.0754319, 0.21018648, -0.016580105, -0.1810546, 0.13857666, -0.0327626, 0.03161804, -0.32589525) * go_1(0.0, -1.0);
result += mat4(-0.18272439, -0.17595461, 0.047229152, 0.14596708, 0.40453747, 0.5658558, -0.17969102, 0.21557859, -0.34232348, 0.40355968, 0.53874254, 0.0012561383, 0.28154096, -0.06745097, -0.13049632, 0.42997465) * go_1(0.0, 0.0);
result += mat4(0.081179485, -0.0041369614, -0.12001932, -0.102107175, -0.050293338, 0.29165673, 0.08062538, 0.22925815, 0.19389379, 0.28463286, -0.057207666, 0.23133168, -0.07545728, 0.06729763, -0.103593476, 0.014468794) * go_1(0.0, 1.0);
result += mat4(0.069821335, -0.010299579, 0.069458775, 0.03894593, -0.054688405, 0.32758355, 0.13935772, 0.37506017, 0.24083133, -0.06105339, 0.25636867, 0.09627044, 0.08939188, 0.006728639, 0.10629504, 0.07887502) * go_1(1.0, -1.0);
result += mat4(0.10563019, 0.077379815, 0.045456886, 0.09303406, 0.11326298, 0.28762257, -0.35142374, 0.10285745, 0.28762287, 0.3592446, 0.23816557, 0.22676824, 0.030372012, -0.028023086, -0.30956736, -0.27588373) * go_1(1.0, 0.0);
result += mat4(0.110499, 0.009828844, 0.086689755, 0.1839749, 0.16656482, 0.083707325, 0.19506347, -0.01547141, 0.13804145, 0.2206598, -0.16484791, -0.0021595939, -0.06844408, -0.07861768, 0.040771082, -0.13347322) * go_1(1.0, 1.0);
result += mat4(0.02667995, 0.019265587, -0.18211095, -0.102116466, -0.042541366, -0.07700912, -0.020587347, -0.03532171, 0.14816427, -0.1672272, -0.17522137, -0.04657808, 0.013430233, -0.0021270285, 0.109880306, 0.004838907) * go_2(-1.0, -1.0);
result += mat4(0.14285165, -0.1364756, 0.017568532, -0.27690783, -0.015461915, 0.045437083, 0.018187419, 0.12473493, 0.17991658, -0.15642665, 0.10009151, -0.19040193, 0.1734127, -0.13817501, 0.0710856, -0.12921426) * go_2(-1.0, 0.0);
result += mat4(-0.14114712, -0.18893671, 0.16121174, 0.035988737, 0.17872387, -0.106395856, -0.23183517, 0.012380416, 0.043066982, -0.28539032, -0.049011275, -0.21125022, -0.11976977, -0.015564958, 0.18880925, -0.0034812456) * go_2(-1.0, 1.0);
result += mat4(-0.05894521, 0.17266215, -0.0458901, 0.08049924, 0.0156061025, -0.0047465423, 0.09714626, 0.045990974, -0.08786066, -0.37803304, -0.19629405, -0.08546443, 0.014874948, 0.16931784, 0.24799919, 0.06316819) * go_2(0.0, -1.0);
result += mat4(-0.28352743, 0.29973608, -0.014540065, 0.2865005, 0.048086923, 0.18976144, 0.22969759, 0.1643124, -0.11259408, -0.107592925, 0.184308, 0.30998367, -1.0860825, -0.29118305, -0.51242536, -0.38492215) * go_2(0.0, 0.0);
result += mat4(-0.17199941, -0.14274743, -0.14213641, -0.1691383, -0.17294803, -0.013992068, -0.12135059, 0.082377024, -0.11255549, -0.124990575, -0.32526177, -0.08199375, -0.25591666, 0.1882329, 0.07895415, 0.22012262) * go_2(0.0, 1.0);
result += mat4(0.026025832, -0.07267515, 0.09738688, 0.074536435, -0.060470507, -0.037861936, 0.0507819, -0.054857653, 0.0043173633, -0.18107842, -0.02996759, 0.04072402, -0.012617744, 0.061665237, 0.0013981885, 0.08679919) * go_2(1.0, -1.0);
result += mat4(0.27913737, 0.39656082, 0.1579819, 0.2774727, -0.007996453, 0.08704765, -0.016933938, 0.07066135, 0.12361742, -0.20802726, -0.13705719, -0.18794124, 0.037409827, -0.03351758, -0.2970392, -0.11001984) * go_2(1.0, 0.0);
result += mat4(-0.027419567, 0.043236237, -0.19843115, -0.056489736, -0.017010912, 0.070949584, -0.14881176, -0.0780235, 0.0039477753, -0.16772608, -0.009547604, -0.14060417, 0.0103197545, 0.07129672, 0.034949142, 0.014112084) * go_2(1.0, 1.0);
result += mat4(-0.06467971, 0.084101565, 0.26296136, 0.08878442, -0.11232121, -0.054373942, -0.17263442, 0.046408508, 0.032239515, 0.042490713, 0.036938053, -0.034339923, -0.07139367, 0.032505415, 0.0045828503, 0.24428385) * go_3(-1.0, -1.0);
result += mat4(0.053585388, -0.08175568, -0.04787236, 0.06061965, -0.0740297, 0.11113596, -0.12467945, 0.08229154, -0.01941305, 0.12903687, 0.09095716, -0.13062255, -0.0102068605, 0.107291475, 0.030279635, 0.07464777) * go_3(-1.0, 0.0);
result += mat4(0.11041978, -0.0123585425, 0.11147018, 0.07380536, 0.06632908, 0.011784447, 0.029638765, -0.01566135, 0.009105331, 0.05252663, -0.17972581, 0.01210126, -0.10749957, -0.028144639, -0.105761215, 0.083784826) * go_3(-1.0, 1.0);
result += mat4(-0.058018316, 0.15083058, 0.2725673, 0.024263225, -0.067711554, 0.051117413, -0.31144425, -0.15761986, 0.017503206, -0.14361219, -0.38261738, -0.20354146, -0.04211545, 0.12921454, -0.01319619, 0.35809723) * go_3(0.0, -1.0);
result += mat4(-0.107978396, 0.3230084, -0.13806303, 0.12903036, 0.039864987, -0.006241628, 0.18701774, -0.10785807, 0.30056882, -0.3092082, -0.4273322, 0.3784662, -0.026107281, 0.23165871, 0.35258314, -0.06654702) * go_3(0.0, 0.0);
result += mat4(-0.15840323, 0.15210885, 0.04086692, 0.19169305, 0.11847602, 0.0009038581, 0.095951624, 0.043941673, 0.1512248, 0.0749449, -0.027045414, -0.19729601, 0.08265063, -0.045218006, -0.10732461, 0.05197371) * go_3(0.0, 1.0);
result += mat4(0.13637526, 0.28841978, 0.10298119, -0.005948496, 0.020897362, -0.02186902, -0.16207378, -0.021084815, 0.029192554, 0.07076991, -0.07210881, -0.06752328, 0.0006557475, 0.08986717, -0.29430988, 0.21411087) * go_3(1.0, -1.0);
result += mat4(0.18667863, 0.3117322, -0.0859705, -0.038189936, 0.10214859, -0.11244034, 0.2680223, -0.072901204, -0.07434324, -0.17855306, 0.23134363, -0.055360887, -0.020968167, 0.0858459, 0.078975916, 0.13254759) * go_3(1.0, 0.0);
result += mat4(-0.15676941, 0.03476677, -0.09922334, -0.15847856, -0.0033982224, 0.020932984, 0.12874377, 0.048792202, 0.06521213, 0.12456798, 0.15958112, 0.15981804, 0.07657683, 0.1759313, 0.012727211, 0.120304115) * go_3(1.0, 1.0);
result += vec4(0.08911729, -0.027969634, -0.010653148, -0.08001697);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!SAVE conv2d_4_tf1
//!WIDTH conv2d_3_tf.w
//!HEIGHT conv2d_3_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.003206617, 0.04896987, 0.049652386, 0.10869342, 0.36313584, -0.070666805, 0.93581825, -0.52484274, -0.14278883, 0.064016834, -0.05534331, 0.02961736, -0.1319316, 0.05740655, 0.2405951, -0.12313382) * go_0(-1.0, -1.0);
result += mat4(0.014092832, 0.07058761, -0.07887866, -0.27478936, -0.31456405, -0.31036922, -0.18380909, -0.11277979, -0.034889866, -0.37914017, -0.056245584, 0.24008954, -0.03414483, -0.023189066, -0.010568316, -0.004604883) * go_0(-1.0, 0.0);
result += mat4(0.15443979, -0.050161768, -0.012300917, -0.08834887, 0.082193285, 0.06878423, 0.1478042, -0.3774468, -0.18659878, 0.14238152, 0.033605397, 0.13560006, -0.032682173, -0.024561955, 0.05656941, -0.034246165) * go_0(-1.0, 1.0);
result += mat4(0.04691462, 0.064624496, -0.15950382, 0.16081297, -0.1417951, -0.109690994, -0.021205869, 0.19361454, -0.006306647, 0.3401972, -0.00014070333, 0.11619607, -0.13437814, 0.05464789, 0.37712076, -0.12470751) * go_0(0.0, -1.0);
result += mat4(-0.40016884, 0.010666597, -0.005395378, 0.51084363, -0.009875391, 0.3969395, 0.47768033, -0.3392299, -0.1509509, -0.057620626, -0.1834601, -0.09998148, 0.10095897, -0.2213528, 0.02546703, -0.28506726) * go_0(0.0, 0.0);
result += mat4(0.26652217, -0.106772706, -0.12609608, -0.0949661, -0.10869194, -0.55331933, -0.011515521, -0.27978876, -0.2124893, 0.03954004, 0.1691768, 0.05590268, 0.1539662, 0.10703386, -0.027286088, 0.2168544) * go_0(0.0, 1.0);
result += mat4(-0.04862511, 0.06919758, -0.12962708, 0.016036907, -0.030030789, -0.20159967, 0.0013158675, -0.07799172, -0.032236706, -0.0035921712, -0.085437834, -0.025374755, -0.06251374, -0.009269627, -0.07519051, -0.01884611) * go_0(1.0, -1.0);
result += mat4(0.23940067, -0.19496065, -0.05494683, 0.11601073, -0.074225076, 0.24976431, 0.41665986, 0.12029472, 0.16815041, -0.115868434, 0.06333614, 0.032145746, 0.15990137, -0.14886795, 0.034102913, -0.07727595) * go_0(1.0, 0.0);
result += mat4(0.14702639, -0.013711502, 0.011437429, -0.11201445, -0.2582659, 0.34539905, 0.058082145, -0.18346462, 0.0027891365, 0.072565466, 0.12716974, 0.050636146, 0.092657596, 0.08541754, -0.1266164, 0.027881607) * go_0(1.0, 1.0);
result += mat4(0.043362036, 0.020758621, 0.09906072, -0.22401148, -0.19104514, -0.25774476, 0.074128486, 0.08558291, -0.075419895, 0.20380639, 0.06398196, 0.015925938, 0.089786015, -0.100721814, -0.1374862, 0.26110905) * go_1(-1.0, -1.0);
result += mat4(-0.12547149, 0.08151811, -0.15953775, -0.33995447, -0.50784314, 0.46155545, 0.24986996, 0.03404644, -0.047789436, -0.12438347, -0.14143273, -0.17951359, -0.08057819, 0.023863006, -0.008539273, -0.06775414) * go_1(-1.0, 0.0);
result += mat4(0.1430169, 0.056971863, -0.021576611, -0.045342956, -0.22356391, -0.15344621, -0.0467977, -0.22970036, -0.0125351725, 0.16957329, -0.0069183917, -0.013949834, -0.048609708, 0.05261722, 0.023262242, 0.2123519) * go_1(-1.0, 1.0);
result += mat4(-0.019523792, 0.008228363, -0.04616012, -0.14341992, -0.19307113, 0.005937241, 0.24048887, -0.04279845, 0.022574252, 0.15558265, -0.035000063, 0.18318397, -0.05392528, -0.26044658, -0.13493988, 0.056433514) * go_1(0.0, -1.0);
result += mat4(-0.28926027, -0.17381874, 0.07685766, -0.0061521684, -0.47455552, -0.49213487, 0.36924496, 0.29042044, 0.201094, -0.14280887, -0.4531411, -0.52902204, -0.28123, 0.1401882, 0.32054895, -0.11357518) * go_1(0.0, 0.0);
result += mat4(0.14173324, -0.12069898, -0.07242415, 0.105665006, 0.017373435, -0.056042343, 0.07270201, 0.022111928, -0.01106541, 0.01666006, 0.013564169, -0.36628693, -0.25450787, -0.28179473, -0.04721874, -0.21912882) * go_1(0.0, 1.0);
result += mat4(-0.09464695, -0.027919646, 0.13088459, 0.17504032, -0.101641014, 0.29687008, 0.08832321, 0.020538324, -0.15108941, -0.21930224, -0.026915176, -0.07078217, 0.10723033, 0.034364715, 0.18183397, -0.119012214) * go_1(1.0, -1.0);
result += mat4(-0.21713468, -0.0846604, 0.046551514, -0.14989382, 0.08672032, -0.07933831, 0.08093595, -0.064147756, -0.15980323, 0.50000644, -0.091568656, 0.03201994, -0.1848647, -0.0646309, 0.03288009, 0.046442386) * go_1(1.0, 0.0);
result += mat4(0.053532355, -0.054523747, -0.040242642, -0.31438905, 0.06452703, -0.18785381, -0.14987698, -0.067642935, -0.19892459, -0.057256676, 0.05943023, -0.17331842, 0.02588534, 0.13134238, -0.07121775, 0.23446162) * go_1(1.0, 1.0);
result += mat4(0.20633182, 0.01686198, 0.17934167, -0.02063493, 0.042606052, -0.05289458, 0.031508356, 0.00082803797, 0.0756423, -0.047548845, 0.01456339, 0.15910533, -0.20119642, 0.029213727, 0.111036316, -0.047010012) * go_2(-1.0, -1.0);
result += mat4(0.09258436, -0.27904224, -0.086695746, 0.33095327, -0.20126075, -0.050745636, -0.048944805, -0.10536587, -0.012995092, 0.07926994, 0.15071853, -0.13644052, -0.05188447, -0.06750699, -0.14227037, 0.028751127) * go_2(-1.0, 0.0);
result += mat4(-0.18562223, 0.10250865, -0.17573993, 0.20434102, -0.05187468, -0.06441594, -0.052127104, -0.01925564, 0.02927959, -0.12711872, 0.059629507, 0.15696885, -0.010168965, 0.09971862, -0.03177664, -0.022744441) * go_2(-1.0, 1.0);
result += mat4(0.21474063, -0.15679085, 0.09609374, 0.109079376, -0.049934637, -0.07393633, 0.16688468, -0.018888129, 0.04240162, -0.31895876, -0.106516436, 0.20008606, -0.054410245, 0.028970616, -0.18008347, -0.013362003) * go_2(0.0, -1.0);
result += mat4(0.37891293, 0.042730846, -0.24735828, -0.5234527, -0.3681344, -0.06609157, -0.14993733, -0.020316398, 0.123008475, 0.29632482, 0.32149333, 0.35999274, -0.18967044, 0.46154186, -0.016041815, 0.097378336) * go_2(0.0, 0.0);
result += mat4(-0.14873263, 0.07600569, -0.051758345, 0.1803135, -0.23121934, 0.13574593, 0.043973465, -0.13992754, -0.061972607, -0.124083005, -0.049196843, -0.07700431, 0.21572952, -0.25241727, 0.1218322, -0.07773728) * go_2(0.0, 1.0);
result += mat4(0.040287063, 0.024240922, 0.021917762, -0.050616946, -0.023174169, 0.05977014, 0.018892275, 0.04014965, 0.11715485, 0.062129, 0.024620812, 0.013617107, 0.075699426, 0.1858111, -0.11769179, -0.08085602) * go_2(1.0, -1.0);
result += mat4(-0.3194255, 0.08695645, -0.09453595, 0.2564516, 0.02192303, 0.08167247, -0.06257352, 0.043801844, 0.04392246, 0.2020571, 0.045180902, 0.18857521, 0.1835961, -0.043788187, -0.08768916, -0.14755538) * go_2(1.0, 0.0);
result += mat4(-0.22074097, 0.13768476, -0.16183749, 0.059949517, -0.011375954, 0.08581876, 0.004800447, 0.019403988, 0.014646056, 0.07363176, -0.058036458, 0.0706421, 0.08082624, 0.17740329, -0.05484784, 0.050796065) * go_2(1.0, 1.0);
result += mat4(-0.032330472, -0.067666024, 0.18980837, -0.19077848, 0.1111905, 0.03855666, -0.11272314, -0.00577739, 0.17697452, -0.053044144, -0.07510145, 0.061853852, -0.024240626, 0.14846492, 0.14804313, -0.20275854) * go_3(-1.0, -1.0);
result += mat4(0.17133904, -0.16356844, 0.1978664, 0.13877816, 0.28208038, 0.031539194, 0.11313891, -0.0014802719, 0.0033749861, 0.046372313, 0.054808807, -0.0024151779, 0.0068782056, -0.16414621, -0.07545907, -0.2521294) * go_3(-1.0, 0.0);
result += mat4(-0.1746992, -0.037628956, -0.0044012754, -0.004390821, 0.0050341445, -0.112742625, 0.051241755, 0.01984483, 0.0003531837, 0.043500375, 0.030881992, 0.003503799, 0.13611782, -0.02509031, -0.007503557, -0.009321301) * go_3(-1.0, 1.0);
result += mat4(0.087250136, 0.12374122, 0.2959519, 0.11314702, 0.22080182, 0.106726184, -0.29768205, 0.14931595, 0.23356548, -0.008709153, -0.0797829, 0.046940215, -0.07027616, 0.20533602, 0.0723021, -0.1963585) * go_3(0.0, -1.0);
result += mat4(0.00609982, 0.35277408, -0.22781096, -0.28912535, 0.42393112, -0.07654207, 0.12636793, 0.049337976, -0.0967726, -0.19349189, 0.36800626, 0.09745645, 0.47663373, 0.03876107, -0.042987954, 0.016161885) * go_3(0.0, 0.0);
result += mat4(-0.047490966, -0.05823166, 0.036158644, 0.025337253, -0.046618905, 0.108276576, -0.024148034, 0.0026794411, 0.1497962, -0.09328474, -0.03160641, 0.24351281, -0.05198027, 0.030720685, 0.00014528916, -0.2224931) * go_3(0.0, 1.0);
result += mat4(-0.007338369, 0.18710312, 0.14617369, -0.0070655346, 0.10464997, -0.029674934, -0.11842202, -0.09114357, 0.08524458, -0.08082762, 0.06479597, -0.023760766, 0.07523641, 0.0067315935, 0.101266846, -0.2780903) * go_3(1.0, -1.0);
result += mat4(0.14181875, -0.19523518, 0.1068169, -0.10284853, 0.11634046, -0.117397435, 0.09113022, 0.009371062, -0.022120507, -0.1127032, 0.092574745, -0.021989716, -0.088107705, -0.13541982, 0.08130504, -0.0678927) * go_3(1.0, 0.0);
result += mat4(0.09948295, 0.23699793, -0.042369924, 0.16744529, -0.10045506, -0.045623623, 0.04871897, -0.0023967526, 0.02602692, -0.089873284, -0.050681606, -0.09332558, -0.09596149, -0.06988313, 0.0007193808, -0.11936899) * go_3(1.0, 1.0);
result += vec4(-0.04928105, -0.003357327, -0.03886671, 0.076106146);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.13425097, -0.23487093, 0.2480183, -0.2806276, -0.041303713, 0.100773126, -0.110890545, 0.036205858, -0.331331, -0.12929262, 0.16300063, 0.3776673, -0.20316373, -0.011239426, 0.10650887, -0.027857736) * go_0(-1.0, -1.0);
result += mat4(0.09517376, -0.3004956, 0.05033304, -0.07464521, 0.009204248, -0.23034886, 0.30492118, -0.1215848, 0.15728685, -0.10430078, 0.04038878, 0.08034804, 0.04320418, -0.2929594, -0.018968396, 0.02542387) * go_0(-1.0, 0.0);
result += mat4(-0.10651935, -0.2736715, 0.19267319, -0.033337504, -0.06697293, 0.028424729, 0.047814637, 0.44929537, 0.02565344, -0.253426, -0.040931404, -0.05018104, 0.032979824, -0.035349697, -0.039578713, -0.3116414) * go_0(-1.0, 1.0);
result += mat4(0.09176126, 0.031713437, 0.24861507, 0.31351718, 0.36284143, 0.3622709, 0.16165464, 0.07319267, -0.6303202, -0.21209712, -0.02169929, 0.037275597, -0.1295319, 0.033090707, -0.029330662, 0.054679472) * go_0(0.0, -1.0);
result += mat4(0.15021572, -0.15177831, 0.1318225, 0.46864823, 0.059443284, 0.07404233, 0.22612074, 0.21105285, 0.319694, 0.09397257, 0.14277866, -0.0235649, -0.037205156, -0.40715128, -0.18572816, 0.058741573) * go_0(0.0, 0.0);
result += mat4(-0.122751735, -0.20926422, 0.2099333, -0.11627138, 0.04171681, 0.0669586, -0.03831368, 0.27334675, 0.0492008, 0.12854317, 0.03308622, 0.45236585, 0.03122829, 0.13853219, 0.05084764, -0.3965012) * go_0(0.0, 1.0);
result += mat4(-0.0019293908, -0.15562099, 0.12418126, 0.0045440597, 0.05442391, -0.15613738, 0.14828286, -0.17687118, -0.053517755, -0.33350968, -0.062314924, -0.31358472, -0.09670371, 0.043190923, 0.008150662, 0.09928506) * go_0(1.0, -1.0);
result += mat4(-0.06698031, -0.099411525, 0.24259582, -0.1073659, 0.06762824, 0.059605874, -0.20944163, -0.1598055, 0.32746908, -0.17759447, 0.2859796, -0.1274256, 0.30796206, -0.00791448, 0.114059694, 0.14775705) * go_0(1.0, 0.0);
result += mat4(0.16291203, -0.14958477, 0.14716864, 0.2056065, -0.019337546, 0.032286238, 0.0030445335, -0.08208513, -0.14208078, 0.13601872, -0.23367858, -0.19092909, -0.20207883, -0.016950991, 0.009309007, 0.1376546) * go_0(1.0, 1.0);
result += mat4(-0.11093091, -0.32362202, -0.041845415, 0.029758021, -0.07261404, -0.048653398, 0.19167832, 0.09343212, 0.030472826, -0.15078579, -0.0056376588, 0.0045257527, -0.24521805, -0.10473077, 0.11163019, -0.1724187) * go_1(-1.0, -1.0);
result += mat4(-0.08601668, 0.16612485, -0.07751539, 0.07261594, -0.19028407, 0.23896623, -0.10416726, 0.23500614, 0.1955228, 0.08699591, -0.049277775, 0.13447775, 0.19434914, -0.11481196, 0.088043146, 0.13352895) * go_1(-1.0, 0.0);
result += mat4(-0.013221233, 0.07521129, 0.042819552, -0.11163175, 0.066080205, -0.25043094, -0.010348969, -0.013258202, 0.09444396, 0.29623637, 0.025016114, 0.050744686, -0.12219596, -0.0735393, -0.024817836, -0.06897588) * go_1(-1.0, 1.0);
result += mat4(-0.25720942, 0.19861753, -0.18535058, 0.12190362, -0.33756095, -0.0038898317, 0.09739055, 0.41227046, -0.10030796, 0.025445882, -0.23542109, 0.08677691, 0.08140194, -0.22716106, 0.14016968, -0.0927231) * go_1(0.0, -1.0);
result += mat4(0.58745646, -0.12533307, 0.30129984, 0.08898194, -0.07972344, -0.37581098, 0.06863413, -0.13185541, 0.21801205, 0.31779078, -0.3804784, -0.3200699, 0.14534226, 0.05912262, 0.07938948, -0.34869507) * go_1(0.0, 0.0);
result += mat4(0.024675166, -0.067802526, 0.030065436, 0.06509131, 0.14367498, 0.022554757, 0.014991865, -0.029914752, 0.5123549, -0.012557206, -0.13014166, -0.34184244, -0.09080884, 0.13782553, -0.018931886, -0.35642785) * go_1(0.0, 1.0);
result += mat4(-0.37336427, -0.02705006, 0.14392053, 0.024049882, -0.024705589, 0.14556128, -0.12120506, -0.06275598, -0.1284325, 0.11409197, -0.08397436, -0.075944416, 0.056465942, 0.04016099, 0.096723564, -0.08359723) * go_1(1.0, -1.0);
result += mat4(0.20243345, -0.09287934, -0.11676041, 0.005206654, -0.2879361, 0.41677123, -0.16924824, 0.22429213, 0.082279116, -0.1780833, 0.20209241, 0.12970525, -0.030272234, -0.19200714, 0.0015769673, -0.1389732) * go_1(1.0, 0.0);
result += mat4(0.04211243, 0.07331798, -0.055724114, 0.04086206, -0.04635456, 0.027212424, 0.021861525, 0.12424812, 0.43009162, 0.021664696, 0.20828371, 0.11859106, 0.07390811, -0.1861182, 0.034559406, 0.18561925) * go_1(1.0, 1.0);
result += mat4(0.22596797, 0.025346763, -0.056839246, 0.09137385, 0.07363095, -0.12382036, 0.08911783, -0.012355983, -0.07869761, 0.051298574, 0.00816572, -0.044984274, 0.07962154, -0.2254524, -0.007821531, -0.04936664) * go_2(-1.0, -1.0);
result += mat4(0.06265961, -0.17783198, 0.11678783, -0.12965304, 0.014506855, -0.17513473, -0.23593299, 0.14054537, 0.1580306, 0.31872272, -0.0042505316, -0.070422255, -0.01316396, 0.0058355615, 0.062464185, -0.06086727) * go_2(-1.0, 0.0);
result += mat4(-0.079526044, 0.23932967, -0.1139716, 0.15888569, 0.06526993, -0.06958436, -0.04070066, -0.12081254, 0.026716579, 0.014887845, 0.0061467467, 0.127956, 0.040913627, -0.0032820841, 0.086145625, 0.22520025) * go_2(-1.0, 1.0);
result += mat4(0.25577608, 0.02553098, -0.14822578, -0.11907723, -0.09787419, -0.03544863, -0.08098151, -0.01305555, 0.20404844, 0.11294246, 0.10096346, 0.15795162, 0.2554626, 0.09361069, 0.001985862, -0.0051444587) * go_2(0.0, -1.0);
result += mat4(-0.24454486, -0.014714279, -0.2954907, -0.39995646, -0.15907967, 0.30107877, -0.34781745, 0.095281735, -0.12492393, -0.28375402, -0.16872306, 0.2531788, -0.52085644, 0.35986066, 0.07716912, 0.09565738) * go_2(0.0, 0.0);
result += mat4(0.2493129, 0.06395661, -0.09491958, 0.19702488, 0.109871864, -0.051376317, 0.15404263, -0.21282886, 0.1188967, 0.07824094, -0.016752928, -0.14027214, 0.10949832, -0.27629098, 0.081909016, 0.1354018) * go_2(0.0, 1.0);
result += mat4(0.18950915, -0.034574565, -0.10378051, -0.15800652, -0.06835184, -0.06987467, 0.035007782, 0.04686656, 0.054061133, 0.014833506, -0.0035361175, 0.016156103, 0.120767444, -0.10196722, 0.10668838, -0.09058739) * go_2(1.0, -1.0);
result += mat4(-0.032248627, 0.056413256, 0.042716432, 0.06681831, 0.047605485, -0.07629479, 0.14311917, -0.06909803, 0.10640394, 0.10701861, -0.0051839007, -0.15133362, -0.32146424, -0.039978918, -0.12280021, 0.0048507582) * go_2(1.0, 0.0);
result += mat4(-0.1954503, -0.09257865, 0.11023244, -0.01817947, -0.0035485283, -0.015536726, 0.0071826433, 0.042538714, -0.015454641, 0.079593316, -0.07242554, 0.031178504, 0.2319168, -0.10519467, 0.013837495, -0.040088437) * go_2(1.0, 1.0);
result += mat4(0.12625901, 0.04531166, 0.038758352, -0.05790713, -0.10029771, -0.118265375, -0.23944628, 0.11955388, 0.070732996, 0.19404806, -0.019913414, 0.04609079, 0.06262817, 0.022330387, -0.029681094, 0.03719176) * go_3(-1.0, -1.0);
result += mat4(-0.07737922, 0.0024623116, -0.037666153, -0.19271135, -0.015002153, -0.0059966356, 0.0024538909, -0.0401021, -0.18540399, -0.11140236, -0.11102473, -0.06390247, 0.016754225, 0.35000673, -0.19139731, 0.07363001) * go_3(-1.0, 0.0);
result += mat4(0.02150171, -0.2311761, -0.025124706, 0.16819553, -0.0013348719, 0.32091036, -0.061826598, 0.12579474, -0.036611024, -0.018266583, -0.11280143, 0.11073158, 0.050171874, -0.14706045, 0.029553955, 0.0052631944) * go_3(-1.0, 1.0);
result += mat4(0.19249865, -0.22854832, 0.09472751, 0.014705341, 0.059496958, 0.13427268, -0.06309558, -0.07153743, -0.31890163, -0.0657967, -0.040345218, 0.09544393, 0.07359761, 0.11245483, 0.00033233972, 0.031550154) * go_3(0.0, -1.0);
result += mat4(-0.24668917, -0.37181908, -0.50614715, -0.101197146, -0.1569055, 0.27734125, 0.17144768, -0.04336267, 0.03658949, 0.06747124, 0.30720958, 0.56301194, -0.11314631, -0.29258573, 0.16256689, 0.5221001) * go_3(0.0, 0.0);
result += mat4(-0.022761503, 0.13063031, 0.002526217, -0.03466151, -0.15225072, 0.40217137, -0.089131154, 0.19195192, -0.1379853, -0.04640692, 0.104670234, 0.12268618, -0.012009209, -0.20534724, 0.028777445, 0.22195113) * go_3(0.0, 1.0);
result += mat4(0.23697586, 0.08793654, -0.10565018, 0.013993297, -0.025932996, -0.13859354, 0.14333159, -0.099132575, -0.049601994, -0.0917448, -0.0021633878, -0.009032609, -0.034750953, -0.30761167, 0.058994945, -0.19427797) * go_3(1.0, -1.0);
result += mat4(-0.26944515, 0.30523893, -0.17787015, 0.10827742, 0.06457236, -0.12202401, 0.15371302, 0.011699893, -0.06253491, -0.10976804, -0.37283847, -0.23996784, -0.2750512, -0.024101513, -0.094127975, -0.17462716) * go_3(1.0, 0.0);
result += mat4(-0.026286924, 0.06250577, 0.095423855, -0.02849258, -0.12916361, -0.10954709, -0.05825132, -0.102924265, -0.19550376, -0.11730307, 0.032346163, -0.17682706, 0.16651174, 0.031927045, -0.004800601, -0.06323844) * go_3(1.0, 1.0);
result += vec4(0.0095873345, 0.04959374, -0.15246227, 0.0044831373);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!SAVE conv2d_5_tf1
//!WIDTH conv2d_4_tf.w
//!HEIGHT conv2d_4_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(-0.021453971, -0.108874515, 0.0005208881, -0.09774453, -0.0053757126, 0.20114918, 0.24454592, 0.04932251, -0.0037210248, -0.0240578, -0.07736935, 0.27604944, -0.12430849, -0.13093218, -0.014840212, 0.13450128) * go_0(-1.0, -1.0);
result += mat4(-0.19143668, -0.23023333, -0.10232715, 0.24396868, 0.056112397, 0.14535592, -0.25882182, -0.26274678, -0.23119931, 0.07735849, -0.14785223, -0.21026523, -0.2064457, -0.34512606, -0.17808662, 0.30146623) * go_0(-1.0, 0.0);
result += mat4(0.0072161015, -0.013303738, 0.07591899, 0.027883789, 0.210858, 0.1422139, -0.027882019, 0.2618474, -0.048504543, 0.07377317, -0.05427271, -0.10014041, -0.12974857, -0.13140713, -0.02249253, 0.08203184) * go_0(-1.0, 1.0);
result += mat4(0.07855138, -0.13984342, 0.10037151, -0.056781758, 0.24686107, -0.0048190085, -0.2693424, 0.31722167, -0.28716075, -0.06422215, -0.06738793, -0.06723655, -0.08194382, -0.007975044, 0.20108353, -0.13338897) * go_0(0.0, -1.0);
result += mat4(0.35129568, 0.27930936, 0.024239251, -0.10712293, 0.48684034, -0.04380574, -0.0064479653, 0.03754327, -0.13139078, -0.44939983, -1.0460628, -0.016004754, -0.14476573, -0.07113434, 0.515311, -0.400374) * go_0(0.0, 0.0);
result += mat4(0.13104302, -0.23410062, 0.091530964, -0.003652217, 0.16696814, 0.16406855, -0.08138474, 0.047526445, 0.25358474, 0.37850454, 0.0362802, -0.046476766, -0.093869686, -0.4143772, 0.08641024, 0.115896136) * go_0(0.0, 1.0);
result += mat4(-0.04416574, -0.052188106, 0.05141859, -0.008132604, -0.013658864, 0.1021097, 0.19391364, -0.09257973, 0.15225394, -0.16920799, -0.16172324, 0.41466942, -0.07087308, 0.08632938, -0.07496043, -0.023530172) * go_0(1.0, -1.0);
result += mat4(0.09337352, 0.062108494, -0.219173, -0.046151914, 0.22507025, -0.08966131, -0.123690315, 0.08666376, -0.10731867, -0.08518657, 0.024199447, 0.17898631, 0.120247275, 0.089923285, -0.08756211, 0.1775775) * go_0(1.0, 0.0);
result += mat4(0.20326594, -0.060535498, -0.061659336, 0.113954924, -0.073462196, 0.15917051, 0.11728326, -0.072256014, -0.0752342, 0.06265616, -0.19494365, -0.25413772, -0.06641352, -0.015642308, 0.16825356, 0.0027654327) * go_0(1.0, 1.0);
result += mat4(-0.17029639, -0.05388927, -0.13159063, 0.0795609, 0.00501164, -0.0703107, -0.08229201, 0.07546247, 0.092942156, 0.059050936, -0.07987315, 0.010874322, 0.037708692, -0.0017377702, -0.030414931, 0.28946167) * go_1(-1.0, -1.0);
result += mat4(-0.2692667, 0.2258295, 0.062060453, 0.1934921, -0.023051793, -0.038611185, 0.21473692, 0.33520013, 0.029885106, 0.103782356, 0.05217351, -0.13349791, -0.034186684, -0.3015818, 0.033423528, 0.21218027) * go_1(-1.0, 0.0);
result += mat4(-0.013587494, 0.021273775, -0.022650799, -0.011939531, -0.11202949, 0.09365859, -0.042938907, -0.009910716, 0.27254924, 0.07752608, 0.029586637, 0.024899973, 0.04375618, 0.31453863, -0.006775175, 0.008228053) * go_1(-1.0, 1.0);
result += mat4(-0.49562672, -0.12472124, -0.13618441, 0.09660054, -0.2275429, -0.0902811, 0.18311924, 0.11677185, -0.13325182, -0.061613016, -0.011462703, -0.12538978, 0.054934092, 0.06742866, 0.25515345, 0.35692096) * go_1(0.0, -1.0);
result += mat4(0.5266911, -0.09655596, -0.41069564, -0.3174325, 0.1431904, -0.17732115, -0.36320353, 0.37975433, -0.5158582, -0.21019879, 0.06852925, -0.06648648, -0.18956456, -0.018139647, 0.35707653, 0.07378416) * go_1(0.0, 0.0);
result += mat4(0.04151976, -0.037361674, 0.06936584, -0.10462262, -0.22264048, -0.043842267, -0.12742832, -0.21778631, 0.0715335, -0.17921853, -0.3856251, -0.16335362, 0.21045755, -0.5026229, 0.14405337, 0.23096423) * go_1(0.0, 1.0);
result += mat4(-0.32437655, 0.07860345, -0.0021187086, 0.123870686, -0.16616751, 0.11004699, 0.04754715, -0.0075211064, -0.08026408, 0.04284957, -0.018143758, 0.032623176, 0.06614686, -0.035856936, 0.13667971, -0.15696613) * go_1(1.0, -1.0);
result += mat4(0.11260625, 0.03274457, -0.033769324, -0.11558525, -0.35377702, 0.0019119612, 0.24906515, -0.06853208, 0.0009843144, -0.0050376705, 0.063123666, 0.009872904, 0.19592324, 0.0028321196, -0.114693984, 0.16404222) * go_1(1.0, 0.0);
result += mat4(-0.03699667, 0.011842293, -0.12273219, 0.04081692, 0.008484447, -0.052331816, 0.07151068, 0.018538639, 0.077749036, 0.07189092, 0.22443593, -0.2436085, 0.023654116, -0.05127411, 0.27350748, 0.12180999) * go_1(1.0, 1.0);
result += mat4(0.16090482, 0.059198547, 0.04856637, -0.19173436, 0.12747662, -0.079715036, -0.20203276, -0.13818277, -0.123076215, -0.07168488, 0.0644838, 0.03524764, 0.0005124138, -0.06789178, 0.048645556, -0.098922126) * go_2(-1.0, -1.0);
result += mat4(0.29220074, 0.25197285, 0.09825887, 0.030363245, -0.033246458, -0.08370418, -0.12231589, -0.023000835, 0.082732, -0.16907515, -0.052518822, 0.07991363, 0.06222654, -0.06747275, -0.18931144, -0.42009747) * go_2(-1.0, 0.0);
result += mat4(0.02667354, 0.03842717, -0.012755562, 0.061840586, 0.01060547, -0.29081437, 0.010907111, 0.07930905, 0.12273201, 0.017574295, 0.051024225, 0.019036688, 0.07671181, 0.049130872, -0.09734168, -0.070569195) * go_2(-1.0, 1.0);
result += mat4(0.08517651, 0.0767222, -0.15657257, 0.18501835, -0.13749431, -0.2833894, 0.109219365, 0.033763003, 0.18988928, 0.13461404, -0.036578514, -0.13256857, -0.097819485, -0.17316358, -0.06512401, 0.1937444) * go_2(0.0, -1.0);
result += mat4(-0.32173568, -0.072075866, 0.13004705, -0.15507852, -0.23741087, -0.29364398, 0.10723945, -0.11976219, 0.20620506, 0.17970093, 0.24463713, -0.12555319, -0.021192182, -0.1374317, 0.5359718, 0.59974134) * go_2(0.0, 0.0);
result += mat4(-0.01101575, 0.040466793, -0.009630791, 0.13422947, -0.13290837, -0.24789505, -0.061713737, -0.07838521, 0.05379315, -0.14643523, -0.09155805, -0.049997047, 0.06696885, 0.20043123, -0.07542329, -0.08041673) * go_2(0.0, 1.0);
result += mat4(0.022160506, 0.01611432, -0.10189221, -0.022767285, -0.06682965, 0.047138248, 0.06860934, -0.012574086, 0.04010214, -0.041280016, -0.034621384, -0.018262599, 0.09731754, -0.059062295, 0.14786182, -0.15185094) * go_2(1.0, -1.0);
result += mat4(-0.052484483, 0.06899427, 0.18380043, -0.058414727, 0.07685985, -0.07206598, -0.101362616, -0.012002652, 0.008517392, 0.079471916, -0.30394664, 0.028600946, -0.03270232, -0.23564856, 0.045065008, -0.0034684737) * go_2(1.0, 0.0);
result += mat4(-0.049757, 0.07614825, 0.16394803, 0.027053174, 0.0451278, -0.09351286, -0.0042182617, 0.12332257, -0.025281021, -0.03843008, 0.12857373, -0.07611989, -0.0062898803, 0.022618141, -0.13122174, -0.03328411) * go_2(1.0, 1.0);
result += mat4(0.12251631, 0.047008447, 0.027589995, -0.12207328, -0.1510795, 0.06724553, 0.17066906, 0.16992114, -0.0026905634, -0.035480864, 0.033738773, 0.018674552, 0.028614907, -0.019945908, -0.0156899, -0.09562145) * go_3(-1.0, -1.0);
result += mat4(0.116588935, 0.14205505, 0.099545434, -0.045527786, -0.049273346, 0.20760757, 0.053965498, -0.12198069, -0.14654607, 0.041820496, 0.038068503, 0.24565905, 0.09786504, 0.18309233, 0.23802327, -0.085740186) * go_3(-1.0, 0.0);
result += mat4(-0.1262052, -0.011846116, -0.058820397, -0.019373653, -0.09569547, -0.08265971, -0.05178388, -0.020502446, -0.17525336, -0.22874829, 0.0075891856, -0.189923, 0.09809122, 0.109637566, -0.0005973885, -0.06477921) * go_3(-1.0, 1.0);
result += mat4(0.28209856, 0.11276813, 0.054377034, -0.00891202, -0.095922634, 0.071109876, -0.039932176, -0.047409832, -0.06504704, 0.11923986, 0.0013364811, -0.122095086, -0.20282102, -0.022717483, -0.115474045, 0.020858249) * go_3(0.0, -1.0);
result += mat4(-0.16130303, 0.072821185, -0.021358958, -0.11687897, -0.15543966, 0.05783285, 0.10317231, -0.12240756, 0.053357504, -0.090291016, -0.21943556, 0.46947235, 0.19072579, 0.017349033, -0.55443907, -0.10510661) * go_3(0.0, 0.0);
result += mat4(-0.4155687, 0.019206723, -0.20055711, 0.028732464, -0.1981807, 0.20637372, 0.03305817, -0.17949893, -0.21051097, 0.21483344, 0.0061496794, -0.48980987, -0.26750582, 0.09230394, -0.117223755, -0.07636286) * go_3(0.0, 1.0);
result += mat4(0.20611528, -0.00095511036, -0.21555157, -0.07065484, 0.06880411, 0.068082534, -0.10104979, 0.16050354, -0.07437897, -0.13145325, -0.017651044, 0.055096775, -0.05443345, -0.018634815, -0.011232755, -0.10835) * go_3(1.0, -1.0);
result += mat4(-0.2637829, 0.07681072, 0.015995527, 0.004554211, 0.07495561, 0.18873464, -0.14303622, 0.25786543, -0.14117226, -0.008715274, -0.17176823, -0.0006595096, -0.06566383, -0.19184378, -0.18945406, 0.20968987) * go_3(1.0, 0.0);
result += mat4(-0.03293623, 0.003399063, 0.08051177, -0.0072856937, -0.07375858, 0.075319655, -0.10791501, -0.002204552, -0.093564905, -0.122712255, -0.10658267, -0.015067637, -0.033247817, 0.09952069, -0.13724248, 0.068189256) * go_3(1.0, 1.0);
result += vec4(-0.001935585, 0.05018077, -0.0154469935, -0.034524206);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.0053346683, 0.010174534, -0.050979972, -0.06134544, -0.007238652, -0.012790015, 0.036398683, -0.09181499, 0.11328388, -0.14236617, -0.17519625, -0.34661606, 0.008069393, -0.028871074, -0.02980949, -0.0853359) * go_0(-1.0, -1.0);
result += mat4(-0.05187267, -0.09381704, 0.035209883, 0.29482442, -0.0018002815, -0.029504262, 0.2609028, -0.09480671, -0.0737553, -0.070559524, 0.081991084, 0.1513024, 0.048344653, -0.09336617, 0.0034569732, 0.10530542) * go_0(-1.0, 0.0);
result += mat4(-0.06749591, 0.0065624053, 0.013237342, 0.14225115, 0.27183163, -0.15656447, 0.031672053, 0.009592649, -0.0202286, 0.26220062, 0.19387855, -0.18505628, 0.040554795, 0.07295961, -0.13291295, -0.12600344) * go_0(-1.0, 1.0);
result += mat4(0.039192002, 0.0846215, -0.06593224, 0.28147796, 0.06301313, 0.26323164, -0.16742979, 0.22004774, -0.17470881, 0.060716614, 0.15430811, 0.18970133, 0.08858931, -0.027321626, -0.037833836, 0.07344837) * go_0(0.0, -1.0);
result += mat4(0.0633813, 0.35046157, -0.101075254, 0.015974075, 0.19010352, -0.7135035, -0.24324696, -0.42072615, 0.06825536, -0.052808974, 0.28965715, -0.0015640302, -0.27062586, 0.4279925, 0.035623744, 0.46321228) * go_0(0.0, 0.0);
result += mat4(0.02639867, 0.26469797, -0.09086266, 0.07440796, -0.192054, 0.1010368, -0.04398074, 0.056824226, -0.27057743, -0.20455118, 0.19338831, -0.21843775, 0.20736177, -0.26259273, -0.07667085, -0.19504389) * go_0(0.0, 1.0);
result += mat4(-0.007056104, 0.04284205, 0.01933733, 0.07267832, 0.0012616975, -0.30140647, -0.019223223, -0.046687007, -0.037844718, -0.014929125, 0.022630794, 0.046716493, 0.057279173, -0.08055539, -0.027891241, 0.019557232) * go_0(1.0, -1.0);
result += mat4(0.035518404, -0.10087327, 0.0011048123, -0.123707846, 0.37190285, 0.43751532, -0.022599256, -0.041709043, 0.11357196, -0.029839104, -0.056960747, -0.17228557, 0.08558022, 0.046361133, 0.021548864, 0.24297418) * go_0(1.0, 0.0);
result += mat4(-0.043598346, -0.09812348, 0.056599542, -0.09833163, -0.07193007, 0.015760094, -0.053177495, -0.015448543, 0.035163186, -0.03889347, 0.121799015, 0.15738566, -0.115644835, 0.043310717, 0.060173217, -0.059635755) * go_0(1.0, 1.0);
result += mat4(-0.111604795, 0.1678389, 0.049967546, 0.045353863, -0.013896185, 0.035128903, 0.040686198, -0.16442506, 0.1149577, -0.14343217, -0.08858, 0.02656137, 0.059526477, -0.13914491, 0.12757027, 0.034920372) * go_1(-1.0, -1.0);
result += mat4(0.15849945, 0.12067003, -0.1579611, 0.30790725, -0.041249942, 0.03948043, -0.12535375, -0.02566875, 0.3150059, 0.027081972, -0.026308673, -0.25326517, 0.016824603, -0.13551097, 0.1412756, 0.037750524) * go_1(-1.0, 0.0);
result += mat4(0.1562541, -0.041948073, -0.14951487, 0.119380556, -0.21773878, -0.019281754, 0.08185942, 0.09982689, 0.017187534, -0.18181366, -0.09270861, 0.08527679, 0.051988564, 0.08686172, -0.12665209, -0.07205808) * go_1(-1.0, 1.0);
result += mat4(0.08860466, -0.17931758, 0.10191625, -0.47623265, 0.1562338, -0.2960855, 0.013664795, 0.29452285, 0.1463958, 0.17562817, -0.41623253, -0.196999, -0.049113005, 0.0556021, 0.054452494, 0.14073615) * go_1(0.0, -1.0);
result += mat4(-0.5345973, -0.069205046, 0.37001884, 0.6955835, 0.22635284, -0.09021557, -0.04693607, -0.4458824, 0.25049326, -0.06503396, 0.07584689, 0.5394811, 0.33387923, -0.010540017, 0.038980547, -0.13371105) * go_1(0.0, 0.0);
result += mat4(-0.04414677, -0.22056313, 0.05580458, 0.11914465, 0.19864987, -0.1025625, -0.010050287, 0.15919746, -0.40589634, 0.4966349, -0.47632688, -0.022637444, 0.17247641, -0.51093113, 0.21157944, -0.2890017) * go_1(0.0, 1.0);
result += mat4(-0.034673482, -0.0075900992, -0.061077584, -0.03859898, 0.32444152, -0.14619137, -0.1375446, -0.030322462, 0.029679669, 0.079344586, -0.03862258, -0.05766807, 0.104488336, 0.006179548, -0.1168102, 0.069729604) * go_1(1.0, -1.0);
result += mat4(0.08504003, 0.042162962, -0.17509954, -0.06258286, -0.45796555, -0.061748773, 0.25438437, -0.02988987, -0.06897794, 0.105180845, -0.08879189, -0.120605074, -0.1478659, -0.13201937, -0.01755498, 0.020606143) * go_1(1.0, 0.0);
result += mat4(0.08932581, 0.1453785, -0.12802933, 0.049442187, 0.045360584, 0.16079827, -0.14142223, 0.10168491, 0.20244479, -0.17981426, 0.19759466, 0.05217847, 0.04889828, 0.06941533, -0.111836776, -0.08046399) * go_1(1.0, 1.0);
result += mat4(-0.011953735, 0.11362504, -0.122588776, -0.10408559, 0.051712614, -0.05161036, -0.068698496, -0.015663281, -0.06346889, 0.06561636, 0.03783044, 0.02756004, -0.036310352, -0.16962235, -0.062494226, 0.0069608325) * go_2(-1.0, -1.0);
result += mat4(-0.16857432, -0.17322211, 0.15971284, 0.19980437, -0.007965961, -0.015480705, 0.036090557, 0.07414387, -0.2941106, -0.24430539, 0.01070864, 0.22401866, -0.34321144, 0.09537491, -0.08020218, 0.45404655) * go_2(-1.0, 0.0);
result += mat4(-0.021609096, -0.11348408, -0.01450652, -0.063170746, 0.06990935, -0.035983834, -0.038010992, -0.10578655, 0.29232737, 0.048835874, 0.054028947, -0.12924139, -0.03058583, 0.028469706, 0.09563202, 0.085674495) * go_2(-1.0, 1.0);
result += mat4(-0.01894022, 0.037628658, -0.102314636, -0.28041583, 0.07495663, -0.058895253, 0.16422969, -0.07163792, 0.039416216, -0.13800906, -0.039811566, -0.10612402, -0.047593113, -0.28491783, 0.41632858, 0.15253194) * go_2(0.0, -1.0);
result += mat4(0.26240867, -0.05335849, -0.014135048, 0.055749495, -0.020126658, 0.2952794, -0.015241771, 0.36143306, 0.43075684, 0.1921996, -0.4329065, 0.5114495, 0.7326109, -0.054901246, -0.076693356, -0.26104695) * go_2(0.0, 0.0);
result += mat4(0.14548428, 0.14578429, 0.17193514, -0.07973242, 0.011952286, -0.047767498, 0.025101405, 0.0016503566, -0.26948047, -0.16503395, -0.061791085, 0.030557185, 0.15400517, -0.054951698, -0.14611247, 0.3550633) * go_2(0.0, 1.0);
result += mat4(-0.05926111, -0.083442695, 0.046579204, -0.017723244, 0.12846185, 0.018434443, -0.17914511, -0.077696435, 0.060048338, 0.02956987, -0.11914462, 0.057770032, -0.054673657, -0.005353606, -0.39014184, 0.08306877) * go_2(1.0, -1.0);
result += mat4(0.07357362, 0.23051825, -0.22640751, 0.080715515, -0.14467078, 0.009734264, 0.054320686, 0.24534328, -0.16038458, 0.06575425, 0.058553413, 0.17755087, 0.08184439, 0.17078212, 0.148369, -0.09309279) * go_2(1.0, 0.0);
result += mat4(-0.11160211, -0.07590204, -0.01676188, -0.062253337, 0.016433533, 0.0146132, -0.040350936, 0.06749202, -0.031521842, 0.1441664, -0.09916073, 0.050578352, -0.06560962, -0.31174552, 0.056873083, -0.077912) * go_2(1.0, 1.0);
result += mat4(0.09344025, 0.075936995, -0.1627903, -0.04781558, -0.01878236, 0.045879602, -0.11507387, -0.025356822, -0.09113391, 0.07263937, 0.08232447, 0.08727616, -0.024921807, 0.051639438, 0.006532631, -0.018751068) * go_3(-1.0, -1.0);
result += mat4(0.022455849, -0.12924309, 0.26318657, -0.32464805, -0.09627585, 0.04496843, -0.09630052, -0.025761643, -0.090804085, 0.24410398, -0.03162944, -0.1961483, 0.14065808, -0.064709485, -0.0040163463, 0.05445074) * go_3(-1.0, 0.0);
result += mat4(-0.020935195, -0.1028065, 0.0012804621, 0.02302866, -0.00924972, -0.0041193594, 0.0060590385, -0.003394384, -0.23241943, -0.023235107, 0.08077456, 0.15720141, 0.06568382, -0.09971436, 0.09056065, 0.04271102) * go_3(-1.0, 1.0);
result += mat4(-0.20997737, -0.12892777, 0.4658528, 0.13622813, -0.2867294, -0.09359254, 0.18821026, 0.25550604, -0.18562363, 0.080713026, 0.13463654, 0.045504905, -0.013133853, -0.1316404, 0.08379897, -0.00047142128) * go_3(0.0, -1.0);
result += mat4(0.3276134, 0.21952826, -0.80777377, -0.69810224, 0.34190908, -0.09293263, 0.33313555, -0.27255502, -0.24287084, -0.07741488, 0.06090265, -0.10161252, -0.37684909, 0.4678029, 0.13506591, 0.42470258) * go_3(0.0, 0.0);
result += mat4(0.080790855, -0.09707547, -0.05506975, 0.027011644, -0.1434346, 0.01363872, 0.12616752, 0.16789167, 0.1656414, -0.11586835, 0.059612263, -0.074029386, -0.19813071, 0.46032718, -0.03935981, 0.0067143585) * go_3(0.0, 1.0);
result += mat4(0.10322512, 0.0822636, -0.16766444, 0.041008063, -0.027768405, 0.23103505, 0.06737122, 0.15258405, 0.04557388, -0.18179403, 0.12489025, -0.09759324, -0.05925805, 0.04869987, 0.07329833, -0.09738542) * go_3(1.0, -1.0);
result += mat4(-0.10823879, -0.403376, 0.3264802, -0.16503738, -0.057512645, -0.20902547, -0.14862378, -0.3192005, -0.046263676, 0.12744917, -0.019174274, -0.02318789, -0.085088454, -0.05723332, 0.0039772973, 0.07991316) * go_3(1.0, 0.0);
result += mat4(0.10313916, 0.04410904, 0.03286652, 0.059946325, 0.019948404, 0.070217304, -0.017572487, 0.20332281, 0.06776308, 0.029285522, -0.14116238, -0.05864782, -0.18382367, -0.06568212, 0.11855615, 0.101256005) * go_3(1.0, 1.0);
result += vec4(-0.036374483, 0.029420665, 0.04437756, -0.04474691);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x3x3x16
//!HOOK MAIN
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!SAVE conv2d_6_tf1
//!WIDTH conv2d_5_tf.w
//!HEIGHT conv2d_5_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))
#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))
vec4 hook() {
vec4 result = mat4(0.059325468, 0.10884231, 0.018158086, 0.031802185, 0.10368743, -0.06776637, 0.048326045, -0.06312353, -0.0025675546, 0.09309577, -0.025533969, 0.029684044, 0.017237723, 0.062099144, 0.047039766, 0.050348036) * go_0(-1.0, -1.0);
result += mat4(-0.04767078, -0.06409279, 0.112965874, 0.04621161, -0.28172916, -0.13897015, -0.022806352, 0.26966885, 0.02019569, -0.10707113, -0.43058416, -0.14103983, -0.13225646, -0.020053176, -0.17319782, -0.009653082) * go_0(-1.0, 0.0);
result += mat4(0.0031349238, -0.060933832, 0.107986666, -0.019791966, -0.23946726, -0.18045186, 0.18286318, -0.05431065, 0.11742379, -0.019123906, 0.33327517, 0.07455424, -0.035427105, 0.18659347, -0.050884776, 0.019193258) * go_0(-1.0, 1.0);
result += mat4(-0.22954239, 0.011265787, -0.026520751, -0.12629737, -0.07009803, 0.44925988, -0.15938939, 0.11956771, 0.11535644, -0.1302371, 0.1235775, 0.16483483, 0.022965495, 0.110546246, 0.00064579415, -0.12753843) * go_0(0.0, -1.0);
result += mat4(0.047553673, 0.16213869, 0.7510964, 0.21228868, 0.40994287, 0.61919236, 0.3982374, -0.016163021, 0.3291035, 0.1134356, 0.12384387, -0.31114763, 0.21338554, -0.04721641, 0.122114286, 0.2717476) * go_0(0.0, 0.0);
result += mat4(-0.06529201, -0.08936482, 0.031857736, -0.02372691, 0.0416097, 0.28484538, -0.38181338, -0.05129518, 0.40150553, -0.01970737, 0.1043854, 0.11986372, -0.2267319, 0.0014845231, -0.035269983, 0.11712099) * go_0(0.0, 1.0);
result += mat4(0.079867415, -0.09982735, 0.10313241, 0.055490237, -0.42685422, -0.3431141, -0.06037366, 0.17539841, -0.010511819, -0.09743252, 0.050748866, 0.11064108, -0.09785722, -0.10230299, -0.04106169, -0.016831731) * go_0(1.0, -1.0);
result += mat4(-0.06847075, -0.026447225, -0.123430386, 0.063637204, -0.37617612, -0.09615662, -0.26226708, -0.008175561, -0.08101131, 0.11093525, -0.13149206, -0.06363292, -0.0482858, -0.2771799, 0.10528571, 0.119109035) * go_0(1.0, 0.0);
result += mat4(0.09151277, 0.029019276, 0.041349206, -0.011239478, 0.035083957, 0.05281079, -0.0742173, -0.018509442, -0.17175299, -0.4226507, -0.118186444, -0.0771296, 0.107038856, 0.0819975, 0.12445646, 0.07091557) * go_0(1.0, 1.0);
result += mat4(0.1275357, -0.097659886, -0.0114354445, 0.023900568, -0.02511702, 0.005830569, -0.010882143, -0.04046068, -0.08638482, 0.08664022, -0.15654318, 0.03333846, -0.12521335, -0.11987078, 0.028556254, -0.020760164) * go_1(-1.0, -1.0);
result += mat4(-0.38474286, -0.15288061, 0.04925842, 0.050009686, 0.23555282, 0.054784663, -0.0971203, 0.017791113, -0.35539824, -0.08806168, 0.08992579, 0.22714761, -0.047685623, -0.17510797, 0.1137738, -0.069451034) * go_1(-1.0, 0.0);
result += mat4(-0.16623408, -0.08202571, -0.03291826, 0.0016267949, 0.20682698, 0.08788948, 0.10241089, 0.019209227, -0.14802241, 0.091788374, -0.238735, -0.06633396, 0.02360112, 0.1521805, -0.022510838, -0.08931379) * go_1(-1.0, 1.0);
result += mat4(0.034280665, -0.12431295, 0.092791, 0.15279225, -0.43373865, 0.20077267, -0.15919733, -0.27969292, -0.26948065, 0.19652127, -0.27456176, 0.04137772, 0.006545539, 0.0031402514, 0.03849979, -0.10978278) * go_1(0.0, -1.0);
result += mat4(0.62025917, -0.32462567, 0.2817292, -0.18380783, -0.3338593, -0.49056754, 0.32645953, 0.4146035, 0.3773462, 0.54346967, -0.032203436, -0.14506778, -0.30044907, 0.40134314, 0.24155408, 0.24397472) * go_1(0.0, 0.0);
result += mat4(0.089335114, -0.05529855, -0.18364899, -0.153323, -0.18347202, -0.060125064, -0.29216367, -0.2717291, 0.10592963, 0.38889876, 0.25363386, 0.33723134, -0.103703365, 0.14922962, -0.21206948, -0.20289616) * go_1(0.0, 1.0);
result += mat4(-0.035760924, 0.18820894, -0.12723185, -0.018780319, 0.124459654, 0.28909087, -0.2763883, -0.45110545, 0.098143585, 0.16052029, -0.055098705, -0.14840914, -0.0019514654, 0.07090622, -0.055036955, -0.0035953245) * go_1(1.0, -1.0);
result += mat4(-0.124669634, 0.23131305, -0.05750295, -0.056296032, 0.35691026, 0.2640789, 0.49912274, 0.26795143, -0.26460487, -0.026896512, -0.07179325, 0.17373477, -0.13186656, 0.0021319336, -0.016407885, 0.3014283) * go_1(1.0, 0.0);
result += mat4(-0.09491939, 0.11503968, -0.14077829, -0.043197304, -0.061866064, -0.1574549, 0.0054375776, 0.066160634, -0.17686372, -0.26767558, -0.038844116, 0.122724466, -0.05043839, 0.063884266, 0.0064002997, -0.13583377) * go_1(1.0, 1.0);
result += mat4(0.031301867, -0.02947819, -0.0016769855, 0.12952408, -0.025022922, 0.065425046, -0.072289295, -0.071249105, 0.14579567, -0.09058119, 0.12663712, 0.1515388, 0.44767743, 0.02971349, 0.015892735, -0.08058422) * go_2(-1.0, -1.0);
result += mat4(-0.2868111, -0.10812653, -0.29182926, -0.38444322, -0.0875354, -0.07220258, 0.05978065, 0.093328245, 0.058548283, -0.013913258, -0.20954674, -0.16400063, 0.3185215, 0.068897314, 0.15869021, 0.022877626) * go_2(-1.0, 0.0);
result += mat4(0.116845705, -0.12729645, 0.056697316, -0.21263942, -0.07000074, 0.073977455, -0.09006404, -0.029770354, -0.20823102, -0.20088868, 0.15658094, 0.24306639, -0.0453592, -0.16011035, 0.08521533, -0.032264974) * go_2(-1.0, 1.0);
result += mat4(0.1114789, -0.1083731, 0.10465276, -0.08903837, -0.06455987, 0.040030345, -0.07937248, -0.20654759, -0.26873547, -0.19390975, -0.039021965, -0.025602374, -0.5575801, -0.08876011, -0.19116728, -0.2401055) * go_2(0.0, -1.0);
result += mat4(0.37626424, -0.0912155, -0.6153361, -0.71465075, 0.018208932, -0.14997734, 0.23627761, 0.20832567, 0.07427123, -0.37869486, -0.26574427, 0.187582, -0.37201726, 0.17809474, -0.02568795, 0.23900814) * go_2(0.0, 0.0);
result += mat4(-0.085337594, -0.50634587, 0.30636734, -0.2760558, 0.01893911, -0.08425695, -0.023656169, 0.021421626, 0.16813251, -0.039550815, 0.21165498, -0.027628547, -0.123874225, 0.013802332, -0.2732087, -0.09419671) * go_2(0.0, 1.0);
result += mat4(-0.07190724, -0.019237598, 0.020249542, 0.07541295, -0.03817686, 0.09266451, -0.12214172, -0.01344174, 0.03281797, 0.057655178, -0.059896503, 0.014948791, -0.13952477, 0.18810949, -0.19016883, 0.06842416) * go_2(1.0, -1.0);
result += mat4(-0.13111524, 0.14539744, -0.10212538, -0.2169032, 0.13810973, -0.12576458, 0.124372825, 0.04992259, 0.21758182, -0.22160134, 0.24321079, 0.017698256, 0.39995426, 0.074034885, 0.120019354, -0.15522505) * go_2(1.0, 0.0);
result += mat4(0.023914235, 0.1424257, 0.010302871, 0.15150794, -0.040021677, 0.015862139, 0.14459212, 0.08632827, 0.04257336, 0.055059638, 0.0030461506, 0.011985334, -0.049230937, 0.07851301, -0.05119983, -0.111701734) * go_2(1.0, 1.0);
result += mat4(0.04485158, 0.116597414, 0.00014909732, -0.012128512, 0.15801767, 0.18273115, -0.033926453, 0.05170487, -0.040683754, -0.18606974, 0.08324687, 0.069539666, 0.07098698, -0.014132968, 0.029499048, -0.07263477) * go_3(-1.0, -1.0);
result += mat4(0.04309544, 0.089722805, -0.018306322, 0.29061043, 0.15191254, 0.15917647, 0.0073858183, 0.039199475, 0.42514518, -0.053955313, 0.10820046, -0.09134685, -0.3087313, -0.16339037, -0.05226669, 0.044995327) * go_3(-1.0, 0.0);
result += mat4(0.008636428, 0.029086163, -0.09151674, -0.36466715, -0.0128008155, 0.018820466, -0.02700147, -0.0064047636, 0.28287655, 0.02709404, -0.05233492, -0.08967187, -0.042183813, -0.13990502, -0.005085154, -0.028511493) * go_3(-1.0, 1.0);
result += mat4(0.00022532263, -0.09108507, 0.0089569865, 0.052016005, -0.19314727, -0.355347, 0.08082937, 0.2134498, 0.21036889, -0.10165983, 0.20334485, 0.14575538, 0.017676214, -0.13149881, -0.018741794, -0.019599862) * go_3(0.0, -1.0);
result += mat4(-0.20513605, 0.47578803, -0.18631598, 0.2535432, -0.049522053, -0.37224755, 0.11227206, -0.37000927, 0.19969453, -0.47287735, -0.07506754, -0.0957071, 0.82927394, -0.54057014, 0.5800732, 0.08937558) * go_3(0.0, 0.0);
result += mat4(-0.022189412, 0.14622113, -0.4772564, -0.31178755, 0.10667427, -0.07335338, 0.06144331, 0.00056827103, -0.08263861, -0.009126272, -0.22802618, -0.20760304, 0.12688845, -0.061324466, 0.33361357, 0.38350767) * go_3(0.0, 1.0);
result += mat4(0.021188622, 0.1151918, -0.10654739, -0.03341855, 0.24870358, -0.06689332, 0.11881217, -0.0045951125, -0.039464932, -0.030190004, 0.014174111, -0.025356272, 0.07469406, -0.0059695644, 0.008267219, -0.0991054) * go_3(1.0, -1.0);
result += mat4(-0.009981438, -0.36484948, 0.04801225, 0.22368562, -0.055985868, 0.229039, -0.10823553, 0.1477355, -0.0091677625, 0.06279847, 0.034393013, 0.031901076, 0.28783056, 0.086422645, 0.20860936, 0.054018307) * go_3(1.0, 0.0);
result += mat4(-0.08720452, -0.07756267, 0.018853918, -0.014108689, -0.019337144, 0.021249043, -0.05633926, -0.109904505, -0.088990815, 0.16876367, -0.13149975, -0.054357648, 0.08588134, -0.10262266, 0.12052009, 0.05154292) * go_3(1.0, 1.0);
result += vec4(-0.010602045, 0.053976092, 0.008913503, 0.0011945717);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.105475314, -0.07022547, -0.16326137, -0.12503424, -0.004623021, -0.0143323885, 0.042996034, 0.03422294, -0.38310882, -0.4431925, -0.28772846, -0.3213578, -0.018014904, 0.02429277, -0.07177951, -0.04458822) * g_0;
result += mat4(-0.0973233, -0.032439478, -0.08420249, -0.054693196, 0.012960555, 0.06929602, 0.004247494, 0.061315402, -0.09607745, -0.16862066, 0.01537482, -0.038459156, 0.019662246, 0.059920583, -0.1071646, -0.06478967) * g_1;
result += mat4(0.15711947, 0.0754732, 0.17891979, 0.098270796, 0.14122486, 0.14893766, 0.12408279, 0.14845194, 0.16199848, 0.14090912, 0.13496809, 0.1119815, 0.03974558, -0.057513904, 0.09213575, -0.0012252429) * g_2;
result += mat4(-0.011343602, -0.02488338, 0.07799659, 0.06503721, 0.06380687, 0.048929837, -0.05555838, -0.050519127, 0.14673206, 0.18085165, 0.07261422, 0.09738158, 0.07395791, 0.005573146, -0.05454926, -0.13565786) * g_3;
result += mat4(-0.08591514, -0.05664865, 0.23980616, 0.24876402, 0.19052829, 0.011938714, 0.21487322, 0.058656186, 0.036630988, 0.14918756, 0.013127693, 0.13092093, -0.37889576, -0.4068804, -0.27258882, -0.30605716) * g_4;
result += mat4(-0.25149816, -0.21979512, -0.24949454, -0.20483162, -0.10972783, -0.17315808, -0.08562763, -0.16086778, 0.044681527, 0.050807394, -0.019424994, -0.022418005, 0.10039492, -0.013666552, -0.22373566, -0.34493732) * g_5;
result += mat4(0.1419155, 0.081392206, -0.18103191, -0.2122926, -0.1445937, -0.015969204, -0.12368782, -0.0044421684, -0.09534078, -0.14815839, -0.1052107, -0.16341865, 0.3050403, 0.34488317, 0.16171226, 0.18700944) * g_6;
result += mat4(0.12444696, 0.08712589, 0.06266247, 0.031022022, 0.17707655, 0.24904409, 0.20961654, 0.2610619, -0.099262595, -0.06900819, -0.034567446, -0.020191457, -0.1468561, -0.04683958, 0.14910224, 0.244686) * g_7;
result += mat4(-0.002428158, -0.012889509, 0.0006541127, -0.0058380975, 0.096147396, 0.07791617, 0.119144954, 0.11699654, -0.024602454, -0.07894611, -0.00021709128, -0.03979557, 0.0028512406, -0.015790012, 0.0082511455, 0.029357092) * g_8;
result += mat4(-0.01410329, -0.004162405, -0.09005045, -0.07753674, 0.004509965, -0.024188736, 0.13799691, 0.10589621, -0.023018798, 0.0064198375, -0.103344224, -0.07463909, -0.060048997, -0.071094714, -0.13042289, -0.14482167) * g_9;
result += mat4(-0.009015246, 0.01581748, -0.035448726, -0.012348933, -0.101627484, -0.05530413, -0.14063041, -0.121775225, 0.074719116, 0.033839386, 0.045573987, -0.006698053, 0.0015141299, 0.003634417, 0.017102007, 0.0074890694) * g_10;
result += mat4(0.0042357175, 0.018735386, 0.058959343, 0.057424515, -0.021633089, -0.037194982, -0.14109972, -0.1506368, 0.004357002, -0.006871023, 0.05337361, 0.039684236, 0.087463334, 0.07772685, 0.12278512, 0.1224218) * g_11;
result += mat4(0.018359886, 0.046934873, -0.008225237, 0.020650858, -0.03961538, -0.014779162, -0.04161338, -0.00953579, 0.0017313146, 0.0068857935, -0.0024282748, 0.0047545764, 0.02635904, 0.027336216, 0.02701322, 0.029939381) * g_12;
result += mat4(-0.00067966996, 0.024480496, -0.015218739, -0.010472019, -0.03994461, -0.052318517, -0.04450191, -0.043226667, -0.03166469, -0.03799331, 0.015428865, -0.018422252, 0.00040845043, 0.03558268, -0.0099401595, -0.00054432114) * g_13;
result += mat4(-0.0032104475, 0.019604867, -0.02486679, 0.002134673, 0.014368818, -0.0013395248, 0.017318068, 0.0021403218, -0.02198377, 0.010297547, -0.041619625, -0.02740482, -0.067249276, -0.03040953, -0.021304253, -0.009557115) * g_14;
result += mat4(-0.019099236, -0.037010793, 0.013720462, 0.023708181, 0.016356282, -0.00028589502, -0.010570909, -0.009186907, 0.03493662, 0.055599142, -0.017043956, 0.004204044, -0.013573257, -0.013537684, 0.008151195, 0.0074913655) * g_15;
result += mat4(0.009309031, -0.0014795153, 0.025114728, -0.0066442797, -0.012085473, -0.0030560147, 0.002144206, 0.0009732741, 0.022301642, -0.0091133695, 0.0011837826, -0.020275833, -0.021349607, -0.011693419, -0.018912962, -0.022418445) * g_16;
result += mat4(-0.0045772395, 0.031085191, 0.01215795, 0.023887333, 0.023408212, 0.0005998807, 0.011254428, -0.004634461, 0.016601006, 0.046663348, 0.031117432, 0.04910873, -0.113230005, -0.035702843, -0.058746565, -0.053893737) * g_17;
result += mat4(-0.020218112, 0.056803435, -0.0037077996, 0.05123925, -0.016713811, -0.05551032, -0.005916611, -0.037839632, -0.007671626, -0.009099201, -0.0010055836, 0.003332688, 0.020744357, 0.01957675, 0.057906736, 0.041446246) * g_18;
result += mat4(0.022438819, 0.04616756, 0.035925094, 0.0639705, 0.0009332198, 0.020964272, -0.010805394, 0.031757344, 0.051255573, 0.032838948, 0.00055445684, -0.03195623, 0.04753827, 0.016436901, 0.04788274, 0.022093765) * g_19;
result += mat4(0.03479086, 0.035946105, 0.04343359, 0.04015664, 0.06081792, 0.061758887, 0.10128842, 0.007471392, -0.027261607, -0.01290544, -0.029938918, -0.050834358, -0.015550162, 0.0072828676, -0.04580556, -0.029642029) * g_20;
result += mat4(0.011150116, 0.029789668, -0.00354488, 0.045047592, -0.018265083, -0.020843878, 0.015457328, 0.0053232997, 0.0791804, -0.028661052, 0.079342775, -0.039631505, 0.14613943, 0.08323415, 0.049641483, 0.047863442) * g_21;
result += mat4(-0.103034586, -0.107580125, 0.00044325445, 0.007830247, -0.017059505, 0.010152936, -0.02845979, -0.01841766, -0.10722863, -0.025262646, -0.07402096, -0.025055556, 0.0013303137, 0.12574737, -0.0161103, 0.06077798) * g_22;
result += mat4(-0.0420636, -0.062703885, -0.06476972, -0.10516001, 0.018120673, 0.024305122, -0.013997766, 0.015815413, -0.06317691, -0.03968166, -0.054052643, -0.016300509, -0.08255892, -0.01612941, -0.04194852, -0.012637189) * g_23;
result += mat4(0.042659573, -0.10762496, -0.077143244, 0.12583935, -0.022020226, -0.0042312425, -0.016734738, 0.027007964, -0.06609771, -0.056038737, -0.0058528963, 0.035508137, -0.019722374, -0.055094264, 0.010977759, -0.009833099) * g_24;
result += mat4(0.063830875, -0.019885639, 0.055574782, 0.039456647, 0.01576898, -0.1389799, 0.063411795, -0.11600623, -0.013968303, -0.03318867, -0.06806915, -0.09373464, -0.022723546, -0.03329239, 0.014282872, 0.027576538) * g_25;
result += mat4(-0.018100513, 0.06204485, 0.010761461, -0.045085587, 0.009286288, 0.02310671, 0.10633246, -0.090849996, 0.13112675, -0.01639808, 0.0022725316, -0.076779045, 0.11831251, 0.1460306, -0.10849466, -0.07749171) * g_26;
result += mat4(-0.15850247, 0.118011266, -0.10121594, -0.007109052, 0.071873754, 0.06954878, 0.0377852, 0.044174008, -0.062925555, -0.01758927, 0.1416964, 0.17206357, -0.035632525, -0.04652215, 0.061932907, 0.034339) * g_27;
result += vec4(-0.11952045, -0.10779418, -0.0626279, -0.042614873);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf1
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(-0.009000901, -0.018048609, 0.013095594, 0.002321373, 0.0004716619, 0.00504148, -0.016826658, -0.014922383, 0.15059204, 0.16593806, 0.115392484, 0.12520894, 0.05049829, 0.060210057, 0.086421266, 0.07242362) * g_0;
result += mat4(0.06268658, 0.030466434, 0.07876877, 0.04129863, 0.04142328, 0.009963961, 0.051785357, 0.012811113, 0.1295883, 0.139931, 0.07733839, 0.08014211, 0.07156476, 0.0342396, 0.051614303, 0.043559864) * g_1;
result += mat4(0.00041542648, 0.016051646, -0.011512418, 0.013076814, 0.03734479, 0.02791584, 0.012426691, 0.022044811, -0.034128398, -0.027107332, -0.021998279, -0.012139807, -0.033177473, -0.016310865, -0.078221664, -0.041203145) * g_2;
result += mat4(-0.008398536, -0.010332053, -0.050231732, -0.039691273, -0.042082537, -0.030281143, -0.014039778, -0.0020190612, -0.11956351, -0.13638765, -0.09794402, -0.10228069, -0.08344795, -0.07944541, -0.004189214, -0.028206991) * g_3;
result += mat4(0.0002908945, -0.00831185, -0.06870294, -0.083311856, -0.024992501, 0.0038247898, -0.049389005, -0.020098582, -0.0135326125, -0.040408995, -0.012083491, -0.042174604, 0.16112538, 0.13720983, 0.13937058, 0.10870099) * g_4;
result += mat4(0.078961425, 0.082619205, 0.06910667, 0.06579004, -0.0077012256, -0.00038692637, 0.00015553503, -0.012561662, 0.00053048285, -0.01461681, 0.02600344, 0.024862211, -0.06958201, -0.048246548, 0.058762506, 0.036662634) * g_5;
result += mat4(-0.023527982, -0.0028001352, 0.047800142, 0.09616409, 0.049143843, 0.030836122, 0.057244994, 0.025672587, 0.027565151, 0.039868724, 0.045296676, 0.04623187, -0.124759234, -0.14106254, -0.06337279, -0.076839216) * g_6;
result += mat4(-0.0911771, -0.064436875, -0.05308137, -0.022082496, -0.0040269364, 0.0014464161, -0.0029555515, 0.016098293, -0.026650434, -0.014081368, -0.06747348, -0.05481826, 0.097423114, 0.08620988, -0.01607732, -0.015440677) * g_7;
result += mat4(-0.014001735, -0.015001655, -0.013250577, -0.009930805, 0.04885879, 0.07092224, 0.025783395, 0.03792237, -0.04332465, -0.06244993, -0.046748653, -0.07132349, -0.0053951666, -0.016514057, 0.023807624, 0.044013456) * g_8;
result += mat4(-0.009097996, -0.016898679, -0.05043909, -0.063178614, -0.016210863, -0.02157998, -0.02654472, -0.042961173, 0.012103852, 0.019015301, 0.02492281, 0.03389976, 0.015276502, 0.009577683, 0.04132527, -0.00070621347) * g_9;
result += mat4(-0.0057500796, 0.00728164, -0.003422421, 0.0038979584, -0.03127353, -0.019125199, -0.012988815, -0.031890683, 0.09352588, 0.019210607, 0.09824038, 0.016637104, 0.010692808, 0.022393884, 0.008312123, 0.014120716) * g_10;
result += mat4(0.013895599, 0.023097904, 0.009370535, 0.014099512, 0.0124661345, -0.015076684, 0.03287286, 0.005912471, -0.03944815, -0.020340785, -0.06822037, -0.059383288, 0.03634978, 0.007832939, -0.007142306, -0.0061968984) * g_11;
result += mat4(0.033002097, 0.0516016, -0.021056438, 0.005715988, -0.02223013, -0.007962324, -0.024417123, -0.0014790733, 0.002167189, 0.00043749413, -0.007284963, -0.0027283782, 0.026238248, 0.01756047, 0.008969755, 0.014201024) * g_12;
result += mat4(0.011576685, 0.02087598, 0.0026766327, -0.0041780816, -0.05277701, -0.05412841, -0.05958835, -0.050426245, -0.00662945, -0.021645393, 0.03423904, -0.0064581474, -0.030403355, 0.018391011, -0.026089542, -0.0051510665) * g_13;
result += mat4(-0.046202097, -0.0066081425, -0.03698851, 0.0034165455, -0.011859245, -0.020945566, -0.0028196946, -0.010053285, -0.011400397, 0.030595876, -0.018915813, 0.006780077, -0.060040582, -0.009586898, -0.004477886, 0.011279908) * g_14;
result += mat4(-0.028692413, -0.032535568, 0.0017473884, 0.02207169, 0.0192618, 0.008956797, -0.0033381556, 0.006326402, 0.0169569, 0.041449737, -0.02611751, 0.0006410355, 0.006233776, 0.0008467914, 0.011884985, 0.009222136) * g_15;
result += mat4(0.017076496, -0.0045380928, 0.03444613, -0.009804047, -0.004829834, -0.004889702, 0.0057807956, 0.0015014127, 0.03458368, -0.0035773432, -0.007769679, -0.032449644, -0.021396799, -0.017612215, -0.012764735, -0.025224172) * g_16;
result += mat4(-0.011824532, 0.02335273, 0.00764845, 0.019215155, 0.022186808, 0.0066053392, 0.0071694753, -0.0036117272, 0.032144524, 0.05025988, 0.03982363, 0.052400436, -0.10555114, -0.03809396, -0.05334183, -0.05524487) * g_17;
result += mat4(-0.024599254, 0.058805298, 0.00069874676, 0.06263439, -0.018460508, -0.053566024, -0.0022889362, -0.035818785, -0.0135854995, -0.015712813, 0.0012080368, 0.005957637, 0.009450094, 0.03186346, 0.059969924, 0.057706963) * g_18;
result += mat4(0.026783831, 0.05475865, 0.027565574, 0.06032707, -0.0015639095, 0.024381682, -0.010199071, 0.037544634, 0.039889377, 0.03318851, -0.016529158, -0.0343188, 0.045666486, 0.021665907, 0.042189375, 0.02444145) * g_19;
result += mat4(0.03791853, 0.043746054, 0.056224477, 0.05098111, 0.075256795, 0.074653305, 0.116220035, 0.01853866, -0.04133627, -0.009134169, -0.0420953, -0.05210053, -0.021748418, 0.004422131, -0.05422814, -0.035721727) * g_20;
result += mat4(0.013814317, 0.03149986, -0.004971173, 0.04782029, -0.01693027, -0.017984565, 0.019328078, 0.008521426, 0.0845641, -0.027555496, 0.08150416, -0.04623306, 0.16494128, 0.09300831, 0.074097835, 0.0627848) * g_21;
result += mat4(-0.10307174, -0.112654425, -0.005589254, -0.0062108496, -0.012491583, 0.011512013, -0.03142282, -0.023683488, -0.099848576, -0.031290524, -0.07236223, -0.037460987, 0.008760208, 0.1473594, -0.009216949, 0.07251379) * g_22;
result += mat4(-0.04915367, -0.07121096, -0.06572174, -0.10967046, 0.019548079, 0.023992533, -0.019842865, 0.012366459, -0.07207817, -0.04237792, -0.054463565, -0.015374731, -0.092071235, -0.020860313, -0.054475963, -0.02303954) * g_23;
result += mat4(0.04160816, -0.118427366, -0.08661791, 0.12787233, -0.01990174, 0.0012960634, -0.016121056, 0.031429946, -0.06830865, -0.057132352, -0.0022302791, 0.03845933, -0.026981276, -0.063532256, 0.011805961, -0.009616678) * g_24;
result += mat4(0.07094465, -0.022284096, 0.060676746, 0.042626668, 0.011207256, -0.14960343, 0.05866539, -0.12742221, -0.021092903, -0.039463162, -0.07879986, -0.10232898, -0.026127055, -0.038111385, 0.019167708, 0.032637425) * g_25;
result += mat4(-0.014270794, 0.07157703, 0.013714203, -0.047801998, 0.0060221693, 0.022788104, 0.10630103, -0.09606649, 0.12690987, -0.017079826, -0.0077072172, -0.082584485, 0.13256006, 0.16012523, -0.10966099, -0.07927409) * g_26;
result += mat4(-0.17171615, 0.12114435, -0.10746857, -0.0074188868, 0.07854815, 0.07759372, 0.04310874, 0.051465522, -0.05963588, -0.014506484, 0.15522978, 0.18746643, -0.03845241, -0.0489534, 0.05837339, 0.032978524) * g_27;
result += vec4(0.05825913, 0.051491056, 0.038389463, 0.03321517);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Conv-4x1x1x112
//!HOOK MAIN
//!BIND conv2d_tf
//!BIND conv2d_tf1
//!BIND conv2d_1_tf
//!BIND conv2d_1_tf1
//!BIND conv2d_2_tf
//!BIND conv2d_2_tf1
//!BIND conv2d_3_tf
//!BIND conv2d_3_tf1
//!BIND conv2d_4_tf
//!BIND conv2d_4_tf1
//!BIND conv2d_5_tf
//!BIND conv2d_5_tf1
//!BIND conv2d_6_tf
//!BIND conv2d_6_tf1
//!SAVE conv2d_last_tf2
//!WIDTH conv2d_tf.w
//!HEIGHT conv2d_tf.h
//!COMPONENTS 4
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))
#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))
#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))
#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))
#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))
#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))
#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))
#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))
#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))
#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))
#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))
#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))
#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))
#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))
vec4 hook() {
vec4 result = mat4(0.2006987, 0.17832398, 0.26342955, 0.23500517, -0.012297829, -0.009008417, -0.039950736, -0.039973143, 0.34800097, 0.32196492, 0.30505183, 0.29214156, -0.21410535, -0.21166423, -0.16437815, -0.19172792) * g_0;
result += mat4(-0.008804151, -0.07085123, 0.013577994, -0.05192605, -0.08981402, -0.14702585, -0.09145975, -0.14835288, -0.15882517, -0.14785844, -0.2381482, -0.22867912, 0.010898514, 0.031957507, 0.040597558, 0.078252345) * g_1;
result += mat4(-0.21658613, -0.1803885, -0.25954962, -0.20839214, -0.09597461, -0.09222647, -0.03909875, -0.03456191, -0.19723509, -0.16976605, -0.2041716, -0.1751425, 0.22901416, 0.24922715, 0.1800083, 0.23346905) * g_2;
result += mat4(0.110020064, 0.103858806, 0.052446555, 0.061105963, 0.032901537, 0.07140097, 0.11518793, 0.13860466, 0.13930707, 0.12712196, 0.19071707, 0.18399614, -0.08036458, -0.07349171, 0.021504594, 0.0024937368) * g_3;
result += mat4(0.059065036, 0.00698257, -0.099622436, -0.15676253, -0.10942482, -0.04869624, -0.13654554, -0.07341863, -0.014169851, -0.06390744, 0.016093008, -0.04540248, 0.29041344, 0.24451919, 0.26292154, 0.22136512) * g_4;
result += mat4(0.107946776, 0.097849295, 0.10266876, 0.09360328, 0.08931344, 0.08896482, 0.046495322, 0.044040844, -0.020361643, -0.030911373, 0.026598722, 0.019815676, -0.072677925, -0.042410247, 0.14127749, 0.13434973) * g_5;
result += mat4(-0.08809133, -0.03476601, 0.06420393, 0.14691353, 0.09296839, 0.06162562, 0.10992992, 0.0615685, 0.0168736, 0.06520281, 0.020010693, 0.046929173, -0.2219495, -0.21249783, -0.14622301, -0.14599061) * g_6;
result += mat4(-0.13251069, -0.08977477, -0.08930347, -0.045490693, -0.10980218, -0.09510885, -0.07299872, -0.064053826, 0.011365247, 0.014091111, -0.054976214, -0.056936122, 0.10148144, 0.07451642, -0.08138598, -0.10161657) * g_7;
result += mat4(-0.0075518745, -0.005738622, -0.007577811, -0.00032088626, 0.032614008, 0.04858922, 0.00054855715, 0.011565026, -0.022675224, -0.034442738, -0.03580643, -0.05069376, -0.0020376542, -0.01505518, 0.019388825, 0.03746554) * g_8;
result += mat4(-0.011413172, -0.016877454, -0.048923567, -0.055012885, -0.007709447, -0.016109072, -0.047132388, -0.07146396, 0.002604099, 0.00014681708, 0.03429465, 0.043265607, 0.029014807, 0.03337814, 0.07582056, 0.041660666) * g_9;
result += mat4(-0.020768544, -0.014378527, -0.01999972, -0.01385916, -0.012264676, -0.009959511, 0.0119015165, -0.016787319, 0.07266734, -0.0029914333, 0.08549183, 0.004367342, 0.008236065, 0.020370748, 0.0043428927, 0.007301017) * g_10;
result += mat4(0.011654731, 0.025318999, -0.0029306612, 0.007426217, -0.00010868774, -0.020845588, 0.041991003, 0.024147986, -0.030741083, -0.012328637, -0.06617428, -0.06103115, 0.010491518, -0.013338451, -0.04666634, -0.046481613) * g_11;
result += mat4(0.0268538, 0.043785956, -0.01799385, 0.008743307, -0.013197458, -0.015049436, -0.017189734, -0.0047999253, -0.00059730676, -0.0008936153, -0.016006093, -0.0073406673, 0.014875853, 0.011491735, 9.819833e-05, 0.0073417514) * g_12;
result += mat4(0.019930955, 0.027112626, 0.01307941, 0.005268897, -0.060213763, -0.050415818, -0.069006495, -0.051405095, 0.0036414433, -0.008606397, 0.037427194, -0.0018103109, -0.037434716, 0.010187546, -0.026227329, -0.0033639795) * g_13;
result += mat4(-0.03634798, 0.0007093891, -0.026819145, 0.009025687, -0.01750318, -0.020098133, -0.0063864207, -0.006606755, 0.0008565766, 0.028647956, -0.0024974607, 0.015250743, -0.048884176, -0.004310685, -0.0010757383, 0.00974984) * g_14;
result += mat4(-0.031253602, -0.031743724, -0.009083253, 0.0145388115, 0.02048611, 0.0058071036, -0.0038228377, 0.00049654936, 0.0059105973, 0.03437731, -0.025785418, 0.004187733, 0.009980489, -4.08268e-05, 0.009384428, 0.0019492983) * g_15;
result += mat4(0.012587245, -0.0032654977, 0.029739188, -0.009440694, -0.0018237908, -0.0080032, 0.010499013, 0.0012466761, 0.03461923, -0.0060207327, -0.008771263, -0.034545746, -0.015023473, -0.008901684, -0.011490296, -0.01976464) * g_16;
result += mat4(-0.009444331, 0.020809013, 0.009985801, 0.020350901, 0.013234775, 0.004382635, -0.0007761826, -0.005247294, 0.034115106, 0.05190378, 0.039124765, 0.050993033, -0.0898732, -0.030428126, -0.044204578, -0.052484997) * g_17;
result += mat4(-0.020434443, 0.053520404, 0.0007571144, 0.05895061, -0.018991265, -0.043982152, -0.004035192, -0.025452444, -0.012197152, -0.013770753, 0.0012919102, 0.003996682, 0.0056104586, 0.025686186, 0.05128293, 0.05105745) * g_18;
result += mat4(0.030201769, 0.052521482, 0.029641917, 0.05559941, 0.0018870027, 0.020112835, -0.0043867202, 0.035877172, 0.02961142, 0.02163634, -0.027972858, -0.040669747, 0.03393723, 0.013455979, 0.03313782, 0.01968004) * g_19;
result += mat4(0.034817442, 0.04045217, 0.054816365, 0.05092461, 0.06600807, 0.062576495, 0.09923777, 0.006663677, -0.039958935, -0.010009866, -0.041522443, -0.04959681, -0.020962957, 0.003845031, -0.04910384, -0.03233655) * g_20;
result += mat4(0.015433112, 0.028965838, -0.0055138534, 0.042267464, -0.012690953, -0.009424165, 0.017896382, 0.01186686, 0.07231686, -0.038834292, 0.07033086, -0.052548733, 0.15721905, 0.09334892, 0.07676042, 0.06701375) * g_21;
result += mat4(-0.09797534, -0.11201098, -0.0037222446, -0.008105951, -0.01152357, 0.012165641, -0.029051905, -0.021293389, -0.09600697, -0.028819272, -0.069084235, -0.035421908, 0.0054322914, 0.14168966, -0.0200274, 0.06505187) * g_22;
result += mat4(-0.05034882, -0.06622497, -0.062471002, -0.100628324, 0.018115615, 0.019867867, -0.018836644, 0.007562053, -0.06317378, -0.034458403, -0.047243826, -0.009989589, -0.08270121, -0.018645251, -0.05160367, -0.023690399) * g_23;
result += mat4(0.03897899, -0.10862529, -0.081805214, 0.1202324, -0.021866674, -0.00041882638, -0.018235246, 0.027227063, -0.0656312, -0.053432237, -0.0029235696, 0.03549672, -0.022848906, -0.057047505, 0.013400545, -0.0072439364) * g_24;
result += mat4(0.06879516, -0.018637763, 0.058062725, 0.041045032, 0.011702424, -0.13693465, 0.05674195, -0.11679955, -0.022940686, -0.03856922, -0.07531371, -0.09692582, -0.019870926, -0.032572743, 0.026138868, 0.037639033) * g_25;
result += mat4(-0.015270301, 0.06478719, 0.011016518, -0.04533957, 0.00688319, 0.024815995, 0.10159924, -0.08467507, 0.11939162, -0.01939453, -0.0058689644, -0.077881604, 0.118726775, 0.14489114, -0.10831982, -0.07972515) * g_26;
result += mat4(-0.16734359, 0.10685446, -0.102714166, -0.010225307, 0.07306756, 0.07014447, 0.040464073, 0.04688462, -0.05489714, -0.01525318, 0.14690581, 0.17514132, -0.03250648, -0.03688211, 0.05047889, 0.03078089) * g_27;
result += vec4(0.06614842, 0.045779686, 0.032838725, 0.017085627);
return result;
}
//!DESC Anime4K-v3.2-Upscale-Denoise-CNN-x2-(VL)-Depth-to-Space
//!HOOK MAIN
//!BIND MAIN
//!BIND conv2d_last_tf
//!BIND conv2d_last_tf1
//!BIND conv2d_last_tf2
//!SAVE MAIN
//!WIDTH conv2d_last_tf.w 2 *
//!HEIGHT conv2d_last_tf.h 2 *
//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *
vec4 hook() {
vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);
ivec2 i0 = ivec2(f0 * vec2(2.0));
float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];
vec2 f1 = fract(conv2d_last_tf1_pos * conv2d_last_tf1_size);
ivec2 i1 = ivec2(f1 * vec2(2.0));
float c1 = conv2d_last_tf1_tex((vec2(0.5) - f1) * conv2d_last_tf1_pt + conv2d_last_tf1_pos)[i1.y * 2 + i1.x];
vec2 f2 = fract(conv2d_last_tf2_pos * conv2d_last_tf2_size);
ivec2 i2 = ivec2(f2 * vec2(2.0));
float c2 = conv2d_last_tf2_tex((vec2(0.5) - f2) * conv2d_last_tf2_pt + conv2d_last_tf2_pos)[i2.y * 2 + i2.x];
float c3 = c2;
return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);
}
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 bloc97
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+10 -17
View File
@@ -1,29 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import 'src/screens/connect_screen.dart'; import 'src/screens/home/ui.dart';
import 'src/screens/session_screen.dart'; import 'src/shell/app_shell.dart';
import 'src/screens/welcome_screen.dart'; import 'src/widgets/app_keys.dart';
import 'src/state/session_state.dart'; import 'src/widgets/app_menu.dart';
class WatchTogetherApp extends StatelessWidget { class WatchTogetherApp extends StatelessWidget {
const WatchTogetherApp({super.key}); const WatchTogetherApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final session = SessionState.instance;
return MaterialApp( return MaterialApp(
title: 'Watch Together', title: appName,
theme: ThemeData.dark(), // Material's own menus (the player's track menus, sort) wear the
home: SignalBuilder( // app menu's surface.
builder: (context) { theme: AppMenuStyle.theme(ThemeData.dark()),
return switch (session.route.value) { shortcuts: appShortcuts,
AppRoute.welcome => const WelcomeScreen(), home: const AppKeys(child: AppShell()),
AppRoute.connect => const ConnectScreen(),
AppRoute.session => const SessionScreen(),
};
},
),
); );
} }
} }
+46 -17
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -7,19 +8,22 @@ import 'package:sync_relay_core/core.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'app.dart'; import 'app.dart';
import 'src/playback/yt_dlp_app.dart';
import 'src/platform/app_log.dart';
import 'src/platform/desktop_entry.dart';
import 'src/platform/window_memory.dart';
import 'src/screens/home/ui.dart';
import 'src/state/session_state.dart'; import 'src/state/session_state.dart';
import 'src/update/updater.dart'; import 'src/state/settings.dart';
import 'src/update/update_controller.dart' show launchArgs;
Future<void> main(List<String> args) async { Future<void> main(List<String> args) async {
launchArgs = List.unmodifiable(args);
if (args.contains('--upgrade')) { if (args.contains('--upgrade')) {
exitCode = await runUpgrade(); exitCode = await runUpgrade();
exit(exitCode); exit(exitCode);
} }
initLogging(); initLogging();
if (args.contains('--headless')) {
await runHeadless(args);
return;
}
FlutterError.onError = (details) { FlutterError.onError = (details) {
debugPrint('FLUTTER ERROR: ${details.exceptionAsString()}\n${details.stack}'); debugPrint('FLUTTER ERROR: ${details.exceptionAsString()}\n${details.stack}');
FlutterError.presentError(details); FlutterError.presentError(details);
@@ -38,32 +42,57 @@ Future<void> main(List<String> args) async {
), ),
); );
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await startAppLogging();
await Hive.initFlutter();
await AppSettings.instance.ensureLoaded();
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
const windowOptions = WindowOptions( await windowManager.setPreventClose(true);
size: Size(1280, 720), final memory = WindowMemory(Hive.isBoxOpen(settingsBoxName) ? Hive.box(settingsBoxName) : null);
minimumSize: Size(160, 90), windowManager.addListener(_CloseGuard(memory));
center: true, final windowOptions = WindowOptions(
backgroundColor: Color(0xFF000000), size: memory.size,
minimumSize: const Size(160, 90),
center: !memory.hasPosition,
backgroundColor: const Color(0xFF000000),
titleBarStyle: TitleBarStyle.hidden, titleBarStyle: TitleBarStyle.hidden,
title: 'Watch Together', title: appName,
); );
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
await memory.restore();
await windowManager.show(); await windowManager.show();
await windowManager.focus(); await windowManager.focus();
}); });
await Hive.initFlutter();
try { try {
await SessionState.instance.loadForm(); await SessionState.instance.loadForm();
} catch (e, st) { } catch (e, st) {
debugPrint('load connect form failed: $e\n$st'); debugPrint('load connect form failed: $e\n$st');
} }
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
// yt-dlp for web page links: downloaded on first run, kept current.
AppYtDlp.instance.warmUp();
// Linux: the launcher entry and icon Wayland needs (release installs).
unawaited(installDesktopEntry());
runApp(const WatchTogetherApp()); runApp(const WatchTogetherApp());
} }
Future<void> runHeadless(List<String> args) async { /// Close button / Alt+F4: leave gracefully first (others see "left", a
final session = SessionState.instance; /// hosted server shuts down), bounded so a hung socket can't keep the
await session.startHeadless(args); /// window open.
await ProcessSignal.sigint.watch().first; class _CloseGuard with WindowListener {
await session.stopServer(); final WindowMemory memory;
bool _closing = false;
_CloseGuard(this.memory);
@override
Future<void> onWindowClose() async {
if (_closing) return;
_closing = true;
await memory.save();
try {
await SessionState.instance.leave().timeout(const Duration(seconds: 2));
} catch (e) {
debugPrint('leave on close failed: $e');
}
await windowManager.destroy();
}
} }
@@ -0,0 +1,182 @@
import 'dart:async';
import 'package:flutter/material.dart';
/// What keeps the player's controls up besides the mouse on them: the
/// mouse on a floating element over the picture (a pill, a card, the
/// status pill: [ControlsHoldRegion]) or an open popover. While anything
/// holds, the controls stay up; on release they hide on their usual
/// countdown. Both designs follow it ([ControlsHoverProbe] with
/// media_kit's controls, `UoscControls`).
class ControlsHold extends ChangeNotifier {
final _by = <Object>{};
bool _told = false;
bool _queued = false;
bool _disposed = false;
bool get held => _told;
/// [who] holds ([on]) or lets go. Listeners hear it in a microtask, never
/// during a build or a dispose.
void set(Object who, bool on) {
if (on ? !_by.add(who) : !_by.remove(who)) return;
if (_queued) return;
_queued = true;
scheduleMicrotask(() {
_queued = false;
if (_disposed) return;
final now = _by.isNotEmpty;
if (now == _told) return;
_told = now;
notifyListeners();
});
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
/// The mouse on [child] counts as the mouse on the controls: it holds
/// [hold] while it is there.
class ControlsHoldRegion extends StatefulWidget {
final ControlsHold? hold;
final Widget child;
const ControlsHoldRegion({super.key, required this.hold, required this.child});
@override
State<ControlsHoldRegion> createState() => _ControlsHoldRegionState();
}
class _ControlsHoldRegionState extends State<ControlsHoldRegion> {
@override
void didUpdateWidget(ControlsHoldRegion oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.hold != widget.hold) oldWidget.hold?.set(this, false);
}
@override
void dispose() {
widget.hold?.set(this, false);
super.dispose();
}
@override
Widget build(BuildContext context) {
final hold = widget.hold;
if (hold == null) return widget.child;
return MouseRegion(
onEnter: (_) => hold.set(this, true),
onExit: (_) => hold.set(this, false),
child: widget.child,
);
}
}
/// Follows whether media_kit's desktop controls around [child] are up,
/// by the same rule they use: up on mount and on any mouse movement,
/// down [hideAfter] later or when the mouse leaves. media_kit does not
/// expose its visibility; this lets the player draw a progress line while
/// they are down. With [hold] held it keeps them (and itself) up. Never
/// takes input.
class ControlsHoverProbe extends StatefulWidget {
final ValueNotifier<bool> shown;
final Duration hideAfter;
final ControlsHold? hold;
final Widget child;
const ControlsHoverProbe(
{super.key, required this.shown, required this.hideAfter, this.hold, required this.child});
@override
State<ControlsHoverProbe> createState() => _ControlsHoverProbeState();
}
class _ControlsHoverProbeState extends State<ControlsHoverProbe> {
Timer? _timer;
// While held: media_kit's own countdown is restarted before it ends.
Timer? _keep;
bool get _held => widget.hold?.held ?? false;
@override
void initState() {
super.initState();
widget.hold?.addListener(_holdChanged);
// Not during this build: the line listening to [shown] may be built.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _wake();
});
}
@override
void didUpdateWidget(ControlsHoverProbe oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.hold != widget.hold) {
oldWidget.hold?.removeListener(_holdChanged);
widget.hold?.addListener(_holdChanged);
_holdChanged();
}
}
@override
void dispose() {
widget.hold?.removeListener(_holdChanged);
_timer?.cancel();
_keep?.cancel();
super.dispose();
}
void _holdChanged() {
if (!mounted) return;
_keep?.cancel();
_keep = null;
if (_held) {
_keep = Timer.periodic(widget.hideAfter * 0.5, (_) => _pokeControls());
}
_pokeControls();
_wake();
}
/// media_kit's controls state is private: a guarded dynamic call to its
/// hover handler, as a mouse movement on it would make.
void _pokeControls() {
if (!mounted) return;
void visit(Element e) {
if (e is StatefulElement && e.state.runtimeType.toString() == '_MaterialDesktopVideoControlsState') {
try {
(e.state as dynamic).onHover();
} catch (_) {}
return;
}
e.visitChildElements(visit);
}
context.visitChildElements(visit);
}
void _wake() {
widget.shown.value = true;
_timer?.cancel();
_timer = Timer(widget.hideAfter, _hide);
}
void _hide() {
_timer?.cancel();
if (!mounted || _held) return;
widget.shown.value = false;
}
@override
Widget build(BuildContext context) => MouseRegion(
opaque: false,
hitTestBehavior: HitTestBehavior.translucent,
onEnter: (_) => _wake(),
onHover: (_) => _wake(),
// Held (the mouse went onto a pill or the popover): media_kit's
// controls just hid on their own exit; bring them straight back.
onExit: (_) => _held ? _pokeControls() : _hide(),
child: widget.child,
);
}
@@ -0,0 +1,402 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart' show JellyfinStreamType;
import '../jellyfin/track_prefs.dart' show MpvTrack;
import '../playback/playback_controller.dart' show PlaybackController, RenderMode;
import '../shaders/shader_presets.dart';
import '../state/session_state.dart';
import '../shell/mark_problem.dart';
import '../shell/motion.dart';
import '../state/settings.dart';
import '../widgets/app_menu.dart';
import 'uosc/buttons.dart';
import 'uosc/controls.dart';
// Everything about the player, defined once: the rows of the ⚙ card in
// both control designs and of Settings → Player. Each row reads and
// writes [AppSettings] and applies to a running player right away.
/// One setting: a label, one line of help under it, the control at the
/// right ([below]: an extra line under the row, e.g. a text field).
class SettingRow extends StatelessWidget {
final String label;
final String? help;
final Widget control;
final Widget? below;
const SettingRow({super.key, required this.label, this.help, required this.control, this.below});
@override
Widget build(BuildContext context) {
final muted = Theme.of(context).colorScheme.onSurfaceVariant;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
if (help != null) Text(help!, style: TextStyle(color: muted, fontSize: 12)),
],
),
),
const SizedBox(width: 12),
control,
],
),
?below,
],
),
);
}
}
AppSelect<T> _dropdown<T>({
required Key key,
required T value,
required List<(T, String)> items,
required ValueChanged<T> onChanged,
bool expanded = false,
}) =>
AppSelect<T>(key: key, value: value, items: items, onChanged: onChanged, expanded: expanded);
/// Standard (our bars over media_kit's controls) or uosc. Live.
class PlayerDesignSetting extends StatelessWidget {
const PlayerDesignSetting({super.key});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(
builder: (context) => SettingRow(
label: 'Player design',
help: 'uosc is modeled on mpv: a thick timeline with chapters and what is cached',
control: _dropdown<bool>(
key: const ValueKey('player-design'),
value: s.mpvControls.value,
items: const [(false, 'Standard'), (true, 'uosc')],
onChanged: s.setMpvControls,
),
),
);
}
}
/// The 2 px line at the bottom while the controls are hidden. Live.
class ProgressLineSetting extends StatelessWidget {
const ProgressLineSetting({super.key});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(
builder: (context) => SettingRow(
label: 'Progress line when hidden',
help: 'A thin bar at the bottom of the video while the controls are hidden',
control: Switch(
key: const ValueKey('progress-line'),
value: s.progressLine.value,
onChanged: s.setProgressLine,
),
),
);
}
}
/// Saves a shader choice (also used on the next join) and puts it on the
/// running player, if any; the player announces the result.
void applyShaderChoice(ShaderPreset preset, ShaderQuality? quality) {
final s = AppSettings.instance;
if (quality != null) s.setShaderQuality(quality);
s.setShaderPreset(preset);
final coord = SessionState.instance.coordinator;
coord.markInput();
coord.playback.applyShaders(announce: true);
}
/// Anime4K preset (or custom files) and HQ / Fast. Live.
class ShaderPresetSetting extends StatelessWidget {
/// The running player's render mode (in the player); else the last one.
final ReadonlySignal<RenderMode>? renderMode;
const ShaderPresetSetting({super.key, this.renderMode});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(builder: (context) {
final preset = s.shaderPreset.value;
final quality = s.shaderQuality.value;
final hasCustom = s.customShaders.value.isNotEmpty;
final mode = (renderMode ?? PlaybackController.lastRenderMode).value;
final warning = switch (mode) {
RenderMode.software => 'Software rendering: shaders have no effect',
RenderMode.softwareGl => 'OpenGL on the CPU: shaders will be slow',
_ => null,
};
return SettingRow(
label: 'Upscaling shader',
help: 'Anime4K sharpens anime on your GPU, only you see it. '
'A for 1080p, B for 720p, C for 480p · Ctrl+1…6, Ctrl+0 off',
control: Row(
mainAxisSize: MainAxisSize.min,
children: [
_dropdown<ShaderPreset>(
key: const ValueKey('shader-preset'),
value: preset,
items: [
for (final p in ShaderPreset.values)
if (p != ShaderPreset.custom || hasCustom || preset == p) (p, p.label),
],
onChanged: (p) => applyShaderChoice(p, null),
),
if (preset.isAnime4k) ...[
const SizedBox(width: 8),
_dropdown<ShaderQuality>(
key: const ValueKey('shader-quality'),
value: quality,
items: [for (final q in ShaderQuality.values) (q, q.label)],
onChanged: (q) => applyShaderChoice(preset, q),
),
],
],
),
below: warning == null
? null
: Text(warning, style: const TextStyle(fontSize: 12, color: Colors.orange)),
);
});
}
}
/// Puts the hwdec setting on the running player (mpv switches decoders
/// on the fly). `SYNC_RELAY_HWDEC` wins, as at player start.
Future<void> applyHwdecLive() async {
if ((Platform.environment['SYNC_RELAY_HWDEC'] ?? '').isNotEmpty) return;
final playback = SessionState.instance.coordinator.playback;
if (!playback.alive.peek()) return;
try {
final dynamic platform = playback.player.platform;
await platform?.setProperty('hwdec', AppSettings.instance.hwdec.peek());
} catch (e) {
debugPrint('live hwdec failed: $e');
}
}
/// mpv's `hwdec`: auto, off, or a custom value. Live.
class HwdecSetting extends StatefulWidget {
const HwdecSetting({super.key});
@override
State<HwdecSetting> createState() => _HwdecSettingState();
}
class _HwdecSettingState extends State<HwdecSetting> {
final _s = AppSettings.instance;
late final _custom = TextEditingController(text: _s.hwdecCustom.peek());
@override
void dispose() {
_custom.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SignalBuilder(builder: (context) {
final mode = _s.hwdecMode.value;
return SettingRow(
label: 'Hardware decoding',
help: 'Decodes video on the GPU. Turn it off if the picture glitches or stays black',
control: _dropdown<HwdecMode>(
key: const ValueKey('hwdec'),
value: mode,
items: const [(HwdecMode.auto, 'Auto'), (HwdecMode.off, 'Off'), (HwdecMode.custom, 'Custom')],
onChanged: (m) {
_s.setHwdecMode(m);
applyHwdecLive();
},
),
below: mode != HwdecMode.custom
? null
: Padding(
padding: const EdgeInsets.only(top: 8),
child: TextField(
controller: _custom,
decoration: const InputDecoration(
isDense: true,
labelText: 'mpv hwdec value (Enter applies)',
hintText: 'e.g. vaapi, nvdec, d3d11va, auto-safe',
border: OutlineInputBorder(),
),
onChanged: _s.setHwdecCustom,
onSubmitted: (_) => applyHwdecLive(),
),
),
);
});
}
}
/// Audio or subtitle track of the running player (in the player only).
/// The pick is remembered for the next Jellyfin item.
class TrackSetting extends StatelessWidget {
final JellyfinStreamType type;
const TrackSetting({super.key, required this.type});
bool get _audio => type == JellyfinStreamType.audio;
@override
Widget build(BuildContext context) {
final coord = SessionState.instance.coordinator;
return SignalBuilder(builder: (context) {
final list = [
for (final t in coord.playback.tracks.value)
if (t.type == type) t,
];
// -1: subtitles off.
final selected = list.where((t) => t.selected).firstOrNull?.id ?? (_audio ? null : -1);
final items = <(int, String)>[
if (!_audio) (-1, 'Off'),
for (final MpvTrack t in list) (t.id, t.label),
];
return SettingRow(
label: _audio ? 'Audio' : 'Subtitles',
help: 'Remembered for the next episode',
control: SizedBox(
width: 190,
child: items.isEmpty || selected == null
? Text(items.isEmpty ? 'None' : 'Pick one…',
key: ValueKey(_audio ? 'audio-track' : 'subtitle-track'), textAlign: TextAlign.end)
: _dropdown<int>(
key: ValueKey(_audio ? 'audio-track' : 'subtitle-track'),
value: selected,
expanded: true,
items: items,
onChanged: (id) {
coord.markInput();
if (id == -1) {
coord.selectTrack(subtitlesOff: true);
} else if (_audio) {
coord.selectTrack(aid: id);
} else {
coord.selectTrack(sid: id);
}
},
),
),
);
});
}
}
/// The player rows of Settings → Player and the ⚙ card, in order.
List<Widget> playerSettingRows({ReadonlySignal<RenderMode>? renderMode}) => [
const PlayerDesignSetting(),
const ProgressLineSetting(),
ShaderPresetSetting(renderMode: renderMode),
const HwdecSetting(),
];
/// The ⚙ card: every player setting, plus the running player's tracks.
class PlayerSettingsCard extends StatelessWidget {
final ReadonlySignal<RenderMode>? renderMode;
/// Show the audio / subtitle pickers (a player is running).
final bool tracks;
const PlayerSettingsCard({super.key, this.renderMode, this.tracks = true});
@override
Widget build(BuildContext context) {
// The app's glass, like the Up next popover and the menus.
return AppGlass(
key: const ValueKey('player-settings-card'),
child: SizedBox(
width: 420,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Player', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
const SizedBox(height: 4),
...playerSettingRows(renderMode: renderMode),
if (tracks) ...[
const Divider(),
const TrackSetting(type: JellyfinStreamType.subtitle),
const TrackSetting(type: JellyfinStreamType.audio),
],
const SizedBox(height: 4),
Row(
children: [
Expanded(
child: Text(
'Custom shader files: Settings → Player',
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12),
),
),
const MarkProblemButton(),
],
),
],
),
),
),
);
}
}
/// Opens the ⚙ card over the player, near the controls' right end. The
/// uosc controls stay up while it is open.
Future<void> showPlayerSettings(BuildContext context, {ReadonlySignal<RenderMode>? renderMode}) {
Future<void> open() => showPopover<void>(
context,
at: const Alignment(0.96, 0.5),
child: PlayerSettingsCard(renderMode: renderMode),
);
final controls = UoscControls.maybeOf(context);
return controls == null ? open() : controls.holdDuring(open);
}
/// ⚙ in the standard controls' bottom bar.
class PlayerSettingsButton extends StatelessWidget {
final FocusNode? focusNode;
final ReadonlySignal<RenderMode>? renderMode;
const PlayerSettingsButton({super.key, this.focusNode, this.renderMode});
@override
Widget build(BuildContext context) {
return IconButton(
key: const ValueKey('player-settings'),
focusNode: focusNode,
tooltip: 'Player settings',
color: Colors.white,
iconSize: 20,
visualDensity: VisualDensity.compact,
icon: const Icon(Icons.tune),
onPressed: () => showPlayerSettings(context, renderMode: renderMode),
);
}
}
/// ⚙ in the uosc controls' button row.
class UoscPlayerSettingsButton extends StatelessWidget {
final ReadonlySignal<RenderMode>? renderMode;
const UoscPlayerSettingsButton({super.key, this.renderMode});
@override
Widget build(BuildContext context) {
return UoscButton(
key: const ValueKey('uosc-player-settings'),
icon: Icons.tune_rounded,
tooltip: 'Player settings',
onPressed: () => showPlayerSettings(context, renderMode: renderMode),
);
}
}
@@ -0,0 +1,359 @@
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart' show JellyfinStreamType;
import '../../jellyfin/track_prefs.dart' show MpvTrack;
import '../../playback/media_marks.dart';
import '../../playback/playback_controller.dart' show RenderMode;
import '../../screens/session/player_controls.dart' show TrackPick;
import '../../shaders/shader_menu.dart';
import '../../shaders/shader_presets.dart';
import '../../state/settings.dart';
import '../volume.dart';
import 'controls.dart';
import 'menu.dart';
import 'style.dart';
/// Flat uosc button: a white icon (optionally a [label] beside it and a
/// small count [badge] in the corner), a faint highlight on hover, a dark
/// tooltip. Never takes focus (the session's keys are global).
class UoscButton extends StatefulWidget {
final IconData icon;
final String tooltip;
final VoidCallback? onPressed;
final String? label;
final int? badge;
/// Tint for an "on" state (ready, shaders on).
final Color? color;
const UoscButton({
super.key,
required this.icon,
required this.tooltip,
required this.onPressed,
this.label,
this.badge,
this.color,
});
@override
State<UoscButton> createState() => _UoscButtonState();
}
class _UoscButtonState extends State<UoscButton> {
bool _hover = false;
@override
Widget build(BuildContext context) {
final w = widget;
final color = w.onPressed == null ? UoscStyle.dim.withValues(alpha: 0.4) : (w.color ?? UoscStyle.text);
final badge = w.badge;
return UoscTooltip(
message: w.tooltip,
child: MouseRegion(
cursor: w.onPressed == null ? SystemMouseCursors.basic : SystemMouseCursors.click,
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: w.onPressed,
child: Container(
height: UoscControls.buttonRowHeight,
constraints: const BoxConstraints(minWidth: UoscControls.buttonRowHeight),
padding: EdgeInsets.symmetric(horizontal: w.label == null ? 0 : 8),
decoration: BoxDecoration(
color: _hover && w.onPressed != null ? UoscStyle.hover : Colors.transparent,
borderRadius: BorderRadius.circular(UoscStyle.radius),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
clipBehavior: Clip.none,
children: [
Icon(w.icon, color: color, size: 22),
if (badge != null && badge > 0)
Positioned(
right: -8,
bottom: -5,
child: Container(
key: const ValueKey('uosc-badge'),
padding: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
color: UoscStyle.text,
borderRadius: BorderRadius.circular(3),
),
child: Text('$badge',
style: const TextStyle(
color: Colors.black, fontSize: 9, height: 1.3, fontWeight: FontWeight.w700)),
),
),
],
),
if (w.label != null) ...[
const SizedBox(width: 6),
Text(w.label!, style: TextStyle(color: color, fontSize: UoscStyle.fontSize)),
],
],
),
),
),
),
);
}
}
/// Badge-style text button ("HQ" in uosc): a small outlined box with a
/// short label, filled when [active].
class UoscBadgeButton extends StatefulWidget {
final String text;
final String tooltip;
final bool active;
final VoidCallback onPressed;
const UoscBadgeButton({
super.key,
required this.text,
required this.tooltip,
required this.active,
required this.onPressed,
});
@override
State<UoscBadgeButton> createState() => _UoscBadgeButtonState();
}
class _UoscBadgeButtonState extends State<UoscBadgeButton> {
bool _hover = false;
@override
Widget build(BuildContext context) {
final w = widget;
return UoscTooltip(
message: w.tooltip,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: w.onPressed,
child: Container(
height: UoscControls.buttonRowHeight,
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: _hover ? UoscStyle.hover : Colors.transparent,
borderRadius: BorderRadius.circular(UoscStyle.radius),
),
alignment: Alignment.center,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: w.active ? UoscStyle.text : Colors.transparent,
border: Border.all(color: w.active ? UoscStyle.text : UoscStyle.dim, width: 1.4),
borderRadius: BorderRadius.circular(3),
),
child: Text(w.text,
style: TextStyle(
color: w.active ? Colors.black : UoscStyle.dim,
fontSize: 11,
fontWeight: FontWeight.w800,
letterSpacing: 0.3)),
),
),
),
),
);
}
}
/// Volume: click mutes / unmutes, hovering shows a [VolumeBar] up to
/// [maxVolume], the wheel steps [volumeStep].
class UoscVolumeButton extends StatefulWidget {
/// 0..[maxVolume].
final double volume;
final ValueChanged<double> onChanged;
const UoscVolumeButton({super.key, required this.volume, required this.onChanged});
@override
State<UoscVolumeButton> createState() => _UoscVolumeButtonState();
}
class _UoscVolumeButtonState extends State<UoscVolumeButton> {
final _steps = WheelSteps();
double _beforeMute = 100;
bool _hover = false;
@override
Widget build(BuildContext context) {
final v = widget.volume;
final icon = v <= 0 ? Icons.volume_off_rounded : (v < 50 ? Icons.volume_down_rounded : Icons.volume_up_rounded);
return MouseRegion(
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: Listener(
// Claimed here, so the player's own wheel handler skips it.
onPointerSignal: (e) => _steps.handle(e, (step) => widget.onChanged(stepVolume(v, step * volumeStep))),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedSize(
duration: const Duration(milliseconds: 150),
child: _hover
? Padding(
padding: const EdgeInsets.only(left: 6, right: 2),
child: VolumeBar(
key: const ValueKey('uosc-volume-bar'),
volume: v,
onChanged: widget.onChanged,
color: UoscStyle.text,
trackColor: UoscStyle.dim.withValues(alpha: 0.5),
),
)
: const SizedBox.shrink(),
),
UoscButton(
key: const ValueKey('uosc-volume'),
icon: icon,
color: v > 100 ? boostColor : null,
tooltip: 'Volume ${v.round()} % (scroll to change)',
onPressed: () {
if (v > 0) {
_beforeMute = v;
widget.onChanged(0);
} else {
widget.onChanged(_beforeMute <= 0 ? 100 : _beforeMute);
}
},
),
],
),
),
);
}
}
/// Subtitles or audio: the icon with the track count; the menu lists the
/// tracks (mpv's selection active).
class UoscTracksButton extends StatelessWidget {
final JellyfinStreamType type;
final List<MpvTrack> tracks;
final void Function(TrackPick pick) onPick;
final ValueChanged<bool>? onMenuOpen;
const UoscTracksButton({super.key, required this.type, required this.tracks, required this.onPick, this.onMenuOpen});
@override
Widget build(BuildContext context) {
final audio = type == JellyfinStreamType.audio;
final count = tracks.where((t) => t.type == type).length;
return UoscButton(
key: ValueKey(audio ? 'uosc-audio' : 'uosc-subtitles'),
icon: audio ? Icons.graphic_eq_rounded : Icons.subtitles_outlined,
tooltip: audio ? 'Audio' : 'Subtitles',
badge: count,
onPressed: () async {
onMenuOpen?.call(true);
final pick = await showUoscMenu<TrackPick>(context,
title: audio ? 'Audio' : 'Subtitles', items: trackMenuItems(type, tracks));
onMenuOpen?.call(false);
if (pick != null) onPick(pick);
},
);
}
}
/// Chapters: the list with start times, the playing one active; a pick
/// jumps there. Nothing without chapters.
class UoscChaptersButton extends StatelessWidget {
final List<MediaChapter> chapters;
final int? current;
final Duration duration;
final ValueChanged<MediaChapter> onPick;
final ValueChanged<bool>? onMenuOpen;
const UoscChaptersButton({
super.key,
required this.chapters,
required this.current,
required this.duration,
required this.onPick,
this.onMenuOpen,
});
@override
Widget build(BuildContext context) {
if (chapters.isEmpty) return const SizedBox.shrink();
return UoscButton(
key: const ValueKey('uosc-chapters'),
icon: Icons.bookmarks_outlined,
tooltip: 'Chapters (PgUp / PgDn)',
onPressed: () async {
onMenuOpen?.call(true);
final pick = await showUoscMenu<MediaChapter>(context,
title: 'Chapters', items: chapterMenuItems(chapters, current, duration));
onMenuOpen?.call(false);
if (pick != null) onPick(pick);
},
);
}
}
/// Shaders as uosc's quality badge: the active Anime4K preset ("A HQ"),
/// "4K" dimmed when off. Opens the shader menu.
class UoscShaderButton extends StatelessWidget {
final ReadonlySignal<RenderMode> renderMode;
final void Function(ShaderPreset preset, ShaderQuality? quality) onPick;
final ValueChanged<bool>? onMenuOpen;
const UoscShaderButton({super.key, required this.renderMode, required this.onPick, this.onMenuOpen});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(builder: (context) {
final preset = s.shaderPreset.value;
final quality = s.shaderQuality.value;
final short = shaderShortLabel(preset, quality);
return Builder(
builder: (context) => UoscBadgeButton(
key: const ValueKey('uosc-shaders'),
text: short ?? '4K',
active: short != null,
tooltip: short == null
? 'Shaders: off (Ctrl+1..6)'
: 'Shaders: ${shaderLabel(preset, quality)} (Ctrl+0 off)',
onPressed: () {
final controls = UoscControls.maybeOf(context);
Future<void> open() =>
showShaderMenu(context, renderMode: renderMode, onPick: onPick, onMenuOpen: onMenuOpen);
controls == null ? open() : controls.holdDuring(open);
},
),
);
});
}
}
/// One entry of the ≡ menu.
typedef UoscAction = ({String title, String? hint, VoidCallback run});
/// ≡: the session's actions as a menu.
class UoscMainMenuButton extends StatelessWidget {
final List<UoscAction> actions;
final ValueChanged<bool>? onMenuOpen;
const UoscMainMenuButton({super.key, required this.actions, this.onMenuOpen});
@override
Widget build(BuildContext context) {
return UoscButton(
key: const ValueKey('uosc-menu-button'),
icon: Icons.menu_rounded,
tooltip: 'Menu',
onPressed: () async {
onMenuOpen?.call(true);
final pick = await showUoscMenu<UoscAction>(context, items: [
for (final a in actions) UoscMenuItem(value: a, title: a.title, hint: a.hint),
]);
onMenuOpen?.call(false);
pick?.run();
},
);
}
}
@@ -0,0 +1,293 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import '../../state/settings.dart';
import '../controls_probe.dart' show ControlsHold;
// The mpv-style (uosc) controls frame: a full-width timeline at the
// bottom with a row of flat buttons above it, the title top left and a
// corner widget top right.
// Mouse movement shows them; after [UoscControls.hideAfter] without any
// they fade out, the timeline shrinks to uosc's progress line (gone when
// "Progress line when hidden" is off) and the cursor hides. Our own frame: no
// media_kit controls state to reach into.
class UoscControls extends StatefulWidget {
/// The timeline; [expanded] is false while the controls are hidden.
final Widget Function(BuildContext context, bool expanded) timeline;
/// Buttons left and right of the row above the timeline.
final List<Widget> left;
final List<Widget> right;
/// Top-right corner (chat, back to the library), shown with the controls.
final Widget? top;
/// Top left (what is playing), shown with the controls; gets the width
/// [top] leaves.
final Widget? topLeft;
final VoidCallback? onDoubleTap;
final Duration hideAfter;
/// Follows whether the controls are up (for overlays outside them).
final ValueNotifier<bool>? shown;
/// Keeps them up while held (the mouse on an overlay, an open popover).
final ControlsHold? holdBy;
const UoscControls({
super.key,
required this.timeline,
this.left = const [],
this.right = const [],
this.top,
this.topLeft,
this.onDoubleTap,
this.hideAfter = const Duration(seconds: 3),
this.shown,
this.holdBy,
});
static const buttonRowHeight = 40.0;
/// Under the button row, above the timeline.
static const rowBottomPadding = 4.0;
/// The controls around [context], for widgets that need them kept up
/// (a timeline drag, an open menu).
static UoscControlsState? maybeOf(BuildContext context) =>
context.findAncestorStateOfType<UoscControlsState>();
@override
State<UoscControls> createState() => UoscControlsState();
}
class UoscControlsState extends State<UoscControls> {
bool _visible = true;
int _holds = 0;
bool _heldBy = false;
Timer? _timer;
bool get visible => _visible;
@override
void initState() {
super.initState();
widget.holdBy?.addListener(_holdByChanged);
// Not during this build: an overlay listening to [shown] may be built.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.shown?.value = true;
});
_hideLater();
}
@override
void didUpdateWidget(UoscControls oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.holdBy != widget.holdBy) {
oldWidget.holdBy?.removeListener(_holdByChanged);
widget.holdBy?.addListener(_holdByChanged);
_holdByChanged();
}
}
@override
void dispose() {
widget.holdBy?.removeListener(_holdByChanged);
_timer?.cancel();
super.dispose();
}
void _holdByChanged() {
final on = widget.holdBy?.held ?? false;
if (on == _heldBy || !mounted) return;
_heldBy = on;
hold(on);
}
/// Show the controls and restart the hide countdown.
void wake() {
if (!_visible && mounted) {
setState(() => _visible = true);
widget.shown?.value = true;
}
_hideLater();
}
/// Keep the controls up while [on] (paired calls): a drag sends no
/// hover events and a menu route takes the mouse.
void hold(bool on) {
_holds = on ? _holds + 1 : (_holds - 1).clamp(0, 1 << 20);
wake();
}
/// Runs [open] (a menu) with the controls held up.
Future<T> holdDuring<T>(Future<T> Function() open) async {
hold(true);
try {
return await open();
} finally {
if (mounted) hold(false);
}
}
void _hideLater() {
_timer?.cancel();
_timer = Timer(widget.hideAfter, _hide);
}
void _hide() {
if (!mounted || _holds > 0 || !_visible) return;
setState(() => _visible = false);
widget.shown?.value = false;
}
@override
Widget build(BuildContext context) {
final visible = _visible;
const fade = Duration(milliseconds: 150);
return MouseRegion(
cursor: visible ? SystemMouseCursors.basic : SystemMouseCursors.none,
onEnter: (_) => wake(),
onHover: (_) => wake(),
onExit: (_) {
_timer?.cancel();
_hide();
},
child: Stack(
children: [
Positioned.fill(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onDoubleTap: widget.onDoubleTap,
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 160,
child: IgnorePointer(
child: AnimatedOpacity(
duration: fade,
opacity: visible ? 1 : 0,
child: const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x00000000), Color(0x8C000000)],
),
),
),
),
),
),
if (widget.topLeft != null)
Positioned(
left: 0,
right: 0,
top: 0,
height: 120,
child: IgnorePointer(
child: AnimatedOpacity(
duration: fade,
opacity: visible ? 1 : 0,
child: const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x8C000000), Color(0x00000000)],
),
),
),
),
),
),
if (widget.top != null || widget.topLeft != null)
Positioned(
top: 0,
left: 0,
right: 0,
child: IgnorePointer(
key: const ValueKey('uosc-top'),
ignoring: !visible,
child: AnimatedOpacity(
duration: fade,
opacity: visible ? 1 : 0,
// The buttons centred on the title, like the standard
// design's top bar.
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: widget.topLeft == null
? const SizedBox.shrink()
: Align(alignment: AlignmentDirectional.centerStart, child: widget.topLeft),
),
),
if (widget.top != null)
Padding(padding: const EdgeInsets.fromLTRB(0, 12, 6, 0), child: widget.top),
],
),
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
IgnorePointer(
key: const ValueKey('uosc-buttons'),
ignoring: !visible,
child: AnimatedOpacity(
duration: fade,
opacity: visible ? 1 : 0,
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 0, 6, UoscControls.rowBottomPadding),
child: SizedBox(
height: UoscControls.buttonRowHeight,
child: Row(children: [...widget.left, const Spacer(), ...widget.right]),
),
),
),
),
// Hidden: the collapsed timeline is the progress line,
// unless the player settings turn it off.
SignalBuilder(
builder: (context) {
final line = AppSettings.instance.progressLine.value;
return AnimatedOpacity(
key: const ValueKey('uosc-timeline-wrap'),
duration: fade,
opacity: visible || line ? 1 : 0,
child: widget.timeline(context, visible),
);
},
),
],
),
),
],
),
);
}
}
/// The session's controls: the standard ones ([material]) or, with the
/// "Player design" setting on uosc, the uosc ones ([mpv]). Switches live
/// when the setting changes.
class SessionControlsSwitch extends StatelessWidget {
final WidgetBuilder material;
final WidgetBuilder mpv;
const SessionControlsSwitch({super.key, required this.material, required this.mpv});
@override
Widget build(BuildContext context) {
return SignalBuilder(
builder: (context) =>
AppSettings.instance.mpvControls.value ? mpv(context) : material(context),
);
}
}
@@ -0,0 +1,249 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart' show JellyfinStreamType;
import '../../jellyfin/track_prefs.dart' show MpvTrack;
import '../../playback/media_marks.dart';
import '../../screens/session/player_controls.dart' show TrackPick;
import 'controls.dart';
import 'style.dart';
/// One row of a uosc menu: [title], a dim [hint] on the right (a time, a
/// key), [active] = the current one (highlighted).
class UoscMenuItem<T> {
final T value;
final String title;
final String? hint;
final bool active;
final bool enabled;
const UoscMenuItem({
required this.value,
required this.title,
this.hint,
this.active = false,
this.enabled = true,
});
}
/// A flat dark menu over the controls, above the widget of [context] (the
/// button that opens it), with an optional [title] row. Resolves to the
/// picked value, or null when dismissed (click outside, Esc). Keeps the
/// controls up while open.
Future<T?> showUoscMenu<T>(
BuildContext context, {
String? title,
required List<UoscMenuItem<T>> items,
}) {
final navigator = Navigator.of(context);
final overlay = navigator.overlay!.context.findRenderObject() as RenderBox;
final box = context.findRenderObject() as RenderBox;
final anchor = Rect.fromPoints(
box.localToGlobal(Offset.zero, ancestor: overlay),
box.localToGlobal(box.size.bottomRight(Offset.zero), ancestor: overlay),
);
Future<T?> open() => navigator.push<T>(_UoscMenuRoute<T>(anchor: anchor, title: title, items: items));
final controls = UoscControls.maybeOf(context);
return controls == null ? open() : controls.holdDuring(open);
}
class _UoscMenuRoute<T> extends PopupRoute<T> {
final Rect anchor;
final String? title;
final List<UoscMenuItem<T>> items;
_UoscMenuRoute({required this.anchor, required this.title, required this.items});
@override
Color? get barrierColor => null;
@override
bool get barrierDismissible => true;
@override
String? get barrierLabel => 'Close menu';
@override
Duration get transitionDuration => Duration.zero;
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return LayoutBuilder(builder: (context, constraints) {
const width = 360.0;
final screen = constraints.biggest;
final left = anchor.left.clamp(8.0, math.max(8.0, screen.width - width - 8));
final maxHeight = math.max(120.0, anchor.top - 16);
return Stack(
children: [
Positioned(
left: left.toDouble(),
bottom: screen.height - anchor.top + 6,
width: math.min(width, screen.width - 16),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: maxHeight),
child: UoscMenuPanel<T>(
title: title,
items: items,
onPick: (v) => Navigator.of(context).pop(v),
),
),
),
],
);
});
}
}
/// The menu's panel alone (tests pump it without a route).
class UoscMenuPanel<T> extends StatefulWidget {
final String? title;
final List<UoscMenuItem<T>> items;
final ValueChanged<T> onPick;
const UoscMenuPanel({super.key, this.title, required this.items, required this.onPick});
static const rowHeight = 36.0;
@override
State<UoscMenuPanel<T>> createState() => _UoscMenuPanelState<T>();
}
class _UoscMenuPanelState<T> extends State<UoscMenuPanel<T>> {
// Long lists open on the current row.
late final _scroll = ScrollController(
initialScrollOffset: math.max(0, widget.items.indexWhere((i) => i.active) - 3) * UoscMenuPanel.rowHeight);
@override
void dispose() {
_scroll.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final title = widget.title;
return Material(
key: const ValueKey('uosc-menu'),
color: UoscStyle.panel,
borderRadius: BorderRadius.circular(UoscStyle.radius * 2),
clipBehavior: Clip.antiAlias,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (title != null)
Container(
height: 34,
alignment: Alignment.center,
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Color(0x22FFFFFF)))),
child: Text(title,
style: const TextStyle(color: UoscStyle.dim, fontSize: UoscStyle.fontSize, fontWeight: FontWeight.w600)),
),
Flexible(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(4),
controller: _scroll,
itemCount: widget.items.length,
itemExtent: UoscMenuPanel.rowHeight,
itemBuilder: (context, i) => _Row<T>(item: widget.items[i], onPick: widget.onPick),
),
),
],
),
);
}
}
class _Row<T> extends StatefulWidget {
final UoscMenuItem<T> item;
final ValueChanged<T> onPick;
const _Row({super.key, required this.item, required this.onPick});
@override
State<_Row<T>> createState() => _RowState<T>();
}
class _RowState<T> extends State<_Row<T>> {
bool _hover = false;
@override
Widget build(BuildContext context) {
final item = widget.item;
final color = item.active
? UoscStyle.fill
: _hover && item.enabled
? UoscStyle.hover
: Colors.transparent;
final textColor = item.enabled ? UoscStyle.text : UoscStyle.dim.withValues(alpha: 0.4);
return MouseRegion(
cursor: item.enabled ? SystemMouseCursors.click : SystemMouseCursors.basic,
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: GestureDetector(
key: ValueKey('uosc-menu-item-${item.title}'),
behavior: HitTestBehavior.opaque,
onTap: item.enabled ? () => widget.onPick(item.value) : null,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(UoscStyle.radius)),
child: Row(
children: [
Expanded(
child: Text(item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor,
fontSize: UoscStyle.fontSize,
fontWeight: item.active ? FontWeight.w600 : FontWeight.normal)),
),
if (item.hint != null) ...[
const SizedBox(width: 12),
Text(item.hint!,
style: TextStyle(
color: item.active ? UoscStyle.text : UoscStyle.dim,
fontSize: 12,
fontFeatures: const [FontFeature.tabularFigures()])),
],
],
),
),
),
);
}
}
/// Chapters with their start times, the playing one active.
List<UoscMenuItem<MediaChapter>> chapterMenuItems(List<MediaChapter> chapters, int? current, Duration duration) {
final withHours = duration >= const Duration(hours: 1);
return [
for (final c in chapters)
UoscMenuItem(
value: c,
title: c.title.isEmpty ? 'Chapter ${c.index + 1}' : c.title,
hint: formatUoscTime(c.start, withHours: withHours),
active: c.index == current,
),
];
}
/// The audio or subtitle tracks ("Japanese · E-AC3 2.0 · title"), mpv's
/// selection active; subtitles start with Off.
List<UoscMenuItem<TrackPick>> trackMenuItems(JellyfinStreamType type, List<MpvTrack> tracks) {
final audio = type == JellyfinStreamType.audio;
final list = tracks.where((t) => t.type == type).toList();
return [
if (!audio)
UoscMenuItem(
value: (aid: null, sid: null, subtitlesOff: true),
title: 'Off',
active: !list.any((t) => t.selected),
),
for (final t in list)
UoscMenuItem(
value: audio ? (aid: t.id, sid: null, subtitlesOff: false) : (aid: null, sid: t.id, subtitlesOff: false),
title: t.label,
hint: '#${t.id}',
active: t.selected,
),
];
}
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
// Look of the experimental mpv-style controls, after the uosc theme as the
// owner runs it: flat black panels, white icons, a muted blue fill.
abstract final class UoscStyle {
/// The active menu row.
static const fill = Color(0xFF4C6A8E);
/// Menus and tooltips.
static const panel = Color(0xF0101010);
static const hover = Color(0x24FFFFFF);
static const text = Colors.white;
static const dim = Color(0xA6FFFFFF);
static const radius = 4.0;
static const fontSize = 13.0;
}
/// uosc's clock: "01:51", "1:02:03" style but zero-padded ("01:02:03")
/// once [withHours] (the media is an hour or longer).
String formatUoscTime(Duration d, {bool withHours = false}) {
final t = d.isNegative ? Duration.zero : d;
String two(int n) => n.toString().padLeft(2, '0');
final h = t.inHours, m = t.inMinutes % 60, s = t.inSeconds % 60;
return withHours || h > 0 ? '${two(h)}:${two(m)}:${two(s)}' : '${two(m)}:${two(s)}';
}
/// Dark rounded hover tip ("Subtitles"), shown above its child.
class UoscTooltip extends StatelessWidget {
final String message;
final Widget child;
const UoscTooltip({super.key, required this.message, required this.child});
@override
Widget build(BuildContext context) {
if (message.isEmpty) return child;
return Tooltip(
message: message,
preferBelow: false,
verticalOffset: 26,
waitDuration: const Duration(milliseconds: 250),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: UoscStyle.panel,
borderRadius: BorderRadius.circular(UoscStyle.radius),
),
textStyle: const TextStyle(color: UoscStyle.text, fontSize: 12),
child: child,
);
}
}
/// A small dark box with one line of text (the timeline's hover time and
/// chapter title).
class UoscBox extends StatelessWidget {
final String text;
final Color color;
final TextStyle style;
const UoscBox(
this.text, {
super.key,
this.color = UoscStyle.panel,
this.style = const TextStyle(color: UoscStyle.text, fontSize: UoscStyle.fontSize),
});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxWidth: 360),
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(UoscStyle.radius)),
child: Text(text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: style.copyWith(fontFeatures: const [FontFeature.tabularFigures()])),
);
}
}
@@ -0,0 +1,645 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../playback/media_marks.dart';
import 'controls.dart';
import 'style.dart';
// A port of uosc's timeline (`elements/Timeline.lua`, defaults from
// `uosc.conf` / `main.lua`), style `line`, scale 1: a translucent black
// band, the position a thin white line (no filled progress), the
// uncached part hatched, chapter diamonds on the top edge, elapsed time
// left and the time remaining right, the buffered seconds next to the
// position while under the threshold, and a tooltip with the time (and
// the chapter title) under the mouse.
/// uosc's defaults at scale 1.
abstract final class UoscTimelineSpec {
/// `timeline_size`: the band's height when expanded.
static const size = 40.0;
/// `timeline_border`: band above the line, also the diamonds' border.
static const border = 1.0;
/// `timeline_line_width`: the position line.
static const lineWidth = 2.0;
/// `progress_size`: what is left when the controls hide.
static const progressSize = 2.0;
/// `progress_line_width`: the position line once minimized.
static const progressLineWidth = 20.0;
/// floor(min((size + 60) * 0.2, size * 0.96)).
static const fontSize = 20.0;
/// max(floor((size - fontSize) / 2.5), 4): the times' inset.
static const spacing = 8.0;
/// `text_border`: the outline of text drawn on the band.
static const textBorder = 1.2;
/// `opacity.timeline` (the band) and `opacity.chapters` (diamond fill).
static const opacity = 0.9;
static const chaptersOpacity = 0.8;
/// Gap between the band and its tooltips, and between tooltips.
static const tooltipGap = 2.0;
/// `buffered_time_threshold`: the cache label shows below this.
static const bufferedThreshold = Duration(seconds: 60);
/// max((size + border) / 10, 3): diamond radius cap.
static const chapterSize = (size + border) / 10;
}
/// uosc's `format_time(seconds, duration)`: mpv's "HH:MM:SS", trimmed to
/// "MM:SS" for media under an hour and "SS" under a minute; negative
/// values keep their minus ("-01:47:30").
String uoscClock(Duration d, Duration max) {
final neg = d.isNegative && d.inSeconds != 0;
final t = d.abs();
String two(int n) => n.toString().padLeft(2, '0');
final full = '${two(t.inHours)}:${two(t.inMinutes % 60)}:${two(t.inSeconds % 60)}';
final trim = max < const Duration(minutes: 1) ? 6 : (max < const Duration(hours: 1) ? 3 : 0);
return '${neg ? '-' : ''}${full.substring(trim)}';
}
/// Fraction of [total] at [d], 0..1 (0 without a duration).
double timelineFraction(Duration d, Duration total) {
final t = total.inMicroseconds;
if (t <= 0) return 0;
return (d.inMicroseconds / t).clamp(0.0, 1.0);
}
/// Where the uncached part starts (it runs to the end), as a fraction;
/// null when nothing known is uncached. We know only the end of mpv's
/// cache ahead ([buffered]); without it (0) uosc draws no ranges either,
/// and a cache that reaches the end (within uosc's 0.5 s) leaves none.
double? uncachedFrom({required Duration position, required Duration buffered, required Duration duration}) {
if (duration <= Duration.zero || buffered <= Duration.zero) return null;
final from = buffered > position ? buffered : position;
if (duration - from <= const Duration(milliseconds: 500)) return null;
return timelineFraction(from, duration);
}
/// The cache label next to the position ("12s"): the seconds buffered
/// ahead, shown while under [UoscTimelineSpec.bufferedThreshold] and the
/// cache does not reach the end (uosc drops it at eof-cached).
String? cacheLabel({required Duration position, required Duration buffered, required Duration duration}) {
if (duration <= Duration.zero || buffered <= Duration.zero) return null;
if (duration - buffered <= const Duration(milliseconds: 500)) return null;
final ahead = buffered > position ? buffered - position : Duration.zero;
if (ahead >= UoscTimelineSpec.bufferedThreshold) return null;
return '${(ahead.inMilliseconds / 1000).round()}s';
}
/// The band's geometry at one size, as uosc lays it out: the position
/// line takes [lineWidth] pixels, and times map around it (`t2x`) so no
/// time lies inside the line.
class UoscTimelineGeometry {
final double width;
/// The foreground height (without the top border).
final double size;
/// The playing position, 0..1.
final double progress;
final double lineWidth;
const UoscTimelineGeometry({
required this.width,
required this.size,
required this.progress,
this.lineWidth = UoscTimelineSpec.lineWidth,
});
/// [size] while the band animates between hidden (0) and shown (1)
/// (`get_effective_size`).
static double sizeAt(double visibility) =>
UoscTimelineSpec.progressSize +
((UoscTimelineSpec.size - UoscTimelineSpec.progressSize) * visibility).ceilToDouble();
/// The position line's width at [size]: it grows to
/// [UoscTimelineSpec.progressLineWidth] over the last eighth of the
/// collapse.
static double lineWidthAt(double size) {
const p = UoscTimelineSpec.progressSize;
final minimized = 1 - math.min((size - p) / ((UoscTimelineSpec.size - p) / 8), 1.0);
return UoscTimelineSpec.lineWidth + (UoscTimelineSpec.progressLineWidth - UoscTimelineSpec.lineWidth) * minimized;
}
double get height => size + UoscTimelineSpec.border;
/// Top of the foreground (below the border).
double get top => UoscTimelineSpec.border;
double get lineLeft => ((width - lineWidth) * progress).roundToDouble();
double get lineRight => ((width - lineWidth) * progress + lineWidth).roundToDouble();
/// x of [fraction] (uosc's `t2x`): past the position, shifted over the line.
double x(double fraction) {
final lw = lineWidth - 1;
final at = 0.5 + (width - lw - 1) * fraction;
return fraction <= progress ? at : at + lw;
}
/// The fraction at [x] (uosc's `get_time_at_x`): x on the line is the
/// position.
double fractionAt(double x) {
const lw = UoscTimelineSpec.lineWidth - 1;
final timeWidth = width - lw - 1;
if (timeWidth <= 0) return 0;
final a = timeWidth * progress, b = a + lw;
var at = x - 0.5;
if (at > b) {
at -= lw;
} else if (at > a) {
at = a;
}
return (at / timeWidth).clamp(0.0, 1.0);
}
/// Chapter diamond radius at this size; doubled while hovered.
double get diamondRadius => math.min(math.max(1.0, size * 0.8), UoscTimelineSpec.chapterSize);
}
/// The uosc timeline. [expanded] false (the controls hidden) shrinks it,
/// animated, to the progress line. Seeks ([onSeek]) on press, on every
/// drag move and on release; a press on a hovered chapter diamond jumps
/// to that chapter. The session sends those through the user-seek path.
class UoscTimeline extends StatefulWidget {
final Duration position;
final Duration duration;
/// End of the cached range ([PlaybackController.buffered]).
final Duration buffered;
final List<MediaChapter> chapters;
final bool expanded;
final ValueChanged<Duration> onSeek;
const UoscTimeline({
super.key,
required this.position,
required this.duration,
this.buffered = Duration.zero,
this.chapters = const [],
this.expanded = true,
required this.onSeek,
});
static const height = UoscTimelineSpec.size + UoscTimelineSpec.border;
static const collapsedHeight = UoscTimelineSpec.progressSize + UoscTimelineSpec.border;
/// uosc's `animation_duration`.
static const animation = Duration(milliseconds: 100);
@override
State<UoscTimeline> createState() => _UoscTimelineState();
}
class _UoscTimelineState extends State<UoscTimeline> {
Offset? _hover;
bool _dragging = false;
double _dragFraction = 0;
Duration? _lastSent;
// A released seek, shown until the position catches up (else the line
// jumps back to the old position for a moment).
double? _pending;
Timer? _pendingTimer;
Duration _at(double fraction) => widget.duration * fraction.clamp(0.0, 1.0);
void _send(Duration d) {
if (d == _lastSent) return;
_lastSent = d;
widget.onSeek(d);
}
void _drag(bool on) {
if (_dragging == on) return;
setState(() => _dragging = on);
UoscControls.maybeOf(context)?.hold(on);
}
void _settle(double fraction) {
_pending = fraction;
_pendingTimer?.cancel();
_pendingTimer = Timer(const Duration(seconds: 2), () {
if (mounted) setState(() => _pending = null);
});
}
@override
void didUpdateWidget(UoscTimeline old) {
super.didUpdateWidget(old);
if (!widget.expanded && old.expanded) _hover = null;
final p = _pending;
if (p != null && widget.duration > Duration.zero) {
final off = (widget.position - _at(p)).abs();
if (off < const Duration(milliseconds: 1500)) {
_pending = null;
_pendingTimer?.cancel();
}
}
}
@override
void dispose() {
_pendingTimer?.cancel();
// A drag cut short by a rebuild must not keep the controls up.
if (_dragging) UoscControls.maybeOf(context)?.hold(false);
super.dispose();
}
/// The chapter whose diamond is within twice its radius of [at].
int? _hoveredChapter(UoscTimelineGeometry geo, List<MediaChapter> marks, Offset? at) {
if (at == null || _dragging) return null;
final reach = geo.diamondRadius * 2;
int? best;
var bestD = double.infinity;
for (var i = 0; i < marks.length; i++) {
final cx = geo.x(timelineFraction(marks[i].start, widget.duration));
final d = (Offset(cx, 0) - at).distance;
if (d <= reach && d < bestD) {
best = i;
bestD = d;
}
}
return best;
}
@override
Widget build(BuildContext context) {
final font = DefaultTextStyle.of(context).style.fontFamily ?? Theme.of(context).textTheme.bodyMedium?.fontFamily;
return LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
return TweenAnimationBuilder<double>(
tween: Tween(end: widget.expanded ? 1 : 0),
duration: UoscTimeline.animation,
builder: (context, visibility, _) => _band(width, visibility, font),
);
},
);
}
Widget _band(double width, double visibility, String? font) {
final expanded = widget.expanded;
final duration = widget.duration;
final size = UoscTimelineGeometry.sizeAt(visibility);
final progress = _dragging ? _dragFraction : (_pending ?? timelineFraction(widget.position, duration));
final shown = _at(progress);
final geo = UoscTimelineGeometry(
width: width,
size: size,
progress: progress,
lineWidth: UoscTimelineGeometry.lineWidthAt(size),
);
final marks = duration > Duration.zero ? widget.chapters : const <MediaChapter>[];
final hover = expanded ? _hover : null;
final hovered = _hoveredChapter(geo, marks, hover);
final hoverFraction = hovered != null
? timelineFraction(marks[hovered].start, duration)
: (hover == null || duration <= Duration.zero ? null : geo.fractionAt(hover.dx));
final hoverAt = hoverFraction == null ? null : _at(hoverFraction);
final cursorX = hovered != null ? geo.x(hoverFraction!) : hover?.dx;
final hoverChapter = hoverAt == null ? null : chapterIndexAt(widget.chapters, hoverAt);
final hoverTitle = hoverChapter == null ? '' : widget.chapters[hoverChapter].title;
// Text fades out just before it would overflow the shrinking band.
const hideBelow = UoscTimelineSpec.fontSize * 0.8;
final textOpacity = (size - hideBelow).clamp(0.0, hideBelow / 2) / (hideBelow / 2);
final radius = geo.diamondRadius;
final timestampGap = UoscTimelineSpec.tooltipGap + (hovered != null ? radius * 2 : radius).roundToDouble();
final painter = UoscTimelinePainter(
geometry: geo,
visibility: visibility,
uncached: uncachedFrom(position: widget.position, buffered: widget.buffered, duration: duration),
chapters: [for (final c in marks) timelineFraction(c.start, duration)],
hoveredChapter: hovered,
hoverX: cursorX,
textOpacity: textOpacity,
elapsed: duration > Duration.zero ? uoscClock(shown, duration) : null,
remaining: duration > Duration.zero ? uoscClock(shown - duration, duration) : null,
cache: cacheLabel(position: widget.position, buffered: widget.buffered, duration: duration),
fontFamily: font,
);
final bar = SizedBox(
key: const ValueKey('uosc-timeline'),
width: width,
height: geo.height,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: RepaintBoundary(child: CustomPaint(painter: painter)),
),
if (hoverAt != null && cursorX != null)
Positioned(
left: 0,
right: 0,
bottom: geo.height + timestampGap,
height: 160,
child: IgnorePointer(
child: CustomMultiChildLayout(
delegate: _TooltipLayout(x: cursorX),
children: [
LayoutId(
id: #time,
child: UoscBox(
uoscClock(hoverAt, duration),
key: const ValueKey('uosc-hover-time'),
color: Colors.black,
style: _tipStyle,
),
),
if (hoverTitle.isNotEmpty)
LayoutId(
id: #chapter,
child: UoscBox(
hoverTitle,
key: const ValueKey('uosc-hover-chapter'),
color: Colors.black,
style: _tipStyle.copyWith(fontWeight: FontWeight.bold),
),
),
],
),
),
),
],
),
);
if (!expanded) return IgnorePointer(child: bar);
Offset clampTo(Offset p) => Offset(p.dx.clamp(0.0, width), p.dy);
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (e) => setState(() => _hover = clampTo(e.localPosition)),
onHover: (e) => setState(() => _hover = clampTo(e.localPosition)),
onExit: (_) {
if (!_dragging) setState(() => _hover = null);
},
child: Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (e) {
if (duration <= Duration.zero || width <= 0) return;
final at = clampTo(e.localPosition);
final chapter = _hoveredChapter(geo, marks, at);
_lastSent = null;
if (chapter != null) {
final f = timelineFraction(marks[chapter].start, duration);
_settle(f);
_send(marks[chapter].start);
setState(() {});
return;
}
_pending = null;
_dragFraction = geo.fractionAt(at.dx);
_drag(true);
_send(_at(_dragFraction));
},
onPointerMove: (e) {
if (!_dragging) return;
final at = clampTo(e.localPosition);
setState(() {
_hover = at;
_dragFraction = geo.fractionAt(at.dx);
});
_send(_at(_dragFraction));
},
onPointerUp: (e) {
if (!_dragging) return;
_send(_at(_dragFraction));
_settle(_dragFraction);
final p = e.localPosition;
if (p.dx < 0 || p.dx > width || p.dy < 0 || p.dy > geo.height) _hover = null;
_drag(false);
},
onPointerCancel: (_) => _drag(false),
child: bar,
),
);
}
static const _tipStyle = TextStyle(color: UoscStyle.text, fontSize: UoscTimelineSpec.fontSize, height: 1);
}
/// The hover tooltips: the time centred on [x], the chapter title over
/// it; each kept [UoscTimelineSpec.tooltipGap] inside the band's ends.
class _TooltipLayout extends MultiChildLayoutDelegate {
final double x;
_TooltipLayout({required this.x});
@override
void performLayout(Size size) {
const gap = UoscTimelineSpec.tooltipGap;
double left(double w) => (x - w / 2).clamp(gap, math.max(gap, size.width - gap - w));
final loose = BoxConstraints.loose(size);
final time = layoutChild(#time, loose);
final timeTop = size.height - time.height;
positionChild(#time, Offset(left(time.width), timeTop));
if (hasChild(#chapter)) {
final chapter = layoutChild(#chapter, loose);
positionChild(#chapter, Offset(left(chapter.width), timeTop - gap - chapter.height));
}
}
@override
bool shouldRelayout(_TooltipLayout old) => old.x != x;
}
/// Paints the band as uosc does: background, the position line, the
/// uncached hatching, chapter diamonds, the times, the hover line.
class UoscTimelinePainter extends CustomPainter {
final UoscTimelineGeometry geometry;
/// 0 hidden .. 1 shown (picks the hatching texture and its strength).
final double visibility;
/// Start of the uncached part (to the end), as a fraction.
final double? uncached;
/// Chapter starts, as fractions.
final List<double> chapters;
final int? hoveredChapter;
/// The hover line (the mouse, or the hovered chapter).
final double? hoverX;
final double textOpacity;
final String? elapsed;
final String? remaining;
final String? cache;
final String? fontFamily;
const UoscTimelinePainter({
required this.geometry,
this.visibility = 1,
this.uncached,
this.chapters = const [],
this.hoveredChapter,
this.hoverX,
this.textOpacity = 1,
this.elapsed,
this.remaining,
this.cache,
this.fontFamily,
});
double get played => geometry.progress;
@override
void paint(Canvas canvas, Size size) {
final g = geometry;
final w = size.width, bottom = size.height, top = g.top;
final line = Rect.fromLTRB(g.lineLeft, top, g.lineRight, bottom);
// Background (`opacity.timeline`) around the position line.
final bg = Path()
..fillType = PathFillType.evenOdd
..addRect(Offset.zero & size)
..addRect(line);
canvas.drawPath(bg, Paint()..color = Colors.black.withValues(alpha: UoscTimelineSpec.opacity));
canvas.drawRect(line, Paint()..color = Colors.white);
_hatch(canvas, w, top, bottom);
_chapters(canvas);
if (textOpacity > 0) _times(canvas, w, top, bottom, line);
final x = hoverX;
if (x != null) {
final onLine = line.left - 0.5 < x && x < line.right + 0.5;
canvas.drawRect(
Rect.fromLTRB(x - 0.5, top, x + 0.5, bottom),
Paint()..color = (onLine ? Colors.black : Colors.white).withValues(alpha: 0.33),
);
}
}
/// uosc's texture "b" (shown) or "a" (hidden) at 80 px: "/" stripes
/// 1.6 px thick, white then black shifted by size / 24 (or / 28).
void _hatch(Canvas canvas, double w, double top, double bottom) {
final from = uncached;
if (from == null) return;
final ax = from * (geometry.width) < 0.5 ? 0.0 : geometry.x(from).floorToDouble();
if (ax >= w) return;
final shown = visibility > 0;
const tile = 80.0, unit = tile / 100;
final period = (shown ? 100 / 12 : 100 / 14) * unit;
final phase = shown ? 0.0 : 5.727 * unit;
final offset = tile / (shown ? 24 : 28);
final h = bottom - top;
canvas.save();
canvas.clipRect(Rect.fromLTRB(ax, top, w, bottom));
void stripes(Color color, double opacity, double anchor) {
final paint = Paint()
..color = color.withValues(alpha: opacity)
..strokeWidth = 2 * unit;
// x + (y - bottom) = anchor + phase + k * period.
final base = anchor + phase;
final k0 = ((ax - h - base) / period).floor() - 1;
for (var k = k0; base + k * period <= w + 1; k++) {
final c = base + k * period;
canvas.drawLine(Offset(c, bottom), Offset(c + h, top), paint);
}
}
stripes(Colors.white, 0.4 - 0.2 * visibility, 0);
stripes(Colors.black, 0.6 - 0.2 * visibility, offset);
canvas.restore();
}
void _chapters(Canvas canvas) {
final g = geometry;
final r = g.diamondRadius;
if (r <= 0) return;
void diamond(double fraction, double radius) {
final c = Offset(g.x(fraction), 0);
Path shape(double r) => Path()
..moveTo(c.dx - r, c.dy)
..lineTo(c.dx, c.dy - r)
..lineTo(c.dx + r, c.dy)
..lineTo(c.dx, c.dy + r)
..close();
final inner = shape(radius);
// A 1 px border outside the fill (`\bord`), the fill translucent.
final outer = shape(radius + UoscTimelineSpec.border * math.sqrt2);
canvas.drawPath(Path.combine(PathOperation.difference, outer, inner), Paint()..color = Colors.black);
canvas.drawPath(inner, Paint()..color = Colors.white.withValues(alpha: UoscTimelineSpec.chaptersOpacity));
}
for (var i = 0; i < chapters.length; i++) {
if (i != hoveredChapter) diamond(chapters[i], r);
}
final h = hoveredChapter;
if (h != null && h < chapters.length) diamond(chapters[h], r * 2);
}
TextPainter _text(String s, double fontSize, Color color, {Paint? foreground}) => TextPainter(
text: TextSpan(
text: s,
style: TextStyle(
fontFamily: fontFamily,
fontSize: fontSize,
height: 1,
color: foreground == null ? color : null,
foreground: foreground,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
textDirection: TextDirection.ltr,
)..layout();
/// Text on the band: white with a black outline, inverted where it
/// crosses the position line (uosc clips one copy to each).
void _label(Canvas canvas, String s, double fontSize, double opacity, Offset at, Rect line, Size size) {
void copy(Color fill, Color outline) {
_text(
s,
fontSize,
fill,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = UoscTimelineSpec.textBorder * 2
..strokeJoin = StrokeJoin.round
..color = outline.withValues(alpha: opacity),
).paint(canvas, at);
_text(s, fontSize, fill.withValues(alpha: opacity)).paint(canvas, at);
}
canvas.save();
canvas.clipPath(
Path()
..fillType = PathFillType.evenOdd
..addRect(Rect.fromLTWH(-20, -20, size.width + 40, size.height + 40))
..addRect(line),
);
copy(Colors.white, Colors.black);
canvas.restore();
canvas.save();
canvas.clipRect(line);
copy(Colors.black, Colors.white);
canvas.restore();
}
void _times(Canvas canvas, double w, double top, double bottom, Rect line) {
const fs = UoscTimelineSpec.fontSize, spacing = UoscTimelineSpec.spacing;
final size = Size(w, bottom);
final cy = top + (bottom - top) / 2;
final e = elapsed, r = remaining;
final ew = e == null ? 0.0 : _text(e, fs, Colors.white).width;
final rw = r == null ? 0.0 : _text(r, fs, Colors.white).width;
final c = cache;
if (c != null) {
const cfs = fs * 0.8, margin = 5.0;
final cw = _text(c, cfs, Colors.white).width;
var x = line.right + margin;
final minX = spacing + margin + ew, maxX = w - spacing - margin - rw;
if (x < minX) {
x = minX;
} else if (x + cw > maxX) {
x = maxX - cw;
}
_label(canvas, c, cfs, textOpacity * 0.6, Offset(x, cy - cfs / 2), line, size);
}
if (e != null) _label(canvas, e, fs, textOpacity, Offset(spacing, cy - fs / 2), line, size);
if (r != null) _label(canvas, r, fs, textOpacity, Offset(w - spacing - rw, cy - fs / 2), line, size);
}
@override
bool shouldRepaint(UoscTimelinePainter old) => true;
}
@@ -0,0 +1,128 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../volume.dart' show maxVolume;
// uosc's volume slider (`elements/Volume.lua`) as it flashes on a volume
// change: a vertical band at the right edge, black at `opacity.slider`
// with a white gauge from the bottom, notches at 100 %, the value at the
// bottom (black on the gauge, white off it).
/// The flashed uosc volume slider; size it with [UoscVolumeSlider.width]
/// and the height uosc gives it (see [UoscVolumeSlider.heightFor]).
class UoscVolumeSlider extends StatelessWidget {
/// 0..[maxVolume].
final double volume;
const UoscVolumeSlider({super.key, required this.volume});
/// `volume_size`.
static const width = 40.0;
/// uosc: min(size * 8, 80 % of the room between the bars), less the
/// mute button (one [width]) below it.
static double heightFor(double room) => math.min(width * 8, room * 0.8) - width;
@override
Widget build(BuildContext context) => CustomPaint(
key: const ValueKey('uosc-volume-flash'),
painter: UoscVolumePainter(
volume: volume,
fontFamily: DefaultTextStyle.of(context).style.fontFamily ?? Theme.of(context).textTheme.bodyMedium?.fontFamily,
),
);
}
class UoscVolumePainter extends CustomPainter {
final double volume;
final String? fontFamily;
const UoscVolumePainter({required this.volume, this.fontFamily});
/// `volume_border`, `border_radius`.
static const border = 1.0;
static const radius = 4.0;
@override
void paint(Canvas canvas, Size size) {
final w = size.width, h = size.height;
if (w <= 0 || h <= w) return;
final v = volume.clamp(0.0, maxVolume);
final nudgeY = h - (h * (100 / maxVolume)).roundToDouble();
final nudge = (w * 0.18).roundToDouble();
final spacing = (w * 0.2).roundToDouble();
Path notches(double inset) {
final n = nudge - inset;
return Path()
..moveTo(0, nudgeY - nudge)
..lineTo(inset + n, nudgeY)
..lineTo(0, nudgeY + nudge)
..close()
..moveTo(w, nudgeY - nudge)
..lineTo(w - inset - n, nudgeY)
..lineTo(w, nudgeY + nudge)
..close();
}
final bg = RRect.fromRectAndRadius(Offset.zero & size, const Radius.circular(radius + border));
final volumeY = border + (h - border * 2) * (1 - math.min(v / maxVolume, 1));
final gauge = RRect.fromRectAndRadius(
Rect.fromLTRB(border, math.min(volumeY, h - border), w - border, h - border),
const Radius.circular(radius),
);
// The notches cut into both.
canvas.save();
canvas.clipPath(
Path()
..fillType = PathFillType.evenOdd
..addRect(Offset.zero & size)
..addPath(notches(0), Offset.zero),
);
canvas.drawPath(
Path()
..fillType = PathFillType.evenOdd
..addRRect(bg)
..addRRect(gauge),
Paint()..color = Colors.black.withValues(alpha: 0.9),
);
canvas.restore();
canvas.save();
canvas.clipPath(
Path()
..fillType = PathFillType.evenOdd
..addRect(Offset.zero & size)
..addPath(notches(border), Offset.zero),
);
canvas.drawRRect(gauge, Paint()..color = Colors.white);
canvas.restore();
// round(volume * 10) / 10, as Lua prints it ("85", "85.5").
final tenths = (v * 10).round();
final text = tenths % 10 == 0 ? '${tenths ~/ 10}' : (tenths / 10).toString();
final fontSize = (w * 0.6 - text.length * (w / 20)).roundToDouble();
TextPainter label(Color color) => TextPainter(
text: TextSpan(
text: text,
style: TextStyle(fontFamily: fontFamily, fontSize: fontSize, height: 1, color: color),
),
textDirection: TextDirection.ltr,
)..layout();
final dark = label(Colors.black), light = label(Colors.white);
final at = Offset((w - dark.width) / 2, h - spacing - dark.height);
canvas.save();
canvas.clipRRect(gauge);
dark.paint(canvas, at);
canvas.restore();
canvas.save();
canvas.clipPath(
Path()
..fillType = PathFillType.evenOdd
..addRect(Offset.zero & size)
..addRRect(gauge),
);
light.paint(canvas, at);
canvas.restore();
}
@override
bool shouldRepaint(UoscVolumePainter old) => old.volume != volume || old.fontFamily != fontFamily;
}
+317
View File
@@ -0,0 +1,317 @@
import 'dart:async';
import 'package:flutter/foundation.dart' show ValueListenable;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import '../playback/playback_controller.dart' show PlaybackController;
import '../widgets/app_menu.dart' show AppGlass;
/// The player's top volume (mpv's `volume-max`): past 100 % mpv amplifies.
const maxVolume = PlaybackController.maxVolume * 1.0;
/// The part of the volume bar above 100 %.
const boostColor = Color(0xFFFFB74D);
/// One wheel notch or arrow key.
const volumeStep = 5.0;
/// [volume] moved by [delta] (wheel, keys), kept within 0..[maxVolume].
double stepVolume(double volume, double delta) => (volume + delta).clamp(0.0, maxVolume);
/// Turns wheel deltas into volume steps: a mouse notch (a big delta) is
/// one step, small smooth-scroll deltas add up to one. Mostly horizontal
/// scrolls (Shift+wheel, sideways pans) are no steps.
class WheelSteps {
// Flutter makes a notch 53 px on Linux, 60 on Windows (3 lines).
static const _notch = 50.0;
double _sum = 0;
/// Whether [e] is a vertical scroll (the only kind that steps).
static bool vertical(PointerSignalEvent e) =>
e is PointerScrollEvent && e.scrollDelta.dy != 0 && e.scrollDelta.dy.abs() > e.scrollDelta.dx.abs();
/// +1 louder (wheel up), -1 quieter, 0 not a step yet.
int take(PointerScrollEvent e) {
final dy = e.scrollDelta.dy;
if (dy.abs() >= _notch) {
_sum = 0;
return dy < 0 ? 1 : -1;
}
if (_sum != 0 && _sum.sign != dy.sign) _sum = 0;
_sum += dy;
if (_sum.abs() < _notch) return 0;
final step = _sum < 0 ? 1 : -1;
_sum = 0;
return step;
}
/// Claims [e] if it is a vertical scroll that nothing under the mouse
/// claimed first (a list, a menu), then calls [onStep] per step.
void handle(PointerSignalEvent e, ValueChanged<int> onStep) {
if (!vertical(e)) return;
GestureBinding.instance.pointerSignalResolver.register(e, (event) {
final step = take(event as PointerScrollEvent);
if (step != 0) onStep(step);
});
}
}
/// The wheel anywhere over [child] steps the volume ([onStep]: +1 / -1),
/// unless something under the mouse scrolls (it claims the event first)
/// or [enabled] is off.
class VolumeWheel extends StatefulWidget {
final ValueChanged<int> onStep;
final bool enabled;
final Widget child;
const VolumeWheel({super.key, required this.onStep, this.enabled = true, required this.child});
@override
State<VolumeWheel> createState() => _VolumeWheelState();
}
class _VolumeWheelState extends State<VolumeWheel> {
final _steps = WheelSteps();
@override
Widget build(BuildContext context) => Listener(
onPointerSignal: (e) {
if (widget.enabled) _steps.handle(e, widget.onStep);
},
child: widget.child,
);
}
/// Shows [builder] for a second after each [trigger] tick (a wheel or
/// key volume step), fading in and out; ignores the mouse.
class VolumeFlash extends StatefulWidget {
final ValueListenable<int> trigger;
final WidgetBuilder builder;
const VolumeFlash({super.key, required this.trigger, required this.builder});
/// uosc's `flash_duration`.
static const hold = Duration(seconds: 1);
@override
State<VolumeFlash> createState() => _VolumeFlashState();
}
class _VolumeFlashState extends State<VolumeFlash> {
bool _on = false;
Timer? _timer;
@override
void initState() {
super.initState();
widget.trigger.addListener(_flash);
}
@override
void didUpdateWidget(VolumeFlash old) {
super.didUpdateWidget(old);
if (old.trigger != widget.trigger) {
old.trigger.removeListener(_flash);
widget.trigger.addListener(_flash);
}
}
@override
void dispose() {
widget.trigger.removeListener(_flash);
_timer?.cancel();
super.dispose();
}
void _flash() {
if (!mounted) return;
setState(() => _on = true);
_timer?.cancel();
_timer = Timer(VolumeFlash.hold, () {
if (mounted) setState(() => _on = false);
});
}
@override
Widget build(BuildContext context) => IgnorePointer(
child: AnimatedOpacity(
duration: const Duration(milliseconds: 150),
opacity: _on ? 1 : 0,
child: _on || _timer != null ? widget.builder(context) : const SizedBox.shrink(),
),
);
}
/// The standard design's volume readout for [VolumeFlash]: a glass pill
/// (the player's pill look) with the icon, a [VolumeBar] and the
/// percentage.
class VolumeReadout extends StatelessWidget {
final double volume;
const VolumeReadout({super.key, required this.volume});
@override
Widget build(BuildContext context) {
final v = volume.clamp(0.0, maxVolume);
return AppGlass.pill(
key: const ValueKey('volume-readout'),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 14, 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
v <= 0 ? Icons.volume_off : (v < 50 ? Icons.volume_down : Icons.volume_up),
size: 20,
color: v > 100 ? boostColor : Colors.white,
),
const SizedBox(width: 8),
VolumeBar(volume: v, onChanged: (_) {}, width: 96),
const SizedBox(width: 10),
SizedBox(
width: 44,
child: Text(
'${v.round()} %',
textAlign: TextAlign.right,
style: const TextStyle(color: Colors.white, fontSize: 13, fontFeatures: [FontFeature.tabularFigures()]),
),
),
],
),
),
);
}
}
/// A thin volume track over 0..[maxVolume]: filled up to the volume, the
/// part above 100 % in [boostColor], a tick at 100 %. Click or drag sets
/// it.
class VolumeBar extends StatelessWidget {
final double volume;
final ValueChanged<double> onChanged;
final double width;
final Color color;
final Color trackColor;
const VolumeBar({
super.key,
required this.volume,
required this.onChanged,
this.width = 64,
this.color = Colors.white,
this.trackColor = Colors.white30,
});
@override
Widget build(BuildContext context) {
void at(double x) => onChanged((x / width * maxVolume).clamp(0.0, maxVolume).roundToDouble());
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (e) => at(e.localPosition.dx),
onHorizontalDragUpdate: (e) => at(e.localPosition.dx),
child: SizedBox(
width: width,
height: 24,
child: CustomPaint(painter: _VolumePainter(volume.clamp(0.0, maxVolume), color, trackColor)),
),
);
}
}
class _VolumePainter extends CustomPainter {
final double volume;
final Color color, trackColor;
_VolumePainter(this.volume, this.color, this.trackColor);
@override
void paint(Canvas canvas, Size size) {
final y = size.height / 2;
double x(double v) => size.width * v / maxVolume;
final line = Paint()
..strokeWidth = 3
..strokeCap = StrokeCap.round;
canvas.drawLine(Offset(0, y), Offset(size.width, y), line..color = trackColor);
final full = x(100);
canvas.drawLine(
Offset(full, y - 5),
Offset(full, y + 5),
Paint()
..color = trackColor
..strokeWidth = 1.5,
);
if (volume > 0) canvas.drawLine(Offset(0, y), Offset(x(volume.clamp(0, 100)), y), line..color = color);
if (volume > 100) canvas.drawLine(Offset(full, y), Offset(x(volume), y), line..color = boostColor);
canvas.drawCircle(Offset(x(volume), y), 5, Paint()..color = volume > 100 ? boostColor : color);
}
@override
bool shouldRepaint(_VolumePainter old) => old.volume != volume || old.color != color || old.trackColor != trackColor;
}
/// Volume for the standard bar (instead of media_kit's, which stops at
/// 100): the icon mutes / unmutes, hovering shows a [VolumeBar], the
/// wheel steps [volumeStep].
class VolumeButton extends StatefulWidget {
final Player player;
final FocusNode? focusNode;
const VolumeButton({super.key, required this.player, this.focusNode});
@override
State<VolumeButton> createState() => _VolumeButtonState();
}
class _VolumeButtonState extends State<VolumeButton> {
final _steps = WheelSteps();
bool _hover = false;
double _beforeMute = 100;
@override
Widget build(BuildContext context) {
final player = widget.player;
return StreamBuilder<double>(
stream: player.stream.volume,
initialData: player.state.volume,
builder: (context, snap) {
final v = snap.data ?? 100;
return MouseRegion(
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: Listener(
// Claimed here, so the player's own wheel handler skips it.
onPointerSignal: (e) =>
_steps.handle(e, (step) => player.setVolume(stepVolume(player.state.volume, step * volumeStep))),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 4),
IconButton(
key: const ValueKey('volume-button'),
focusNode: widget.focusNode,
tooltip: 'Volume ${v.round()} %',
iconSize: 22,
color: Colors.white,
onPressed: () {
if (v > 0) {
_beforeMute = v;
player.setVolume(0);
} else {
player.setVolume(_beforeMute <= 0 ? 100 : _beforeMute);
}
},
icon: Icon(v <= 0 ? Icons.volume_off : (v < 50 ? Icons.volume_down : Icons.volume_up)),
),
AnimatedSize(
duration: const Duration(milliseconds: 150),
child: _hover
? Padding(
padding: const EdgeInsets.only(left: 4, right: 12),
child: VolumeBar(volume: v, onChanged: player.setVolume),
)
: const SizedBox.shrink(),
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,84 @@
import 'package:flutter/foundation.dart';
import 'package:hive_ce/hive.dart';
import 'package:signals/signals_flutter.dart';
// Files source state, one Hive box: the folders the user added and the
// local files played last. Same shape as AppSettings: signals start empty,
// take the stored values once [FolderStore.ensureLoaded] finishes, and
// mutators write through.
const filesBoxName = 'files';
const _keyFolders = 'folders';
const _keyRecent = 'recent';
/// Recent files kept, most recent first.
const maxRecentFiles = 30;
class FolderStore {
static final FolderStore instance = FolderStore();
/// Watched folders, in the order they were added.
final folders = signal<List<String>>(const []);
/// Local files played, most recent first, no duplicates.
final recent = signal<List<String>>(const []);
Future<void>? _load;
Box? _box;
Future<void> ensureLoaded() => _load ??= _loadNow();
Future<void> _loadNow() async {
try {
final box = _box = await Hive.openBox(filesBoxName);
final f = box.get(_keyFolders);
if (f is List) folders.value = List.unmodifiable(f.whereType<String>());
final r = box.get(_keyRecent);
if (r is List) {
recent.value = List.unmodifiable(
r.whereType<String>().toSet().take(maxRecentFiles));
}
} catch (e, st) {
debugPrint('load files store failed: $e\n$st');
}
}
/// False when [dir] is already watched.
bool addFolder(String dir) {
if (folders.value.contains(dir)) return false;
_setFolders([...folders.value, dir]);
return true;
}
void removeFolder(String dir) =>
_setFolders([...folders.value.where((d) => d != dir)]);
/// Moves [path] to the top of [recent].
void addRecent(String path) => _setRecent(
[path, ...recent.value.where((p) => p != path)].take(maxRecentFiles));
void removeRecent(String path) =>
_setRecent(recent.value.where((p) => p != path));
void clearRecent() => _setRecent(const []);
void _setFolders(List<String> list) {
folders.value = List.unmodifiable(list);
_put(_keyFolders, [...list]);
}
void _setRecent(Iterable<String> list) {
recent.value = List.unmodifiable(list);
_put(_keyRecent, [...recent.value]);
}
Future<void> _put(String key, Object value) async {
try {
final box = _box ??
(Hive.isBoxOpen(filesBoxName) ? Hive.box(filesBoxName) : null);
await box?.put(key, value);
} catch (e, st) {
debugPrint('save files store $key failed: $e\n$st');
}
}
}
+196
View File
@@ -0,0 +1,196 @@
// Release file names -> series / season / episode, anitomy-style but small:
// enough to group a downloads folder into shows. Unknown shapes fall back to
// the cleaned name with no episode, never to a wrong number.
/// What a release file name says.
class ParsedName {
/// Title without group, season marker, episode, year or release tags.
final String series;
final int? season;
final int? episode;
/// Release group: leading `[Group]` or scene-style trailing `-GRP`.
final String? group;
/// Release year, taken out of [series] (movies, `Title (2019)`).
final int? year;
/// Non-episode marker (`NCOP1`, `NCED`, `OVA 2`, `Special`): such files
/// carry no [episode] so they never sort among the real ones.
final String? extra;
const ParsedName(
this.series, {
this.season,
this.episode,
this.group,
this.year,
this.extra,
});
/// `S01E03`, `E25`, the [extra] marker, `S02` for a season pack, else ''.
String get label {
final s = season == null ? '' : 'S${season.toString().padLeft(2, '0')}';
if (episode != null) return '${s}E${episode.toString().padLeft(2, '0')}';
return extra ?? s;
}
/// Grouping key: case, punctuation and spacing do not split a series.
String get seriesKey => normalizeSeries(series);
@override
String toString() => 'ParsedName($series, s=$season, e=$episode, '
'group=$group, year=$year, extra=$extra)';
}
/// Lowercase letters and digits of [s], single spaces between words.
String normalizeSeries(String s) => s
.toLowerCase()
.replaceAll(RegExp(r"['’`]"), '')
.replaceAll(RegExp(r'[^\p{L}\p{N}]+', unicode: true), ' ')
.trim();
// Release tags: the title ends where the first of these starts.
final _junk = RegExp(
r'\b(?:2160p|1440p|1080[pi]|720p|576p|480p|4K|UHD|[xh]\.?26[45]|HEVC|AVC|'
r'AV1|VP9|WEB(?:-?DL|-?Rip)?|BD(?:Rip|Remux)?|Blu-?Ray|BRRip|DVD(?:Rip)?|'
r'HDTV|HDR(?:10)?|DV|REMUX|AAC(?:\d\.\d)?|FLAC|Opus|DTS(?:-HD)?|E?-?AC-?3|'
r'DDP?\d?|TrueHD|Atmos|10-?bit|8-?bit|Hi10P?|PROPER|REPACK|Dual[ -]Audio|'
r'Multi(?:ple)?[ -]Subs?(?:titles?)?|AMZN|NF|CR|DSNP|HMAX|ATVP|'
r'Batch|Complete)\b',
caseSensitive: false,
);
final _ext = RegExp(r'\.(?=[a-z0-9]*[a-z])[a-z0-9]{2,4}$', caseSensitive: false);
final _leadingGroup = RegExp(r'^\s*\[([^\]]+)\]');
final _sceneGroup = RegExp(r'-([A-Za-z0-9]+)$');
final _bracketYear = RegExp(r'[(\[]((?:19|20)\d{2})[)\]]');
final _brackets = RegExp(r'\[[^\]]*\]|\([^)]*\)|\{[^}]*\}|【[^】]*】');
// S01E02, S01.E02, S1 E2; a batch `S01E01-E02` / `S01E01E02` keeps the first.
final _sxe = RegExp(r'\bS(\d{1,2})[ .]?E(\d{1,4})(?:v\d)?(?!\d)',
caseSensitive: false);
final _nxn = RegExp(r'\b(\d{1,2})x(\d{2,3})\b');
// Anime: `Title - 25`, `- 03v2`, `- 01-02` (batch keeps the first).
final _dashNumber = RegExp(r'\s[-–]\s(\d{1,4})(?:v\d)?(?=$|[\s\-~.])');
final _episodeWord = RegExp(
r'\b(?:Episode|Ep\.?|E)\s?(\d{1,4})(?:v\d)?\b',
caseSensitive: false,
);
// Extras: unambiguous words anywhere, short ones only right after the dash.
final _extraAnywhere = RegExp(
r'(?:^|\s)(NCOP|NCED|OVA|OAD|ONA)\s?(\d{1,2})?(?:v\d)?(?=$|\s)',
caseSensitive: false,
);
final _extraAfterDash = RegExp(
r'\s[-–]\s(OP|ED|SP|Specials?|Extras?|Recap|Preview|PV|Menu)\s?(\d{1,2})?'
r'(?:v\d)?(?=$|\s)',
caseSensitive: false,
);
final _seasonInTitle = RegExp(
r'\s(?:S(\d{1,2})|Season\s?(\d{1,2})|(\d{1,2})(?:st|nd|rd|th)\s+Season)\s*$',
caseSensitive: false,
);
final _trailingYear = RegExp(r'\s((?:19|20)\d{2})$');
/// Parses a file name (with or without the extension, no directories).
ParsedName parseReleaseName(String fileName) {
var s = fileName.replaceFirst(_ext, '').trim();
final original = s;
String? group = _leadingGroup.firstMatch(s)?.group(1)?.trim();
int? year;
final by = _bracketYear.firstMatch(s);
if (by != null) year = int.parse(by.group(1)!);
s = s.replaceAll(_brackets, ' ');
// Dots and underscores as separators (scene style), unless the name
// already has spaces and only the odd `Dr. Stone` dot.
final dotted = !s.contains(' ') || RegExp(r'\w\.\w').allMatches(s).length > 2;
if (dotted) {
// `...H264-GRP`, but not `...WEB-DL`.
final tags = _junk.allMatches(s);
if (group == null && tags.isNotEmpty && tags.last.end < s.length) {
final g = _sceneGroup.firstMatch(s);
if (g != null) {
group = g.group(1);
s = s.substring(0, g.start);
}
}
s = s.replaceAll('.', ' ');
}
s = s.replaceAll('_', ' ').replaceAll(RegExp(r'\s+'), ' ').trim();
// Release tags end the interesting part.
final junk = _junk.firstMatch(s);
if (junk != null && junk.start > 0) s = s.substring(0, junk.start).trim();
int? season, episode;
String? extra;
var cut = s.length;
final extraMatch =
_extraAfterDash.firstMatch(s) ?? _extraAnywhere.firstMatch(s);
final sxe = _sxe.firstMatch(s);
if (extraMatch != null) {
final word = extraMatch.group(1)!;
final upper = word.length <= 4 ? word.toUpperCase() : _title(word);
// `OVA - 02`: the number may follow the dash instead.
final n = extraMatch.group(2) ??
_dashNumber.firstMatch(s.substring(extraMatch.end))?.group(1);
extra = n == null
? upper
: (upper.startsWith('NC') || upper.length <= 2)
? '$upper${int.parse(n)}'
: '$upper ${int.parse(n)}';
cut = extraMatch.start;
if (sxe != null && sxe.start < cut) {
season = int.parse(sxe.group(1)!);
cut = sxe.start;
}
} else if (sxe != null) {
season = int.parse(sxe.group(1)!);
episode = int.parse(sxe.group(2)!);
cut = sxe.start;
} else {
final nxn = _nxn.firstMatch(s);
if (nxn != null) {
season = int.parse(nxn.group(1)!);
episode = int.parse(nxn.group(2)!);
cut = nxn.start;
} else {
final m = _dashNumber.firstMatch(s) ?? _episodeWord.firstMatch(s);
if (m != null) {
episode = int.parse(m.group(1)!);
cut = m.start;
}
}
}
var series = s.substring(0, cut);
// Season pack or season marker kept in the title: `Title S2`,
// `Title Season 2`, `Title 2nd Season`, `Title.S01.1080p`.
final sIn = _seasonInTitle.firstMatch(series);
if (sIn != null) {
season ??= int.parse(sIn.group(1) ?? sIn.group(2) ?? sIn.group(3)!);
series = series.substring(0, sIn.start);
}
series = series.replaceAll(RegExp(r'[\s\-–:,~]+$'), '').trim();
// A trailing year is the release year, unless it is the whole title or
// too far ahead to be one (`Blade Runner 2049`).
final ty = _trailingYear.firstMatch(series);
if (ty != null) {
final y = int.parse(ty.group(1)!);
if (y <= DateTime.now().year + 1) {
year ??= y;
series = series.substring(0, ty.start).trim();
}
}
series = series.replaceAll(RegExp(r'[\s\-–:,~]+$'), '').trim();
if (series.isEmpty) {
series = original.replaceAll(RegExp(r'[._]+'), ' ').trim();
}
return ParsedName(
series,
season: season,
episode: episode,
group: group,
year: year,
extra: extra,
);
}
String _title(String w) => w[0].toUpperCase() + w.substring(1).toLowerCase();
+257
View File
@@ -0,0 +1,257 @@
import 'dart:io';
import 'dart:isolate';
import 'name_parser.dart';
// Folder scan for the Files source: videos with their parsed names,
// sidecar subtitles, everything else counted and hidden. The walk is
// synchronous I/O, so it runs in its own isolate.
const videoExtensions = {
'mkv', 'mp4', 'webm', 'avi', 'mov', 'm4v', 'ts', 'm2ts', 'wmv', 'flv', //
};
const subtitleExtensions = {'ass', 'ssa', 'srt', 'vtt', 'sup'};
/// Files looked at per scan; past this the result is [FolderScan.truncated].
const maxScannedFiles = 20000;
/// Samples and trailers under this size are hidden next to the real thing.
const _sampleMaxBytes = 300 << 20;
enum FileKind { video, subtitle, other }
FileKind kindOf(String path) {
final ext = _extension(_baseName(path));
if (videoExtensions.contains(ext)) return FileKind.video;
if (subtitleExtensions.contains(ext)) return FileKind.subtitle;
return FileKind.other;
}
class VideoFile {
final String path;
final int size;
final ParsedName parsed;
final DateTime modified;
const VideoFile(this.path, this.size, this.parsed, this.modified);
String get name => _baseName(path);
}
class FolderScan {
final List<VideoFile> videos;
/// Non-video files and samples (subtitles and dot-entries not counted).
final int hiddenCount;
/// Up to 6 of the hidden file names, for "and N other files" hints.
final List<String> hiddenExamples;
/// Video path -> subtitle files next to it with the same base name
/// (`Ep 01.mkv` -> `Ep 01.srt`, `Ep 01.en.ass`).
final Map<String, List<String>> subtitles;
/// Stopped after [maxScannedFiles] files.
final bool truncated;
const FolderScan({
required this.videos,
required this.hiddenCount,
required this.hiddenExamples,
required this.subtitles,
required this.truncated,
});
static const empty = FolderScan(
videos: [],
hiddenCount: 0,
hiddenExamples: [],
subtitles: {},
truncated: false,
);
}
/// Scans [dir] recursively off the UI isolate. Symlinks are not followed,
/// hidden (dot) files and folders are skipped, unreadable subfolders are
/// left out; never throws (a missing folder is [FolderScan.empty]).
Future<FolderScan> scanFolder(String dir, {int maxFiles = maxScannedFiles}) =>
Isolate.run(() => scanFolderSync(dir, maxFiles: maxFiles))
.catchError((Object _) => FolderScan.empty);
/// The walk behind [scanFolder]; blocks, so call it from an isolate.
FolderScan scanFolderSync(String dir, {int maxFiles = maxScannedFiles}) {
final videos = <VideoFile>[];
final subs = <String>[];
final examples = <String>[];
var hidden = 0, seen = 0;
var truncated = false;
final pending = [Directory(dir)];
walk:
while (pending.isNotEmpty) {
final List<FileSystemEntity> entries;
try {
entries = pending.removeLast().listSync(followLinks: false);
} catch (_) {
continue;
}
entries.sort((a, b) => a.path.compareTo(b.path));
for (final e in entries) {
final name = _baseName(e.path);
if (name.startsWith('.')) continue;
if (e is Directory) {
pending.add(e);
continue;
}
if (e is! File) continue; // links
if (++seen > maxFiles) {
truncated = true;
break walk;
}
final kind = kindOf(name);
if (kind == FileKind.subtitle) {
subs.add(e.path);
continue;
}
FileStat? stat;
if (kind == FileKind.video) {
try {
stat = e.statSync();
} catch (_) {}
}
final size = stat?.size ?? 0;
final lower = name.toLowerCase();
final sample = kind == FileKind.video &&
(lower.contains('sample') || lower.contains('trailer')) &&
size < _sampleMaxBytes;
if (kind != FileKind.video || sample) {
hidden++;
if (examples.length < 6) examples.add(name);
continue;
}
videos.add(VideoFile(e.path, size, parseReleaseName(name),
stat?.modified ?? DateTime.fromMillisecondsSinceEpoch(0)));
}
}
return FolderScan(
videos: videos,
hiddenCount: hidden,
hiddenExamples: examples,
subtitles: _sidecars(videos, subs),
truncated: truncated,
);
}
/// Subtitles in the same folder whose name is the video's base name,
/// optionally followed by `.lang` / `.forced` style suffixes.
Map<String, List<String>> _sidecars(List<VideoFile> videos, List<String> subs) {
final byStem = <String, List<String>>{};
for (final s in subs) {
final stem = _stripExtension(s);
byStem.putIfAbsent(stem, () => []).add(s);
// `Ep 01.en.ass`, `Ep 01.en.forced.ass`: also index the shorter stems.
var shorter = stem;
for (var i = 0; i < 2; i++) {
final dot = shorter.lastIndexOf('.');
if (dot <= shorter.lastIndexOf(RegExp(r'[/\\]')) + 1) break;
final suffix = shorter.substring(dot + 1);
// Language / flag tags only (en, eng, pt-BR, forced, sdh): a suffix
// with digits is part of the name (`Show.S01E01.srt` is not `Show`'s).
if (!RegExp(r'^[A-Za-z]{2,8}(-[A-Za-z]{2,4})?$').hasMatch(suffix)) break;
shorter = shorter.substring(0, dot);
byStem.putIfAbsent(shorter, () => []).add(s);
}
}
return {
for (final v in videos)
if (byStem[_stripExtension(v.path)] case final list?)
v.path: (list.toSet().toList()..sort()),
};
}
/// One show: its episodes in season/episode order plus its extras
/// (NCOP, OVA...) in name order.
class SeriesGroup {
/// Display title: the most common spelling among the files.
final String title;
final String key;
final List<VideoFile> episodes;
final List<VideoFile> extras;
const SeriesGroup(this.title, this.key, this.episodes, this.extras);
/// Distinct seasons, ascending; files without one count as season 1.
List<int> get seasons =>
({for (final e in episodes) e.parsed.season ?? 1}.toList()..sort());
}
/// [groupBySeries] result: shows, and videos that are not episodes.
class GroupedVideos {
final List<SeriesGroup> series;
final List<VideoFile> loose;
const GroupedVideos(this.series, this.loose);
}
/// Groups videos with an episode number (or an extra marker) by
/// [ParsedName.seriesKey]; the rest (movies, clips) stay loose, by name.
/// An extra whose series has no episodes in the list is loose too.
GroupedVideos groupBySeries(Iterable<VideoFile> videos) {
final episodes = <String, List<VideoFile>>{};
final extras = <String, List<VideoFile>>{};
final loose = <VideoFile>[];
for (final v in videos) {
final p = v.parsed;
final key = p.seriesKey;
if (p.episode != null && key.isNotEmpty) {
episodes.putIfAbsent(key, () => []).add(v);
} else if (p.extra != null && key.isNotEmpty) {
extras.putIfAbsent(key, () => []).add(v);
} else {
loose.add(v);
}
}
for (final e in extras.entries) {
if (!episodes.containsKey(e.key)) loose.addAll(e.value);
}
int byName(VideoFile a, VideoFile b) =>
a.name.toLowerCase().compareTo(b.name.toLowerCase());
final groups = [
for (final e in episodes.entries)
SeriesGroup(
_commonTitle(e.value),
e.key,
e.value..sort(_byEpisode),
(extras[e.key] ?? [])..sort(byName),
),
]..sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return GroupedVideos(groups, loose..sort(byName));
}
int _byEpisode(VideoFile a, VideoFile b) {
final pa = a.parsed, pb = b.parsed;
final s = (pa.season ?? 1).compareTo(pb.season ?? 1);
if (s != 0) return s;
final e = pa.episode!.compareTo(pb.episode!);
if (e != 0) return e;
// Same episode twice (v2, another group): newest release first.
return b.name.compareTo(a.name);
}
String _commonTitle(List<VideoFile> files) {
final counts = <String, int>{};
for (final f in files) {
counts.update(f.parsed.series, (n) => n + 1, ifAbsent: () => 1);
}
return counts.entries
.reduce((a, b) => b.value > a.value ? b : a)
.key;
}
String _baseName(String path) {
final i = path.lastIndexOf(RegExp(r'[/\\]'));
return i < 0 ? path : path.substring(i + 1);
}
String _extension(String name) {
final dot = name.lastIndexOf('.');
return dot <= 0 ? '' : name.substring(dot + 1).toLowerCase();
}
String _stripExtension(String path) {
final dot = path.lastIndexOf('.');
final sep = path.lastIndexOf(RegExp(r'[/\\]'));
return dot > sep + 1 ? path.substring(0, dot) : path;
}
+181
View File
@@ -0,0 +1,181 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:media_kit/media_kit.dart';
import 'package:path_provider/path_provider.dart';
// Poster frames for the Files source, made by mpv itself (no ffmpeg on
// users' machines; Windows ships only libmpv). A hidden player per file
// with `vo=null`: mpv still keeps the last decoded frame, so
// `screenshot-to-file` encodes it with mpv's own JPEG writer. `start=25%`
// makes the first decoded frame the one we want, `vf=scale` keeps it
// small. Results (and failures) are cached on disk by path+size+mtime.
class Thumbnail {
/// JPEG on disk, at most [thumbnailWidth] wide.
final String imagePath;
final Duration duration;
const Thumbnail(this.imagePath, this.duration);
}
const thumbnailWidth = 480;
/// Where thumbnails are cached; swapped in tests.
Future<Directory> Function() thumbnailCacheRoot = getApplicationCacheDirectory;
/// Generations running at once: each is a full decoder.
const _maxConcurrent = 2;
const _timeout = Duration(seconds: 20);
int _running = 0;
final _waiting = <Completer<void>>[];
final _inFlight = <String, Future<Thumbnail?>>{};
/// The cached or freshly made thumbnail of the video at [path], or null
/// when it can't be made (not a video, unreadable, timed out, cancelled).
/// Never throws. [cancelled] is checked while queued and before the
/// expensive steps: a scrolled-away tile returns null without decoding.
Future<Thumbnail?> thumbnailFor(String path, {bool Function()? cancelled}) {
final existing = _inFlight[path];
if (existing != null) return existing;
final f = _thumbnailFor(path, cancelled ?? () => false).catchError((
Object e,
StackTrace st,
) {
debugPrint('thumbnail $path failed: $e\n$st');
return null;
});
_inFlight[path] = f;
f.whenComplete(() => _inFlight.remove(path));
return f;
}
/// Cache key: the same file at the same size and mtime reuses its frame.
String thumbnailKey(String path, int size, DateTime modified) => sha1
.convert(utf8.encode('$path|$size|${modified.millisecondsSinceEpoch}'))
.toString();
Future<Thumbnail?> _thumbnailFor(String path, bool Function() cancelled) async {
final stat = await File(path).stat();
if (stat.type != FileSystemEntityType.file) return null;
final dir = Directory(
'${(await thumbnailCacheRoot()).path}${Platform.pathSeparator}thumbnails');
await dir.create(recursive: true);
final base =
'${dir.path}${Platform.pathSeparator}${thumbnailKey(path, stat.size, stat.modified)}';
final cached = await _readCache(base);
if (cached != null) return cached.$1;
if (cancelled()) return null;
await _acquire();
try {
if (cancelled()) return null;
final made = await _generate(path, '$base.jpg', cancelled)
.timeout(_timeout, onTimeout: () => null);
if (made == null) {
// Remember broken files, not cancellations.
if (!cancelled()) await File('$base.json').writeAsString('{}');
return null;
}
await File('$base.json')
.writeAsString(jsonEncode({'ms': made.duration.inMilliseconds}));
return made;
} finally {
_release();
}
}
/// (thumbnail or null for a remembered failure), or null when not cached.
Future<(Thumbnail?,)?> _readCache(String base) async {
try {
final meta = File('$base.json');
if (!await meta.exists()) return null;
final ms = (jsonDecode(await meta.readAsString()) as Map)['ms'];
if (ms is! int) return (null,);
final jpg = File('$base.jpg');
if (!await jpg.exists()) return null;
return (Thumbnail(jpg.path, Duration(milliseconds: ms)),);
} catch (_) {
return null;
}
}
Future<void> _acquire() async {
if (_running < _maxConcurrent) {
_running++;
return;
}
final c = Completer<void>();
_waiting.add(c);
await c.future; // the releaser hands its slot over
}
void _release() {
if (_waiting.isNotEmpty) {
_waiting.removeAt(0).complete();
} else {
_running--;
}
}
Future<Thumbnail?> _generate(
String path,
String out,
bool Function() cancelled,
) async {
final player = Player(
configuration: const PlayerConfiguration(
vo: 'null',
muted: true,
osc: false,
libass: false,
title: 'thumbnail',
bufferSize: 4 << 20,
),
);
try {
final mpv = player.platform as NativePlayer;
for (final (k, v) in [
('ao', 'null'),
('aid', 'no'),
('sid', 'no'),
('vid', 'auto'), // media_kit starts with vid=no
('hwdec', 'no'),
('cache', 'no'),
('hr-seek', 'no'), // nearest keyframe is fine for a poster
('start', '25%'),
('vf', 'scale=w=$thumbnailWidth:h=-2'),
('screenshot-format', 'jpg'),
('screenshot-jpeg-quality', '85'),
('screenshot-tag-colorspace', 'no'),
]) {
await mpv.setProperty(k, v);
}
await player.open(Media(path), play: false);
final target = File(out);
final deadline = DateTime.now().add(_timeout);
while (DateTime.now().isBefore(deadline)) {
if (cancelled()) return null;
// Unreadable or no video (with aid=no, audio-only stops too): mpv
// drops the file and goes idle at once.
if (await mpv.getProperty('idle-active') == 'yes') return null;
// A frame is up once the video has a size and the seek is done.
final w = await mpv.getProperty('video-params/w');
final seeking = await mpv.getProperty('seeking');
if (w.isNotEmpty && w != '0' && seeking != 'yes') {
await mpv.command(['screenshot-to-file', out, 'video']);
if (await target.exists() && await target.length() > 0) {
final d = double.tryParse(await mpv.getProperty('duration')) ?? 0;
return Thumbnail(out, Duration(milliseconds: (d * 1000).round()));
}
}
await Future.delayed(const Duration(milliseconds: 50));
}
return null;
} finally {
await player.dispose();
}
}
@@ -0,0 +1,330 @@
import 'dart:io';
import 'package:flutter/material.dart';
import '../../shell/avatar.dart';
import '../name_parser.dart';
import '../scanner.dart';
import 'thumb.dart';
// The Files page's rows and cards: a series header, an episode row (also
// used for Recent), a loose-video card, and the empty states.
/// What a play control is doing right now.
enum PlayState { idle, starting, playing }
/// Last path segment, ignoring trailing separators (`/media/anime/`).
String baseName(String path) {
final parts = path.split(RegExp(r'[/\\]')).where((p) => p.isNotEmpty).toList();
return parts.isEmpty ? path : parts.last;
}
/// Name of the folder [path] is in.
String parentName(String path) => baseName(File(path).parent.path);
/// Cleaned title of a loose video: `Title (2019)`.
String cleanTitle(ParsedName p) => p.year == null ? p.series : '${p.series} (${p.year})';
class SeriesHeader extends StatelessWidget {
final SeriesGroup group;
final ThumbCache thumbs;
/// Its episodes are listed below; [onTap] opens or closes them.
final bool expanded;
final VoidCallback? onTap;
const SeriesHeader(
{super.key, required this.group, required this.thumbs, this.expanded = true, this.onTap});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final first = group.episodes.first;
final n = group.episodes.length;
final extras = group.extras.length;
final detail = [
'$n ${n == 1 ? 'episode' : 'episodes'}',
if (extras > 0) '$extras ${extras == 1 ? 'extra' : 'extras'}',
?first.parsed.group,
].join(' · ');
return Padding(
padding: const EdgeInsets.only(top: 14),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: ThumbBuilder(
cache: thumbs,
path: first.path,
builder: (context, t, _) => ThumbImage(t, width: 96, height: 54),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(group.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium),
Text(detail,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12)),
],
),
),
if (onTap != null)
AnimatedRotation(
turns: expanded ? 0.5 : 0,
duration: const Duration(milliseconds: 200),
child: Icon(Icons.expand_more, color: scheme.onSurfaceVariant),
),
const SizedBox(width: 8),
],
),
),
),
);
}
}
/// One file as a row: an episode under its series header, or a Recent
/// entry ([title] given, [indent] off).
class FileRow extends StatelessWidget {
final String path;
final String title;
final String subtitle;
/// Size text; null while unknown.
final String? size;
final PlayState state;
final ThumbCache thumbs;
/// Null disables the row.
final VoidCallback? onTap;
final bool indent;
final Widget? trailing;
const FileRow({
super.key,
required this.path,
required this.title,
required this.subtitle,
required this.size,
required this.state,
required this.thumbs,
required this.onTap,
this.indent = true,
this.trailing,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final playing = state == PlayState.playing;
final enabled = onTap != null;
final muted = scheme.onSurfaceVariant;
final icon = switch (state) {
PlayState.starting => const SizedBox.square(
dimension: 16, child: CircularProgressIndicator(strokeWidth: 2)),
PlayState.playing => Icon(Icons.graphic_eq, size: 20, color: scheme.primary),
PlayState.idle => Icon(Icons.play_circle_outline,
size: 20, color: enabled ? scheme.primary : scheme.onSurface.withValues(alpha: 0.38)),
};
return ThumbBuilder(
cache: thumbs,
path: path,
builder: (context, t, _) {
final duration = durationText(t);
final meta = [?duration, ?size].join(' · ');
return Material(
color: playing ? scheme.primary.withValues(alpha: 0.08) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: EdgeInsets.fromLTRB(indent ? 20 : 4, 5, 8, 5),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: ThumbImage(t, width: 80, height: 45),
),
const SizedBox(width: 10),
SizedBox(width: 20, child: Center(child: icon)),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w600,
color: playing ? scheme.primary : null,
),
),
),
if (playing) ...[
const SizedBox(width: 8),
Pill('now playing', scheme.primary),
],
],
),
Text(subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: muted, fontSize: 12)),
],
),
),
if (meta.isNotEmpty) ...[
const SizedBox(width: 12),
Text(meta, style: TextStyle(color: muted, fontSize: 12)),
],
?trailing,
],
),
),
),
);
},
);
}
}
/// Text under a [VideoCard]'s 16:9 frame (title up to two lines, size).
const videoCardTextHeight = 66.0;
/// A loose video (movie, clip): 16:9 frame with its duration, then the
/// cleaned title and size.
class VideoCard extends StatelessWidget {
final VideoFile video;
final PlayState state;
final ThumbCache thumbs;
final VoidCallback? onTap;
const VideoCard({
super.key,
required this.video,
required this.state,
required this.thumbs,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final playing = state == PlayState.playing;
return ThumbBuilder(
cache: thumbs,
path: video.path,
builder: (context, t, _) {
final duration = durationText(t);
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: playing ? scheme.primary : Colors.transparent,
width: 2,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Stack(
fit: StackFit.expand,
children: [
ThumbImage(t),
if (duration != null)
Positioned(right: 6, bottom: 6, child: _Badge(duration)),
if (state != PlayState.idle)
Container(
color: Colors.black38,
alignment: Alignment.center,
child: state == PlayState.playing
? const Icon(Icons.graphic_eq, color: Colors.white, size: 32)
: const SizedBox.square(
dimension: 28,
child: CircularProgressIndicator(
strokeWidth: 3, color: Colors.white)),
),
],
),
),
),
),
const SizedBox(height: 6),
Flexible(
child: Text(
cleanTitle(video.parsed),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
color: playing ? scheme.primary : null,
),
),
),
Text(formatSize(video.size),
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12)),
],
),
);
},
);
}
}
class _Badge extends StatelessWidget {
final String text;
const _Badge(this.text);
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(color: Colors.black87, borderRadius: BorderRadius.circular(4)),
child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 11)),
);
}
class FilesEmpty extends StatelessWidget {
final IconData icon;
final String title;
final String detail;
final Widget? action;
const FilesEmpty(this.icon, this.title, this.detail, {super.key, this.action});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(top: 60, bottom: 20),
child: Column(
children: [
Icon(icon, size: 44, color: scheme.onSurfaceVariant),
const SizedBox(height: 10),
Text(title, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center),
const SizedBox(height: 4),
Text(detail, style: TextStyle(color: scheme.onSurfaceVariant), textAlign: TextAlign.center),
if (action != null) ...[const SizedBox(height: 14), action!],
],
),
);
}
}
@@ -0,0 +1,788 @@
import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import '../../shell/motion.dart';
import '../../widgets/app_menu.dart';
import '../folder_store.dart';
import '../name_parser.dart';
import '../scanner.dart';
import 'file_tiles.dart';
import 'thumb.dart';
export 'thumb.dart' show ThumbnailLoader;
// The Files source: watched folders on the left (chips on top when
// narrow), the open one on the right grouped into series and loose
// videos, or Recent. A folder is scanned when first opened and on Rescan;
// scans are kept for the app session. Series start collapsed; the ones
// opened stay open for the app session. Playing goes through [onPlay].
/// Below this width the sidebar turns into chips above the list.
const filesNarrowWidth = 700.0;
/// Scans kept for the app session, by folder.
final _scans = <String, FolderScan>{};
final _running = <String, Future<FolderScan>>{};
String? _lastOpen;
/// Series opened this session, by `folder\nseries key`.
final _expanded = <String>{};
final _showRequests = ValueNotifier(0);
/// Opens [dir] (null: Recent) in the Files page: now if one is showing,
/// else when it next opens.
void showFilesFolder(String? dir) {
_lastOpen = dir;
_showRequests.value++;
}
/// Forgets the session's scans, open series and the open folder.
@visibleForTesting
void resetFilesPageSession() {
_scans.clear();
_running.clear();
_expanded.clear();
_lastOpen = null;
}
class FilesPage extends StatefulWidget {
/// Nobody else watches (offline or solo): no relay notice.
final bool solo;
/// The file will stream from this computer through the server.
final bool relayed;
/// Plays [path] in the session; returns an error text, or null.
final Future<String?> Function(String path) onPlay;
/// Adds [path] to Up next (right-click on a video); null: no server.
final Future<String?> Function(String path, {bool next})? onAddToQueue;
final String? nowPlayingPath;
/// Defaults to [thumbnailFor].
final ThumbnailLoader? thumbnails;
/// Defaults to [scanFolder].
final Future<FolderScan> Function(String dir)? scan;
/// Defaults to [FolderStore.instance].
final FolderStore? store;
/// Default: the system folder / file dialogs.
final Future<String?> Function()? pickFolder;
final Future<String?> Function()? pickVideo;
const FilesPage({
super.key,
this.solo = false,
required this.relayed,
required this.onPlay,
this.onAddToQueue,
this.nowPlayingPath,
this.thumbnails,
this.scan,
this.store,
this.pickFolder,
this.pickVideo,
});
@override
State<FilesPage> createState() => _FilesPageState();
}
/// A scan grouped for display, made once per scan.
class _Library {
final GroupedVideos grouped;
final int subtitleFiles;
_Library(FolderScan scan)
: grouped = groupBySeries(scan.videos),
subtitleFiles = {for (final l in scan.subtitles.values) ...l}.length;
/// Series rows in order: a header (video null), then, for the series
/// [open] says, its episodes and extras.
List<(SeriesGroup, VideoFile?)> rows(bool Function(SeriesGroup g) open) => [
for (final g in grouped.series) ...[
(g, null),
if (open(g))
for (final v in [...g.episodes, ...g.extras]) (g, v),
],
];
}
final _libraries = Expando<_Library>();
class _FilesPageState extends State<FilesPage> {
late final ThumbCache _thumbs =
widget.thumbnails == null ? ThumbCache.shared : ThumbCache(widget.thumbnails!);
FolderStore get _store => widget.store ?? FolderStore.instance;
/// Null: Recent.
String? _open = _lastOpen;
bool _showHidden = false;
String? _starting;
String? _error;
@override
void initState() {
super.initState();
_showRequests.addListener(_shown);
_store.ensureLoaded().then((_) {
if (!mounted) return;
final open = _open;
if (open != null && !_store.folders.value.contains(open)) {
_select(null);
} else if (open != null) {
_scan(open);
}
});
}
@override
void dispose() {
_showRequests.removeListener(_shown);
super.dispose();
}
void _shown() => _select(_lastOpen);
Future<FolderScan> _runScan(String dir) => (widget.scan ?? scanFolder)(dir);
void _select(String? dir) {
setState(() {
_open = _lastOpen = dir;
_showHidden = false;
});
if (dir != null && !_scans.containsKey(dir)) _scan(dir);
}
Future<void> _scan(String dir, {bool rescan = false}) async {
var f = _running[dir];
if (f == null || rescan) {
f = _running[dir] = _runScan(dir);
if (rescan) _thumbs.forget([for (final v in _scans[dir]?.videos ?? const <VideoFile>[]) v.path]);
}
if (mounted) setState(() {});
FolderScan result;
try {
result = await f;
} catch (_) {
result = FolderScan.empty;
}
if (_running[dir] == f) {
_running.remove(dir);
_scans[dir] = result;
}
if (mounted) setState(() {});
}
Future<void> _addFolder() async {
String? dir;
try {
dir = await (widget.pickFolder ??
() => FilePicker.getDirectoryPath(dialogTitle: 'Add a folder with videos'))();
} catch (e) {
_showError('Folder dialog unavailable (${e.runtimeType})');
}
if (dir == null || !mounted) return;
_store.addFolder(dir);
_select(dir);
}
Future<void> _browse() async {
String? path;
try {
path = await (widget.pickVideo ?? _pickVideo)();
} catch (e) {
_showError('File dialog unavailable (${e.runtimeType})');
}
if (path != null && mounted) await _play(path);
}
static Future<String?> _pickVideo() async {
final files = await FilePicker.pickFiles(
dialogTitle: 'Pick a video',
type: FileType.custom,
allowedExtensions: videoExtensions.toList(),
);
return files.isEmpty ? null : files.first.path;
}
void _removeFolder(String dir) {
_store.removeFolder(dir);
_scans.remove(dir);
if (_open == dir) _select(null);
}
Future<void> _play(String path) async {
if (_starting != null) return;
setState(() {
_starting = path;
_error = null;
});
String? error;
try {
error = await widget.onPlay(path);
} catch (e) {
error = '$e';
}
if (error == null) _store.addRecent(path);
if (!mounted) return;
setState(() {
_starting = null;
_error = error;
});
}
void _showError(String text) {
if (mounted) setState(() => _error = text);
}
PlayState _stateOf(String path) => path == _starting
? PlayState.starting
: path == widget.nowPlayingPath
? PlayState.playing
: PlayState.idle;
VoidCallback _tapOf(String path) => () => _play(path);
/// Right-click on a video ([context]: its tile): Put it up, Add to
/// queue, Play next.
Future<void> _menu(BuildContext context, String path) async {
final add = widget.onAddToQueue;
if (add == null) return;
final pick = await showAppMenu<int>(context, items: [
AppMenuItem(value: 0, label: widget.solo ? 'Play' : 'Put it up'),
const AppMenuItem(value: 1, label: 'Add to queue'),
const AppMenuItem(value: 2, label: 'Play next'),
]);
if (pick == null || !mounted) return;
if (pick == 0) return _play(path);
String? error;
try {
error = await add(path, next: pick == 2);
} catch (e) {
error = '$e';
}
if (error != null) _showError(error);
}
/// [child] with the right-click menu (when there is a server).
Widget _menuable(String path, Widget child) => widget.onAddToQueue == null
? child
: KeyedSubtree(
key: ValueKey('menu:$path'),
child: Builder(
builder: (context) => GestureDetector(onSecondaryTap: () => _menu(context, path), child: child)));
@override
Widget build(BuildContext context) => LayoutBuilder(
builder: (context, box) {
final narrow = box.maxWidth < filesNarrowWidth;
return SignalBuilder(builder: (context) {
final folders = _store.folders.value;
final recent = _store.recent.value;
final open = _open != null && folders.contains(_open) ? _open : null;
final main = fadeThrough(
KeyedSubtree(
key: ValueKey(open ?? ''),
child: _main(context, narrow, folders, recent, open),
),
);
if (narrow) return main;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 260, child: _sidebar(context, folders, open)),
Expanded(child: main),
],
);
});
},
);
Widget _browseButton(Widget Function(VoidCallback? onPressed) build) => build(_browse);
Widget _sidebar(BuildContext context, List<String> folders, String? open) {
final scheme = Theme.of(context).colorScheme;
final shape = RoundedRectangleBorder(borderRadius: BorderRadius.circular(10));
return ListView(
padding: const EdgeInsets.fromLTRB(20, 20, 8, 20),
children: [
const Text('Folders', style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 8),
ListTile(
dense: true,
selected: open == null,
shape: shape,
leading: const Icon(Icons.history, size: 18),
title: const Text('Recent'),
onTap: () => _select(null),
),
for (final f in folders)
ListTile(
key: ValueKey('folder:$f'),
dense: true,
selected: open == f,
shape: shape,
leading: const Icon(Icons.folder_outlined, size: 18),
title: Text(baseName(f), maxLines: 1, overflow: TextOverflow.ellipsis),
subtitle: switch (_folderStatus(f)) {
final s? => Text(s, style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 11)),
null => null,
},
trailing: IconButton(
tooltip: 'Remove folder',
icon: const Icon(Icons.close, size: 16),
onPressed: () => _removeFolder(f),
),
onTap: () => _select(f),
),
Align(
alignment: Alignment.centerLeft,
child: TextButton.icon(
onPressed: _addFolder,
icon: const Icon(Icons.add, size: 18),
label: const Text('Add folder'),
),
),
const Divider(),
_browseButton((onPressed) => OutlinedButton.icon(
onPressed: onPressed,
icon: const Icon(Icons.folder_open, size: 18),
label: const Text('Browse…'),
)),
const SizedBox(height: 6),
Text(
'The system file picker, for anything outside your folders.',
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 11),
),
],
);
}
/// "scanning…", "N videos", or null before the first scan.
String? _folderStatus(String dir) {
if (_running.containsKey(dir)) return 'scanning…';
final n = _scans[dir]?.videos.length;
return n == null ? null : '$n ${n == 1 ? 'video' : 'videos'}';
}
Widget _main(
BuildContext context,
bool narrow,
List<String> folders,
List<String> recent,
String? open,
) {
final scan = open == null ? null : _scans[open];
final scanning = open != null && _running.containsKey(open);
final top = <Widget>[
_header(context, open),
if (narrow) _chips(folders, open),
if (!widget.solo && widget.relayed) const _RelayNotice(),
AnimatedSize(
duration: easeTime,
curve: ease,
alignment: Alignment.topLeft,
child: _error == null
? const SizedBox(width: double.infinity)
: _ErrorBanner(_error!, onClose: () => setState(() => _error = null)),
),
];
final slivers = <Widget>[
SliverList.list(children: top),
if (open == null)
..._recent(context, recent)
else if (scan == null)
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.only(top: 60),
child: Center(child: CircularProgressIndicator()),
),
)
else
..._folder(context, open, scan, scanning),
];
return CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.fromLTRB(narrow ? 20 : 12, 20, 20, 20),
sliver: SliverMainAxisGroup(slivers: slivers),
),
],
);
}
Widget _header(BuildContext context, String? open) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Row(
children: [
Icon(open == null ? Icons.history : Icons.folder, color: scheme.primary),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
open == null ? 'Recent' : baseName(open),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.headlineSmall,
),
if (open != null)
Text(
open,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12),
),
],
),
),
],
),
);
}
Widget _chips(List<String> folders, String? open) => Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
ChoiceChip(
label: const Text('Recent'),
selected: open == null,
onSelected: (_) => _select(null),
),
for (final f in folders)
// InputChip: selectable like the others, plus the remove x
// the sidebar has.
InputChip(
key: ValueKey('chip:$f'),
label: Text(baseName(f)),
selected: open == f,
onSelected: (_) => _select(f),
onDeleted: () => _removeFolder(f),
deleteButtonTooltipMessage: 'Remove folder',
),
ActionChip(
avatar: const Icon(Icons.add, size: 16),
label: const Text('Folder'),
onPressed: _addFolder,
),
_browseButton((onPressed) => ActionChip(
avatar: const Icon(Icons.folder_open, size: 16),
label: const Text('Browse…'),
onPressed: onPressed,
)),
],
),
);
List<Widget> _recent(BuildContext context, List<String> recent) {
if (recent.isEmpty) {
return [
SliverToBoxAdapter(
child: FilesEmpty(
Icons.video_library_outlined,
'Nothing played yet',
'Add a folder with your videos, or Browse for a single file.',
action: FilledButton.icon(
onPressed: _addFolder,
icon: const Icon(Icons.create_new_folder_outlined),
label: const Text('Add folder'),
),
),
),
];
}
return [
SliverList.builder(
itemCount: recent.length,
itemBuilder: (context, i) {
final path = recent[i];
return _menuable(
path,
_RecentRow(
key: ValueKey(path),
path: path,
state: _stateOf(path),
thumbs: _thumbs,
onTap: _tapOf(path),
onRemove: () => _store.removeRecent(path),
));
},
),
];
}
List<Widget> _folder(BuildContext context, String dir, FolderScan scan, bool scanning) {
final scheme = Theme.of(context).colorScheme;
final lib = _libraries[scan] ??= _Library(scan);
final series = lib.grouped.series.length;
String count(int n, String one, String many) => '$n ${n == 1 ? one : many}';
final summary = [
count(scan.videos.length, 'video', 'videos') + (series > 0 ? ' in $series series' : ''),
if (scan.hiddenCount > 0)
'${count(scan.hiddenCount, 'other file', 'other files')} hidden',
if (lib.subtitleFiles > 0) count(lib.subtitleFiles, 'subtitle file', 'subtitle files'),
if (scan.truncated) 'stopped after $maxScannedFiles files',
].join(' · ');
final loose = lib.grouped.loose;
String seriesId(SeriesGroup g) => '$dir\n${g.key}';
final rows = lib.rows((g) => _expanded.contains(seriesId(g)));
return [
SliverToBoxAdapter(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Icon(Icons.filter_alt_outlined, size: 16, color: scheme.onSurfaceVariant),
const SizedBox(width: 6),
Text(summary, style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12)),
if (scan.hiddenCount > 0) ...[
const SizedBox(width: 8),
TextButton(
onPressed: () => setState(() => _showHidden = !_showHidden),
child: Text(_showHidden ? 'Hide them' : 'Show some'),
),
],
TextButton.icon(
onPressed: scanning ? null : () => _scan(dir, rescan: true),
icon: scanning
? const SizedBox.square(
dimension: 14, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.refresh, size: 16),
label: Text(scanning ? 'Scanning…' : 'Rescan'),
),
],
),
),
SliverToBoxAdapter(
child: AnimatedSize(
duration: easeTime,
curve: ease,
alignment: Alignment.topLeft,
child: !_showHidden
? const SizedBox(width: double.infinity)
: Column(
children: [
for (final f in scan.hiddenExamples)
ListTile(
dense: true,
enabled: false,
leading: const Icon(Icons.insert_drive_file_outlined, size: 18),
title: Text(f, maxLines: 1, overflow: TextOverflow.ellipsis),
trailing: Text(
kindOf(f) == FileKind.video ? 'sample' : 'not a video',
style: const TextStyle(fontSize: 12),
),
),
if (scan.hiddenCount > scan.hiddenExamples.length)
ListTile(
dense: true,
enabled: false,
title: Text('and ${scan.hiddenCount - scan.hiddenExamples.length} more'),
),
],
),
),
),
if (scan.videos.isEmpty)
SliverToBoxAdapter(
child: Directory(dir).existsSync()
? const FilesEmpty(
Icons.search_off,
'No videos here',
'Only video files are listed (mkv, mp4, webm…).',
)
: const FilesEmpty(
Icons.folder_off_outlined,
'Folder not found',
'It may have been moved, renamed or be on a drive that is not connected.',
),
),
// Series: files whose names carry an episode number; a header
// click opens or closes its episodes.
SliverList.builder(
itemCount: rows.length,
itemBuilder: (context, i) {
final (group, v) = rows[i];
if (v == null) {
final id = seriesId(group);
return SeriesHeader(
key: ValueKey('series:${group.key}'),
group: group,
thumbs: _thumbs,
expanded: _expanded.contains(id),
onTap: () => setState(() => _expanded.contains(id) ? _expanded.remove(id) : _expanded.add(id)),
);
}
return _menuable(
v.path,
FileRow(
key: ValueKey(v.path),
path: v.path,
title: v.parsed.label.isEmpty ? v.parsed.series : v.parsed.label,
subtitle: v.name,
size: formatSize(v.size),
state: _stateOf(v.path),
thumbs: _thumbs,
onTap: _tapOf(v.path),
));
},
),
// Everything else (movies, clips) as a thumbnail grid.
if (loose.isNotEmpty) ...[
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 22, bottom: 10),
child: Text(
series == 0 ? 'Videos' : 'Other videos',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
SliverLayoutBuilder(builder: (context, c) {
const gap = 12.0;
final w = c.crossAxisExtent;
final cols = (w / 240).floor().clamp(2, 8);
final cardW = (w - gap * (cols - 1)) / cols;
return SliverGrid.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: cols,
mainAxisSpacing: gap,
crossAxisSpacing: gap,
mainAxisExtent: cardW * 9 / 16 + videoCardTextHeight,
),
itemCount: loose.length,
itemBuilder: (context, i) {
final v = loose[i];
return _menuable(
v.path,
VideoCard(
key: ValueKey(v.path),
video: v,
state: _stateOf(v.path),
thumbs: _thumbs,
onTap: _tapOf(v.path),
));
},
);
}),
],
];
}
}
/// A Recent entry: `Series · S01E03` over the file name, or the cleaned
/// title over its folder; greyed when the file is gone.
class _RecentRow extends StatefulWidget {
final String path;
final PlayState state;
final ThumbCache thumbs;
final VoidCallback? onTap;
final VoidCallback onRemove;
const _RecentRow({
super.key,
required this.path,
required this.state,
required this.thumbs,
required this.onTap,
required this.onRemove,
});
@override
State<_RecentRow> createState() => _RecentRowState();
}
class _RecentRowState extends State<_RecentRow> {
FileStat? _stat;
@override
void initState() {
super.initState();
File(widget.path).stat().then((s) {
if (mounted) setState(() => _stat = s);
}, onError: (Object _) {});
}
@override
Widget build(BuildContext context) {
final path = widget.path;
final p = parseReleaseName(baseName(path));
final missing = _stat?.type == FileSystemEntityType.notFound;
final episode = p.episode != null || p.extra != null;
return FileRow(
path: path,
title: episode ? '${p.series} · ${p.label}' : cleanTitle(p),
subtitle: missing ? 'File not found' : (episode ? baseName(path) : parentName(path)),
size: _stat == null || missing ? null : formatSize(_stat!.size),
state: widget.state,
thumbs: widget.thumbs,
onTap: missing ? null : widget.onTap,
indent: false,
trailing: IconButton(
tooltip: 'Remove from recent',
icon: const Icon(Icons.close, size: 16),
onPressed: widget.onRemove,
),
);
}
}
class _RelayNotice extends StatelessWidget {
const _RelayNotice();
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.lightBlueAccent.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(10),
),
child: const Row(
children: [
Icon(Icons.cell_tower, size: 18, color: Colors.lightBlueAccent),
SizedBox(width: 10),
Expanded(
child: Text(
'Files stream from this computer through the server. '
'Keep the app open while it plays.',
),
),
],
),
);
}
class _ErrorBanner extends StatelessWidget {
final String text;
final VoidCallback onClose;
const _ErrorBanner(this.text, {required this.onClose});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: scheme.errorContainer,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(Icons.error_outline, size: 18, color: scheme.onErrorContainer),
const SizedBox(width: 10),
Expanded(child: Text(text, style: TextStyle(color: scheme.onErrorContainer))),
IconButton(
tooltip: 'Dismiss',
icon: Icon(Icons.close, size: 16, color: scheme.onErrorContainer),
onPressed: onClose,
),
],
),
);
}
}
+213
View File
@@ -0,0 +1,213 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../shell/motion.dart';
import '../thumbnails.dart';
// Lazy poster frames for the Files page. A tile asks for its frame only
// after it has stayed on screen for a moment, so a fast scroll requests
// nothing; tiles sharing a file share one request, and the request is
// cancelled once no tile on screen wants it any more.
/// [thumbnailFor]'s shape; swapped in tests.
typedef ThumbnailLoader = Future<Thumbnail?> Function(String path, {bool Function()? cancelled});
/// A tile waits this long on screen before asking for its frame.
const thumbRequestDelay = Duration(milliseconds: 120);
/// Results of a [ThumbnailLoader] and who is waiting for them.
class ThumbCache {
final ThumbnailLoader load;
ThumbCache(this.load);
final _done = <String, Thumbnail?>{};
final _pending = <String, Future<Thumbnail?>>{};
final _waiting = <String, int>{};
/// Shared by every page using [thumbnailFor].
static final shared = ThumbCache(thumbnailFor);
bool has(String path) => _done.containsKey(path);
Thumbnail? cached(String path) => _done[path];
/// Pair every call with one [release].
Future<Thumbnail?> request(String path) {
_waiting.update(path, (n) => n + 1, ifAbsent: () => 1);
if (_done.containsKey(path)) return Future.value(_done[path]);
return _pending[path] ??= _start(path);
}
void release(String path) {
final n = (_waiting[path] ?? 0) - 1;
if (n > 0) {
_waiting[path] = n;
} else {
_waiting.remove(path);
}
}
/// Drops results, so the next request asks the loader again (rescan).
void forget(Iterable<String> paths) {
for (final p in paths) {
_done.remove(p);
}
}
Future<Thumbnail?> _start(String path) {
var gaveUp = false;
return load(path, cancelled: () => gaveUp = !_waiting.containsKey(path))
.catchError((Object _) => null)
.then((t) {
if (t == null && gaveUp) {
// Cancelled: not a result. Someone may have come back meanwhile.
if (_waiting.containsKey(path)) return _start(path);
_pending.remove(path);
return null;
}
_pending.remove(path);
_done[path] = t;
return t;
});
}
}
/// Builds with the frame of [path] (null while loading or when there is
/// none); [loaded] is false until the request finished.
class ThumbBuilder extends StatefulWidget {
final ThumbCache cache;
final String path;
final Widget Function(BuildContext context, Thumbnail? thumb, bool loaded) builder;
const ThumbBuilder({super.key, required this.cache, required this.path, required this.builder});
@override
State<ThumbBuilder> createState() => _ThumbBuilderState();
}
class _ThumbBuilderState extends State<ThumbBuilder> {
Thumbnail? _thumb;
bool _loaded = false;
Timer? _delay;
// The request held right now: its path, and a token so a late answer to
// an older request is ignored.
String? _holding;
Object? _token;
@override
void initState() {
super.initState();
_begin();
}
@override
void didUpdateWidget(ThumbBuilder old) {
super.didUpdateWidget(old);
if (old.path != widget.path || old.cache != widget.cache) {
_end(old.cache);
_begin();
}
}
@override
void dispose() {
_end(widget.cache);
super.dispose();
}
void _begin() {
final cache = widget.cache, path = widget.path;
_loaded = cache.has(path);
_thumb = cache.cached(path);
if (_loaded) return;
_delay = Timer(thumbRequestDelay, () {
_delay = null;
_holding = path;
final token = _token = Object();
cache.request(path).then((t) {
if (_token != token) return;
_holding = _token = null;
cache.release(path);
if (!mounted) return;
setState(() {
_thumb = t;
_loaded = true;
});
});
});
}
void _end(ThumbCache cache) {
_delay?.cancel();
_delay = null;
final held = _holding;
_holding = _token = null;
if (held != null) cache.release(held);
}
@override
Widget build(BuildContext context) => widget.builder(context, _thumb, _loaded);
}
/// The frame, fading in over a placeholder.
class ThumbImage extends StatelessWidget {
final Thumbnail? thumb;
final double? width, height;
const ThumbImage(this.thumb, {super.key, this.width, this.height});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final placeholder = Container(
width: width,
height: height,
color: scheme.surfaceContainerHighest,
alignment: Alignment.center,
child: Icon(Icons.movie_outlined, color: scheme.onSurfaceVariant, size: 20),
);
final t = thumb;
if (t == null) return placeholder;
final dpr = MediaQuery.devicePixelRatioOf(context);
return Stack(
fit: width == null ? StackFit.expand : StackFit.loose,
children: [
placeholder,
Image.file(
File(t.imagePath),
width: width,
height: height,
fit: BoxFit.cover,
cacheWidth: width == null ? null : (width! * dpr).round(),
gaplessPlayback: true,
errorBuilder: (_, _, _) => const SizedBox.shrink(),
frameBuilder: (context, child, frame, sync) => sync
? child
: AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: easeTime,
curve: ease,
child: child,
),
),
],
);
}
}
/// `1:02:03` / `24:05`.
String formatDuration(Duration d) {
final h = d.inHours, m = d.inMinutes % 60, s = d.inSeconds % 60;
String two(int n) => n.toString().padLeft(2, '0');
return h > 0 ? '$h:${two(m)}:${two(s)}' : '${two(m)}:${two(s)}';
}
/// `1.4 GB` / `350 MB` / `12 KB`.
String formatSize(int bytes) {
if (bytes >= 1 << 30) return '${(bytes / (1 << 30)).toStringAsFixed(1)} GB';
if (bytes >= 1 << 20) return '${(bytes / (1 << 20)).toStringAsFixed(0)} MB';
return '${(bytes / 1024).ceil()} KB';
}
/// The frame's duration, or null when unknown (no frame, 0 length).
String? durationText(Thumbnail? t) =>
t == null || t.duration <= Duration.zero ? null : formatDuration(t.duration);
+573
View File
@@ -0,0 +1,573 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:hive_ce/hive.dart';
import 'package:sync_relay_core/core.dart';
import '../screens/home/picked_media.dart';
// Linked Jellyfin servers, one account per ServerId, in their own Hive box.
//
// Only tokens this app obtained itself (Quick Connect or password through
// [JellyfinSignIn]) are ever stored or used: Jellyfin overwrites a token's
// device name and app version with whatever header comes with it, so
// borrowing another client's token would rename that client's device.
const jellyfinBoxName = 'jellyfin';
const _keyDeviceId = 'deviceId';
const _keyAccounts = 'accounts';
const _keyDefault = 'defaultServer';
const _keyLastPick = 'lastPick';
const _keyBrowsePrefix = 'browse:';
const _keyDismissedPrefix = 'dismissed:';
/// One signed-in user on one server.
class JellyfinAccount {
final String serverId;
final String serverName;
final String serverVersion;
/// Addresses this server was reached at, preferred first.
final List<String> urls;
final String userId;
final String userName;
final String? userImageTag;
final String token;
const JellyfinAccount({
required this.serverId,
required this.serverName,
this.serverVersion = '',
required this.urls,
required this.userId,
required this.userName,
this.userImageTag,
required this.token,
});
String get baseUrl => urls.first;
JellyfinUser get user =>
JellyfinUser(id: userId, name: userName, serverId: serverId, primaryImageTag: userImageTag);
/// "Example Server" or the host when the server has no name.
String get displayName =>
serverName.isNotEmpty ? serverName : (Uri.tryParse(baseUrl)?.host ?? baseUrl);
JellyfinAccount copyWith({
String? serverName,
String? serverVersion,
List<String>? urls,
String? userName,
String? userImageTag,
}) =>
JellyfinAccount(
serverId: serverId,
serverName: serverName ?? this.serverName,
serverVersion: serverVersion ?? this.serverVersion,
urls: urls ?? this.urls,
userId: userId,
userName: userName ?? this.userName,
userImageTag: userImageTag ?? this.userImageTag,
token: token,
);
Map<String, Object?> toJson() => {
'serverId': serverId,
'serverName': serverName,
'serverVersion': serverVersion,
'urls': urls,
'userId': userId,
'userName': userName,
'userImageTag': ?userImageTag,
'token': token,
};
static JellyfinAccount? fromJson(Object? j) {
if (j is! Map) return null;
final sid = j['serverId'], uid = j['userId'], token = j['token'], urls = j['urls'];
if (sid is! String || uid is! String || token is! String || urls is! List) return null;
final list = urls.whereType<String>().toList();
if (sid.isEmpty || token.isEmpty || list.isEmpty) return null;
String s(Object? v) => v is String ? v : '';
return JellyfinAccount(
serverId: sid,
serverName: s(j['serverName']),
serverVersion: s(j['serverVersion']),
urls: list,
userId: uid,
userName: s(j['userName']),
userImageTag: j['userImageTag'] is String ? j['userImageTag'] as String : null,
token: token,
);
}
}
enum JellyfinAccountStatus {
/// Not checked yet this run.
unknown,
checking,
online,
/// Unreachable (network error); the token may still be fine.
offline,
/// The server rejected the token: sign in again ("Reconnect").
expired,
}
/// Key-value persistence behind [JellyfinAccounts]; tests use
/// [MemoryJellyfinStore].
abstract class JellyfinStore {
Future<void> open();
Object? get(String key);
Future<void> put(String key, Object? value);
}
class HiveJellyfinStore implements JellyfinStore {
HiveJellyfinStore({this.boxName = jellyfinBoxName});
final String boxName;
Box? _box;
@override
Future<void> open() async => _box ??= await Hive.openBox(boxName);
@override
Object? get(String key) => _box?.get(key);
@override
Future<void> put(String key, Object? value) async {
final box = _box;
if (box == null) return;
value == null ? await box.delete(key) : await box.put(key, value);
}
}
class MemoryJellyfinStore implements JellyfinStore {
final Map<String, Object?> data;
MemoryJellyfinStore([Map<String, Object?>? data]) : data = data ?? {};
@override
Future<void> open() async {}
@override
Object? get(String key) => data[key];
@override
Future<void> put(String key, Object? value) async =>
value == null ? data.remove(key) : data[key] = value;
}
/// All linked Jellyfin servers, their live status and one authenticated
/// [JellyfinClient] each. [ensureLoaded] reads the box and then checks every
/// token in the background; a rejected token turns the account
/// [JellyfinAccountStatus.expired] (shown as "Reconnect"), never a silent
/// sign-out.
class JellyfinAccounts extends ChangeNotifier {
static final JellyfinAccounts instance = JellyfinAccounts();
JellyfinAccounts({
JellyfinStore? store,
this.httpFactory,
this.deviceName,
}) : _store = store ?? HiveJellyfinStore();
final JellyfinStore _store;
/// Transport for every client made here; null = real HTTP. Tests fake it.
final JellyfinHttp Function()? httpFactory;
/// Device name sent to Jellyfin; null = the hostname.
final String? deviceName;
List<JellyfinAccount> _accounts = const [];
final _status = <String, JellyfinAccountStatus>{};
final _errors = <String, String>{};
final _clients = <String, JellyfinClient>{};
final _memo = <String, ({DateTime at, Future<Object?> value})>{};
String? _defaultServerId;
String _deviceId = '';
JellyfinMedia? _lastPick;
Future<void>? _load;
bool _loaded = false;
bool _disposed = false;
List<JellyfinAccount> get accounts => _accounts;
bool get loaded => _loaded;
bool get isEmpty => _accounts.isEmpty;
JellyfinAccount? account(String serverId) =>
_accounts.where((a) => a.serverId == serverId).firstOrNull;
/// [itemId]'s page in the web UI of linked server [serverId] ("Open in
/// Jellyfin"); null when that server is not linked here.
String? webUrlOf(String serverId, String itemId) {
final a = account(serverId);
return a == null ? null : jellyfinWebUrl(a.baseUrl, itemId, serverId: serverId);
}
/// The chosen default, else the first linked server.
JellyfinAccount? get defaultAccount =>
(_defaultServerId == null ? null : account(_defaultServerId!)) ?? _accounts.firstOrNull;
String? get defaultServerId => defaultAccount?.serverId;
JellyfinAccountStatus statusOf(String serverId) =>
_status[serverId] ?? JellyfinAccountStatus.unknown;
/// Last failure text for an offline account.
String? errorOf(String serverId) => _errors[serverId];
/// The last Jellyfin pick put up, restored in the Library on the next start.
JellyfinMedia? get lastPick => _lastPick;
/// Stable per install: Jellyfin keys devices and tokens by it.
String get deviceId => _deviceId;
JellyfinClientInfo get clientInfo => JellyfinClientInfo(
// The old name on purpose: Jellyfin lists devices under it.
client: 'Watch Together',
device: deviceName ?? _hostname(),
deviceId: _deviceId.isEmpty ? 'unknown' : _deviceId,
version: appVersion,
);
static String _hostname() {
try {
final h = Platform.localHostname;
return h.isEmpty ? 'Desktop' : h;
} catch (_) {
return 'Desktop';
}
}
Future<void> ensureLoaded({bool validate = true}) =>
_load ??= _loadNow(validate: validate);
Future<void> _loadNow({required bool validate}) async {
try {
await _store.open();
final id = _store.get(_keyDeviceId);
if (id is String && id.isNotEmpty) {
_deviceId = id;
} else {
_deviceId = _newDeviceId();
await _store.put(_keyDeviceId, _deviceId);
}
_accounts = _decodeAccounts(_store.get(_keyAccounts));
final def = _store.get(_keyDefault);
_defaultServerId = def is String ? def : null;
final pick = _store.get(_keyLastPick);
_lastPick = pick is String ? JellyfinMedia.fromJson(_tryJson(pick)) : null;
} catch (e, st) {
debugPrint('load jellyfin accounts failed: $e\n$st');
if (_deviceId.isEmpty) _deviceId = _newDeviceId();
}
_loaded = true;
_notify();
if (validate) unawaited(validateAll());
}
static String _newDeviceId() {
final r = Random.secure();
return [for (var i = 0; i < 16; i++) r.nextInt(256).toRadixString(16).padLeft(2, '0')].join();
}
static Object? _tryJson(String s) {
try {
return jsonDecode(s);
} catch (_) {
return null;
}
}
static List<JellyfinAccount> _decodeAccounts(Object? raw) {
if (raw is! String) return const [];
final list = _tryJson(raw);
if (list is! List) return const [];
final out = <JellyfinAccount>[];
for (final j in list) {
final a = JellyfinAccount.fromJson(j);
if (a != null && out.every((o) => o.serverId != a.serverId)) out.add(a);
}
return out;
}
Future<void> _save() async {
try {
await _store.put(_keyAccounts, jsonEncode([for (final a in _accounts) a.toJson()]));
await _store.put(_keyDefault, _defaultServerId);
} catch (e, st) {
debugPrint('save jellyfin accounts failed: $e\n$st');
}
}
void _notify() {
if (!_disposed) notifyListeners();
}
// ------------------------------------------------------------- clients
/// A client without a token, for probing an address and signing in.
/// The caller closes it.
JellyfinClient anonymousClient(String baseUrl) =>
JellyfinClient(baseUrl: baseUrl, clientInfo: clientInfo, http: httpFactory?.call());
/// The signed-in client of a linked server (null if not linked). Shared;
/// do not close it.
JellyfinClient? clientFor(String serverId) {
final a = account(serverId);
if (a == null) return null;
final c = _clients[serverId];
if (c != null && c.baseUrl == a.baseUrl && c.accessToken == a.token) return c;
c?.close();
return _clients[serverId] = JellyfinClient(
baseUrl: a.baseUrl,
clientInfo: clientInfo,
accessToken: a.token,
userId: a.userId,
http: httpFactory?.call(),
);
}
// ---------------------------------------------------------- validation
Future<void> validateAll() => Future.wait([for (final a in _accounts) validate(a.serverId)]);
/// Checks the saved token. Tries each known address in order and moves
/// the first that answers to the front.
Future<JellyfinAccountStatus> validate(String serverId) async {
final start = account(serverId);
if (start == null) return JellyfinAccountStatus.unknown;
_status[serverId] = JellyfinAccountStatus.checking;
_notify();
Object? lastError;
for (final url in start.urls) {
final client = JellyfinClient(
baseUrl: url,
clientInfo: clientInfo,
accessToken: start.token,
userId: start.userId,
http: httpFactory?.call(),
);
try {
final user = await client.validateToken();
if (user == null) return _setStatus(serverId, JellyfinAccountStatus.expired);
final a = account(serverId);
if (a == null) return JellyfinAccountStatus.unknown;
_replace(a.copyWith(
userName: user.name.isEmpty ? null : user.name,
userImageTag: user.primaryImageTag,
urls: [url, ...a.urls.where((u) => u != url)],
));
unawaited(_save());
_errors.remove(serverId);
return _setStatus(serverId, JellyfinAccountStatus.online);
} catch (e) {
lastError = e;
} finally {
client.close();
}
}
_errors[serverId] = _short(lastError);
return _setStatus(serverId, JellyfinAccountStatus.offline);
}
static String _short(Object? e) {
final s = '$e'.replaceFirst('JellyfinException: ', '');
return s.length > 160 ? '${s.substring(0, 160)}…' : s;
}
JellyfinAccountStatus _setStatus(String serverId, JellyfinAccountStatus s) {
_status[serverId] = s;
_notify();
return s;
}
/// A browse call got 401: the token is gone, ask to reconnect.
void markExpired(String serverId) {
if (statusOf(serverId) == JellyfinAccountStatus.expired) return;
_setStatus(serverId, JellyfinAccountStatus.expired);
}
void _replace(JellyfinAccount a) {
_accounts = [for (final o in _accounts) o.serverId == a.serverId ? a : o];
}
// ------------------------------------------------------- link / unlink
/// Stores a sign-in made by this app. Signing in again to a linked server
/// replaces its account (and best-effort revokes the old token).
Future<JellyfinAccount> link({
required String baseUrl,
required JellyfinServerInfo server,
required JellyfinAuthResult auth,
}) async {
await ensureLoaded(validate: false);
final serverId = auth.serverId.isNotEmpty ? auth.serverId : server.id;
final old = account(serverId);
final a = JellyfinAccount(
serverId: serverId,
serverName: server.name,
serverVersion: server.version,
urls: [baseUrl, ...?old?.urls.where((u) => u != baseUrl)],
userId: auth.user.id,
userName: auth.user.name,
userImageTag: auth.user.primaryImageTag,
token: auth.accessToken,
);
if (old != null && old.token != a.token) unawaited(_revoke(old));
_accounts = old == null ? [..._accounts, a] : [for (final o in _accounts) o.serverId == serverId ? a : o];
_defaultServerId ??= serverId;
_status[serverId] = JellyfinAccountStatus.online;
_errors.remove(serverId);
_clearMemo(serverId);
await _save();
_notify();
return a;
}
Future<void> _revoke(JellyfinAccount a) async {
final c = JellyfinClient(
baseUrl: a.baseUrl,
clientInfo: clientInfo,
accessToken: a.token,
userId: a.userId,
http: httpFactory?.call());
try {
await c.logout();
} catch (_) {
} finally {
c.close();
}
}
/// Revokes the token on the server (best effort: an offline server or an
/// already dead token still unlinks) and forgets the account.
Future<void> signOut(String serverId) async {
final a = account(serverId);
if (a == null) return;
final c = _clients.remove(serverId);
c?.close();
await _revoke(a).timeout(const Duration(seconds: 8), onTimeout: () {});
_accounts = [for (final o in _accounts) if (o.serverId != serverId) o];
_status.remove(serverId);
_errors.remove(serverId);
_clearMemo(serverId);
if (_defaultServerId == serverId) _defaultServerId = _accounts.firstOrNull?.serverId;
if (_lastPick?.serverId == serverId) await rememberPick(null);
await _save();
_notify();
}
Future<void> setDefault(String serverId) async {
if (account(serverId) == null || _defaultServerId == serverId) return;
_defaultServerId = serverId;
_notify();
await _save();
}
Future<void> rememberPick(JellyfinMedia? pick) async {
_lastPick = pick;
try {
await _store.put(_keyLastPick, pick == null ? null : jsonEncode(pick.toJson()));
} catch (e) {
debugPrint('save jellyfin pick failed: $e');
}
}
/// The browser's last location on [serverId] (encoded route stack, see
/// `browse_scope.dart`), restored by every browser and across restarts.
Object? browseLocation(String serverId) {
try {
final v = _store.get('$_keyBrowsePrefix$serverId');
return v is String ? _tryJson(v) : null;
} catch (_) {
return null;
}
}
Future<void> saveBrowseLocation(String serverId, Object? location) async {
try {
await _store.put(
'$_keyBrowsePrefix$serverId', location == null ? null : jsonEncode(location));
} catch (e) {
debugPrint('save browse location failed: $e');
}
}
/// Continue-row entries removed by hand on [serverId] (key -> when), so a
/// next-up series (which Jellyfin cannot drop) stays out until it is
/// played again. Keys are `continueKey`s.
Map<String, DateTime> continueDismissed(String serverId) {
try {
final v = _store.get('$_keyDismissedPrefix$serverId');
final j = v is String ? _tryJson(v) : null;
if (j is! Map) return const {};
return {
for (final e in j.entries)
if (e.key is String && e.value is int)
e.key as String: DateTime.fromMillisecondsSinceEpoch(e.value as int, isUtc: true),
};
} catch (_) {
return const {};
}
}
/// Keeps [key] out of the Continue row on [serverId] until it is played
/// after [at] (default now). The newest 200 are kept.
Future<void> dismissContinue(String serverId, String key, {DateTime? at}) async {
final all = {...continueDismissed(serverId), key: (at ?? DateTime.now()).toUtc()};
final newest = all.entries.toList()..sort((a, b) => b.value.compareTo(a.value));
try {
await _store.put('$_keyDismissedPrefix$serverId', jsonEncode({
for (final e in newest.take(200)) e.key: e.value.millisecondsSinceEpoch,
}));
} catch (e) {
debugPrint('save dismissed continue failed: $e');
}
}
// --------------------------------------------------------------- cache
/// Short-lived memo for browse calls, so going back does not refetch.
/// Failures are not kept; a 401 marks the account expired.
Future<T> cached<T>(String serverId, String key, Future<T> Function() load,
{Duration ttl = const Duration(minutes: 3)}) {
final k = '$serverId|$key';
final hit = _memo[k];
if (hit != null && DateTime.now().difference(hit.at) < ttl) {
return hit.value.then((v) => v as T);
}
final f = load();
_memo[k] = (at: DateTime.now(), value: f);
f.catchError((Object e) {
if (identical(_memo[k]?.value, f)) _memo.remove(k);
if (e is JellyfinException && e.isUnauthorized) markExpired(serverId);
throw e;
}).ignore();
return f;
}
void _clearMemo(String serverId) =>
_memo.removeWhere((k, _) => k.startsWith('$serverId|'));
/// Drops cached browse results (refresh button, after a pick).
void invalidate(String serverId) => _clearMemo(serverId);
final _watched = StreamController<String>.broadcast();
/// Server ids whose watched state this app just changed (a stop report):
/// their cached lists are gone, and browse views reload.
Stream<String> get watched => _watched.stream;
void watchedChanged(String serverId) {
_clearMemo(serverId);
_watched.add(serverId);
}
@override
void dispose() {
_disposed = true;
for (final c in _clients.values) {
c.close();
}
_clients.clear();
super.dispose();
}
}
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import '../screens/home/ui.dart';
import 'accounts.dart';
import 'browser.dart' show JellyfinAvatar, StatusDot, showReconnectDialog;
import 'sign_in_panel.dart';
/// Integrations > Jellyfin: linked servers (avatar, status, default,
/// reconnect, sign out) and the inline add-server flow.
class JellyfinAccountsPanel extends StatefulWidget {
final JellyfinAccounts accounts;
final Duration pollInterval;
const JellyfinAccountsPanel({
super.key,
required this.accounts,
this.pollInterval = const Duration(seconds: 3),
});
@override
State<JellyfinAccountsPanel> createState() => _JellyfinAccountsPanelState();
}
class _JellyfinAccountsPanelState extends State<JellyfinAccountsPanel> {
bool _adding = false;
@override
void initState() {
super.initState();
widget.accounts.ensureLoaded();
}
Future<void> _signOut(JellyfinAccount a) async {
final ok = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Sign out of ${a.displayName}?'),
content: const Text(
'This app\'s sign-in is revoked on the server and the server is unlinked. '
'You can add it again any time.'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
FilledButton(
key: const ValueKey('jf-sign-out-confirm'),
onPressed: () => Navigator.pop(context, true),
child: const Text('Sign out')),
],
),
);
if (ok == true) await widget.accounts.signOut(a.serverId);
}
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return ListenableBuilder(
listenable: widget.accounts,
builder: (context, _) {
final list = widget.accounts.accounts;
return Panel(
key: const ValueKey('jf-accounts'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.video_library_outlined,
size: 32, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: kGap),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Jellyfin', style: t.titleMedium),
const SizedBox(height: 4),
const Text(
'Browse your libraries in the Library tab and put up movies and episodes. '
'Sign in with Quick Connect or your password; several servers are fine.',
style: TextStyle(color: Colors.grey),
),
],
),
),
const SizedBox(width: kGap),
if (!_adding)
FilledButton.icon(
key: const ValueKey('jf-add-server'),
onPressed: () => setState(() => _adding = true),
icon: const Icon(Icons.add, size: 18),
label: const Text('Add server'),
),
],
),
if (list.isNotEmpty) const SizedBox(height: kGap),
for (final a in list)
_AccountTile(
accounts: widget.accounts,
account: a,
isDefault: widget.accounts.defaultServerId == a.serverId,
showDefault: list.length > 1,
onSignOut: () => _signOut(a),
),
if (_adding) ...[
const SizedBox(height: kGap),
const Divider(height: 1),
const SizedBox(height: kGap),
JellyfinSignInPanel(
accounts: widget.accounts,
pollInterval: widget.pollInterval,
onCancel: () => setState(() => _adding = false),
onDone: (_) => setState(() => _adding = false),
),
],
],
),
);
},
);
}
}
class _AccountTile extends StatelessWidget {
final JellyfinAccounts accounts;
final JellyfinAccount account;
final bool isDefault;
final bool showDefault;
final VoidCallback onSignOut;
const _AccountTile({
required this.accounts,
required this.account,
required this.isDefault,
required this.showDefault,
required this.onSignOut,
});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final status = accounts.statusOf(account.serverId);
final statusText = switch (status) {
JellyfinAccountStatus.online => 'Connected',
JellyfinAccountStatus.offline => 'Offline',
JellyfinAccountStatus.expired => 'Sign-in expired',
JellyfinAccountStatus.checking => 'Checking…',
JellyfinAccountStatus.unknown => 'Not checked',
};
return Padding(
key: ValueKey('jf-account-${account.serverId}'),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
JellyfinAvatar(accounts: accounts, account: account),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${account.userName} on ${account.displayName}', style: t.titleSmall),
Row(children: [
StatusDot(status: status),
const SizedBox(width: 6),
Flexible(
child: Text(
[
statusText,
if (account.serverVersion.isNotEmpty) 'Jellyfin ${account.serverVersion}',
account.baseUrl,
].join(' · '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: t.bodySmall?.copyWith(color: Colors.grey),
),
),
]),
],
),
),
if (showDefault)
IconButton(
key: ValueKey('jf-default-${account.serverId}'),
tooltip: isDefault ? 'Default server' : 'Make default',
onPressed: () => accounts.setDefault(account.serverId),
icon: Icon(isDefault ? Icons.star : Icons.star_border,
color: isDefault ? Theme.of(context).colorScheme.primary : null),
),
if (status == JellyfinAccountStatus.expired)
FilledButton.tonal(
key: ValueKey('jf-reconnect-${account.serverId}'),
onPressed: () => showReconnectDialog(context, accounts, account),
child: const Text('Reconnect'),
)
else if (status == JellyfinAccountStatus.offline)
TextButton(
onPressed: () => accounts.validate(account.serverId),
child: const Text('Retry'),
),
const SizedBox(width: 4),
TextButton(
key: ValueKey('jf-sign-out-${account.serverId}'),
onPressed: onSignOut,
child: const Text('Sign out'),
),
],
),
);
}
}
@@ -0,0 +1,478 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
import 'accounts.dart';
import 'format.dart';
import '../widgets/app_menu.dart';
import '../widgets/copy_open.dart';
/// One level of the library browser's back stack.
sealed class BrowseRoute {
const BrowseRoute();
String get label;
/// Identity for keeping a page's state while it is in the stack.
String get key;
}
final class HomeRoute extends BrowseRoute {
const HomeRoute();
@override
String get label => 'Home';
@override
String get key => 'home';
}
/// A library or folder grid, optionally opened with a sort ("Show all"
/// next to Latest opens it newest first).
final class LibraryRoute extends BrowseRoute {
final JellyfinItem folder;
/// A `LibrarySort` name; null = by name.
final String? sort;
const LibraryRoute(this.folder, {this.sort});
@override
String get label => folder.name;
@override
String get key => sort == null ? 'lib:${folder.id}' : 'lib:${folder.id}:$sort';
}
final class SeriesRoute extends BrowseRoute {
final JellyfinItem series;
/// Season to open on (from a season card or an episode's "open series").
final String? seasonId;
const SeriesRoute(this.series, {this.seasonId});
@override
String get label => series.name;
@override
String get key => 'series:${series.id}';
}
/// A route stack as JSON for [JellyfinAccounts.saveBrowseLocation]: only
/// ids, names and the fields the pages start from (each page loads the
/// full item itself).
List<Map<String, Object?>> encodeBrowseStack(List<BrowseRoute> stack) => [
for (final r in stack)
switch (r) {
HomeRoute() => {'r': 'home'},
LibraryRoute(:final folder, :final sort) => {'r': 'lib', ..._itemJson(folder), 'sort': ?sort},
SeriesRoute(:final series, :final seasonId) => {
'r': 'series',
..._itemJson(series),
'seasonId': ?seasonId,
},
}
];
/// Inverse of [encodeBrowseStack]; always starts with Home. Null when
/// [raw] is not a stack. Entries it no longer knows are skipped.
List<BrowseRoute>? decodeBrowseStack(Object? raw) {
if (raw is! List) return null;
final out = <BrowseRoute>[const HomeRoute()];
for (final e in raw) {
if (e is! Map) return null;
final item = _itemFrom(e);
final route = switch (e['r']) {
'lib' when item != null =>
LibraryRoute(item, sort: e['sort'] is String ? e['sort'] as String : null),
'series' when item != null =>
SeriesRoute(item, seasonId: e['seasonId'] is String ? e['seasonId'] as String : null),
_ => null,
};
if (route != null && out.every((r) => r.key != route.key)) out.add(route);
}
return out;
}
Map<String, Object?> _itemJson(JellyfinItem i) => {
'id': i.id,
'name': i.name,
'type': i.type,
if (i.isFolder) 'isFolder': true,
'collectionType': ?i.collectionType,
'seriesId': ?i.seriesId,
'seriesName': ?i.seriesName,
'seasonId': ?i.seasonId,
'indexNumber': ?i.indexNumber,
'parentIndexNumber': ?i.parentIndexNumber,
};
JellyfinItem? _itemFrom(Map e) {
String? s(String k) => e[k] is String && (e[k] as String).isNotEmpty ? e[k] as String : null;
final id = s('id');
if (id == null) return null;
return JellyfinItem(
id: id,
name: s('name') ?? '',
type: s('type') ?? '',
isFolder: e['isFolder'] == true,
collectionType: s('collectionType'),
seriesId: s('seriesId'),
seriesName: s('seriesName'),
seasonId: e['r'] == 'series' ? null : s('seasonId'),
indexNumber: e['indexNumber'] is int ? e['indexNumber'] as int : null,
parentIndexNumber: e['parentIndexNumber'] is int ? e['parentIndexNumber'] as int : null,
);
}
/// Where a browser opens instead of the remembered location: a server and
/// a route stack (e.g. the series of the episode playing).
class BrowseStart {
final String serverId;
final List<BrowseRoute> stack;
const BrowseStart(this.serverId, this.stack);
/// The series page of [episode] with its season selected.
static BrowseStart? seriesOf(String serverId, JellyfinItem episode) {
final sid = episode.seriesId;
if (sid == null) return null;
return BrowseStart(serverId, [
const HomeRoute(),
SeriesRoute(JellyfinItem(id: sid, name: episode.seriesName ?? '', type: 'Series'),
seasonId: episode.seasonId),
]);
}
}
/// The card selected in the library (details panel): an item on a server.
/// [item] is what the card had, when known (the panel loads it in full).
class JellyfinSelection {
final String serverId;
final String itemId;
final JellyfinItem? item;
const JellyfinSelection(this.serverId, this.itemId, [this.item]);
@override
bool operator ==(Object other) =>
other is JellyfinSelection && other.serverId == serverId && other.itemId == itemId;
@override
int get hashCode => Object.hash(serverId, itemId);
}
/// Browser location shared by the browser, the library chips in the
/// Library header and the details panel: the server shown, its route
/// stack (saved per server on every move), a data version that bumps after
/// watched-state changes (rows reload), and the keys in the Continue row.
class BrowseController extends ChangeNotifier {
final JellyfinAccounts accounts;
/// Clock for the Continue row's age cut-off (tests pin it).
final DateTime Function() now;
BrowseController(this.accounts, {this.now = DateTime.now}) {
// Back from watching: the rows show what was just watched.
_watchedSub = accounts.watched.listen((id) {
if (!_disposed && id == serverId) dataChanged();
});
}
late final StreamSubscription<String> _watchedSub;
String? _serverId;
List<BrowseRoute> _stack = const [HomeRoute()];
bool _started = false;
int _version = 0;
Set<String> _continueKeys = const {};
List<BrowseRoute> get stack => _stack;
int get version => _version;
/// [continueKey]s of the cards in the Continue row now.
Set<String> get continueKeys => _continueKeys;
JellyfinAccount? get account =>
(_serverId == null ? null : accounts.account(_serverId!)) ?? accounts.defaultAccount;
String? get serverId => account?.serverId;
/// First use (accounts loaded): [start], else the server of [serverHint]
/// at its remembered location, else the default server's.
void start({BrowseStart? start, String? serverHint}) {
if (_started) return;
_started = true;
if (start != null && accounts.account(start.serverId) != null) {
_serverId = start.serverId;
_stack = start.stack.isEmpty ? const [HomeRoute()] : start.stack;
_remember();
} else {
_serverId = serverHint != null && accounts.account(serverHint) != null
? serverHint
: accounts.defaultServerId;
_stack = _restored(_serverId);
}
}
List<BrowseRoute> _restored(String? serverId) =>
(serverId == null ? null : decodeBrowseStack(accounts.browseLocation(serverId))) ??
const [HomeRoute()];
void _remember() {
final id = serverId;
if (id != null) unawaited(accounts.saveBrowseLocation(id, encodeBrowseStack(_stack)));
}
void switchServer(String serverId) {
if (serverId == this.serverId) return;
_serverId = serverId;
_stack = _restored(serverId);
_continueKeys = const {};
notifyListeners();
}
void open(BrowseRoute route) {
if (_stack.last.key == route.key) return;
_stack = [..._stack, route];
_remember();
notifyListeners();
}
/// Home, then [route] (library chips: a library is a top-level place).
void openTop(BrowseRoute route) {
_stack = [const HomeRoute(), route];
_remember();
notifyListeners();
}
bool back() {
if (_stack.length <= 1) return false;
_stack = _stack.sublist(0, _stack.length - 1);
_remember();
notifyListeners();
return true;
}
void popTo(int index) {
_stack = _stack.sublist(0, index + 1);
_remember();
notifyListeners();
}
/// Watched state changed on the server: drop its cached lists and let
/// the rows reload.
void dataChanged() {
final id = serverId;
if (id != null) accounts.invalidate(id);
_version++;
notifyListeners();
}
set continueKeys(Set<String> keys) {
if (setEquals(keys, _continueKeys)) return;
_continueKeys = keys;
// Set while building a row: tell listeners after the frame.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_disposed) notifyListeners();
});
}
bool _disposed = false;
@override
void dispose() {
_disposed = true;
unawaited(_watchedSub.cancel());
super.dispose();
}
}
/// An item's title to copy: an episode with its series ("Show · Name").
String jellyfinCopyTitle(JellyfinItem item) {
final series = item.seriesName;
return item.kind == JellyfinItemKind.episode && series != null && series.isNotEmpty
? '$series · ${item.name}'
: item.name;
}
/// What every browser page needs, provided by [JellyfinBrowser].
class BrowseScope extends InheritedWidget {
final JellyfinAccounts accounts;
final JellyfinAccount account;
final JellyfinClient client;
final BrowseController controller;
/// Item id of the selected card on this server, highlighted everywhere.
final String? selectedItemId;
/// Click: select (details panel).
final ValueChanged<JellyfinItem> onSelect;
/// Double-click: put it up (a series: its next episode).
final ValueChanged<JellyfinItem> onPutUp;
/// Adds it to Up next (with next: right after what plays); null: no
/// server (the cards' menu then only copies and opens).
final void Function(JellyfinItem item, {bool next})? onAddToQueue;
/// Home shows the library chips itself (the Library tab has them in its
/// header instead).
final bool libraryStrip;
const BrowseScope({
super.key,
required this.accounts,
required this.account,
required this.client,
required this.controller,
required this.selectedItemId,
required this.onSelect,
required this.onPutUp,
this.onAddToQueue,
this.libraryStrip = true,
required super.child,
});
static BrowseScope of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<BrowseScope>()!;
void onOpen(BrowseRoute route) => controller.open(route);
/// Right-click on a card ([context]: the card): Put it up, Add to queue,
/// Play next (playable, in a server), Copy title, Open in Jellyfin.
Future<void> cardMenu(BuildContext context, JellyfinItem item) async {
final queue = isSelectable(item) ? onAddToQueue : null;
final pick = await showAppMenu<int>(context, items: [
if (queue != null) ...const [
AppMenuItem(value: 0, label: 'Put it up'),
AppMenuItem(value: 1, label: 'Add to queue'),
AppMenuItem(value: 2, label: 'Play next'),
],
const AppMenuItem(value: 3, label: ReuseText.copyTitle),
const AppMenuItem(value: 4, label: ReuseText.openInJellyfin),
]);
switch (pick) {
case 0:
onPutUp(item);
case 1:
queue!(item);
case 2:
queue!(item, next: true);
case 3:
await copyToClipboard(jellyfinCopyTitle(item), done: 'Title copied');
case 4:
await openLink(jellyfinWebUrl(client.baseUrl, item.id, serverId: account.serverId));
}
}
/// Cached browse call on this server.
Future<T> load<T>(String key, Future<T> Function(JellyfinClient c) fn) =>
accounts.cached(account.serverId, key, () => fn(client));
/// Click / Enter on a card: series, movies and episodes are selected,
/// seasons open their series, libraries and folders open.
void activate(JellyfinItem item) {
if (isSelectable(item)) {
onSelect(item);
return;
}
switch (item.kind) {
case JellyfinItemKind.season:
final sid = item.seriesId;
if (sid != null) {
onOpen(SeriesRoute(
JellyfinItem(id: sid, name: item.seriesName ?? item.name, type: 'Series'),
seasonId: item.id));
}
default:
onOpen(LibraryRoute(item));
}
}
/// Opens the series of an episode.
void openSeriesOf(JellyfinItem episode) {
final sid = episode.seriesId;
if (sid == null) return;
onOpen(SeriesRoute(
JellyfinItem(id: sid, name: episode.seriesName ?? '', type: 'Series'),
seasonId: episode.seasonId));
}
@override
bool updateShouldNotify(BrowseScope old) =>
old.selectedItemId != selectedItemId ||
old.account != account ||
old.client != client ||
old.libraryStrip != libraryStrip;
}
/// Runs [load] once (again on [retry] and when [version] changes) and
/// shows [builder] with the result, a spinner meanwhile, and a compact
/// error with Retry on failure. A reload for a new [version] keeps the old
/// result on screen until the new one arrives.
class Loader<T> extends StatefulWidget {
final Future<T> Function() load;
final Widget Function(BuildContext context, T data) builder;
final Widget? loading;
/// When false the error collapses to nothing (optional sections).
final bool showError;
final Object? version;
const Loader({
super.key,
required this.load,
required this.builder,
this.loading,
this.showError = true,
this.version,
});
@override
State<Loader<T>> createState() => _LoaderState<T>();
}
class _LoaderState<T> extends State<Loader<T>> {
late Future<T> _future = widget.load();
(T,)? _last;
void _retry() => setState(() => _future = widget.load());
@override
void didUpdateWidget(Loader<T> old) {
super.didUpdateWidget(old);
if (old.version != widget.version) _future = widget.load();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<T>(
future: _future,
builder: (context, snap) {
if (snap.hasData || (snap.connectionState == ConnectionState.done && !snap.hasError)) {
_last = (snap.data as T,);
return widget.builder(context, snap.data as T);
}
if (snap.hasError) {
if (!widget.showError) return const SizedBox.shrink();
return LoadError(error: snap.error!, onRetry: _retry);
}
final last = _last;
if (last != null) return widget.builder(context, last.$1);
return widget.loading ??
const Padding(
padding: EdgeInsets.all(32),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
},
);
}
}
class LoadError extends StatelessWidget {
final Object error;
final VoidCallback onRetry;
const LoadError({super.key, required this.error, required this.onRetry});
@override
Widget build(BuildContext context) {
final e = error;
final msg = e is JellyfinException
? (e.isUnauthorized ? 'Sign-in expired' : e.message)
: '$e';
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.cloud_off, size: 18, color: Theme.of(context).colorScheme.error),
const SizedBox(width: 8),
Flexible(
child: Text(msg.length > 160 ? '${msg.substring(0, 160)}…' : msg,
style: const TextStyle(color: Colors.grey)),
),
const SizedBox(width: 8),
TextButton(onPressed: onRetry, child: const Text('Retry')),
],
),
);
}
}
+532
View File
@@ -0,0 +1,532 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'accounts.dart';
import 'browse_scope.dart';
import 'home_page.dart';
import 'library_page.dart';
import 'search_results.dart';
import 'series_page.dart';
import 'sign_in_panel.dart';
/// Library browser for the linked Jellyfin servers: server switcher,
/// breadcrumbs, search, and a back stack of pages (Home rows, library
/// grids, series). Pages below the top stay alive, so going back keeps
/// scroll position and loaded pages. Where it is lives in a
/// [BrowseController] (shared with the Library header's library chips and
/// the details panel; one of its own when none is given).
///
/// A click on a card selects it ([onSelect]; libraries and folders open),
/// a double-click puts it up ([onPutUp]).
///
/// Keyboard: arrows move between cards, Enter selects / opens, Esc /
/// Backspace / Alt+Left go back, / or Ctrl+F searches.
class JellyfinBrowser extends StatefulWidget {
final JellyfinAccounts accounts;
final BrowseController? controller;
/// Highlighted card.
final JellyfinSelection? selected;
final ValueChanged<JellyfinSelection> onSelect;
final ValueChanged<JellyfinSelection> onPutUp;
/// Right-click on a card: Add to queue / Play next; null: no server.
final void Function(JellyfinSelection item, {bool next})? onAddToQueue;
final VoidCallback? onOpenIntegrations;
final Duration searchDebounce;
/// Opens here instead of the remembered location (the series of the
/// episode playing, after "Change video").
final BrowseStart? start;
/// Library chips on Home (off when the Library header shows them).
final bool libraryStrip;
const JellyfinBrowser({
this.start,
super.key,
required this.accounts,
required this.onSelect,
required this.onPutUp,
this.onAddToQueue,
this.controller,
this.selected,
this.onOpenIntegrations,
this.libraryStrip = true,
this.searchDebounce = const Duration(milliseconds: 300),
});
@override
State<JellyfinBrowser> createState() => _JellyfinBrowserState();
}
class _JellyfinBrowserState extends State<JellyfinBrowser> {
BrowseController? _own;
final _search = TextEditingController();
final _searchFocus = FocusNode(debugLabel: 'jf-search');
final _body = FocusScopeNode(debugLabel: 'jf-body');
String _term = '';
Timer? _debounce;
int _refresh = 0;
JellyfinAccounts get _accounts => widget.accounts;
BrowseController get _c => widget.controller ?? (_own ??= BrowseController(widget.accounts));
@override
void initState() {
super.initState();
_c.start(start: widget.start, serverHint: widget.selected?.serverId);
_accounts.addListener(_changed);
_c.addListener(_moved);
}
@override
void didUpdateWidget(JellyfinBrowser old) {
super.didUpdateWidget(old);
if (old.accounts != widget.accounts) {
old.accounts.removeListener(_changed);
widget.accounts.addListener(_changed);
}
final was = old.controller ?? _own;
if (was != _c) {
was?.removeListener(_moved);
_c.addListener(_moved);
}
}
@override
void dispose() {
_accounts.removeListener(_changed);
_c.removeListener(_moved);
_own?.dispose();
_debounce?.cancel();
_search.dispose();
_searchFocus.dispose();
_body.dispose();
super.dispose();
}
void _changed() {
if (mounted) setState(() {});
}
/// Any move (here, from the library chips or a server switch) ends a
/// search.
int _depth = 0;
String? _server;
void _moved() {
if (!mounted) return;
final moved = _c.stack.length != _depth || _c.serverId != _server;
_depth = _c.stack.length;
_server = _c.serverId;
setState(() {
if (moved) _clearSearch();
});
}
JellyfinAccount? get _account => _c.account;
// ------------------------------------------------------------- navigation
bool _back() {
final ok = _c.back();
if (ok) _body.requestFocus();
return ok;
}
void _popTo(int index) {
setState(_clearSearch);
_c.popTo(index);
_body.requestFocus();
}
void _onSearchChanged(String text) {
_debounce?.cancel();
_debounce = Timer(widget.searchDebounce, () {
if (mounted) setState(() => _term = text.trim());
});
}
void _clearSearch() {
_debounce?.cancel();
_search.clear();
_term = '';
}
KeyEventResult _onKey(FocusNode node, KeyEvent e) {
if (e is! KeyDownEvent && e is! KeyRepeatEvent) return KeyEventResult.ignored;
final keys = HardwareKeyboard.instance;
final inField = FocusManager.instance.primaryFocus?.context
?.findAncestorStateOfType<EditableTextState>() !=
null;
final k = e.logicalKey;
if (k == LogicalKeyboardKey.escape) {
if (_search.text.isNotEmpty || _term.isNotEmpty) {
setState(_clearSearch);
_body.requestFocus();
return KeyEventResult.handled;
}
if (inField) {
_body.requestFocus();
return KeyEventResult.handled;
}
return _back() ? KeyEventResult.handled : KeyEventResult.ignored;
}
if (inField) return KeyEventResult.ignored;
if (e.character == '/' || (keys.isControlPressed && k == LogicalKeyboardKey.keyF)) {
_searchFocus.requestFocus();
return KeyEventResult.handled;
}
if (k == LogicalKeyboardKey.backspace ||
(keys.isAltPressed && k == LogicalKeyboardKey.arrowLeft) ||
k == LogicalKeyboardKey.browserBack) {
return _back() ? KeyEventResult.handled : KeyEventResult.ignored;
}
return KeyEventResult.ignored;
}
// ------------------------------------------------------------------ build
Widget _page(BrowseRoute r) => switch (r) {
HomeRoute() => const JellyfinHomePage(),
LibraryRoute(:final folder, :final sort) => JellyfinLibraryPage(folder: folder, sort: sort),
SeriesRoute(:final series, :final seasonId) =>
JellyfinSeriesPage(series: series, seasonId: seasonId),
};
@override
Widget build(BuildContext context) {
final account = _account;
if (account == null) {
return const Center(child: Text('No Jellyfin server linked', style: TextStyle(color: Colors.grey)));
}
final client = _accounts.clientFor(account.serverId)!;
final status = _accounts.statusOf(account.serverId);
final sel = widget.selected;
final searching = _term.isNotEmpty;
final stack = _c.stack;
final pages = Stack(
children: [
for (var i = 0; i < stack.length; i++)
Visibility(
key: ValueKey('$_refresh:${account.serverId}:$i:${stack[i].key}'),
visible: !searching && i == stack.length - 1,
maintainState: true,
child: _page(stack[i]),
),
if (searching) JellyfinSearchResults(key: ValueKey('$_refresh:$_term'), term: _term),
],
);
return BrowseScope(
accounts: _accounts,
account: account,
client: client,
controller: _c,
selectedItemId: sel != null && sel.serverId == account.serverId ? sel.itemId : null,
onSelect: (item) => widget.onSelect(JellyfinSelection(account.serverId, item.id, item)),
onPutUp: (item) => widget.onPutUp(JellyfinSelection(account.serverId, item.id, item)),
onAddToQueue: widget.onAddToQueue == null
? null
: (item, {next = false}) =>
widget.onAddToQueue!(JellyfinSelection(account.serverId, item.id, item), next: next),
libraryStrip: widget.libraryStrip,
child: Focus(
onKeyEvent: _onKey,
child: Column(
key: const ValueKey('jf-browser'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_header(context, account),
if (status == JellyfinAccountStatus.expired || status == JellyfinAccountStatus.offline)
_StatusBanner(accounts: _accounts, account: account, status: status),
const SizedBox(height: 12),
Expanded(child: FocusScope(node: _body, child: pages)),
],
),
),
);
}
Widget _header(BuildContext context, JellyfinAccount account) {
final theme = Theme.of(context);
final t = theme.textTheme;
final stack = _c.stack;
final crumbs = <Widget>[];
for (var i = 0; i < stack.length; i++) {
final last = i == stack.length - 1 && _term.isEmpty;
if (i > 0) {
crumbs.add(const Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
child: Icon(Icons.chevron_right, size: 18, color: Colors.grey),
));
}
final label = Text(
stack[i].label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: t.titleSmall?.copyWith(
color: last ? theme.colorScheme.onSurface : theme.colorScheme.onSurfaceVariant,
fontWeight: last ? FontWeight.w600 : null),
);
crumbs.add(Flexible(
child: last
? label
: InkWell(
key: ValueKey('crumb-$i'),
borderRadius: BorderRadius.circular(6),
onTap: () => _popTo(i),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), child: label),
),
));
}
if (_term.isNotEmpty) {
crumbs
..add(const Icon(Icons.chevron_right, size: 18, color: Colors.grey))
..add(Flexible(
child: Text('Search “$_term”',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: t.titleSmall?.copyWith(fontWeight: FontWeight.w600))));
}
return LayoutBuilder(builder: (context, box) {
final narrow = box.maxWidth < 640;
return Row(
children: [
_ServerSwitcher(
accounts: _accounts,
current: account,
compact: narrow,
onSelect: _c.switchServer,
onOpenIntegrations: widget.onOpenIntegrations,
),
IconButton(
tooltip: 'Back (Esc)',
visualDensity: VisualDensity.compact,
onPressed: stack.length > 1 ? _back : null,
icon: const Icon(Icons.arrow_back, size: 20),
),
const SizedBox(width: 4),
Expanded(child: Row(children: crumbs)),
const SizedBox(width: 8),
SizedBox(
width: narrow ? 170 : 260,
child: TextField(
key: const ValueKey('jf-search'),
controller: _search,
focusNode: _searchFocus,
onChanged: _onSearchChanged,
onSubmitted: (v) {
_debounce?.cancel();
setState(() => _term = v.trim());
},
decoration: InputDecoration(
isDense: true,
hintText: 'Search ( / )',
prefixIcon: const Icon(Icons.search, size: 18),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
suffixIcon: ListenableBuilder(
listenable: _search,
builder: (context, _) => _search.text.isEmpty
? const SizedBox.shrink()
: IconButton(
tooltip: 'Clear (Esc)',
iconSize: 16,
onPressed: () => setState(_clearSearch),
icon: const Icon(Icons.close),
),
),
),
),
),
IconButton(
tooltip: 'Refresh',
visualDensity: VisualDensity.compact,
onPressed: () {
_accounts.invalidate(account.serverId);
setState(() => _refresh++);
},
icon: const Icon(Icons.refresh, size: 20),
),
],
);
});
}
}
/// Current server (avatar + name); a menu when several are linked.
class _ServerSwitcher extends StatelessWidget {
final JellyfinAccounts accounts;
final JellyfinAccount current;
final ValueChanged<String> onSelect;
final VoidCallback? onOpenIntegrations;
/// Narrow: the name is shorter.
final bool compact;
const _ServerSwitcher({
required this.accounts,
required this.current,
required this.onSelect,
this.onOpenIntegrations,
this.compact = false,
});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final chip = Row(
mainAxisSize: MainAxisSize.min,
children: [
StatusDot(status: accounts.statusOf(current.serverId)),
const SizedBox(width: 8),
ConstrainedBox(
constraints: BoxConstraints(maxWidth: compact ? 110 : 200),
child: Text(current.displayName,
maxLines: 1, overflow: TextOverflow.ellipsis, style: t.titleSmall),
),
],
);
if (accounts.accounts.length < 2 && onOpenIntegrations == null) {
return Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: chip);
}
return MenuAnchor(
menuChildren: [
for (final a in accounts.accounts)
MenuItemButton(
key: ValueKey('server-${a.serverId}'),
leadingIcon: StatusDot(status: accounts.statusOf(a.serverId)),
trailingIcon: a.serverId == current.serverId ? const Icon(Icons.check, size: 18) : null,
onPressed: () => onSelect(a.serverId),
child: Text('${a.displayName} · ${a.userName}'),
),
if (onOpenIntegrations != null)
MenuItemButton(
leadingIcon: const Icon(Icons.add, size: 18),
onPressed: onOpenIntegrations,
child: const Text('Manage servers…'),
),
],
builder: (context, menu, _) => TextButton(
key: const ValueKey('jf-server-switch'),
onPressed: () => menu.isOpen ? menu.close() : menu.open(),
child: Row(mainAxisSize: MainAxisSize.min, children: [
chip,
const Icon(Icons.arrow_drop_down, size: 20),
]),
),
);
}
}
class StatusDot extends StatelessWidget {
final JellyfinAccountStatus status;
const StatusDot({super.key, required this.status});
@override
Widget build(BuildContext context) {
final color = switch (status) {
JellyfinAccountStatus.online => Colors.green,
JellyfinAccountStatus.offline => Colors.orange,
JellyfinAccountStatus.expired => Theme.of(context).colorScheme.error,
_ => Colors.grey,
};
return Container(
width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle));
}
}
class _StatusBanner extends StatelessWidget {
final JellyfinAccounts accounts;
final JellyfinAccount account;
final JellyfinAccountStatus status;
const _StatusBanner({required this.accounts, required this.account, required this.status});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final expired = status == JellyfinAccountStatus.expired;
return Container(
key: const ValueKey('jf-status-banner'),
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: (expired ? scheme.errorContainer : Colors.orange.shade900).withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(expired ? Icons.lock_clock : Icons.cloud_off, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(expired
? 'Sign-in to ${account.displayName} expired. Reconnect to keep browsing.'
: "Can't reach ${account.displayName}: ${accounts.errorOf(account.serverId) ?? 'offline'}"),
),
if (expired)
FilledButton.tonal(
onPressed: () => showReconnectDialog(context, accounts, account),
child: const Text('Reconnect'),
)
else
TextButton(
onPressed: () => accounts.validate(account.serverId),
child: const Text('Retry'),
),
],
),
);
}
}
/// Sign in again to an expired account (same server, new token).
Future<void> showReconnectDialog(
BuildContext context, JellyfinAccounts accounts, JellyfinAccount account) {
return showDialog<void>(
context: context,
builder: (context) => Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520),
child: Padding(
padding: const EdgeInsets.all(20),
child: JellyfinSignInPanel(
accounts: accounts,
reconnect: account,
onDone: (_) => Navigator.of(context).pop(),
onCancel: () => Navigator.of(context).pop(),
),
),
),
),
);
}
/// Small helper for other screens: the user's avatar on a linked server.
class JellyfinAvatar extends StatelessWidget {
final JellyfinAccounts accounts;
final JellyfinAccount account;
final double radius;
const JellyfinAvatar({super.key, required this.accounts, required this.account, this.radius = 18});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final initials = account.userName.isEmpty ? '?' : account.userName.characters.first.toUpperCase();
final fallback = CircleAvatar(
radius: radius,
backgroundColor: scheme.primaryContainer,
child: Text(initials, style: TextStyle(color: scheme.onPrimaryContainer)),
);
final client = accounts.clientFor(account.serverId);
final url = client?.userImageUrl(account.user, maxWidth: (radius * 4).round());
if (url == null) return fallback;
return ClipOval(
child: SizedBox(
width: radius * 2,
height: radius * 2,
child: Image.network(url.toString(),
fit: BoxFit.cover,
cacheWidth: (radius * 4).round(),
errorBuilder: (_, _, _) => fallback),
),
);
}
}
+423
View File
@@ -0,0 +1,423 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:sync_relay_core/core.dart';
import 'browse_scope.dart';
import 'format.dart';
import 'images.dart';
import '../shell/motion.dart' show HScroll;
enum CardShape {
/// 2:3 poster (movies, series, latest).
poster(2 / 3, 150),
/// 16:9 thumbnail (continue watching, next up, episodes).
thumb(16 / 9, 260);
/// Image width / height.
final double aspect;
/// Smallest card width; rows and grids add columns past it.
final double minWidth;
const CardShape(this.aspect, this.minWidth);
}
/// Title + subtitle lines under a card image.
const cardTextHeight = 46.0;
const cardGap = 12.0;
/// Card image for [item] in [shape]: thumbs prefer the item's own
/// screenshot / thumb / backdrop, posters the primary (series poster for
/// episodes).
JellyfinImageRef? cardImage(JellyfinItem item, CardShape shape) {
if (shape == CardShape.poster) return item.image(JellyfinImageType.primary);
final own = item.ownImage(JellyfinImageType.primary);
final primaryIsWide = (item.primaryImageAspectRatio ?? 0) > 1.2 ||
item.kind == JellyfinItemKind.episode ||
item.kind == JellyfinItemKind.video;
if (own != null && primaryIsWide) return own;
return item.image(JellyfinImageType.thumb) ??
item.image(JellyfinImageType.backdrop) ??
item.image(JellyfinImageType.primary);
}
IconData kindIcon(JellyfinItem item) => switch (item.kind) {
JellyfinItemKind.series || JellyfinItemKind.season => Icons.live_tv_outlined,
JellyfinItemKind.episode => Icons.tv_outlined,
JellyfinItemKind.folder || JellyfinItemKind.collectionFolder => Icons.folder_outlined,
JellyfinItemKind.boxSet => Icons.collections_bookmark_outlined,
_ => Icons.movie_outlined,
};
/// Two clicks on the same card within this time put it up.
const doubleClickTime = Duration(milliseconds: 350);
/// Poster or thumb card: image with progress bar and state badges, title
/// and one meta line. Click / Enter selects it (details panel; libraries
/// and folders open), a second click soon after puts it up. [display]
/// gives the picture and title when they are not [item]'s own (a series
/// card for a new episode), [subtitle] replaces the meta line.
class MediaCard extends StatefulWidget {
final JellyfinItem item;
final JellyfinItem? display;
final String? subtitle;
final CardShape shape;
/// Rows mixing series (Continue): series name on top, "S2E3 · 12 min
/// left" below.
final bool seriesFirst;
final bool autofocus;
const MediaCard({
super.key,
required this.item,
required this.shape,
this.display,
this.subtitle,
this.seriesFirst = false,
this.autofocus = false,
});
@override
State<MediaCard> createState() => _MediaCardState();
}
class _MediaCardState extends State<MediaCard> {
bool _hover = false;
bool _focus = false;
DateTime? _lastTap;
JellyfinItem get item => widget.item;
void _tap(BrowseScope scope) {
final now = DateTime.now();
final last = _lastTap;
_lastTap = now;
if (last != null && now.difference(last) < doubleClickTime && isSelectable(item)) {
_lastTap = null;
scope.onPutUp(item);
return;
}
scope.activate(item);
}
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final shown = widget.display ?? item;
final selected = scope.selectedItemId == item.id;
final played = item.userData.played;
final progress = progressOf(item);
final unplayed = shown.userData.unplayedItemCount;
final ring = selected
? scheme.primary
: _focus
? scheme.onSurface
: Colors.transparent;
final image = AspectRatio(
aspectRatio: widget.shape.aspect,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.fastEaseInToSlowEaseOut,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: ring, width: 3),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(7),
child: Stack(
fit: StackFit.expand,
children: [
JellyfinImage(
client: scope.client,
image: cardImage(shown, widget.shape),
icon: kindIcon(shown),
),
if (_hover || _focus)
const ColoredBox(color: Color(0x22FFFFFF)),
if (progress > 0)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: LinearProgressIndicator(
value: progress,
minHeight: 4,
backgroundColor: Colors.black54,
),
),
Positioned(
top: 6,
right: 6,
child: selected
? _Badge(color: scheme.primary, child: Icon(Icons.check, size: 14, color: scheme.onPrimary))
: played
? const _Badge(color: Color(0xCC1B5E20), child: Icon(Icons.check, size: 14, color: Colors.white))
: unplayed != null && unplayed > 0 && !isPlayable(shown) && widget.subtitle == null
? _Badge(
color: scheme.primaryContainer,
child: Text('$unplayed',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: scheme.onPrimaryContainer)))
: const SizedBox.shrink(),
),
],
),
),
),
);
final title = widget.display != null ? shown.name : cardTitle(item, seriesFirst: widget.seriesFirst);
final sub = widget.subtitle ??
(widget.seriesFirst && item.kind == JellyfinItemKind.episode
? [cardSubtitle(item), item.name].where((s) => s.isNotEmpty).join(' · ')
: cardSubtitle(item));
return Semantics(
button: true,
selected: selected,
label: title,
child: InkWell(
key: ValueKey('card-${item.id}'),
autofocus: widget.autofocus,
borderRadius: BorderRadius.circular(10),
focusColor: Colors.transparent,
hoverColor: Colors.transparent,
onTap: () => _tap(scope),
onSecondaryTap: () => scope.cardMenu(context, item),
onHover: (h) => setState(() => _hover = h),
onFocusChange: (f) => setState(() => _focus = f),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
image,
const SizedBox(height: 6),
SizedBox(
height: cardTextHeight - 6,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: selected ? scheme.primary : null)),
Text(sub,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(color: Colors.grey)),
],
),
),
],
),
),
);
}
}
class _Badge extends StatelessWidget {
final Color color;
final Widget child;
const _Badge({required this.color, required this.child});
@override
Widget build(BuildContext context) => Container(
constraints: const BoxConstraints(minWidth: 22, minHeight: 22),
padding: const EdgeInsets.symmetric(horizontal: 5),
alignment: Alignment.center,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(11)),
child: child,
);
}
/// Columns and card width that fill [width] exactly with cards at least
/// [minWidth] wide: no half-cut card at the edge, any window width.
({int cols, double itemWidth}) fitColumns(double width, double minWidth, {double gap = cardGap}) {
final cols = math.max(1, ((width + gap) / (minWidth + gap)).floor());
return (cols: cols, itemWidth: (width - gap * (cols - 1)) / cols);
}
/// Grid layout for cards: as many columns as fit, height = image + text.
class CardGridDelegate extends SliverGridDelegate {
final CardShape shape;
final double minWidth;
CardGridDelegate(this.shape, {double? minWidth}) : minWidth = minWidth ?? shape.minWidth;
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
final fit = fitColumns(constraints.crossAxisExtent, minWidth);
final h = fit.itemWidth / shape.aspect + cardTextHeight;
return SliverGridRegularTileLayout(
crossAxisCount: fit.cols,
mainAxisStride: h + 18,
crossAxisStride: fit.itemWidth + cardGap,
childMainAxisExtent: h,
childCrossAxisExtent: fit.itemWidth,
reverseCrossAxis: false,
);
}
@override
bool shouldRelayout(CardGridDelegate old) => old.shape != shape || old.minWidth != minWidth;
}
/// One card of a [MediaRow]: see [MediaCard].
typedef CardEntry = ({JellyfinItem item, JellyfinItem? display, String? subtitle});
List<CardEntry> cardEntries(Iterable<JellyfinItem> items) =>
[for (final i in items) (item: i, display: null, subtitle: null)];
/// A titled horizontal row of cards sized to fill the width; mouse drag
/// and the wheel move it ([HScroll]), arrows (on hover) page through it.
/// Lazy: only visible cards are built.
class MediaRow extends StatefulWidget {
final String title;
final List<CardEntry> entries;
final CardShape shape;
final bool seriesFirst;
final Widget? trailing;
const MediaRow({
super.key,
required this.title,
required this.entries,
required this.shape,
this.seriesFirst = false,
this.trailing,
});
@override
State<MediaRow> createState() => _MediaRowState();
}
class _MediaRowState extends State<MediaRow> {
final _scroll = ScrollController();
bool _hover = false;
@override
void dispose() {
_scroll.dispose();
super.dispose();
}
void _page(int dir, double width) {
if (!_scroll.hasClients) return;
final p = _scroll.position;
final to = (p.pixels + dir * (width + cardGap)).clamp(0.0, p.maxScrollExtent);
_scroll.animateTo(to, duration: const Duration(milliseconds: 280), curve: Curves.easeOutCubic);
}
@override
Widget build(BuildContext context) {
final entries = widget.entries;
return MouseRegion(
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SectionHeader(widget.title, count: '${entries.length}', trailing: widget.trailing),
LayoutBuilder(builder: (context, box) {
final fit = fitColumns(box.maxWidth, widget.shape.minWidth);
final h = fit.itemWidth / widget.shape.aspect + cardTextHeight;
final overflow = entries.length > fit.cols;
return SizedBox(
height: h,
child: Stack(
children: [
HScroll(
controller: _scroll,
builder: (c) => ListView.separated(
controller: c,
scrollDirection: Axis.horizontal,
itemCount: entries.length,
separatorBuilder: (_, _) => const SizedBox(width: cardGap),
itemBuilder: (context, i) => SizedBox(
width: fit.itemWidth,
child: MediaCard(
item: entries[i].item,
display: entries[i].display,
subtitle: entries[i].subtitle,
shape: widget.shape,
seriesFirst: widget.seriesFirst,
),
),
),
),
if (overflow && _hover) ...[
_PageArrow(left: true, height: h - cardTextHeight, onTap: () => _page(-1, box.maxWidth)),
_PageArrow(left: false, height: h - cardTextHeight, onTap: () => _page(1, box.maxWidth)),
],
],
),
);
}),
],
),
);
}
}
class _PageArrow extends StatelessWidget {
final bool left;
final double height;
final VoidCallback onTap;
const _PageArrow({required this.left, required this.height, required this.onTap});
@override
Widget build(BuildContext context) {
return Positioned(
left: left ? 0 : null,
right: left ? null : 0,
top: 0,
height: height,
child: Center(
child: ExcludeFocus(
child: IconButton.filledTonal(
tooltip: left ? 'Previous' : 'Next',
onPressed: onTap,
icon: Icon(left ? Icons.chevron_left : Icons.chevron_right),
),
),
),
);
}
}
/// Section heading used above grids and rows.
class SectionHeader extends StatelessWidget {
final String title;
final String? count;
final Widget? trailing;
const SectionHeader(this.title, {super.key, this.count, this.trailing});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return Padding(
padding: const EdgeInsets.only(bottom: 8, top: 4),
child: Row(
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis, style: t.titleMedium),
),
if (count != null) ...[
const SizedBox(width: 8),
Text(count!, style: t.bodySmall?.copyWith(color: Colors.grey)),
],
],
),
),
?trailing,
],
),
);
}
}
@@ -0,0 +1,584 @@
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
import '../shell/motion.dart';
import '../widgets/app_menu.dart';
import '../widgets/copy_open.dart';
import 'accounts.dart';
import 'browse_scope.dart';
import 'cards.dart' show doubleClickTime, kindIcon;
import 'format.dart';
import 'home_rows.dart' show continueKey;
import 'images.dart';
/// Width of the details panel (docked or floating).
const detailsPanelWidth = 400.0;
/// What putting up [item] plays: itself when playable; for a series its
/// next episode (resume point or next up), else its first unwatched, else
/// its first episode. Null when there is nothing to play.
Future<JellyfinItem?> playableFor(JellyfinClient c, JellyfinItem item) async {
if (isPlayable(item)) return item;
if (item.kind != JellyfinItemKind.series) return null;
final next = await c.nextUp(seriesId: item.id, limit: 1, enableResumable: true);
if (next.isNotEmpty) return next.first;
final eps = await c.episodes(item.id);
return eps.where((e) => !e.userData.played).firstOrNull ?? eps.firstOrNull;
}
typedef _Data = ({
JellyfinItem item,
JellyfinItem? series,
List<JellyfinItem> seasons,
JellyfinItem? next,
});
/// The selected card's details, at the right of the library: banner,
/// title, meta, "Put it up · S1E3" ("Play · S1E3" when watching
/// [solo]), mark watched / unwatched, remove from Continue, overview and,
/// for a series or an episode, the episode list (click picks which
/// episode goes up, double-click puts it up). Reloads when the
/// [controller]'s data version moves.
class JellyfinDetailsPanel extends StatefulWidget {
final BrowseController controller;
final JellyfinSelection selection;
/// Nobody else watches (offline or solo): the button says Play.
final bool solo;
/// Puts [item] (playable) up; returns an error text, or null when sent.
final Future<String?> Function(JellyfinSelection item) onPutUp;
/// Adds [item] to Up next (with next: right after what plays); null: no
/// server, no queue actions.
final Future<String?> Function(JellyfinSelection item, {bool next})? onAddToQueue;
final VoidCallback onClose;
const JellyfinDetailsPanel({
super.key,
required this.controller,
required this.selection,
required this.onPutUp,
this.onAddToQueue,
required this.onClose,
this.solo = false,
});
@override
State<JellyfinDetailsPanel> createState() => _JellyfinDetailsPanelState();
}
class _JellyfinDetailsPanelState extends State<JellyfinDetailsPanel> {
String? _seasonId;
String? _targetId;
bool _busy = false;
String? _error;
DateTime? _lastTap;
String? _lastTapId;
BrowseController get _c => widget.controller;
JellyfinAccounts get _accounts => _c.accounts;
String get _serverId => widget.selection.serverId;
JellyfinClient? get _client => _accounts.clientFor(_serverId);
Future<T> _cached<T>(String key, Future<T> Function(JellyfinClient c) fn) {
final c = _client;
if (c == null) return Future.error(const JellyfinException('that Jellyfin server is not linked any more'));
return _accounts.cached(_serverId, key, () => fn(c));
}
Future<_Data> _load() async {
await _accounts.ensureLoaded();
final id = widget.selection.itemId;
final item = await _cached('item:$id', (c) => c.item(id));
final sid = item.kind == JellyfinItemKind.series ? item.id : item.seriesId;
if (sid == null || (item.kind != JellyfinItemKind.series && item.kind != JellyfinItemKind.episode)) {
return (item: item, series: null, seasons: const <JellyfinItem>[], next: null);
}
final r = await Future.wait<Object?>([
item.kind == JellyfinItemKind.series ? Future.value(item) : _cached('item:$sid', (c) => c.item(sid)),
_cached('seasons:$sid', (c) => c.seasons(sid)),
_cached('next:$sid', (c) => c.nextUp(seriesId: sid, limit: 1, enableResumable: true))
.then<List<JellyfinItem>?>((v) => v, onError: (_) => null),
]);
final next = r[2] as List<JellyfinItem>?;
return (
item: item,
series: r[0] as JellyfinItem,
seasons: r[1] as List<JellyfinItem>,
next: next == null || next.isEmpty ? null : next.first,
);
}
Future<List<JellyfinItem>> _episodes(String seriesId, String seasonId) =>
_cached('episodes:$seriesId:$seasonId', (c) => c.episodes(seriesId, seasonId: seasonId));
Future<void> _putUp(JellyfinItem target) async {
if (_busy) return;
setState(() {
_busy = true;
_error = null;
});
String? error;
try {
error = await widget.onPutUp(JellyfinSelection(_serverId, target.id, target));
} catch (e) {
error = '$e';
}
if (!mounted) return;
setState(() {
_busy = false;
_error = error;
});
}
Future<void> _queue(JellyfinItem target, {bool next = false}) async {
final add = widget.onAddToQueue;
if (add == null || _busy) return;
setState(() {
_busy = true;
_error = null;
});
String? error;
try {
error = await add(JellyfinSelection(_serverId, target.id, target), next: next);
} catch (e) {
error = '$e';
}
if (!mounted) return;
setState(() {
_busy = false;
_error = error;
});
}
/// Right-click on an episode ([context]: its row): Put it up, Add to
/// queue, Play next (in a server), Copy title, Open in Jellyfin.
Future<void> _episodeMenu(BuildContext context, JellyfinItem e) async {
final queue = widget.onAddToQueue != null;
if (queue) setState(() => _targetId = e.id);
final client = _client;
final pick = await showAppMenu<int>(context, items: [
if (queue) ...[
AppMenuItem(value: 0, label: widget.solo ? 'Play' : 'Put it up'),
const AppMenuItem(value: 1, label: 'Add to queue'),
const AppMenuItem(value: 2, label: 'Play next'),
],
const AppMenuItem(value: 3, label: ReuseText.copyTitle),
if (client != null) const AppMenuItem(value: 4, label: ReuseText.openInJellyfin),
]);
switch (pick) {
case 0:
await _putUp(e);
case 1:
await _queue(e);
case 2:
await _queue(e, next: true);
case 3:
await copyToClipboard(jellyfinCopyTitle(e), done: 'Title copied');
case 4:
await openLink(jellyfinWebUrl(client!.baseUrl, e.id, serverId: _serverId));
}
}
/// Runs a watched-state change, then lets every row reload.
Future<void> _change(Future<void> Function(JellyfinClient c) action) async {
final c = _client;
if (c == null || _busy) return;
setState(() {
_busy = true;
_error = null;
});
String? error;
try {
await action(c);
} on JellyfinException catch (e) {
if (e.isUnauthorized) _accounts.markExpired(_serverId);
error = e.isUnauthorized ? 'Jellyfin sign-in expired' : e.message;
} catch (e) {
error = '$e';
}
_c.dataChanged();
if (!mounted) return;
setState(() {
_busy = false;
_error = error;
});
}
void _toggleWatched(JellyfinItem t) =>
_change((c) => t.userData.played ? c.markUnplayed(t.id) : c.markPlayed(t.id));
void _removeFromContinue(JellyfinItem t) => _change((c) async {
if (t.userData.isResumable) await c.clearResumePosition(t.id);
await _accounts.dismissContinue(_serverId, continueKey(t));
});
void _tapEpisode(JellyfinItem e) {
final now = DateTime.now();
final again = _lastTapId == e.id && _lastTap != null && now.difference(_lastTap!) < doubleClickTime;
_lastTap = again ? null : now;
_lastTapId = e.id;
setState(() => _targetId = e.id);
if (again) _putUp(e);
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Material(
key: const ValueKey('jf-details'),
color: scheme.surfaceContainerLow,
child: SizedBox(
width: detailsPanelWidth,
child: ListenableBuilder(
listenable: _c,
builder: (context, _) => Loader<_Data>(
key: ValueKey('details-${widget.selection.serverId}-${widget.selection.itemId}'),
version: _c.version,
load: _load,
loading: Stack(children: [
const Center(child: CircularProgressIndicator(strokeWidth: 2)),
Positioned(right: 4, top: 4, child: _closeButton()),
]),
builder: (context, d) => _body(context, d),
),
),
),
);
}
Widget _closeButton() => IconButton(
key: const ValueKey('details-close'),
tooltip: 'Close',
onPressed: widget.onClose,
icon: const Icon(Icons.close),
);
Widget _body(BuildContext context, _Data d) {
final series = d.series;
if (series == null) return _view(context, d, null, null);
final preferred = d.item.kind == JellyfinItemKind.episode ? d.item : null;
final baseTarget = preferred ?? d.next;
final seasons = d.seasons;
final seasonId = (_seasonId != null && seasons.any((s) => s.id == _seasonId))
? _seasonId
: seasons.any((s) => s.id == baseTarget?.seasonId)
? baseTarget!.seasonId
: (seasons.where((s) => !s.userData.played).firstOrNull ?? seasons.firstOrNull)?.id;
if (seasonId == null) return _view(context, d, baseTarget, const []);
return Loader<List<JellyfinItem>>(
key: ValueKey('details-episodes-$seasonId'),
version: _c.version,
load: () => _episodes(series.id, seasonId),
loading: _view(context, d, baseTarget, null),
builder: (context, eps) {
final chosen = _targetId == null
? null
: eps.where((e) => e.id == _targetId).firstOrNull ??
(baseTarget?.id == _targetId ? baseTarget : null);
final fresh = baseTarget == null ? null : eps.where((e) => e.id == baseTarget.id).firstOrNull;
final target = chosen ??
fresh ??
baseTarget ??
eps.where((e) => !e.userData.played).firstOrNull ??
eps.firstOrNull;
return _view(context, d, target, eps, seasonId: seasonId);
},
);
}
/// [episodes] null = loading, empty = none (or not a series).
Widget _view(BuildContext context, _Data d, JellyfinItem? target, List<JellyfinItem>? episodes,
{String? seasonId}) {
final theme = Theme.of(context);
final t = theme.textTheme;
final scheme = theme.colorScheme;
final client = _client;
final shown = d.series ?? d.item;
final isSeries = d.series != null;
final playable = isSeries ? target : (isPlayable(d.item) ? d.item : null);
final banner = shown.image(JellyfinImageType.backdrop) ??
shown.image(JellyfinImageType.thumb) ??
(d.item.kind == JellyfinItemKind.episode ? d.item.ownImage(JellyfinImageType.primary) : null) ??
shown.image(JellyfinImageType.primary);
final eps = shown.recursiveItemCount;
final meta = [
if (shown.productionYear != null) '${shown.productionYear}',
if (isSeries && d.seasons.length > 1) '${d.seasons.length} seasons',
if (isSeries && eps != null && eps > 0) '$eps episodes',
if (!isSeries && shown.runTime != null) formatRuntime(shown.runTime!),
?shown.officialRating,
if (shown.communityRating != null) '★ ${shown.communityRating!.toStringAsFixed(1)}',
if (!isSeries && shown.userData.played) 'Watched',
].join(' · ');
final verb = widget.solo ? 'Play' : 'Put it up';
final label = playable != null && isSeries ? '$verb · ${playable.episodeLabel ?? playable.name}' : verb;
final inContinue = playable != null &&
(playable.userData.isResumable || _c.continueKeys.contains(continueKey(playable)));
final left = playable == null ? null : timeLeft(playable);
return ListView(
key: const ValueKey('details-list'),
padding: EdgeInsets.zero,
children: [
SizedBox(
height: 180,
child: Stack(
fit: StackFit.expand,
children: [
if (client != null)
JellyfinImage(client: client, image: banner, alignment: Alignment.topCenter, icon: kindIcon(shown))
else
ColoredBox(color: scheme.surfaceContainerHighest),
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, scheme.surfaceContainerLow],
stops: const [0.55, 1],
),
),
),
Positioned(right: 4, top: 4, child: _closeButton()),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(shown.name, style: t.titleLarge),
if (meta.isNotEmpty) Text(meta, style: t.bodyMedium?.copyWith(color: Colors.grey)),
const SizedBox(height: 12),
Wrap(
spacing: 4,
runSpacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
FilledButton.icon(
key: const ValueKey('details-put-up'),
onPressed: playable == null || _busy ? null : () => _putUp(playable),
icon: _busy
? const SizedBox.square(dimension: 16, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.play_arrow),
label: Text(label, overflow: TextOverflow.ellipsis),
),
if (playable != null && widget.onAddToQueue != null)
GestureDetector(
onSecondaryTap: _busy ? null : () => _queue(playable, next: true),
child: IconButton(
key: const ValueKey('details-add-queue'),
onPressed: _busy ? null : () => _queue(playable),
tooltip: 'Add to queue (right-click: play next)',
icon: const Icon(Icons.playlist_add_rounded),
),
),
if (playable != null)
IconButton(
key: const ValueKey('details-watched'),
onPressed: _busy ? null : () => _toggleWatched(playable),
tooltip: playable.userData.played ? 'Mark unwatched' : 'Mark watched',
icon: Icon(playable.userData.played ? Icons.check_circle : Icons.check_circle_outline),
),
if (inContinue)
IconButton(
key: const ValueKey('details-remove-continue'),
onPressed: _busy ? null : () => _removeFromContinue(playable),
tooltip: 'Remove from Continue',
icon: const Icon(Icons.remove_circle_outline),
),
if (isSeries)
IconButton(
key: const ValueKey('details-open-series'),
tooltip: 'Open the series page',
onPressed: () => _c.open(SeriesRoute(d.series!, seasonId: seasonId)),
icon: const Icon(Icons.open_in_new),
),
if (client != null)
IconButton(
key: const ValueKey('details-open-jellyfin'),
tooltip: ReuseText.openInJellyfin,
onPressed: () => openLink(jellyfinWebUrl(client.baseUrl, shown.id, serverId: _serverId)),
icon: const Icon(Icons.open_in_browser_rounded),
),
],
),
if (_error != null) ...[
const SizedBox(height: 6),
Text(
_error!,
key: const ValueKey('details-hint'),
style: t.bodySmall?.copyWith(color: scheme.error),
),
],
if (isSeries && playable != null) ...[
const SizedBox(height: 10),
Text(
[playable.episodeLabel ?? '', playable.name].where((s) => s.isNotEmpty).join(' · '),
style: t.titleSmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
if (left != null) ...[
const SizedBox(height: 6),
Row(children: [
SizedBox(width: 160, child: LinearProgressIndicator(value: progressOf(playable!), minHeight: 3)),
const SizedBox(width: 8),
Text(left, style: t.bodySmall?.copyWith(color: Colors.grey)),
]),
],
if (shown.overview != null) ...[
const SizedBox(height: 12),
Text(shown.overview!, style: t.bodyMedium),
],
if (!isSeries) ...[
const SizedBox(height: 12),
_Tracks(item: d.item),
],
if (isSeries) ...[
const SizedBox(height: 16),
_episodesHeader(context, d, seasonId),
if (episodes == null)
const Padding(
padding: EdgeInsets.all(24),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
)
else if (episodes.isEmpty)
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('No episodes', style: TextStyle(color: Colors.grey)),
)
else
for (final e in episodes) _episodeRow(context, e, target?.id == e.id),
],
],
),
),
],
);
}
Widget _episodesHeader(BuildContext context, _Data d, String? seasonId) {
final seasons = d.seasons;
return Row(
children: [
const Expanded(child: Text('Episodes', style: TextStyle(fontWeight: FontWeight.w600))),
if (seasons.length > 1)
Flexible(
child: AppSelect<String?>(
key: const ValueKey('details-season'),
value: seasonId,
items: [for (final s in seasons) (s.id, s.name)],
onChanged: (id) => setState(() => _seasonId = id),
),
),
],
);
}
Widget _episodeRow(BuildContext context, JellyfinItem e, bool isTarget) {
final scheme = Theme.of(context).colorScheme;
final t = Theme.of(context).textTheme;
final client = _client;
final sub = e.userData.played ? 'watched' : timeLeft(e) ?? (e.runTime == null ? '' : formatRuntime(e.runTime!));
return Material(
color: isTarget ? scheme.primary.withValues(alpha: 0.10) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
// The row's own context: its menu opens at the row.
child: Builder(builder: (context) => InkWell(
key: ValueKey('details-episode-${e.id}'),
borderRadius: BorderRadius.circular(8),
onTap: () => _tapEpisode(e),
onSecondaryTap: () => _episodeMenu(context, e),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 4),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: SizedBox(
width: 64,
height: 36,
child: client == null
? ColoredBox(color: scheme.surfaceContainerHighest)
: JellyfinImage(
client: client,
image: e.ownImage(JellyfinImageType.primary) ?? e.image(JellyfinImageType.thumb),
icon: Icons.tv_outlined,
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
e.indexNumber == null ? e.name : 'E${e.indexNumber} · ${e.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: t.bodyMedium?.copyWith(
color: isTarget ? scheme.primary : e.userData.played ? Colors.grey : null,
fontWeight: isTarget ? FontWeight.w600 : null,
),
),
if (sub.isNotEmpty) Text(sub, style: t.bodySmall?.copyWith(color: Colors.grey)),
],
),
),
AnimatedOpacity(
opacity: isTarget ? 1 : 0,
duration: easeTime,
child: Icon(Icons.play_circle, color: scheme.primary),
),
],
),
),
)),
);
}
}
/// What the file holds, one line per track kind.
class _Tracks extends StatelessWidget {
final JellyfinItem item;
const _Tracks({required this.item});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final source = item.mediaSources.firstOrNull;
final streams = source?.mediaStreams.isNotEmpty == true ? source!.mediaStreams : item.mediaStreams;
if (streams.isEmpty) return const SizedBox.shrink();
String names(JellyfinStreamType type) => streams
.where((s) => s.type == type)
.map((s) => s.displayTitle ?? [s.language, s.codec].whereType<String>().join(' '))
.join(' · ');
final rows = [
('Video', names(JellyfinStreamType.video)),
('Audio', names(JellyfinStreamType.audio)),
('Subtitles', names(JellyfinStreamType.subtitle)),
if (source?.container != null || source?.size != null)
('File', [
?source?.container,
if (source?.size != null) '${(source!.size! / (1 << 30)).toStringAsFixed(2)} GB',
].join(' · ')),
].where((r) => r.$2.isNotEmpty);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final (label, value) in rows)
Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 76, child: Text(label, style: t.bodySmall?.copyWith(color: Colors.grey))),
Expanded(child: Text(value, style: t.bodySmall)),
],
),
),
],
);
}
}
+93
View File
@@ -0,0 +1,93 @@
import 'package:sync_relay_core/core.dart';
import '../screens/home/picked_media.dart';
import 'accounts.dart';
/// "1 h 42 min", "24 min", "45 s".
String formatRuntime(Duration d) {
final h = d.inHours, m = d.inMinutes % 60;
if (h > 0) return m == 0 ? '$h h' : '$h h $m min';
if (d.inMinutes > 0) return '${d.inMinutes} min';
return '${d.inSeconds} s';
}
/// Watched share of a resumable item, 0 when there is no resume point.
double progressOf(JellyfinItem item) {
final run = item.runTimeTicks;
final pos = item.userData.playbackPositionTicks;
if (run == null || run <= 0 || pos <= 0) return 0;
return (pos / run).clamp(0.0, 1.0);
}
/// "12 min left" for a resumable item, else null.
String? timeLeft(JellyfinItem item) {
final run = item.runTimeTicks;
if (run == null || !item.userData.isResumable) return null;
final left = ticksToDuration(run - item.userData.playbackPositionTicks);
if (left.inMinutes < 1) return 'almost done';
return '${formatRuntime(left)} left';
}
bool isPlayable(JellyfinItem item) => switch (item.kind) {
JellyfinItemKind.movie || JellyfinItemKind.episode || JellyfinItemKind.video => true,
_ => !item.isFolder && item.mediaType == 'Video',
};
/// Cards that select (details panel) on click: playable items and series.
bool isSelectable(JellyfinItem item) => isPlayable(item) || item.kind == JellyfinItemKind.series;
/// Line under a card title: "S2E3 · 12 min left", "2019 · 24 eps", ...
String cardSubtitle(JellyfinItem item, {bool withSeries = false}) {
final parts = <String>[];
switch (item.kind) {
case JellyfinItemKind.episode:
if (withSeries && item.seriesName != null) parts.add(item.seriesName!);
parts.add(item.episodeLabel ?? item.seasonName ?? 'Episode');
case JellyfinItemKind.series:
if (item.productionYear != null) parts.add('${item.productionYear}');
final eps = item.recursiveItemCount, seasons = item.childCount;
if (eps != null && eps > 0) {
parts.add('$eps eps');
} else if (seasons != null && seasons > 0) {
parts.add(seasons == 1 ? '1 season' : '$seasons seasons');
}
case JellyfinItemKind.season:
final n = item.childCount ?? item.recursiveItemCount;
if (n != null) parts.add('$n eps');
default:
if (item.productionYear != null) parts.add('${item.productionYear}');
}
final left = timeLeft(item);
if (left != null) {
parts.add(left);
} else if (isPlayable(item) && item.runTime != null) {
parts.add(formatRuntime(item.runTime!));
}
return parts.join(' · ');
}
/// Title on a card: episodes in rows that mix series show the series name
/// on top and the episode below.
String cardTitle(JellyfinItem item, {bool seriesFirst = false}) =>
seriesFirst && item.kind == JellyfinItemKind.episode && item.seriesName != null
? item.seriesName!
: item.name;
/// The selection for a playable [item] of [account].
JellyfinMedia pickFor(JellyfinAccount account, JellyfinItem item) {
final String subtitle;
if (item.kind == JellyfinItemKind.episode) {
subtitle = [?item.seriesName, ?item.episodeLabel].join(' · ');
} else {
subtitle = item.productionYear?.toString() ?? '';
}
return JellyfinMedia(
serverId: account.serverId,
baseUrl: account.baseUrl,
itemId: item.id,
mediaSourceId: item.mediaSources.length > 1 ? item.mediaSources.first.id : null,
title: item.name,
subtitle: subtitle,
runtime: item.runTime,
);
}
@@ -0,0 +1,260 @@
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart';
import '../state/settings.dart';
import '../widgets/app_menu.dart';
import 'browse_scope.dart';
import 'cards.dart';
import 'home_rows.dart';
/// Libraries that hold videos; music, books, photos and playlists are left
/// out of the browser.
bool isVideoLibrary(JellyfinItem view) => switch (view.collectionType) {
'movies' || 'tvshows' || 'homevideos' || 'boxsets' || 'mixed' || 'musicvideos' || null => true,
_ => false,
};
IconData libraryIcon(JellyfinItem view) => switch (view.collectionType) {
'movies' => Icons.movie_outlined,
'tvshows' => Icons.live_tv_outlined,
'boxsets' => Icons.collections_bookmark_outlined,
'homevideos' => Icons.videocam_outlined,
_ => Icons.folder_outlined,
};
/// The video libraries of the scope's server (cached).
Future<List<JellyfinItem>> videoLibraries(BrowseScope scope) =>
scope.load('views', (c) => c.userViews()).then((all) => all.where(isVideoLibrary).toList());
/// Start page: (library shortcuts,) one Continue row (resume points and
/// next up merged per series, see [mergeContinue]), then the newest
/// additions of each library as series cards ([latestCards]). Each row
/// loads on its own, so a slow library does not hold up the rest; rows
/// reload when watched state changes ([BrowseController.dataChanged]).
class JellyfinHomePage extends StatelessWidget {
const JellyfinHomePage({super.key});
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
return Loader<List<JellyfinItem>>(
key: ValueKey('views-${scope.account.serverId}'),
load: () => videoLibraries(scope),
builder: (context, views) => ListenableBuilder(
listenable: scope.controller,
builder: (context, _) {
final v = scope.controller.version;
return ListView(
key: const PageStorageKey('jf-home'),
padding: const EdgeInsets.fromLTRB(0, 4, 0, 24),
children: [
if (scope.libraryStrip) ...[
LibraryStrip(views: views),
const SizedBox(height: 20),
] else if (views.isEmpty)
const _NoLibraries(),
_ContinueRow(version: v),
for (final view in views) _LatestRow(key: ValueKey('row-latest-${view.id}'), view: view, version: v),
],
);
},
),
);
}
}
class _NoLibraries extends StatelessWidget {
const _NoLibraries();
@override
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.only(bottom: 20),
child: Text('No video libraries on this server for your user.', style: TextStyle(color: Colors.grey)),
);
}
/// The libraries as chips; a click opens one.
class LibraryStrip extends StatelessWidget {
final List<JellyfinItem> views;
const LibraryStrip({super.key, required this.views});
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
if (views.isEmpty) return const _NoLibraries();
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final v in views)
ActionChip(
key: ValueKey('library-${v.id}'),
avatar: Icon(libraryIcon(v), size: 18),
label: Text(v.name),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
onPressed: () => scope.controller.openTop(LibraryRoute(v)),
),
],
);
}
}
class _ContinueRow extends StatelessWidget {
final int version;
const _ContinueRow({required this.version});
static const title = 'Continue';
Future<List<JellyfinItem>> _load(BrowseScope scope) async {
final lists = await Future.wait([
scope.load('resume', (c) => c.resume(limit: 40)),
scope.load('nextup', (c) => c.nextUp(limit: 40)),
// Only for ordering: a failure leaves the row as it is.
scope
.load('played', (c) => c.recentlyPlayed(limit: 150))
.catchError((Object _) => const <JellyfinItem>[]),
]);
return mergeContinue(
resume: lists[0],
nextUp: lists[1],
recentlyPlayed: lists[2],
dismissed: scope.accounts.continueDismissed(scope.account.serverId),
now: scope.controller.now(),
);
}
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
return Loader<List<JellyfinItem>>(
version: version,
load: () => _load(scope),
showError: false,
loading: const _RowSkeleton(title: title, shape: CardShape.thumb),
builder: (context, items) {
scope.controller.continueKeys = {for (final i in items) continueKey(i)};
if (items.isEmpty) return const SizedBox.shrink();
return Padding(
key: const ValueKey('row-continue'),
padding: const EdgeInsets.only(bottom: 24),
child: MediaRow(
title: title,
entries: cardEntries(items),
shape: CardShape.thumb,
seriesFirst: true,
),
);
},
);
}
}
class _LatestRow extends StatelessWidget {
final JellyfinItem view;
final int version;
const _LatestRow({super.key, required this.view, required this.version});
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
final title = 'Latest in ${view.name}';
final shape = view.collectionType == 'homevideos' ? CardShape.thumb : CardShape.poster;
return Loader<List<JellyfinItem>>(
version: version,
load: () => scope.load('latest:${view.id}', (c) => c.latest(view.id, limit: 100, groupItems: false)),
showError: false,
loading: _RowSkeleton(title: title, shape: shape),
builder: (context, items) => SignalBuilder(builder: (context) {
final hide = AppSettings.instance.hideReReleases.value;
final cards = latestCards(items, hideReReleases: hide);
if (cards.isEmpty) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: 24),
child: MediaRow(
key: ValueKey('latest-${view.id}'),
title: title,
entries: [for (final c in cards) (item: c.item, display: c.display, subtitle: c.subtitle)],
shape: shape,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
key: ValueKey('show-all-${view.id}'),
// Newest first, like the row.
onPressed: () => scope.onOpen(LibraryRoute(view, sort: 'added')),
child: const Text('Show all'),
),
AppMenuButton<bool>(
key: ValueKey('latest-menu-${view.id}'),
tooltip: 'Row options',
items: () => [
AppMenuItem(
key: const ValueKey('hide-rereleases'),
value: !hide,
checked: hide,
label: 'Hide re-releases in Latest',
),
],
onSelected: AppSettings.instance.setHideReReleases,
),
],
),
),
);
}),
);
}
}
class _RowSkeleton extends StatelessWidget {
final String title;
final CardShape shape;
const _RowSkeleton({required this.title, required this.shape});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SectionHeader(title),
LayoutBuilder(builder: (context, box) {
final fit = fitColumns(box.maxWidth, shape.minWidth);
return Row(
children: [
for (var i = 0; i < fit.cols; i++) ...[
if (i > 0) const SizedBox(width: cardGap),
Expanded(child: SkeletonCard(shape: shape)),
],
],
);
}),
],
),
);
}
}
class SkeletonCard extends StatelessWidget {
final CardShape shape;
const SkeletonCard({super.key, required this.shape});
@override
Widget build(BuildContext context) {
final c = Theme.of(context).colorScheme.surfaceContainerHigh.withValues(alpha: 0.6);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
AspectRatio(
aspectRatio: shape.aspect,
child: DecoratedBox(
decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(8))),
),
const SizedBox(height: cardTextHeight),
],
);
}
}
@@ -0,0 +1,163 @@
import 'package:sync_relay_core/core.dart';
import 'format.dart';
// What the Jellyfin home rows show, as pure functions over the API lists
// (tested without widgets).
/// Series not watched for longer than this leave the Continue row, unless
/// nothing newer is left.
const continueMaxAge = Duration(days: 60);
/// Added this long after it first aired: a re-release ([isReRelease]).
const reReleaseAge = Duration(days: 30);
/// One Continue card per series: an episode's series, else the item itself.
String continueKey(JellyfinItem item) => item.seriesId ?? item.id;
DateTime? _later(DateTime? a, DateTime? b) =>
a == null ? b : b == null || !b.isAfter(a) ? a : b;
/// The Continue row: Continue watching ([resume]) and Next up ([nextUp])
/// merged into one card per series, the series watched most recently first.
/// A series shows its resume item when that was the last thing played in
/// it, else its next-up episode. When a series was last played comes from
/// the resume points and from [recentlyPlayed] (finished episodes, which
/// next-up items do not carry). Series untouched for [maxAge] are left out
/// unless nothing else is left; [dismissed] (key -> when) hides a series
/// until it is played again after that.
List<JellyfinItem> mergeContinue({
required List<JellyfinItem> resume,
required List<JellyfinItem> nextUp,
List<JellyfinItem> recentlyPlayed = const [],
Map<String, DateTime> dismissed = const {},
required DateTime now,
Duration maxAge = continueMaxAge,
}) {
final finished = <String, DateTime>{};
for (final p in recentlyPlayed) {
final at = p.userData.lastPlayedDate;
if (at != null) finished[continueKey(p)] = _later(finished[continueKey(p)], at)!;
}
final resumeBy = <String, JellyfinItem>{};
final nextBy = <String, JellyfinItem>{};
final order = <String>[];
for (final r in resume) {
final k = continueKey(r);
final had = resumeBy[k];
if (had == null) order.add(k);
final at = r.userData.lastPlayedDate;
if (had == null || (at != null && at.isAfter(had.userData.lastPlayedDate ?? DateTime(0)))) {
resumeBy[k] = r;
}
}
for (final n in nextUp) {
final k = continueKey(n);
if (nextBy.containsKey(k)) continue;
nextBy[k] = n;
if (!resumeBy.containsKey(k)) order.add(k);
}
final cards = <({JellyfinItem item, DateTime? at, int index})>[];
for (final (i, k) in order.indexed) {
final r = resumeBy[k], n = nextBy[k], done = finished[k];
final resumedAt = r?.userData.lastPlayedDate;
final JellyfinItem item;
if (r != null && (n == null || (resumedAt != null && (done == null || !resumedAt.isBefore(done))))) {
item = r;
} else {
item = n!;
}
final at = _later(resumedAt, done);
final hiddenAt = dismissed[k];
if (hiddenAt != null && (at == null || !at.isAfter(hiddenAt))) continue;
cards.add((item: item, at: at, index: i));
}
cards.sort((a, b) {
if (a.at == null || b.at == null) {
return a.at == b.at ? a.index.compareTo(b.index) : (a.at == null ? 1 : -1);
}
final c = b.at!.compareTo(a.at!);
return c != 0 ? c : a.index.compareTo(b.index);
});
final fresh = [for (final c in cards) if (c.at != null && now.difference(c.at!) <= maxAge) c.item];
return fresh.isNotEmpty ? fresh : [for (final c in cards) c.item];
}
/// Added to the library long ([age]) after it first aired: a re-upload or
/// an old season added late, not news. Unknown dates are never re-releases.
bool isReRelease(JellyfinItem item, {Duration age = reReleaseAge}) {
final aired = item.premiereDate, added = item.dateCreated;
return aired != null && added != null && added.difference(aired) > age;
}
/// One card of a "Latest in X" row: [item] is what selecting or putting it
/// up acts on, [display] gives the picture and title, [subtitle] the line
/// under it.
class LatestCard {
final JellyfinItem item;
final JellyfinItem display;
final String subtitle;
const LatestCard(this.item, this.display, this.subtitle);
}
/// A series to show for [episode]: its id, name and poster.
JellyfinItem seriesOf(JellyfinItem episode) => JellyfinItem(
id: episode.seriesId!,
name: episode.seriesName ?? episode.name,
type: JellyfinItemType.series,
isFolder: true,
imageTags: {
if (episode.seriesPrimaryImageTag != null)
JellyfinImageType.primary.apiName: episode.seriesPrimaryImageTag!,
},
parentBackdropItemId: episode.parentBackdropItemId,
parentBackdropImageTags: episode.parentBackdropImageTags,
);
/// "Latest in X" from the library's newest items (ungrouped, newest
/// first): episodes become one card per series (its poster and name,
/// "S1E1" for a single new episode, else "N new"), in order of the newest
/// episode; movies and other items stay as they are. With [hideReReleases]
/// items that [isReRelease] are left out first.
List<LatestCard> latestCards(List<JellyfinItem> items, {bool hideReReleases = true}) {
final kept = hideReReleases ? items.where((i) => !isReRelease(i)) : items;
final groups = <String, List<JellyfinItem>>{};
final out = <Object>[]; // a JellyfinItem, or a series id (group)
for (final i in kept) {
final sid = i.seriesId;
if (i.kind == JellyfinItemKind.episode && sid != null) {
final g = groups[sid];
if (g == null) {
groups[sid] = [i];
out.add(sid);
} else {
g.add(i);
}
} else {
out.add(i);
}
}
return [
for (final o in out)
if (o is JellyfinItem)
LatestCard(
o,
o,
o.kind == JellyfinItemKind.series && (o.childCount ?? 0) > 1
? '${o.childCount} new'
: cardSubtitle(o),
)
else
_seriesCard(groups[o]!),
];
}
LatestCard _seriesCard(List<JellyfinItem> eps) {
final series = seriesOf(eps.first);
if (eps.length == 1) {
final e = eps.single;
return LatestCard(e, series, e.episodeLabel ?? e.name);
}
return LatestCard(series, series, '${eps.length} new');
}
+87
View File
@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
/// Server-side scale steps: nearby sizes share one URL (and one cache
/// entry) instead of fetching per-pixel variants on every resize.
const _widthSteps = [160, 240, 320, 480, 640, 960, 1280, 1920, 2560];
int imageWidthStep(double physicalWidth) {
for (final s in _widthSteps) {
if (s >= physicalWidth) return s;
}
return _widthSteps.last;
}
/// A Jellyfin image sized to its box. The URL carries the image tag and the
/// width step, so Flutter's image cache (keyed by URL) reuses it until the
/// image changes on the server; decode is capped at the same width. Shows
/// [placeholder] (or a tinted box with [icon]) while loading and on error,
/// and fades in when ready.
class JellyfinImage extends StatelessWidget {
final JellyfinClient client;
final JellyfinImageRef? image;
final BoxFit fit;
final IconData icon;
final Alignment alignment;
final Widget? placeholder;
const JellyfinImage({
super.key,
required this.client,
required this.image,
this.fit = BoxFit.cover,
this.icon = Icons.movie_outlined,
this.alignment = Alignment.center,
this.placeholder,
});
@override
Widget build(BuildContext context) {
final ref = image;
final fallback = placeholder ?? _Placeholder(icon: icon);
if (ref == null) return fallback;
return LayoutBuilder(builder: (context, box) {
final dpr = MediaQuery.maybeDevicePixelRatioOf(context) ?? 1;
final w = box.hasBoundedWidth ? box.maxWidth : 480.0;
final step = imageWidthStep(w * dpr);
final url = client.imageUrl(ref, maxWidth: step, quality: 85).toString();
return Image.network(
url,
key: ValueKey(url),
fit: fit,
alignment: alignment,
width: double.infinity,
height: double.infinity,
cacheWidth: step,
filterQuality: FilterQuality.medium,
frameBuilder: (context, child, frame, sync) {
if (sync) return child;
return Stack(fit: StackFit.passthrough, children: [
if (frame == null) fallback,
AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(milliseconds: 180),
child: child,
),
]);
},
errorBuilder: (context, _, _) => fallback,
);
});
}
}
class _Placeholder extends StatelessWidget {
final IconData icon;
const _Placeholder({required this.icon});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return ColoredBox(
color: scheme.surfaceContainerHigh,
child: Center(
child: Icon(icon, size: 28, color: scheme.onSurfaceVariant.withValues(alpha: 0.35)),
),
);
}
}
@@ -0,0 +1,279 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
import 'browse_scope.dart';
import 'cards.dart';
import 'home_page.dart' show SkeletonCard;
enum LibrarySort {
name('Name', JellyfinSortBy.sortName, false),
added('Date added', JellyfinSortBy.dateCreated, true),
premiere('Release date', JellyfinSortBy.premiereDate, true),
rating('Rating', JellyfinSortBy.communityRating, true),
random('Random', JellyfinSortBy.random, false);
final String label;
final String apiName;
/// Natural direction: newest / best first.
final bool descending;
const LibrarySort(this.label, this.apiName, this.descending);
}
/// Item types and recursion for a library: movie and show libraries list
/// their titles flat; anything else is browsed folder by folder.
({List<String> types, bool recursive}) libraryShape(JellyfinItem folder) =>
switch (folder.collectionType) {
'movies' => (types: const [JellyfinItemType.movie], recursive: true),
'tvshows' => (types: const [JellyfinItemType.series], recursive: true),
'boxsets' => (types: const [JellyfinItemType.boxSet], recursive: true),
_ => (types: const <String>[], recursive: false),
};
const libraryPageSize = 100;
/// One library (or folder) as a grid that fills the width, with sort,
/// unplayed / favourite filters and infinite scroll. The scrollbar spans
/// the full count from the start: not-yet-loaded cells are skeletons and
/// pages load as they come near the viewport.
class JellyfinLibraryPage extends StatefulWidget {
final JellyfinItem folder;
/// A [LibrarySort] name to start with (in its natural direction).
final String? sort;
const JellyfinLibraryPage({super.key, required this.folder, this.sort});
@override
State<JellyfinLibraryPage> createState() => _JellyfinLibraryPageState();
}
class _JellyfinLibraryPageState extends State<JellyfinLibraryPage> {
late LibrarySort _sort =
LibrarySort.values.where((s) => s.name == widget.sort).firstOrNull ?? LibrarySort.name;
late bool _descending = _sort.descending;
bool _unplayed = false;
bool _favorites = false;
final _items = <JellyfinItem>[];
int? _total;
bool _loading = false;
Object? _error;
int _gen = 0;
final _scroll = ScrollController();
BrowseScope? _scope;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final scope = BrowseScope.of(context);
final first = _scope == null;
final changedServer = _scope != null && _scope!.client != scope.client;
_scope = scope;
if (first || changedServer) _reload();
}
@override
void dispose() {
_scroll.dispose();
super.dispose();
}
JellyfinItemQuery get _query {
final shape = libraryShape(widget.folder);
return JellyfinItemQuery(
parentId: widget.folder.id,
includeItemTypes: shape.types,
recursive: shape.recursive,
sortBy: [
// Folders first when browsing a folder tree.
if (!shape.recursive && _sort == LibrarySort.name) 'IsFolder',
_sort.apiName,
if (_sort != LibrarySort.name) JellyfinSortBy.sortName,
],
descending: _descending,
filters: [
if (_unplayed) JellyfinFilter.isUnplayed,
if (_favorites) JellyfinFilter.isFavorite,
],
);
}
void _reload() {
_gen++;
_items.clear();
_total = null;
_error = null;
_loading = false;
if (_scroll.hasClients) _scroll.jumpTo(0);
_loadMore();
}
Future<void> _loadMore() async {
final scope = _scope;
if (scope == null || _loading) return;
if (_total != null && _items.length >= _total!) return;
final gen = _gen;
setState(() {
_loading = true;
_error = null;
});
try {
final page = await scope.client.items(_query.page(_items.length, libraryPageSize));
if (gen != _gen || !mounted) return;
setState(() {
_items.addAll(page.items);
// An empty page before the advertised end: the library shrank.
_total = page.items.isEmpty ? _items.length : page.totalRecordCount;
_loading = false;
});
} catch (e) {
if (gen != _gen || !mounted) return;
if (e is JellyfinException && e.isUnauthorized) {
scope.accounts.markExpired(scope.account.serverId);
}
setState(() {
_loading = false;
_error = e;
});
}
}
void _setSort(LibrarySort s) {
setState(() {
_sort = s;
_descending = s.descending;
});
_reload();
}
CardShape get _shape =>
widget.folder.collectionType == 'homevideos' ? CardShape.thumb : CardShape.poster;
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final total = _total;
final toolbar = Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(widget.folder.name, style: t.titleLarge),
const SizedBox(width: 4),
Text(total == null ? '' : '$total ${total == 1 ? 'item' : 'items'}',
key: const ValueKey('library-count'),
style: t.bodySmall?.copyWith(color: Colors.grey)),
const SizedBox(width: 12),
_SortMenu(sort: _sort, onSelected: _setSort),
if (_sort != LibrarySort.random)
IconButton(
tooltip: _descending ? 'Descending' : 'Ascending',
visualDensity: VisualDensity.compact,
onPressed: () {
setState(() => _descending = !_descending);
_reload();
},
icon: Icon(_descending ? Icons.arrow_downward : Icons.arrow_upward, size: 18),
)
else
IconButton(
tooltip: 'Shuffle again',
visualDensity: VisualDensity.compact,
onPressed: _reload,
icon: const Icon(Icons.shuffle, size: 18),
),
FilterChip(
key: const ValueKey('filter-unplayed'),
label: const Text('Unwatched'),
selected: _unplayed,
visualDensity: VisualDensity.compact,
onSelected: (v) {
setState(() => _unplayed = v);
_reload();
},
),
FilterChip(
key: const ValueKey('filter-favorites'),
label: const Text('Favorites'),
selected: _favorites,
visualDensity: VisualDensity.compact,
onSelected: (v) {
setState(() => _favorites = v);
_reload();
},
),
],
);
final count = total ?? (_loading ? libraryPageSize ~/ 4 : 0);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(padding: const EdgeInsets.only(bottom: 12), child: toolbar),
if (_error != null && _items.isEmpty)
LoadError(error: _error!, onRetry: _loadMore)
else if (total == 0)
const Padding(
padding: EdgeInsets.all(32),
child: Center(child: Text('Nothing here', style: TextStyle(color: Colors.grey))),
)
else
Expanded(
child: Scrollbar(
controller: _scroll,
child: CustomScrollView(
controller: _scroll,
slivers: [
SliverGrid(
gridDelegate: CardGridDelegate(_shape),
delegate: SliverChildBuilderDelegate(
(context, i) {
if (i >= _items.length - 40 && _error == null) {
// Near the loaded edge: fetch the next page.
scheduleMicrotask(_loadMore);
}
if (i >= _items.length) return SkeletonCard(shape: _shape);
return MediaCard(item: _items[i], shape: _shape);
},
childCount: count,
),
),
if (_error != null && _items.isNotEmpty)
SliverToBoxAdapter(child: LoadError(error: _error!, onRetry: _loadMore)),
const SliverToBoxAdapter(child: SizedBox(height: 24)),
],
),
),
),
],
);
}
}
class _SortMenu extends StatelessWidget {
final LibrarySort sort;
final ValueChanged<LibrarySort> onSelected;
const _SortMenu({required this.sort, required this.onSelected});
@override
Widget build(BuildContext context) {
return MenuAnchor(
menuChildren: [
for (final s in LibrarySort.values)
MenuItemButton(
key: ValueKey('sort-${s.name}'),
leadingIcon: Icon(s == sort ? Icons.check : null, size: 18),
onPressed: () => onSelected(s),
child: Text(s.label),
),
],
builder: (context, menu, _) => OutlinedButton.icon(
key: const ValueKey('library-sort'),
onPressed: () => menu.isOpen ? menu.close() : menu.open(),
icon: const Icon(Icons.sort, size: 18),
label: Text(sort.label),
),
);
}
}
@@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import '../screens/home/ui.dart';
import 'accounts.dart';
import 'browse_scope.dart';
import 'browser.dart';
import 'sign_in_panel.dart';
/// The Library's Jellyfin source: the library browser once a server is
/// linked, else [empty] (default: an inline add-server panel).
class JellyfinPickerSource extends StatefulWidget {
final JellyfinAccounts accounts;
final BrowseController? controller;
final JellyfinSelection? selected;
final ValueChanged<JellyfinSelection> onSelect;
final ValueChanged<JellyfinSelection> onPutUp;
final void Function(JellyfinSelection item, {bool next})? onAddToQueue;
final VoidCallback? onOpenIntegrations;
final BrowseStart? start;
final bool libraryStrip;
/// Shown while no server is linked.
final WidgetBuilder? empty;
const JellyfinPickerSource({
super.key,
this.start,
required this.accounts,
required this.onSelect,
required this.onPutUp,
this.onAddToQueue,
this.controller,
this.selected,
this.onOpenIntegrations,
this.libraryStrip = true,
this.empty,
});
@override
State<JellyfinPickerSource> createState() => _JellyfinPickerSourceState();
}
class _JellyfinPickerSourceState extends State<JellyfinPickerSource> {
@override
void initState() {
super.initState();
widget.accounts.ensureLoaded();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: widget.accounts,
builder: (context, _) {
final a = widget.accounts;
if (!a.loaded) {
return const Center(child: CircularProgressIndicator(strokeWidth: 2));
}
if (a.isEmpty && widget.empty != null) return widget.empty!(context);
if (a.isEmpty) return _LinkFirst(accounts: a, onOpenIntegrations: widget.onOpenIntegrations);
return JellyfinBrowser(
key: const ValueKey('media-jellyfin'),
accounts: a,
controller: widget.controller,
selected: widget.selected,
onSelect: widget.onSelect,
onPutUp: widget.onPutUp,
onAddToQueue: widget.onAddToQueue,
onOpenIntegrations: widget.onOpenIntegrations,
start: widget.start,
libraryStrip: widget.libraryStrip,
);
},
);
}
}
class _LinkFirst extends StatelessWidget {
final JellyfinAccounts accounts;
final VoidCallback? onOpenIntegrations;
const _LinkFirst({required this.accounts, this.onOpenIntegrations});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
key: const ValueKey('media-jellyfin'),
child: Align(
alignment: Alignment.topLeft,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: Panel(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Link a Jellyfin server to browse it here. '
'You can manage linked servers in Integrations.',
style: TextStyle(color: Colors.grey)),
const SizedBox(height: kGap),
JellyfinSignInPanel(accounts: accounts),
if (onOpenIntegrations != null) ...[
const SizedBox(height: 8),
Align(
alignment: Alignment.centerLeft,
child: TextButton(
onPressed: onOpenIntegrations, child: const Text('Open Integrations')),
),
],
],
),
),
),
),
);
}
}
+214
View File
@@ -0,0 +1,214 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:sync_relay_core/core.dart';
/// What the player shows, sampled about once a second. [aid] / [sid] are
/// mpv track ids (null = none / subtitles off).
class PlayerSnapshot {
final double position;
final bool paused;
final int? aid;
final int? sid;
const PlayerSnapshot({required this.position, required this.paused, this.aid, this.sid});
@override
String toString() => 'PlayerSnapshot($position, paused: $paused, aid: $aid, sid: $sid)';
}
/// Playing / Progress / Stopped for one item played with this viewer's own
/// account, so Jellyfin's watched state and Continue Watching follow what
/// this viewer saw. Fed a [PlayerSnapshot] every tick ([update]); sends
/// Progress every [interval] and at once on pause, resume, seek (a jump of
/// more than 3 s from where playback should be) or a track change. Reports
/// go out one after another; a failure is logged and handed to [onError]
/// (the session shows one quiet toast) and never touches playback. A 401
/// calls [onUnauthorized] and ends reporting.
///
/// Watched or not is [WatchSession]'s rule, not Jellyfin's position
/// percentages (see ARCHITECTURE.md "Jellyfin in a session"): an item
/// played before ([playedBefore]) stays played (re-marked after Playing,
/// Progress without a position, Stopped without one, then marked played
/// again); otherwise Progress leaves the position out past
/// [_noPositionFrom] of the runtime so Jellyfin cannot mark it played on
/// its own, and Stopped carries what the rule says.
class JellyfinProgressReporter {
final JellyfinClient client;
final String itemId;
final JellyfinMediaSource source;
final String playSessionId;
final Duration interval;
final DateTime Function() _clock;
final void Function(Object error)? onError;
final VoidCallback? onUnauthorized;
/// What this viewer watched of the item while it was open here.
final WatchSession watch;
JellyfinProgressReporter({
required this.client,
required this.itemId,
required this.source,
required this.playSessionId,
bool playedBefore = false,
int resumeTicksBefore = 0,
this.interval = const Duration(seconds: 10),
this.onError,
this.onUnauthorized,
this.onStopped,
DateTime Function()? clock,
}) : _clock = clock ?? DateTime.now,
_keepPlayed = playedBefore,
_runtime = (source.runTimeTicks ?? 0) / jellyfinTicksPerSecond,
watch = WatchSession(
playedBefore: playedBefore, resumeBefore: resumeTicksBefore / jellyfinTicksPerSecond);
/// Past this share of the runtime Progress carries no position: Jellyfin
/// marks an item played past its "max resume" share (90 % by default).
static const _noPositionFrom = 0.85;
/// After Stopped (and a mark played) went out: the item's watched
/// state on the server may differ from what lists show.
final void Function()? onStopped;
Future<void> _chain = Future.value();
bool _playing = false;
bool _dead = false;
bool _keepPlayed;
double _runtime;
double? _endingStart;
PlayerSnapshot? _last;
DateTime? _lastAt;
DateTime? _lastSentAt;
/// Between [start] and [stop].
bool get active => _playing && !_dead;
/// The item counts as played here (before, or by the rule at a stop).
bool get keepsPlayed => _keepPlayed;
/// The player's duration (used when Jellyfin gave no runtime) and where
/// the ending starts (Outro segment / ED chapter), seconds.
void timeline({double duration = 0, double? endingStart}) {
if ((source.runTimeTicks ?? 0) <= 0 && duration > 0) _runtime = duration;
_endingStart = endingStart;
}
/// The report body for [s]: ticks, pause, and mpv's aid/sid mapped to
/// Jellyfin stream indices (-1 = subtitles off).
JellyfinPlaybackReport reportFor(PlayerSnapshot s, {bool withPosition = true}) => JellyfinPlaybackReport(
itemId: itemId,
mediaSourceId: source.id,
playSessionId: playSessionId,
positionTicks: withPosition ? _ticks(s.position) : null,
isPaused: s.paused,
audioStreamIndex: s.aid == null
? null
: source.jellyfinIndexForMpvTrack(JellyfinStreamType.audio, s.aid!),
subtitleStreamIndex: s.sid == null
? -1
: source.jellyfinIndexForMpvTrack(JellyfinStreamType.subtitle, s.sid!),
);
static int _ticks(double secs) => (secs * jellyfinTicksPerSecond).round();
/// Progress may carry the position: not for a played item, not without
/// a runtime and not near the end (either would let Jellyfin decide).
bool _progressPosition(double position) =>
!_keepPlayed && _runtime > 0 && position < _noPositionFrom * _runtime;
/// Playing: once the media is open. Again after [stop] (played to the
/// end, then someone seeked back) starts reporting anew. A played item
/// is marked played again right after (Jellyfin up to 10.11 clears it
/// on Playing).
void start(PlayerSnapshot s) {
if (_playing || _dead) return;
_playing = true;
_mark(s);
watch.sample(s.position, playing: !s.paused, at: _clock());
_send('Playing', () => client.reportPlaybackStart(reportFor(s)));
if (_keepPlayed) _send('played', () => client.markPlayed(itemId));
}
/// Progress when due (see the class doc).
void update(PlayerSnapshot s, {bool force = false}) {
if (!_playing || _dead) return;
final last = _last, lastAt = _lastAt, sentAt = _lastSentAt;
final now = _clock();
watch.sample(s.position, playing: !s.paused, at: now);
var due = force || last == null || lastAt == null || sentAt == null;
if (!due) {
final elapsed = now.difference(lastAt).inMilliseconds / 1000;
final expected = last.position + (last.paused ? 0 : elapsed);
due = s.paused != last.paused ||
s.aid != last.aid ||
s.sid != last.sid ||
(s.position - expected).abs() > 3 ||
now.difference(sentAt) >= interval;
}
_last = s;
_lastAt = now;
if (!due) return;
_lastSentAt = now;
final report = reportFor(s, withPosition: _progressPosition(s.position));
_send('Progress', () => client.reportPlaybackProgress(report));
}
/// Stopped (swap, leave, kick, window close, played to the end), as
/// [WatchSession.verdict] says: played (or kept played) -> no position,
/// then marked played; a peek -> the resume point from before; part way
/// -> the position, held under [_noPositionFrom] of the runtime.
/// Completes when it and everything before it went out.
Future<void> stop(PlayerSnapshot s) {
if (!_playing || _dead) return _chain;
_playing = false;
watch.sample(s.position, playing: !s.paused, at: _clock());
final v = _keepPlayed
? const WatchVerdict(WatchOutcome.keepPlayed)
: watch.verdict(runtime: _runtime, endingStart: _endingStart);
debugPrint('jellyfin $itemId: $v (covered ${watch.covered.round()} s of ${_runtime.round()} s)');
final int? ticks;
switch (v.outcome) {
case WatchOutcome.played || WatchOutcome.keepPlayed:
_keepPlayed = true;
ticks = null;
case WatchOutcome.peek:
ticks = _ticks(watch.resumeBefore);
case WatchOutcome.partial:
final at = v.resumeAt ?? s.position;
ticks = _ticks(_runtime > 0 ? math.min(at, _noPositionFrom * _runtime) : at);
}
final report = JellyfinPlaybackReport(
itemId: itemId, mediaSourceId: source.id, playSessionId: playSessionId, positionTicks: ticks);
_send('Stopped', () => client.reportPlaybackStopped(report));
if (ticks == null) {
_send('played', () => client.markPlayed(itemId));
// Marking played keeps a resume point Progress left (Continue would
// still list it, "3 min left"): clear it.
_send('resume cleared', () => client.clearResumePosition(itemId));
}
return _chain = _chain.then((_) => onStopped?.call());
}
void _mark(PlayerSnapshot s) {
_last = s;
_lastAt = _lastSentAt = _clock();
}
void _send(String what, Future<void> Function() call) {
_chain = _chain.then((_) async {
if (_dead) return;
try {
await call().timeout(const Duration(seconds: 10));
} catch (e) {
debugPrint('jellyfin $what report for $itemId failed: $e');
if (e is JellyfinException && e.isUnauthorized) {
_dead = true;
onUnauthorized?.call();
}
onError?.call(e);
}
});
}
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
import 'browse_scope.dart';
import 'cards.dart';
/// Search results grouped by kind: shows and movies as posters, episodes
/// as thumbnails, each a grid that fills the width.
class JellyfinSearchResults extends StatelessWidget {
final String term;
const JellyfinSearchResults({super.key, required this.term});
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
return Loader<List<JellyfinItem>>(
key: ValueKey('search-$term-${scope.account.serverId}'),
load: () => scope.load('search:$term', (c) => c.search(term, limit: 80)),
builder: (context, items) {
if (items.isEmpty) {
return Padding(
padding: const EdgeInsets.all(32),
child: Center(
child: Text('Nothing found for "$term"',
key: const ValueKey('search-empty'),
style: const TextStyle(color: Colors.grey)),
),
);
}
final groups = [
('Shows', CardShape.poster, items.where((i) => i.kind == JellyfinItemKind.series).toList()),
('Movies', CardShape.poster, items.where((i) => i.kind == JellyfinItemKind.movie).toList()),
('Episodes', CardShape.thumb, items.where((i) => i.kind == JellyfinItemKind.episode).toList()),
(
'Other',
CardShape.poster,
items
.where((i) => !const [
JellyfinItemKind.series,
JellyfinItemKind.movie,
JellyfinItemKind.episode
].contains(i.kind))
.toList()
),
].where((g) => g.$3.isNotEmpty);
return CustomScrollView(
key: const ValueKey('search-results'),
slivers: [
for (final (title, shape, list) in groups) ...[
SliverToBoxAdapter(child: SectionHeader(title, count: '${list.length}')),
SliverGrid(
gridDelegate: CardGridDelegate(shape),
delegate: SliverChildBuilderDelegate(
(context, i) => MediaCard(
item: list[i],
shape: shape,
seriesFirst: shape == CardShape.thumb,
),
childCount: list.length,
),
),
const SliverToBoxAdapter(child: SizedBox(height: 16)),
],
],
);
},
);
}
}
@@ -0,0 +1,453 @@
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
import 'browse_scope.dart';
import 'cards.dart' show doubleClickTime;
import 'format.dart';
import 'images.dart';
/// Series page: backdrop + logo header with overview and the smart "next
/// unwatched" button, a season selector and the season's episodes as dense
/// rows (several columns on wide windows).
class JellyfinSeriesPage extends StatefulWidget {
final JellyfinItem series;
final String? seasonId;
const JellyfinSeriesPage({super.key, required this.series, this.seasonId});
@override
State<JellyfinSeriesPage> createState() => _JellyfinSeriesPageState();
}
typedef _SeriesData = ({JellyfinItem series, List<JellyfinItem> seasons, JellyfinItem? next});
class _JellyfinSeriesPageState extends State<JellyfinSeriesPage> {
String? _seasonId;
@override
void initState() {
super.initState();
_seasonId = widget.seasonId;
}
Future<_SeriesData> _load(BrowseScope scope) async {
final id = widget.series.id;
final results = await Future.wait<Object?>([
scope.load('item:$id', (c) => c.item(id)),
scope.load('seasons:$id', (c) => c.seasons(id)),
scope
.load('next:$id', (c) => c.nextUp(seriesId: id, limit: 1, enableResumable: true))
.then<List<JellyfinItem>?>((v) => v, onError: (_) => null),
]);
final next = results[2] as List<JellyfinItem>?;
return (
series: results[0] as JellyfinItem,
seasons: results[1] as List<JellyfinItem>,
next: next == null || next.isEmpty ? null : next.first,
);
}
/// Season to show first: the one holding the next episode, else the first
/// with something unwatched, else the first.
String? _defaultSeason(_SeriesData d) {
if (d.seasons.isEmpty) return null;
final nextSeason = d.next?.seasonId;
if (nextSeason != null && d.seasons.any((s) => s.id == nextSeason)) return nextSeason;
return (d.seasons.where((s) => !s.userData.played).firstOrNull ?? d.seasons.first).id;
}
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
return Loader<_SeriesData>(
key: ValueKey('series-${widget.series.id}-${scope.account.serverId}'),
version: scope.controller.version,
load: () => _load(scope),
builder: (context, d) {
final seasonId = (_seasonId != null && d.seasons.any((s) => s.id == _seasonId))
? _seasonId
: _defaultSeason(d);
return CustomScrollView(
key: PageStorageKey('series-${widget.series.id}'),
slivers: [
SliverToBoxAdapter(child: _SeriesHeader(data: d)),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final s in d.seasons)
ChoiceChip(
key: ValueKey('season-${s.id}'),
label: Text(s.name),
avatar: s.userData.played
? const Icon(Icons.check, size: 16)
: null,
showCheckmark: false,
selected: s.id == seasonId,
onSelected: (_) => setState(() => _seasonId = s.id),
tooltip: s.userData.unplayedItemCount != null
? '${s.userData.unplayedItemCount} unwatched'
: null,
),
],
),
),
),
if (seasonId != null)
_EpisodeList(
key: ValueKey('episodes-$seasonId'),
seriesId: widget.series.id,
seasonId: seasonId,
version: scope.controller.version,
),
const SliverToBoxAdapter(child: SizedBox(height: 24)),
],
);
},
);
}
}
class _SeriesHeader extends StatefulWidget {
final _SeriesData data;
const _SeriesHeader({required this.data});
@override
State<_SeriesHeader> createState() => _SeriesHeaderState();
}
class _SeriesHeaderState extends State<_SeriesHeader> {
bool _more = false;
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
final theme = Theme.of(context);
final t = theme.textTheme;
final s = widget.data.series;
final next = widget.data.next;
final logo = s.image(JellyfinImageType.logo);
final meta = [
if (s.productionYear != null) '${s.productionYear}',
if (s.officialRating != null) s.officialRating!,
if (s.communityRating != null) '★ ${s.communityRating!.toStringAsFixed(1)}',
if (widget.data.seasons.isNotEmpty)
'${widget.data.seasons.length} ${widget.data.seasons.length == 1 ? 'season' : 'seasons'}',
if ((s.userData.unplayedItemCount ?? 0) > 0) '${s.userData.unplayedItemCount} unwatched',
].join(' · ');
final String nextLabel;
if (next == null) {
nextLabel = 'All watched';
} else if (next.userData.isResumable) {
nextLabel = 'Resume ${next.episodeLabel ?? next.name}';
} else {
nextLabel = 'Next up: ${next.episodeLabel ?? ''} ${next.name}'.trim();
}
final selectedNext = next != null && scope.selectedItemId == next.id;
return LayoutBuilder(builder: (context, box) {
final h = (box.maxWidth * 0.28).clamp(220.0, 380.0);
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(
height: h,
child: Stack(
fit: StackFit.expand,
children: [
JellyfinImage(
client: scope.client,
image: s.image(JellyfinImageType.backdrop),
alignment: Alignment.topCenter,
icon: Icons.live_tv_outlined,
),
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
theme.colorScheme.surface.withValues(alpha: 0.96),
theme.colorScheme.surface.withValues(alpha: 0.75),
theme.colorScheme.surface.withValues(alpha: 0.1),
],
stops: const [0, 0.45, 1],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Align(
alignment: Alignment.centerLeft,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 720),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (logo != null)
SizedBox(
height: 72,
width: 320,
child: JellyfinImage(
client: scope.client,
image: logo,
fit: BoxFit.contain,
alignment: Alignment.centerLeft,
placeholder: Align(
alignment: Alignment.centerLeft,
child: Text(s.name, style: t.headlineMedium)),
),
)
else
Text(s.name, style: t.headlineMedium),
const SizedBox(height: 8),
Text(meta, style: t.bodyMedium?.copyWith(color: Colors.grey)),
if (s.overview != null) ...[
const SizedBox(height: 8),
GestureDetector(
onTap: () => setState(() => _more = !_more),
child: Text(s.overview!,
maxLines: _more ? null : 3,
overflow: _more ? null : TextOverflow.ellipsis,
style: t.bodyMedium),
),
],
const SizedBox(height: 14),
Wrap(spacing: 8, runSpacing: 8, children: [
FilledButton.icon(
key: const ValueKey('series-next'),
onPressed: next == null ? null : () => scope.onSelect(next),
icon: Icon(selectedNext ? Icons.check : Icons.play_arrow),
label: Text(nextLabel, overflow: TextOverflow.ellipsis),
),
]),
],
),
),
),
),
),
],
),
),
);
});
}
}
/// Episodes of one season as a lazy grid of rows: one column, more past
/// ~760 px each.
class _EpisodeList extends StatefulWidget {
final String seriesId;
final String seasonId;
final int version;
const _EpisodeList(
{super.key, required this.seriesId, required this.seasonId, required this.version});
@override
State<_EpisodeList> createState() => _EpisodeListState();
}
class _EpisodeListState extends State<_EpisodeList> {
Future<List<JellyfinItem>>? _future;
List<JellyfinItem>? _last;
@override
void didUpdateWidget(_EpisodeList old) {
super.didUpdateWidget(old);
if (old.version != widget.version) _future = null;
}
void _load(BrowseScope scope) => _future = scope.load('episodes:${widget.seriesId}:${widget.seasonId}',
(c) => c.episodes(widget.seriesId, seasonId: widget.seasonId));
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
if (_future == null) _load(scope);
return FutureBuilder<List<JellyfinItem>>(
future: _future,
builder: (context, snap) {
if (snap.hasError) {
return SliverToBoxAdapter(
child: LoadError(error: snap.error!, onRetry: () => setState(() => _load(scope))));
}
final eps = snap.data ?? _last;
if (snap.data != null) _last = snap.data;
if (eps == null) {
return const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(32),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
),
);
}
if (eps.isEmpty) {
return const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('No episodes', style: TextStyle(color: Colors.grey)),
),
);
}
return SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 760,
mainAxisExtent: episodeTileHeight,
crossAxisSpacing: 12,
mainAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(context, i) => EpisodeTile(episode: eps[i]),
childCount: eps.length,
),
);
},
);
}
}
const episodeTileHeight = 6 + 176 * 9 / 16 + 6 + 2;
/// One episode: thumbnail with progress, number + title, runtime / air
/// date, two lines of overview. Click selects it (details panel), a second
/// click soon after puts it up.
class EpisodeTile extends StatefulWidget {
final JellyfinItem episode;
const EpisodeTile({super.key, required this.episode});
@override
State<EpisodeTile> createState() => _EpisodeTileState();
}
class _EpisodeTileState extends State<EpisodeTile> {
bool _focus = false;
bool _hover = false;
DateTime? _lastTap;
void _tap(BrowseScope scope) {
final now = DateTime.now();
final last = _lastTap;
_lastTap = now;
if (last != null && now.difference(last) < doubleClickTime) {
_lastTap = null;
scope.onPutUp(widget.episode);
} else {
scope.onSelect(widget.episode);
}
}
@override
Widget build(BuildContext context) {
final scope = BrowseScope.of(context);
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final t = theme.textTheme;
final e = widget.episode;
final selected = scope.selectedItemId == e.id;
final played = e.userData.played;
final progress = progressOf(e);
final meta = [
?e.episodeLabel,
?timeLeft(e),
if (timeLeft(e) == null && e.runTime != null) formatRuntime(e.runTime!),
if (e.premiereDate != null)
'${e.premiereDate!.year}-${e.premiereDate!.month.toString().padLeft(2, '0')}-${e.premiereDate!.day.toString().padLeft(2, '0')}',
].join(' · ');
return Material(
color: selected
? scheme.primaryContainer.withValues(alpha: 0.35)
: _hover || _focus
? scheme.surfaceContainerHigh
: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: selected ? scheme.primary : _focus ? scheme.onSurface : Colors.transparent,
width: 1.5),
),
clipBehavior: Clip.antiAlias,
child: InkWell(
key: ValueKey('episode-${e.id}'),
focusColor: Colors.transparent,
onTap: () => _tap(scope),
onSecondaryTap: () => scope.cardMenu(context, widget.episode),
onHover: (h) => setState(() => _hover = h),
onFocusChange: (f) => setState(() => _focus = f),
child: Padding(
padding: const EdgeInsets.all(6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 176,
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Stack(fit: StackFit.expand, children: [
JellyfinImage(
client: scope.client,
image: e.ownImage(JellyfinImageType.primary) ??
e.image(JellyfinImageType.thumb) ??
e.image(JellyfinImageType.backdrop),
icon: Icons.tv_outlined,
),
if (progress > 0)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: LinearProgressIndicator(
value: progress, minHeight: 3, backgroundColor: Colors.black54),
),
if (played || selected)
Positioned(
top: 4,
right: 4,
child: CircleAvatar(
radius: 10,
backgroundColor: selected ? scheme.primary : const Color(0xCC1B5E20),
child: Icon(Icons.check,
size: 13, color: selected ? scheme.onPrimary : Colors.white),
),
),
]),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
e.indexNumber == null ? e.name : '${e.indexNumber}. ${e.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: t.titleSmall?.copyWith(
color: selected ? scheme.primary : played ? Colors.grey : null,
),
),
Text(meta, style: t.bodySmall?.copyWith(color: Colors.grey)),
if (e.overview != null) ...[
const SizedBox(height: 4),
Text(e.overview!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: t.bodySmall?.copyWith(color: scheme.onSurfaceVariant)),
],
],
),
),
],
),
),
),
);
}
}
@@ -0,0 +1,355 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:sync_relay_core/core.dart';
import '../screens/home/picked_media.dart';
import 'accounts.dart';
import 'format.dart';
// Jellyfin media in a session: turning picks and pasted links into refs
// (the wire carries only `{serverId, itemId, ...}`), the hosted server's
// relay credential, and how this viewer plays a ref (own account, relay,
// or neither).
/// The wire ref of a browser pick.
JellyfinRef refForPick(JellyfinMedia m) => JellyfinRef(
serverId: m.serverId,
itemId: m.itemId,
mediaSourceId: m.mediaSourceId,
title: m.title,
subtitle: m.subtitle.isEmpty ? null : m.subtitle,
runtimeSecs: m.runtime == null ? null : m.runtime!.inMilliseconds / 1000,
);
/// The `swap` media for a browser pick: a ref, never a URL.
HelloMedia mediaForPick(JellyfinMedia m) => HelloMedia(
name: m.title.isEmpty ? 'Jellyfin item' : m.title,
size: 0,
duration: 0,
jellyfin: refForPick(m));
/// The pick a ref stands for on a linked server (for highlighting it in
/// the browser); null when this install has no account there.
JellyfinMedia? pickForRef(JellyfinAccounts accounts, JellyfinRef ref) {
final a = accounts.account(ref.serverId);
if (a == null) return null;
return JellyfinMedia(
serverId: ref.serverId,
baseUrl: a.baseUrl,
itemId: ref.itemId,
mediaSourceId: ref.mediaSourceId,
title: ref.title,
subtitle: ref.subtitle ?? '',
runtime: ref.runtimeSecs == null
? null
: Duration(milliseconds: (ref.runtimeSecs! * 1000).round()),
);
}
/// The ref in a `state.media` map, or null for other media.
JellyfinRef? refOfStateMedia(Map<String, Object?>? media) {
final jf = media?['jellyfin'];
if (jf is! Map) return null;
try {
final ref = JellyfinRef.fromJson(Map<String, Object?>.from(jf));
return ref.serverId.isEmpty || ref.itemId.isEmpty ? null : ref;
} catch (_) {
return null;
}
}
/// Same item (server, item, source); titles may differ.
bool sameJellyfinItem(JellyfinRef? a, JellyfinRef? b) =>
a != null &&
b != null &&
a.serverId == b.serverId &&
a.itemId == b.itemId &&
a.mediaSourceId == b.mediaSourceId;
final _sourceId = RegExp(r'^[A-Za-z0-9-]{1,64}$');
/// What a pasted link becomes on the wire. A recognised Jellyfin item link
/// (`parseJellyfinLink`) becomes a ref when its ServerId is known (in the
/// link or from `/System/Info/Public`); its `api_key` is never shared then.
/// Anything else, or a server that does not answer, stays a plain link.
/// The title comes from this install's account on that server when there
/// is one.
Future<HelloMedia> mediaForLink(JellyfinAccounts accounts, String url,
{Duration timeout = const Duration(seconds: 6)}) async {
final plain = HelloMedia(name: linkName(url), size: 0, duration: 0, url: url);
final link = parseJellyfinLink(url);
if (link == null) return plain;
await accounts.ensureLoaded();
var serverId = link.serverId;
if (serverId == null || serverId.isEmpty) {
final probe = accounts.anonymousClient(link.baseUrl);
try {
serverId = (await probe.publicInfo().timeout(timeout)).id;
} catch (e) {
debugPrint('jellyfin link: no server id from ${link.baseUrl}: $e');
return plain;
} finally {
probe.close();
}
if (serverId.isEmpty) return plain;
}
final src = Uri.tryParse(url)?.queryParameters['mediaSourceId'];
var ref = JellyfinRef(
serverId: serverId,
itemId: link.itemId,
mediaSourceId: src != null && _sourceId.hasMatch(src) && src != link.itemId ? src : null,
);
final account = accounts.account(serverId);
final client = accounts.clientFor(serverId);
if (account != null && client != null) {
try {
final pick = pickFor(account, await client.item(link.itemId).timeout(timeout));
ref = refForPick(JellyfinMedia(
serverId: serverId,
baseUrl: account.baseUrl,
itemId: link.itemId,
mediaSourceId: ref.mediaSourceId,
title: pick.title,
subtitle: pick.subtitle,
runtime: pick.runtime,
));
} catch (e) {
if (e is JellyfinException && e.isUnauthorized) accounts.markExpired(serverId);
}
}
return HelloMedia(
name: ref.title.isEmpty ? 'Jellyfin item' : ref.title, size: 0, duration: 0, jellyfin: ref);
}
/// Display name for a link: last path segment, decoded, never the query
/// (Jellyfin-style links carry api_key there and names are broadcast).
String linkName(String url) {
final uri = Uri.tryParse(url.trim());
if (uri == null) return 'link';
final segs = uri.pathSegments.where((s) => s.isNotEmpty).toList();
if (segs.isNotEmpty) {
try {
return Uri.decodeComponent(segs.last);
} catch (_) {
return segs.last;
}
}
return uri.host.isNotEmpty ? uri.host : 'link';
}
/// The hosted server's relay login: the account for [serverId] (the
/// hosted item's server), else the default account, when its token is not
/// known to be expired. The device fields are exactly the ones this app
/// sends with that token ([JellyfinAccounts.clientInfo]): Jellyfin rewrites
/// a token's device from the request header, so the relay must not differ.
JellyfinCredential? relayCredentialFor(JellyfinAccounts accounts, {String? serverId}) {
bool usable(JellyfinAccount? a) =>
a != null && accounts.statusOf(a.serverId) != JellyfinAccountStatus.expired;
var a = serverId == null ? null : accounts.account(serverId);
if (!usable(a)) a = accounts.defaultAccount;
if (!usable(a)) return null;
final info = accounts.clientInfo;
return JellyfinCredential(
baseUrl: a!.baseUrl,
serverId: a.serverId,
token: a.token,
client: info.client,
device: info.device,
deviceId: info.deviceId,
version: info.version,
);
}
/// `/j/...` URL of the current Jellyfin media on the host this client
/// dialled, built like the `/m/...` one.
String relayUrlFor(String host, int port, String token, String itemId) =>
'http://$host:$port/j/default/$token/${Uri.encodeComponent(itemId)}';
// ---------------------------------------------------------------- playing
/// How this viewer plays a ref.
sealed class JellyfinPlan {
const JellyfinPlan();
/// `report.jf` for this plan; null when it plays nothing.
bool? get jfAccess;
}
/// Direct play with this install's account: the static stream, the token
/// in [headers] (not in the URL), progress reported per [info].
final class OwnPlay extends JellyfinPlan {
final JellyfinClient client;
final JellyfinAccount account;
final JellyfinPlaybackInfo info;
final JellyfinMediaSource source;
/// Full item (series, episode numbers); null if that call failed.
final JellyfinItem? item;
final String url;
final Map<String, String> headers;
const OwnPlay({
required this.client,
required this.account,
required this.info,
required this.source,
required this.item,
required this.url,
required this.headers,
});
@override
bool get jfAccess => true;
}
/// Through the server: its own Jellyfin login (`/j/...`) or the member
/// who put it up (`/r/...`).
final class RelayPlay extends JellyfinPlan {
final String url;
const RelayPlay(this.url);
@override
bool get jfAccess => false;
}
/// Neither: no (usable) account here and nothing relays it.
final class BlockedPlay extends JellyfinPlan {
final String serverId;
/// Known only when this install has (had) an account there.
final String? serverName;
/// Why the own account did not work, if there is one.
final String? problem;
const BlockedPlay({required this.serverId, this.serverName, this.problem});
@override
bool? get jfAccess => null;
String get message {
final where = serverName == null || serverName!.isEmpty ? 'that Jellyfin server' : serverName!;
final base = 'No Jellyfin account here for $where, and nobody is passing it on';
return problem == null ? base : '$base ($problem)';
}
}
/// Picks how to play [ref]: this install's account first (PlaybackInfo
/// with its own user; a 401 marks the account expired), else the server's
/// own Jellyfin login ([relay] = `state.relay`, [relayUrl] this member's
/// `/j/...` URL), else the member who put it up ([memberRelayUrl], the
/// `/r/...` URL when `media.relay` is set), else [BlockedPlay].
Future<JellyfinPlan> resolveJellyfinRef(
JellyfinAccounts accounts,
JellyfinRef ref, {
required bool relay,
required String? relayUrl,
String? memberRelayUrl,
Duration timeout = const Duration(seconds: 15),
}) async {
await accounts.ensureLoaded();
final account = accounts.account(ref.serverId);
String? problem;
if (account != null && accounts.statusOf(ref.serverId) == JellyfinAccountStatus.expired) {
problem = 'your sign-in expired, reconnect it in Integrations';
} else if (account != null) {
final client = accounts.clientFor(ref.serverId)!;
try {
final infoF = client.playbackInfo(ref.itemId, mediaSourceId: ref.mediaSourceId).timeout(timeout);
final itemF = client
.item(ref.itemId)
.timeout(timeout)
.then<JellyfinItem?>((i) => i, onError: (Object _) => null);
final info = await infoF;
final item = await itemF;
final source = (ref.mediaSourceId == null
? null
: info.mediaSources.where((s) => s.id == ref.mediaSourceId).firstOrNull) ??
info.primarySource;
if (source == null) throw const JellyfinException('no playable source');
return OwnPlay(
client: client,
account: account,
info: info,
source: source,
item: item,
url: client.shareableStreamUrl(ref.itemId, mediaSourceId: source.id).toString(),
headers: client.authHeaders,
);
} catch (e) {
debugPrint('jellyfin resolve ${ref.itemId} with own account failed: $e');
if (e is JellyfinException && e.isUnauthorized) {
accounts.markExpired(ref.serverId);
problem = 'your sign-in expired, reconnect it in Integrations';
} else if (e is JellyfinException && e.statusCode == 404) {
problem = 'your account cannot see this item';
} else {
problem = 'could not reach it';
}
}
}
if (relay && relayUrl != null && relayUrl.isNotEmpty) return RelayPlay(relayUrl);
if (memberRelayUrl != null && memberRelayUrl.isNotEmpty) return RelayPlay(memberRelayUrl);
return BlockedPlay(serverId: ref.serverId, serverName: account?.displayName, problem: problem);
}
// ------------------------------------------------------------ next episode
/// A neighbouring episode as this viewer can put it up.
class EpisodeLink {
final JellyfinItem item;
final JellyfinMedia pick;
const EpisodeLink(this.item, this.pick);
/// "S1E7 Battle with the Heroes".
String get label => [?item.episodeLabel, item.name].join(' ');
}
/// The episodes before and after the one [plan] plays, with its account.
/// Both null for movies, at the ends of a series, or on failure.
Future<({EpisodeLink? previous, EpisodeLink? next})> findAdjacentEpisodes(OwnPlay plan,
{Duration timeout = const Duration(seconds: 15)}) async {
const none = (previous: null, next: null);
final item = plan.item;
if (item == null || item.kind != JellyfinItemKind.episode || item.seriesId == null) return none;
try {
final around = await plan.client.adjacentEpisodes(item).timeout(timeout);
EpisodeLink? link(JellyfinItem? i) => i == null ? null : EpisodeLink(i, pickFor(plan.account, i));
return (previous: link(around.previous), next: link(around.next));
} catch (e) {
debugPrint('episode lookup failed: $e');
return none;
}
}
/// Where the "Next episode" pill's window starts: at [endingStart] (the
/// ED: Jellyfin Outro segment or ED-titled chapter, see `endingRange`)
/// when one is known inside [runtime], else the last two minutes of
/// [runtime] (all of it for shorter media). The pill shows only while the
/// controls are up (`PillGate`), so a guessed window costs nothing.
Duration nextPromptStart(JellyfinItem? item, Duration runtime, {Duration? endingStart}) {
if (endingStart != null && endingStart > Duration.zero && endingStart < runtime) {
return endingStart;
}
const lead = Duration(minutes: 2);
return runtime > lead ? runtime - lead : Duration.zero;
}
/// Who performs the auto-advance swap, so several counting clients do not
/// swap twice: the lowest member id among members that play the item with
/// their own account (`jf[serverId] == true`, not stale, not out). Only what every
/// client sees in `state` decides (who put the item up is not in it: a
/// late joiner never saw that swap), so all clients agree.
String? autoAdvanceSwapper(List<MemberView> members, String serverId) {
final eligible = [
for (final m in members)
if (!m.stale && !m.out && m.jf[serverId] == true) m
];
if (eligible.isEmpty) return null;
eligible.sort((a, b) => compareMemberIds(a.id, b.id));
return eligible.first.id;
}
/// "m2" before "m10": numeric suffix order, then plain text.
int compareMemberIds(String a, String b) {
final na = int.tryParse(a.replaceFirst(RegExp(r'^\D+'), ''));
final nb = int.tryParse(b.replaceFirst(RegExp(r'^\D+'), ''));
if (na != null && nb != null && na != nb) return na.compareTo(nb);
return a.compareTo(b);
}
+182
View File
@@ -0,0 +1,182 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:sync_relay_core/core.dart';
import 'accounts.dart';
enum JellyfinSignInStep {
/// Typing the server address.
address,
probing,
/// Server found: choose Quick Connect or password.
method,
/// Code shown, polling until the user approves it in Jellyfin.
quickConnect,
password,
signingIn,
/// Linked; [JellyfinSignIn.linked] is the account.
done,
}
/// Add-server / reconnect flow: address -> /System/Info/Public -> Quick
/// Connect (code + poll, cancellable) or username/password -> linked in
/// [JellyfinAccounts]. UI-free so tests drive it with a fake transport.
class JellyfinSignIn extends ChangeNotifier {
JellyfinSignIn(this.accounts, {this.pollInterval = const Duration(seconds: 3), this.reconnect});
final JellyfinAccounts accounts;
final Duration pollInterval;
/// Set when re-signing an expired account: the address is prefilled.
final JellyfinAccount? reconnect;
JellyfinSignInStep _step = JellyfinSignInStep.address;
String? _error;
JellyfinServerInfo? _server;
bool _quickConnectAvailable = false;
String? _code;
JellyfinAccount? _linked;
JellyfinClient? _client;
int _gen = 0;
bool _disposed = false;
JellyfinSignInStep get step => _step;
String? get error => _error;
JellyfinServerInfo? get server => _server;
String? get baseUrl => _client?.baseUrl;
bool get quickConnectAvailable => _quickConnectAvailable;
/// The Quick Connect code to show.
String? get code => _code;
JellyfinAccount? get linked => _linked;
bool get busy =>
_step == JellyfinSignInStep.probing || _step == JellyfinSignInStep.signingIn;
void _set(JellyfinSignInStep s, {String? error}) {
_step = s;
_error = error;
if (!_disposed) notifyListeners();
}
/// Checks [input] is a Jellyfin server and whether Quick Connect is on.
Future<void> probe(String input) async {
final url = normalizeServerUrl(input);
if (url == null) {
_set(JellyfinSignInStep.address, error: 'Enter the server address, e.g. jellyfin.example.com');
return;
}
final gen = ++_gen;
_client?.close();
final client = _client = accounts.anonymousClient(url);
_set(JellyfinSignInStep.probing);
try {
final info = await client.publicInfo();
if (gen != _gen) return;
if (info.id.isEmpty) throw const JellyfinException('not a Jellyfin server');
if (reconnect != null && info.id != reconnect!.serverId) {
_set(JellyfinSignInStep.address,
error: 'That address is a different Jellyfin server (${info.name}).');
return;
}
var qc = false;
try {
qc = await client.quickConnectEnabled();
} catch (_) {}
if (gen != _gen) return;
_server = info;
_quickConnectAvailable = qc;
_set(JellyfinSignInStep.method);
} catch (e) {
if (gen != _gen) return;
_set(JellyfinSignInStep.address, error: 'No Jellyfin server at $url (${_short(e)})');
}
}
/// Starts Quick Connect: shows the code, polls until approved, links.
Future<void> startQuickConnect() async {
final client = _client;
if (client == null || _server == null) return;
final gen = ++_gen;
_code = null;
_set(JellyfinSignInStep.quickConnect);
try {
final session = await client.quickConnectInitiate();
if (gen != _gen) return;
_code = session.code;
_set(JellyfinSignInStep.quickConnect);
final auth = await client.waitForQuickConnect(session,
interval: pollInterval, isCancelled: () => gen != _gen || _disposed);
if (auth == null || gen != _gen) return;
await _finish(auth);
} on TimeoutException {
if (gen == _gen) _set(JellyfinSignInStep.method, error: 'The code expired. Try again.');
} catch (e) {
if (gen != _gen) return;
final msg = e is JellyfinException && e.isUnauthorized
? 'Quick Connect is turned off on this server. Use your password.'
: e is JellyfinException && e.statusCode == 404
? 'The code expired. Try again.'
: 'Quick Connect failed: ${_short(e)}';
_set(JellyfinSignInStep.method, error: msg);
}
}
/// Stops waiting for the Quick Connect code.
void cancel() {
_gen++;
_code = null;
_set(_server == null ? JellyfinSignInStep.address : JellyfinSignInStep.method);
}
void usePassword() => _set(JellyfinSignInStep.password);
/// Back to the address field.
void changeAddress() {
_gen++;
_server = null;
_code = null;
_set(JellyfinSignInStep.address);
}
Future<void> signIn(String username, String password) async {
final client = _client;
if (client == null) return;
if (username.trim().isEmpty) {
_set(JellyfinSignInStep.password, error: 'Enter your Jellyfin username');
return;
}
final gen = ++_gen;
_set(JellyfinSignInStep.signingIn);
try {
final auth = await client.authenticateByName(username.trim(), password);
if (gen != _gen) return;
await _finish(auth);
} catch (e) {
if (gen != _gen) return;
_set(JellyfinSignInStep.password,
error: e is JellyfinException && e.isUnauthorized
? 'Wrong username or password'
: 'Sign-in failed: ${_short(e)}');
}
}
Future<void> _finish(JellyfinAuthResult auth) async {
final server = _server!;
if (auth.accessToken.isEmpty) throw const JellyfinException('no token in the answer');
_linked = await accounts.link(baseUrl: _client!.baseUrl, server: server, auth: auth);
_code = null;
_set(JellyfinSignInStep.done);
}
static String _short(Object e) {
final s = '$e'.replaceFirst(RegExp(r'^JellyfinException( \d+)?: '), '');
return s.length > 140 ? '${s.substring(0, 140)}…' : s;
}
@override
void dispose() {
_disposed = true;
_gen++;
_client?.close();
super.dispose();
}
}
@@ -0,0 +1,306 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../screens/home/ui.dart';
import 'accounts.dart';
import 'sign_in.dart';
/// Add a Jellyfin server (or reconnect an expired one): address, then Quick
/// Connect (big code, approve it in Jellyfin) or username and password.
class JellyfinSignInPanel extends StatefulWidget {
final JellyfinAccounts accounts;
final JellyfinAccount? reconnect;
final ValueChanged<JellyfinAccount>? onDone;
final VoidCallback? onCancel;
final Duration pollInterval;
const JellyfinSignInPanel({
super.key,
required this.accounts,
this.reconnect,
this.onDone,
this.onCancel,
this.pollInterval = const Duration(seconds: 3),
});
@override
State<JellyfinSignInPanel> createState() => _JellyfinSignInPanelState();
}
class _JellyfinSignInPanelState extends State<JellyfinSignInPanel> {
late final JellyfinSignIn _flow =
JellyfinSignIn(widget.accounts, pollInterval: widget.pollInterval, reconnect: widget.reconnect);
late final _address = TextEditingController(text: widget.reconnect?.baseUrl ?? '');
late final _user = TextEditingController(text: widget.reconnect?.userName ?? '');
final _password = TextEditingController();
bool _reported = false;
@override
void initState() {
super.initState();
_flow.addListener(_changed);
if (widget.reconnect != null) _flow.probe(widget.reconnect!.baseUrl);
}
@override
void dispose() {
_flow.removeListener(_changed);
_flow.dispose();
_address.dispose();
_user.dispose();
_password.dispose();
super.dispose();
}
void _changed() {
if (!mounted) return;
setState(() {});
final linked = _flow.linked;
if (_flow.step == JellyfinSignInStep.done && linked != null && !_reported) {
_reported = true;
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onDone?.call(linked));
}
}
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final f = _flow;
final body = switch (f.step) {
JellyfinSignInStep.address || JellyfinSignInStep.probing => _addressStep(),
JellyfinSignInStep.method => _methodStep(),
JellyfinSignInStep.quickConnect => _quickConnectStep(),
JellyfinSignInStep.password || JellyfinSignInStep.signingIn => _passwordStep(),
JellyfinSignInStep.done => Row(children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text('Linked as ${f.linked?.userName} on ${f.linked?.displayName}',
key: const ValueKey('jf-linked'))),
]),
};
return Column(
key: const ValueKey('jf-sign-in'),
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.reconnect == null ? 'Add a Jellyfin server' : 'Reconnect ${widget.reconnect!.displayName}',
style: t.titleMedium),
const SizedBox(height: 12),
if (f.server != null && f.step != JellyfinSignInStep.address && f.step != JellyfinSignInStep.done)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _ServerLine(
name: f.server!.name,
version: f.server!.version,
url: f.baseUrl ?? '',
onChange: widget.reconnect == null ? f.changeAddress : null,
),
),
body,
if (f.error != null)
Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(f.error!,
key: const ValueKey('jf-sign-in-error'),
style: TextStyle(color: Theme.of(context).colorScheme.error)),
),
],
);
}
Widget _cancelButton() => widget.onCancel == null
? const SizedBox.shrink()
: TextButton(onPressed: widget.onCancel, child: const Text('Cancel'));
Widget _addressStep() {
final probing = _flow.step == JellyfinSignInStep.probing;
return Row(
children: [
Expanded(
child: TextField(
key: const ValueKey('jf-address'),
controller: _address,
autofocus: true,
enabled: !probing,
decoration: fieldDecoration('Server address', hint: 'jellyfin.example.com'),
keyboardType: TextInputType.url,
onSubmitted: (v) => _flow.probe(v),
),
),
const SizedBox(width: 12),
FilledButton(
key: const ValueKey('jf-connect'),
onPressed: probing ? null : () => _flow.probe(_address.text),
child: probing
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Connect'),
),
_cancelButton(),
],
);
}
Widget _methodStep() {
return Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
if (_flow.quickConnectAvailable)
FilledButton.icon(
key: const ValueKey('jf-quick-connect'),
onPressed: _flow.startQuickConnect,
icon: const Icon(Icons.qr_code_2, size: 18),
label: const Text('Quick Connect'),
),
if (_flow.quickConnectAvailable)
OutlinedButton.icon(
key: const ValueKey('jf-use-password'),
onPressed: _flow.usePassword,
icon: const Icon(Icons.password, size: 18),
label: const Text('Username & password'),
)
else
FilledButton.icon(
key: const ValueKey('jf-use-password'),
onPressed: _flow.usePassword,
icon: const Icon(Icons.password, size: 18),
label: const Text('Username & password'),
),
_cancelButton(),
],
);
}
Widget _quickConnectStep() {
final scheme = Theme.of(context).colorScheme;
final t = Theme.of(context).textTheme;
final code = _flow.code;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (code == null)
const Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(strokeWidth: 2),
)
else
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
),
child: SelectableText(
code,
key: const ValueKey('jf-qc-code'),
style: t.displaySmall?.copyWith(
fontFeatures: const [FontFeature.tabularFigures()],
letterSpacing: 8,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8),
IconButton(
tooltip: 'Copy code',
onPressed: () => Clipboard.setData(ClipboardData(text: code)),
icon: const Icon(Icons.copy, size: 18),
),
],
),
const SizedBox(height: 12),
const Text('Approve it in Jellyfin: Profile → Quick Connect, then enter this code.'),
const SizedBox(height: 12),
Row(
children: [
const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2)),
const SizedBox(width: 10),
const Text('Waiting for approval…', style: TextStyle(color: Colors.grey)),
const SizedBox(width: 16),
TextButton(
key: const ValueKey('jf-qc-cancel'),
onPressed: _flow.cancel,
child: const Text('Cancel'),
),
],
),
],
);
}
Widget _passwordStep() {
final busy = _flow.step == JellyfinSignInStep.signingIn;
void submit() => _flow.signIn(_user.text, _password.text);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
key: const ValueKey('jf-username'),
controller: _user,
autofocus: true,
enabled: !busy,
decoration: fieldDecoration('Username'),
onSubmitted: (_) => submit(),
),
const SizedBox(height: 10),
TextField(
key: const ValueKey('jf-password'),
controller: _password,
enabled: !busy,
obscureText: true,
decoration: fieldDecoration('Password'),
onSubmitted: (_) => submit(),
),
const SizedBox(height: 12),
Row(
children: [
FilledButton(
key: const ValueKey('jf-sign-in-submit'),
onPressed: busy ? null : submit,
child: busy
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Sign in'),
),
const SizedBox(width: 8),
if (_flow.quickConnectAvailable)
TextButton(onPressed: _flow.cancel, child: const Text('Back')),
_cancelButton(),
],
),
],
);
}
}
class _ServerLine extends StatelessWidget {
final String name;
final String version;
final String url;
final VoidCallback? onChange;
const _ServerLine({required this.name, required this.version, required this.url, this.onChange});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return Row(
children: [
Icon(Icons.dns_outlined, size: 20, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name.isEmpty ? 'Jellyfin server' : name,
key: const ValueKey('jf-server-name'), style: t.titleSmall),
Text('Jellyfin $version · $url', style: t.bodySmall?.copyWith(color: Colors.grey)),
],
),
),
if (onChange != null) TextButton(onPressed: onChange, child: const Text('Change')),
],
);
}
}
@@ -0,0 +1,274 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:sync_relay_core/core.dart';
import 'accounts.dart';
// Per-viewer audio/subtitle carry-over: the last choice is kept per series
// (and as a global fallback) and matched against the next Jellyfin item's
// tracks with core's `matchTrack`. Local only; nothing goes on the wire.
const trackPrefsBoxName = 'track_prefs';
const _keyGlobal = 'global';
/// One audio or subtitle track of the open file as mpv's `track-list`
/// has it ([id] is mpv's `aid` / `sid`; [selected] is mpv's choice).
class MpvTrack {
final int id;
final JellyfinStreamType type;
final String? language;
final String? title;
final String? codec;
final int? channels;
final bool isDefault;
final bool isForced;
final bool isHearingImpaired;
final bool isExternal;
final bool selected;
const MpvTrack({
required this.id,
required this.type,
this.language,
this.title,
this.codec,
this.channels,
this.isDefault = false,
this.isForced = false,
this.isHearingImpaired = false,
this.isExternal = false,
this.selected = false,
});
/// As a stream for [matchTrack]; its index is the mpv id.
JellyfinMediaStream get asStream => JellyfinMediaStream(
index: id,
type: type,
language: language,
title: title,
codec: codec,
channels: channels,
isDefault: isDefault,
isForced: isForced,
isHearingImpaired: isHearingImpaired);
/// Menu text: "Japanese · E-AC3 2.0 · Commentary", "Track 2 · ASS".
String get label => [
languageName(language) ?? 'Track $id',
[?codecName(codec), ?channelLayoutName(channels)].join(' '),
if (title != null && title != languageName(language)) title!,
if (isForced) 'Forced',
if (isHearingImpaired) 'SDH',
].where((p) => p.isNotEmpty).join(' · ');
@override
bool operator ==(Object other) =>
other is MpvTrack &&
other.id == id &&
other.type == type &&
other.language == language &&
other.title == title &&
other.codec == codec &&
other.channels == channels &&
other.isDefault == isDefault &&
other.isForced == isForced &&
other.isHearingImpaired == isHearingImpaired &&
other.isExternal == isExternal &&
other.selected == selected;
@override
int get hashCode => Object.hash(id, type, language, title, codec, channels, isDefault, isForced,
isHearingImpaired, isExternal, selected);
@override
String toString() => 'MpvTrack(${type.name} $id ${selected ? '*' : ''}$label)';
}
const _languages = {
'jpn': 'Japanese', 'ja': 'Japanese', 'eng': 'English', 'en': 'English',
'ger': 'German', 'deu': 'German', 'de': 'German', 'fre': 'French', 'fra': 'French',
'fr': 'French', 'spa': 'Spanish', 'es': 'Spanish', 'ita': 'Italian', 'it': 'Italian',
'por': 'Portuguese', 'pt': 'Portuguese', 'rus': 'Russian', 'ru': 'Russian',
'chi': 'Chinese', 'zho': 'Chinese', 'zh': 'Chinese', 'kor': 'Korean', 'ko': 'Korean',
'ara': 'Arabic', 'ar': 'Arabic', 'pol': 'Polish', 'pl': 'Polish', 'dut': 'Dutch',
'nld': 'Dutch', 'nl': 'Dutch', 'swe': 'Swedish', 'sv': 'Swedish', 'fin': 'Finnish',
'fi': 'Finnish', 'nor': 'Norwegian', 'no': 'Norwegian', 'dan': 'Danish', 'da': 'Danish',
'tur': 'Turkish', 'tr': 'Turkish', 'hin': 'Hindi', 'hi': 'Hindi', 'tha': 'Thai',
'th': 'Thai', 'vie': 'Vietnamese', 'vi': 'Vietnamese', 'ind': 'Indonesian', 'id': 'Indonesian',
'may': 'Malay', 'msa': 'Malay', 'ms': 'Malay', 'heb': 'Hebrew', 'he': 'Hebrew',
'gre': 'Greek', 'ell': 'Greek', 'el': 'Greek', 'cze': 'Czech', 'ces': 'Czech', 'cs': 'Czech',
'hun': 'Hungarian', 'hu': 'Hungarian', 'ukr': 'Ukrainian', 'uk': 'Ukrainian',
'rum': 'Romanian', 'ron': 'Romanian', 'ro': 'Romanian', 'und': 'Undetermined',
};
/// "jpn" -> "Japanese"; other codes upper-cased, null for none.
String? languageName(String? code) {
if (code == null || code.isEmpty) return null;
return _languages[code.toLowerCase()] ?? code.toUpperCase();
}
const _codecs = {
'eac3': 'E-AC3', 'ac3': 'AC3', 'aac': 'AAC', 'flac': 'FLAC', 'opus': 'Opus',
'vorbis': 'Vorbis', 'mp3': 'MP3', 'truehd': 'TrueHD', 'dts': 'DTS', 'mlp': 'MLP',
'alac': 'ALAC', 'ass': 'ASS', 'ssa': 'SSA', 'subrip': 'SRT', 'srt': 'SRT',
'webvtt': 'VTT', 'mov_text': 'TX3G', 'hdmv_pgs_subtitle': 'PGS', 'pgssub': 'PGS',
'dvd_subtitle': 'VobSub', 'dvdsub': 'VobSub', 'dvb_subtitle': 'DVB',
};
/// mpv / Jellyfin codec name for menus: "eac3" -> "E-AC3", "subrip" -> "SRT".
String? codecName(String? codec) {
if (codec == null || codec.isEmpty) return null;
final c = codec.toLowerCase();
if (c.startsWith('pcm')) return 'PCM';
return _codecs[c] ?? codec.toUpperCase();
}
/// Channel count as a layout: 2 -> "2.0", 6 -> "5.1", 8 -> "7.1".
String? channelLayoutName(int? channels) => switch (channels) {
null || <= 0 => null,
1 => '1.0',
2 => '2.0',
3 => '2.1',
6 => '5.1',
7 => '6.1',
8 => '7.1',
final n => '${n}ch',
};
/// A viewer's audio/subtitle choice as traits (indices shift between
/// files). [subtitlesOff] wins over [subtitle].
class TrackChoice {
final TrackPreference? audio;
final TrackPreference? subtitle;
final bool subtitlesOff;
const TrackChoice({this.audio, this.subtitle, this.subtitlesOff = false});
Map<String, Object?> toJson() => {
'audio': ?_prefToJson(audio),
'subtitle': ?_prefToJson(subtitle),
'subtitlesOff': subtitlesOff,
};
static TrackChoice? fromJson(Object? j) {
if (j is! Map) return null;
return TrackChoice(
audio: _prefFromJson(j['audio'], JellyfinStreamType.audio),
subtitle: _prefFromJson(j['subtitle'], JellyfinStreamType.subtitle),
subtitlesOff: j['subtitlesOff'] == true,
);
}
/// The choice behind mpv's [aid] / [sid] (null = none / off): traits from
/// the Jellyfin [source] when this viewer has it (title, channels,
/// forced/HI flags), else from mpv's [tracks].
factory TrackChoice.of({
required int? aid,
required int? sid,
JellyfinMediaSource? source,
List<MpvTrack> tracks = const [],
}) {
TrackPreference? pref(JellyfinStreamType type, int? id) {
if (id == null) return null;
if (source != null) {
final index = source.jellyfinIndexForMpvTrack(type, id);
final s = index == null ? null : source.streamAt(index);
if (s != null) return TrackPreference.fromStream(s);
}
final t = tracks.where((t) => t.type == type && t.id == id).firstOrNull;
return t == null ? null : TrackPreference.fromStream(t.asStream);
}
return TrackChoice(
audio: pref(JellyfinStreamType.audio, aid),
subtitle: pref(JellyfinStreamType.subtitle, sid),
subtitlesOff: sid == null,
);
}
}
Map<String, Object?>? _prefToJson(TrackPreference? p) => p == null
? null
: {
'language': ?p.language,
'title': ?p.title,
'codec': ?p.codec,
'channels': ?p.channels,
if (p.isForced) 'forced': true,
if (p.isHearingImpaired) 'hi': true,
};
TrackPreference? _prefFromJson(Object? j, JellyfinStreamType type) {
if (j is! Map) return null;
String? s(Object? v) => v is String && v.isNotEmpty ? v : null;
return TrackPreference(
type: type,
language: s(j['language']),
title: s(j['title']),
codec: s(j['codec']),
channels: j['channels'] is int ? j['channels'] as int : null,
isForced: j['forced'] == true,
isHearingImpaired: j['hi'] == true,
);
}
/// mpv tracks to select for [choice] on a new item. Matches against the
/// Jellyfin [source]'s streams when this viewer has them (mapped to mpv ids
/// with [JellyfinMediaSource.mpvTrackId]), else against mpv's [tracks].
/// Null fields keep the player's default; [subtitlesOff] turns them off.
({int? aid, int? sid, bool subtitlesOff}) chooseTracks(TrackChoice choice,
{JellyfinMediaSource? source, List<MpvTrack> tracks = const []}) {
int? pick(TrackPreference? pref) {
if (pref == null) return null;
if (source != null) {
final s = matchTrack(pref, source.mediaStreams);
return s == null ? null : source.mpvTrackId(s.index);
}
return matchTrack(pref, [for (final t in tracks) t.asStream])?.index;
}
return (
aid: pick(choice.audio),
sid: choice.subtitlesOff ? null : pick(choice.subtitle),
subtitlesOff: choice.subtitlesOff,
);
}
/// The last choice per series and overall, in their own Hive box.
class TrackPrefs {
TrackPrefs({JellyfinStore? store}) : _store = store ?? HiveJellyfinStore(boxName: trackPrefsBoxName);
final JellyfinStore _store;
Future<void>? _open;
Future<void> _ready() => _open ??= _store.open().catchError((Object e) {
debugPrint('open track prefs failed: $e');
});
/// The series' choice, else the global one.
Future<TrackChoice?> load({String? seriesId}) async {
await _ready();
try {
Object? read(String key) {
final v = _store.get(key);
return v is String ? jsonDecode(v) : null;
}
return (seriesId == null ? null : TrackChoice.fromJson(read('series:$seriesId'))) ??
TrackChoice.fromJson(read(_keyGlobal));
} catch (e) {
debugPrint('read track prefs failed: $e');
return null;
}
}
/// Saves [choice] as the global one and, with [seriesId], the series'.
Future<void> save(TrackChoice choice, {String? seriesId}) async {
await _ready();
try {
final v = jsonEncode(choice.toJson());
await _store.put(_keyGlobal, v);
if (seriesId != null) await _store.put('series:$seriesId', v);
} catch (e) {
debugPrint('save track prefs failed: $e');
}
}
}
+107
View File
@@ -0,0 +1,107 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sync_relay_core/core.dart';
import '../playback/yt_dlp_app.dart';
import '../screens/home/ui.dart' show appName, displayVersion;
import '../state/session_state.dart';
// The app's log on disk (<app support>/logs/app-YYYY-MM-DD.log; core's
// logging.dart writes, rotates and redacts it), debugPrint routed into it,
// and the "Copy diagnostics" text of Settings → About.
final _appLog = createLogger('app');
/// The logs folder, once [startAppLogging] ran.
String? appLogDir;
/// Opens the log files and routes `debugPrint` and uncaught errors into
/// the log. Needs Flutter's binding (path_provider).
Future<void> startAppLogging() async {
try {
final base = await getApplicationSupportDirectory();
final dir = '${base.path}${Platform.pathSeparator}logs';
if (startLogFiles(dir) != null) appLogDir = dir;
} catch (e) {
_appLog.warning('no log files: $e');
}
// Everything the app prints for diagnosis lands in the file too (the
// log's console output replaces the print).
debugPrint = (String? message, {int? wrapWidth}) {
if (message == null || message.isEmpty) return;
final bad = RegExp(r'fail|error|crash|exception', caseSensitive: false).hasMatch(message);
bad ? _appLog.warning(message) : _appLog.info(message);
};
PlatformDispatcher.instance.onError = (error, stack) {
_appLog.severe('uncaught: $error', error, stack);
return false;
};
_appLog.info('$appName ${displayVersion()} on ${Platform.operatingSystem} '
'${Platform.operatingSystemVersion}, pid $pid, Dart ${Platform.version.split(' ').first}');
}
/// The text "Copy diagnostics" gives: versions, then the last [since] of
/// the app log (its hosted server's records included), already redacted;
/// with [sessionLog], the server's session log of the same hours after it
/// (every member's shared lines and the server's, merged).
Future<String> diagnosticsText({Duration since = const Duration(hours: 6), bool sessionLog = false}) async {
final b = StringBuffer()
..writeln('$appName ${displayVersion()}')
..writeln('OS: ${Platform.operatingSystem} ${Platform.operatingSystemVersion}')
..writeln('Dart: ${Platform.version.split(' ').first}');
try {
final mpv = await SessionState.instance.coordinator.playback.mpvVersion().timeout(const Duration(seconds: 2));
b.writeln('mpv: ${mpv ?? 'no player yet'}');
} catch (_) {
b.writeln('mpv: unknown');
}
try {
final y = await AppYtDlp.instance.ytDlp.timeout(const Duration(seconds: 2));
final cookies = y.cookies.read(count: false);
b.writeln('yt-dlp: ${y.version ?? 'not ready'}${y.lastError == null ? '' : ' (${y.lastError})'}, '
'cookies: ${cookies == null ? 'none' : 'set'}');
} catch (_) {
b.writeln('yt-dlp: unknown');
}
final files = logFiles;
b.writeln('Log: ${files?.currentPath ?? 'none (console only)'}');
b.writeln('--- last ${since.inHours} h of the log ---');
b.write(files?.recent(since) ?? '');
if (sessionLog) {
final server = await SessionState.instance.coordinator.sessionLog(hours: since.inHours);
b
..writeln()
..writeln("--- last ${since.inHours} h of the server's session log ---")
..write(server ?? "(none: the server keeps no session log, or it couldn't be reached)");
}
return redactLog(b.toString());
}
/// Saves [text] next to the logs as `diagnostics-<time>.txt`; its path,
/// or null when it could not be written.
Future<String?> saveDiagnostics(String text) async {
final dir = appLogDir;
if (dir == null) return null;
final t = DateTime.now();
String two(int n) => n.toString().padLeft(2, '0');
final path = '$dir${Platform.pathSeparator}diagnostics-${t.year}${two(t.month)}${two(t.day)}-'
'${two(t.hour)}${two(t.minute)}${two(t.second)}.txt';
try {
// Older ones go after the log files' week.
for (final e in Directory(dir).listSync()) {
if (e is File &&
e.uri.pathSegments.last.startsWith('diagnostics-') &&
t.difference(e.statSync().modified) > const Duration(days: 7)) {
e.deleteSync();
}
}
await File(path).writeAsString(text);
return path;
} catch (e) {
_appLog.warning('cannot save diagnostics: $e');
return null;
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
/// The runner's window channel (linux/runner/my_application.cc,
/// windows/runner/flutter_window.cpp).
const _window = MethodChannel('starsync/window');
/// Asks the desktop to point at the app's window (taskbar flash, urgency
/// hint) when it is not focused; nothing when it is. The runner clears it
/// once the window gets focus. Quietly nothing where the runner lacks it.
Future<void> requestWindowAttention() async {
try {
await _window.invokeMethod<void>('attention');
} on MissingPluginException {
// Tests, other platforms.
} catch (e) {
debugPrint('window attention: $e');
}
}
@@ -0,0 +1,53 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:sync_relay_core/core.dart' show findInstallRoot;
/// The app's id: GTK's application id, so the Wayland `app_id` the
/// compositor matches to [desktopFileName] for the window's icon.
const appId = 'moe.senkochan.watch_together';
const desktopFileName = '$appId.desktop';
/// Linux release installs (an unpacked tarball) carry no launcher entry, so
/// Wayland shows a generic icon. On start: write the `.desktop` file into
/// `~/.local/share/applications` (Exec = this binary) and the icon into
/// the hicolor theme, only when something changed (a moved install, a new
/// icon). Never in debug runs; failures only log.
Future<void> installDesktopEntry({String? executable, String? dataHome, String? home}) async {
if (!Platform.isLinux || kDebugMode) return;
try {
final exe = executable ?? Platform.resolvedExecutable;
final root = findInstallRoot(exe, windows: false);
if (root == null) return;
final share = dataHome ?? Platform.environment['XDG_DATA_HOME'] ??
'${home ?? Platform.environment['HOME']}/.local/share';
final icons = '${root.path}/data/flutter_assets/assets/icon';
for (final size in const [16, 32, 48, 64, 128, 256, 512]) {
final src = File('$icons/icon_$size.png');
if (!src.existsSync()) continue;
final dst = File('$share/icons/hicolor/${size}x$size/apps/$appId.png');
if (dst.existsSync() && dst.lengthSync() == src.lengthSync()) continue;
await dst.parent.create(recursive: true);
await src.copy(dst.path);
}
final entry = desktopEntryText(exe);
final file = File('$share/applications/$desktopFileName');
if (file.existsSync() && file.readAsStringSync() == entry) return;
await file.parent.create(recursive: true);
await file.writeAsString(entry);
} catch (e) {
debugPrint('desktop entry: $e');
}
}
@visibleForTesting
String desktopEntryText(String exe) => '''[Desktop Entry]
Type=Application
Name=Starsync
Comment=Watch videos in sync with friends
Exec="$exe"
Icon=$appId
StartupWMClass=$appId
Categories=AudioVideo;Video;Player;Network;
Terminal=false
''';
@@ -0,0 +1,106 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:screen_retriever/screen_retriever.dart';
import 'package:window_manager/window_manager.dart';
/// The window's size, position and maximized state across restarts (and
/// hot restarts), in the settings box. The size and position kept are
/// the normal (unmaximized) ones; fullscreen is never saved or restored.
/// Wayland lets no app place its own window, so there only the size and
/// maximized state come back (the compositor decides the position).
class WindowMemory with WindowListener {
static const _key = 'windowBounds';
static const defaultSize = Size(1280, 720);
// Null when the settings box failed to open: defaults, nothing saved.
final Box? _box;
Timer? _save;
WindowMemory(this._box);
static bool get _canPlace => !(Platform.isLinux && Platform.environment['WAYLAND_DISPLAY'] != null);
Map? get _saved => _box?.get(_key) is Map ? _box!.get(_key) as Map : null;
/// The size to open at: the last normal size, else [defaultSize].
Size get size {
final s = _saved;
final w = s?['w'], h = s?['h'];
return w is num && h is num && w >= 160 && h >= 90 ? Size(w.toDouble(), h.toDouble()) : defaultSize;
}
/// Whether a saved position exists to place the window at (else center).
bool get hasPosition => _canPlace && _saved?['x'] is num && _saved?['y'] is num;
/// After the window shows: back to the saved position when it still
/// lands on a connected display, maximized when it was; then listens.
Future<void> restore() async {
final s = _saved;
try {
if (s != null && hasPosition) {
final rect = Offset((s['x'] as num).toDouble(), (s['y'] as num).toDouble()) & size;
if (await _onSomeDisplay(rect)) {
await windowManager.setPosition(rect.topLeft);
} else {
await windowManager.center();
}
}
if (s?['max'] == true) await windowManager.maximize();
} catch (e) {
debugPrint('window restore failed: $e');
}
windowManager.addListener(this);
}
// At least a 100×50 corner of the title area on a display's visible area.
static Future<bool> _onSomeDisplay(Rect r) async {
final grip = Rect.fromLTWH(r.left, r.top, r.width.clamp(0, 100).toDouble(), 50);
for (final d in await screenRetriever.getAllDisplays()) {
final area = (d.visiblePosition ?? Offset.zero) & (d.visibleSize ?? d.size);
if (area.overlaps(grip)) return true;
}
return false;
}
void _later() {
_save?.cancel();
_save = Timer(const Duration(milliseconds: 400), () => unawaited(save()));
}
/// Writes the current state now (also on close).
Future<void> save() async {
_save?.cancel();
final box = _box;
if (box == null) return;
try {
if (await windowManager.isFullScreen() || await windowManager.isMinimized()) return;
final max = await windowManager.isMaximized();
final s = Map<String, Object?>.of((_saved ?? const {}).cast<String, Object?>());
s['max'] = max;
if (!max) {
final b = await windowManager.getBounds();
s['w'] = b.width;
s['h'] = b.height;
if (_canPlace) {
s['x'] = b.left;
s['y'] = b.top;
}
}
await box.put(_key, s);
} catch (e) {
debugPrint('window save failed: $e');
}
}
@override
void onWindowResized() => _later();
@override
void onWindowMoved() => _later();
@override
void onWindowMaximize() => _later();
@override
void onWindowUnmaximize() => _later();
}
@@ -0,0 +1,274 @@
import 'dart:convert';
import 'package:sync_relay_core/core.dart' show JellyfinMediaSegment, JellyfinSegmentType, JellyfinStreamType;
import '../jellyfin/track_prefs.dart' show MpvTrack;
// Chapters, segments and tracks of the open media as plain values, plus
// the skip rules built on them (OP / ED / the 1:30 fallback, chapter
// steps). No Flutter, no player: `PlaybackController` feeds its signals
// through these and any controls UI reads them.
/// One mpv chapter. [end] is the next chapter's start, or the duration for
/// the last one ([start] while the duration is unknown).
class MediaChapter {
final int index;
final String title;
final Duration start;
final Duration end;
const MediaChapter({required this.index, required this.title, required this.start, required this.end});
@override
bool operator ==(Object other) =>
other is MediaChapter &&
other.index == index &&
other.title == title &&
other.start == start &&
other.end == end;
@override
int get hashCode => Object.hash(index, title, start, end);
@override
String toString() => 'MediaChapter($index "$title" $start-$end)';
}
/// A marked range of the item from Jellyfin (MediaSegments): intro,
/// outro, recap, preview or commercial.
class MediaSegment {
final JellyfinSegmentType kind;
final Duration start;
final Duration end;
const MediaSegment({required this.kind, required this.start, required this.end});
factory MediaSegment.fromJellyfin(JellyfinMediaSegment s) =>
MediaSegment(kind: s.type, start: s.start, end: s.end);
@override
String toString() => 'MediaSegment(${kind.name} $start-$end)';
}
/// Where a skip range came from.
enum SkipSource { segment, chapter }
/// An opening or ending: skipping it seeks to [end].
class SkipRange {
final Duration start;
final Duration end;
final SkipSource source;
/// The chapter title when [source] is a chapter.
final String? title;
const SkipRange(this.start, this.end, this.source, {this.title});
@override
bool operator ==(Object other) =>
other is SkipRange &&
other.start == start &&
other.end == end &&
other.source == source &&
other.title == title;
@override
int get hashCode => Object.hash(start, end, source, title);
@override
String toString() => 'SkipRange(${source.name} $start-$end${title == null ? '' : ' "$title"'})';
}
Duration _secs(num s) => Duration(microseconds: (s * 1e6).round());
/// mpv `chapter-list` as JSON (`[{"title": "OP", "time": 30.0}, ...]`).
/// Ends come from the next start, the last from [duration].
List<MediaChapter> parseChapterList(String json, Duration duration) {
final Object? raw;
try {
raw = jsonDecode(json);
} on FormatException {
return const [];
}
if (raw is! List) return const [];
final starts = <(String, Duration)>[
for (final c in raw)
if (c is Map && c['time'] is num && (c['time'] as num).isFinite)
((c['title'] is String ? c['title'] as String : '').trim(), _secs(c['time'] as num)),
]..sort((a, b) => a.$2.compareTo(b.$2));
return chaptersFromStarts(starts, duration);
}
/// Chapters from (title, start) pairs in start order.
List<MediaChapter> chaptersFromStarts(List<(String, Duration)> starts, Duration duration) => [
for (var i = 0; i < starts.length; i++)
MediaChapter(
index: i,
title: starts[i].$1,
start: starts[i].$2,
end: i + 1 < starts.length
? starts[i + 1].$2
: (duration > starts[i].$2 ? duration : starts[i].$2),
),
];
/// mpv `track-list` as JSON: the audio and subtitle tracks (video and
/// unknown types left out), with mpv's selection.
List<MpvTrack> parseTrackList(String json) {
final Object? raw;
try {
raw = jsonDecode(json);
} on FormatException {
return const [];
}
if (raw is! List) return const [];
String? s(Object? v) => v is String && v.isNotEmpty ? v : null;
return [
for (final t in raw)
if (t is Map && t['id'] is int && (t['type'] == 'audio' || t['type'] == 'sub'))
MpvTrack(
id: t['id'] as int,
type: t['type'] == 'audio' ? JellyfinStreamType.audio : JellyfinStreamType.subtitle,
language: s(t['lang']),
title: s(t['title']),
codec: s(t['codec']),
channels: (t['demux-channel-count'] ?? t['audio-channels']) is int
? (t['demux-channel-count'] ?? t['audio-channels']) as int
: null,
isDefault: t['default'] == true,
isForced: t['forced'] == true,
isHearingImpaired: t['hearing-impaired'] == true,
isExternal: t['external'] == true,
selected: t['selected'] == true,
),
];
}
final _openingTitle = RegExp(r'\b(op|opening|intro)\d*\b', caseSensitive: false);
final _endingTitle = RegExp(r'\b(ed|ending|credits|outro)\d*\b', caseSensitive: false);
/// "OP", "OP1", "Opening", "Intro" (whole words, any case).
bool isOpeningTitle(String title) => _openingTitle.hasMatch(title);
/// "ED", "ED2", "Ending", "Credits", "Outro" (whole words, any case).
bool isEndingTitle(String title) => _endingTitle.hasMatch(title);
SkipRange? _range(List<MediaSegment> segments, List<MediaChapter> chapters,
JellyfinSegmentType kind, bool Function(String) titleMatches) {
for (final s in segments) {
if (s.kind == kind && s.end > s.start) return SkipRange(s.start, s.end, SkipSource.segment);
}
for (final c in chapters) {
if (c.end > c.start && titleMatches(c.title)) {
return SkipRange(c.start, c.end, SkipSource.chapter, title: c.title);
}
}
return null;
}
/// The opening: the first Jellyfin Intro segment, else the first chapter
/// titled like an opening ([isOpeningTitle]).
SkipRange? openingRange(List<MediaSegment> segments, List<MediaChapter> chapters) =>
_range(segments, chapters, JellyfinSegmentType.intro, isOpeningTitle);
/// The ending: the first Jellyfin Outro segment, else the first chapter
/// titled like an ending ([isEndingTitle]).
SkipRange? endingRange(List<MediaSegment> segments, List<MediaChapter> chapters) =>
_range(segments, chapters, JellyfinSegmentType.outro, isEndingTitle);
/// Index of the chapter playing at [position], or null (before the first
/// chapter, or none).
int? chapterIndexAt(List<MediaChapter> chapters, Duration position) {
int? at;
for (final c in chapters) {
if (c.start <= position) at = c.index;
}
return at;
}
/// mpv's `chapter-seek-threshold`: going back within this much of a
/// chapter's start goes to the chapter before it.
const chapterBackThreshold = Duration(seconds: 5);
/// Target of a chapter step ([delta] +1 = PgDn, -1 = PgUp), as mpv: back
/// goes to the current chapter's start, or the previous one within
/// [chapterBackThreshold] of it; forward to the next chapter's start.
/// Null when there is nowhere to go.
Duration? chapterStepTarget(List<MediaChapter> chapters, Duration position, int delta) {
if (chapters.isEmpty || delta == 0) return null;
final at = chapterIndexAt(chapters, position);
if (delta > 0) {
final next = (at ?? -1) + delta;
return next < chapters.length ? chapters[next].start : null;
}
if (at == null) return null;
var target = at + delta + 1;
if (position - chapters[at].start < chapterBackThreshold) target--;
return target >= 0 ? chapters[target].start : Duration.zero;
}
/// What the skip pill offers.
enum SkipKind {
/// "Skip OP": to the opening's end.
opening,
/// "Skip ED": to the ending's end, nothing further.
ending,
/// "Skip 1:30": no opening known, +[fallbackSkip] from where it plays.
fixed,
}
class SkipOffer {
final SkipKind kind;
/// Seek target (for [SkipKind.fixed] as of the position it was computed
/// at; the press itself skips relative to the moment it lands).
final Duration target;
const SkipOffer(this.kind, this.target);
@override
bool operator ==(Object other) => other is SkipOffer && other.kind == kind && other.target == target;
@override
int get hashCode => Object.hash(kind, target);
@override
String toString() => 'SkipOffer(${kind.name} -> $target)';
}
/// The fixed skip (pill without an opening, Shift+Right, the controls'
/// "1:30" button).
const fallbackSkip = Duration(seconds: 90);
/// The fallback pill needs media longer than this (an episode, not a clip).
const fallbackMinDuration = Duration(minutes: 20);
/// ... and shows during this first part only.
const fallbackWindow = Duration(minutes: 6);
/// No pill in the last second of a range: the skip would do nothing.
const _skipTail = Duration(seconds: 1);
/// The skip pill at [position]: inside the opening "Skip OP" (to its end),
/// inside the ending "Skip ED" (to its end only: what follows, preview or
/// more episode, is the viewer's call). Without any opening info on media
/// longer than [fallbackMinDuration] and without chapters ([chaptered]: a
/// chaptered file without an OP chapter has none on purpose), "Skip 1:30"
/// during the first [fallbackWindow]. Null otherwise.
SkipOffer? skipOfferAt({
required Duration position,
required Duration duration,
required SkipRange? opening,
required SkipRange? ending,
bool chaptered = false,
}) {
bool inside(SkipRange r) => position >= r.start && position < r.end - _skipTail;
if (opening != null && inside(opening)) return SkipOffer(SkipKind.opening, opening.end);
if (ending != null && inside(ending)) return SkipOffer(SkipKind.ending, ending.end);
if (opening == null && !chaptered && duration > fallbackMinDuration && position < fallbackWindow) {
final to = position + fallbackSkip;
return SkipOffer(SkipKind.fixed, to < duration ? to : duration);
}
return null;
}
/// "1:05", "1:02:03".
String formatClock(Duration d) {
final neg = d.isNegative;
final t = d.abs();
String two(int n) => n.toString().padLeft(2, '0');
final h = t.inHours, m = t.inMinutes % 60, s = t.inSeconds % 60;
final body = h > 0 ? '$h:${two(m)}:${two(s)}' : '$m:${two(s)}';
return neg ? '-$body' : body;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,89 @@
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sync_relay_core/core.dart';
/// mpv's `ytdl-format` for links every player opens itself.
const ytdlFormat = 'bv*[height<=1080]+ba/b';
/// yt-dlp's `--sub-langs` for mpv's hook (its default is `all`), without
/// Bilibili's danmaku: an XML comment feed served raw-deflate, which
/// ffmpeg fails to inflate on every read (a warning each time) while the
/// video waits on it.
const ytdlSubLangs = 'all,-danmaku';
/// The app's yt-dlp (see [YtDlp]): in the app-support folder, shared with
/// the hosted server. [probe] remembers what pasted links are for a while,
/// so the Link preview and the swap ask once.
class AppYtDlp {
static final instance = AppYtDlp();
/// [ytDlp]: a given one (tests); default in the app-support folder.
AppYtDlp([YtDlp? ytDlp]) : _ytDlp = ytDlp == null ? null : Future.value(ytDlp);
Future<YtDlp>? _ytDlp;
Future<YtDlp> get ytDlp => _ytDlp ??= () async {
final base = await getApplicationSupportDirectory();
return YtDlp(dir(base.path));
}();
static String dir(String appSupport) => '$appSupport${Platform.pathSeparator}yt-dlp';
/// Gets yt-dlp ready in the background (download on first run, a daily
/// update check afterwards).
void warmUp() => unawaited(ytDlp.then((y) => y.ensure()).catchError((_) => null));
/// mpv's `ytdl-raw-options`: [ytdlSubLangs] and the cookies in the
/// yt-dlp folder (see [YtDlpCookies]).
Future<String> mpvRawOptions() async {
const subs = 'sub-langs=%${ytdlSubLangs.length}%$ytdlSubLangs';
try {
final cookies = (await ytDlp).cookies.mpvRawOptions();
return cookies.isEmpty ? subs : '$subs,$cookies';
} catch (_) {
return subs;
}
}
final _probes = <String, ({DateTime at, DateTime until, Future<LinkProbe> f})>{};
static const _probeTtl = Duration(minutes: 10);
/// What [url] is, from memory while it is kept (10 minutes from when it
/// was probed; [keep] keeps it longer, for Up next's first item, which
/// is probed again before that runs out). [fresh] probes again: the
/// remembered result stays until the new one is in, and stays when the
/// new one fails.
Future<LinkProbe> probe(String url, {Duration keep = _probeTtl, bool fresh = false}) {
final now = DateTime.now();
_probes.removeWhere((_, v) => now.isAfter(v.until));
final hit = _probes[url];
if (hit != null && !fresh) {
final longer = hit.at.add(keep);
if (longer.isAfter(hit.until)) _probes[url] = (at: hit.at, until: longer, f: hit.f);
return hit.f;
}
final f = ytDlp.then((y) => probeLink(Uri.parse(url), ytDlp: y));
final until = now.add(keep);
if (hit == null) {
_probes[url] = (at: now, until: until, f: f);
f.catchError((Object _) {
if (_probes[url]?.f == f) _probes.remove(url);
return LinkProbe.file(Uri(), -1);
});
} else {
f.then<void>((_) {
_probes[url] = (at: now, until: until, f: f);
}, onError: (Object _) {});
}
return f;
}
/// [url] no longer needs keeping longer: back to the usual 10 minutes.
void release(String url) {
final hit = _probes[url];
if (hit == null) return;
final usual = hit.at.add(_probeTtl);
if (usual.isBefore(hit.until)) _probes[url] = (at: hit.at, until: usual, f: hit.f);
}
}
@@ -0,0 +1,808 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart';
import '../files/name_parser.dart';
import '../files/thumbnails.dart';
import '../jellyfin/accounts.dart';
import '../jellyfin/format.dart' show pickFor;
import '../jellyfin/session_media.dart';
import '../screens/home/picked_media.dart';
import '../session/media_art.dart';
import '../session/session_coordinator.dart';
import '../state/settings.dart';
import '../sync/avatar_cache.dart';
import 'queue_logic.dart';
// Up next on this client (see ARCHITECTURE.md "Up next"): the server's
// queue as signals, adding to it (probed, with a picture), the end-of-media
// countdown for its first item, putting an item up when this app is the
// one that can, and keeping that item ready.
/// The end-of-media card for the first Up next item. [secondsLeft] null:
/// no countdown here (auto-advance off, cancelled, or another app puts
/// it up), the card offers Play now only.
class UpNextCountdown {
final int mediaId;
final QueueItem item;
final int? secondsLeft;
const UpNextCountdown({required this.mediaId, required this.item, this.secondsLeft});
}
/// The first item's readiness on this app.
enum WarmState { idle, warming, ready, failed }
class QueueController {
final SessionCoordinator coord;
QueueController(this.coord);
/// Queued items' pictures by hash, from `/i/`.
late final PictureCache thumbs = PictureCache(coord.sync,
route: 'i', keep: RelayConstants.maxQueueItems + 8, live: (s) => [for (final q in s.queue) q.thumb]);
/// Up next, in order (empty when nothing is queued or not in a server).
late final Computed<List<QueueItem>> items =
computed(() => coord.serverState.value?.queue ?? const <QueueItem>[]);
final countdown = signal<UpNextCountdown?>(null);
/// The first item's id and how ready it is here (null: nothing to get
/// ready on this app).
final warm = signal<({String id, WarmState state})?>(null);
/// The natural continuation of what plays: the next episodes of the
/// Jellyfin series (not queued; shown greyed under Up next).
final continuation = signal<List<EpisodeLink>>(const []);
/// The mediaId whose continuation was cleared here.
final _hiddenFor = signal(-1);
/// The queue popover is open: the Up next button holds the controls up.
final popoverOpen = signal(false);
// What adder-only items stand for, by their key (a link passed on from
// here, a file here). Never leaves this app.
final Map<String, ({PickedMedia pick, DateTime at})> _local = {};
final Map<String, DateTime> _performing = {};
final Set<String> _skipped = {};
int _cancelledFor = -1;
Timer? _timer;
bool _attached = false;
String _continuationFor = '';
final _random = Random.secure();
String get _me => coord.sync.memberId;
void attach() {
if (_attached) return;
_attached = true;
coord.sync.states.listen(_onState);
coord.sync.feeds.listen(_onFeed);
effect(() {
final next = coord.nextEpisode.value;
final plan = coord.jellyfinPlan.value;
unawaited(_loadContinuation(next, plan));
});
// The continuation and the state decide what to get ready; the media
// playing having started (or sitting out) lets the preload go.
effect(() {
shownContinuation.value;
coord.settledMediaId.value;
coord.selfOut.value;
final r = coord.serverState.value;
untracked(() => _warm(r));
});
}
// ------------------------------------------------------------- adding
/// Adds [pick] to Up next (at the end, or with [next] right after what
/// plays). A link is probed first and refused like Put it up when it
/// will not play. Returns an error text, or null when sent.
Future<String?> add(PickedMedia pick, {bool next = false}) async {
coord.markInput();
if (coord.serverState.peek() == null) return 'not in a server';
if (items.peek().length >= RelayConstants.maxQueueItems) {
return 'Up next is full (${RelayConstants.maxQueueItems} items)';
}
final ({QueueItem item, Uint8List? thumb}) built;
try {
final b = await _build(pick);
if (b is String) return b;
built = b as ({QueueItem item, Uint8List? thumb});
} catch (e) {
return "couldn't add it: $e";
}
final item = built.item;
if (item.key.isNotEmpty) _local[item.key] = (pick: pick, at: DateTime.now());
final thumb = built.thumb;
coord.sync.sendQueue(QueueIntent.add(item, next: next, thumb: thumb == null ? '' : base64Encode(thumb)));
coord.showToast('Added ${item.title} to Up next');
return null;
}
/// A continuation episode becomes an Up next item.
Future<String?> promote(EpisodeLink e) => add(e.pick);
/// The item, or an error text.
Future<Object> _build(PickedMedia pick) async {
switch (pick) {
case JellyfinMedia():
final accounts = coord.jellyfin;
if (accounts.account(pick.serverId) == null) return 'that Jellyfin server is not linked any more';
if (accounts.statusOf(pick.serverId) == JellyfinAccountStatus.expired) {
return 'Jellyfin sign-in expired — reconnect it in Settings → Jellyfin & integrations';
}
final client = accounts.clientFor(pick.serverId);
final thumb = client == null ? null : await _jellyfinThumb(client, pick.itemId);
return (
item: QueueItem(
media: mediaForPick(pick),
how: QueueHow.jellyfin,
title: pick.title,
sub: pick.subtitle,
duration: (pick.runtime?.inMilliseconds ?? 0) / 1000,
),
thumb: thumb,
);
case FileMedia(:final path):
final file = File(path);
if (!await file.exists()) return 'file not found — check the path';
final name = pick.label;
final parsed = parseReleaseName(name);
final shot = await thumbnailFor(path);
Uint8List? thumb;
if (shot != null) {
try {
final bytes = await File(shot.imagePath).readAsBytes();
if (bytes.length <= RelayConstants.maxPosterBytes) thumb = bytes;
} catch (_) {}
}
final ep = [
if (parsed.season != null) 'S${parsed.season}',
if (parsed.episode != null) 'E${parsed.episode}',
].join();
return (
item: QueueItem(
media: HelloMedia(name: name, size: await file.length(), duration: 0),
how: QueueHow.file,
title: parsed.series.trim().isEmpty ? name : parsed.series.trim(),
sub: ep,
duration: (shot?.duration.inMilliseconds ?? 0) / 1000,
key: _newKey(),
),
thumb: thumb,
);
case LinkMedia(:final url, :final anyway):
// Alone, a link plays direct: there is nobody to pass it on to.
final relay = coord.solo ? LinkRelay.direct : pick.relay;
final linked = await mediaForLink(coord.jellyfin, url);
final ref = linked.jellyfin;
if (ref != null) {
return (
item: QueueItem(
media: HelloMedia(name: linked.name, size: 0, duration: 0, jellyfin: ref),
how: QueueHow.jellyfin,
title: ref.title.isEmpty ? linked.name : ref.title,
sub: ref.subtitle ?? '',
duration: ref.runtimeSecs ?? 0),
thumb: null,
);
}
LinkProbe? probe;
String? probeError;
try {
probe = await coord.ytDlp.probe(url).timeout(const Duration(seconds: 45));
} catch (e) {
probeError = '$e';
}
final problem = probe == null ? playErrorText(probeError ?? '') : probe.problem;
if (problem != null && !anyway) return problem;
if (relay == LinkRelay.me) {
if (probe == null) return problem ?? "couldn't reach the site";
final relayProblem = probe.relayProblem;
if (relayProblem != null) return relayProblem;
}
if (relay == LinkRelay.server && !coord.serverHasPassword) return 'server fetch needs a server password';
if (relay == LinkRelay.server && probe != null && probe.isPage && probe.info != null) {
final relayProblem = probe.relayProblem;
if (relayProblem != null) return relayProblem;
}
final uri = Uri.tryParse(url) ?? Uri();
final info = probe?.info;
final title = probe?.title ?? linked.name;
final site = info == null
? uri.host
: (info.uploader.isNotEmpty ? info.uploader : (info.extractor.isNotEmpty ? info.extractor : uri.host));
final thumbUrl = info?.thumbnail == null ? null : Uri.tryParse(info!.thumbnail!);
final thumb = thumbUrl == null ? null : (await thumbnailArt(thumbUrl))?.poster;
final how = switch (relay) {
LinkRelay.direct => QueueHow.direct,
LinkRelay.server => QueueHow.server,
LinkRelay.me => QueueHow.through,
};
return (
item: QueueItem(
media: HelloMedia(
name: title,
size: 0,
duration: 0,
url: how == QueueHow.through ? null : url,
relay: how == QueueHow.server ? const MediaRelay(fetch: true) : null),
how: how,
title: title,
sub: site,
duration: info?.duration ?? 0,
key: how == QueueHow.through ? _newKey() : '',
),
thumb: thumb,
);
}
}
String _newKey() => [for (var i = 0; i < 12; i++) _random.nextInt(256).toRadixString(16).padLeft(2, '0')].join();
/// A wide picture of a Jellyfin item: an episode's own still, else the
/// item's (or series') thumb or backdrop, else its poster.
Future<Uint8List?> _jellyfinThumb(JellyfinClient client, String itemId) async {
try {
final item = await client.item(itemId).timeout(const Duration(seconds: 10));
final ref = (item.kind == JellyfinItemKind.episode ? item.ownImage(JellyfinImageType.primary) : null) ??
item.image(JellyfinImageType.thumb) ??
item.image(JellyfinImageType.backdrop) ??
item.image(JellyfinImageType.primary);
if (ref == null) return null;
return await fetchImage(
client.imageUrl(ref, maxWidth: 480, quality: 80), client.authHeaders, RelayConstants.maxPosterBytes);
} catch (e) {
debugPrint('queue: no Jellyfin picture for $itemId: $e');
return null;
}
}
/// [item]'s link as this app may hand it out (Copy link, Open in
/// browser): a link everyone opens (it is in the state), or a link this
/// app passes on (its adder). Null otherwise: a server-fetched link stays
/// with the server, another member's passed-on link with them.
String? linkOf(QueueItem item) => switch (item.how) {
QueueHow.direct => item.media.url,
QueueHow.through when item.by == _me => switch (_local[item.key]?.pick) {
LinkMedia(:final url) => url,
_ => null,
},
_ => null,
};
/// [item]'s page on its Jellyfin server, when that server is linked here.
String? jellyfinPageOf(QueueItem item) {
final ref = item.media.jellyfin;
return ref == null ? null : coord.jellyfin.webUrlOf(ref.serverId, ref.itemId);
}
// ------------------------------------------------------------ editing
void move(String id, int to) {
coord.markInput();
coord.sync.sendQueue(QueueIntent.move(id, to));
}
void remove(String id) {
coord.markInput();
coord.sync.sendQueue(QueueIntent.remove(id));
}
/// Plays [id] now: this app puts it up when it is the one that can,
/// else it asks the server, and the app that can puts it up.
void playNow(String id) {
coord.markInput();
final r = coord.serverState.peek();
final item = r?.queue.where((q) => q.id == id).firstOrNull;
if (r == null || item == null) return;
if (queuePerformer(item, r.members).id == _me) {
_perform(item);
} else {
coord.sync.sendQueue(QueueIntent.play(id));
}
}
/// Plays the first Up next item now.
void skip() {
final first = items.peek().firstOrNull;
if (first != null) playNow(first.id);
}
/// Cancel on the end-of-media card: no countdown here for this media.
void cancelCountdown() {
final c = countdown.peek();
_timer?.cancel();
_timer = null;
if (c == null) return;
_cancelledFor = c.mediaId;
countdown.value = UpNextCountdown(mediaId: c.mediaId, item: c.item);
}
/// "Clear": hides the continuation of what plays now (and its next
/// episode does not come up by itself).
void clearContinuation() {
final id = coord.serverState.peek()?.mediaId;
if (id != null) _hiddenFor.value = id;
if (coord.autoAdvance.peek() != null) coord.cancelAutoAdvance();
}
/// The continuation applies to [mediaId]: nothing is queued and it was
/// not cleared.
bool continuationShown(int mediaId) =>
(coord.serverState.peek()?.queue.isEmpty ?? true) && _hiddenFor.peek() != mediaId;
/// [continuation] unless cleared for the media playing.
late final Computed<List<EpisodeLink>> shownContinuation = computed(() {
final id = coord.serverState.value?.mediaId;
return id != null && _hiddenFor.value == id ? const <EpisodeLink>[] : continuation.value;
});
// --------------------------------------------------------- per state
void _onState(StateBroadcast r) {
_lastState = r;
_prune(r);
final queue = r.queue;
final play = r.queuePlay;
if (play != null) {
final item = queue.where((q) => q.id == play).firstOrNull;
if (item != null) _bringUp(item, r);
}
final finished = r.transport == 'finished';
if (queue.isNotEmpty && r.media == null) {
// Nothing up yet: the first item goes up at once.
_bringUp(queue.first, r);
} else if (finished && queue.isNotEmpty) {
_atEnd(r);
} else {
if (countdown.peek() != null) _stopCountdown();
if (!finished) _cancelledFor = -1;
}
_warm(r);
}
void _onFeed(FeedMessage f) {
if (f.action == 'queue-skip') {
coord.showToast('Skipped ${f.target ?? 'an item'}${f.reason.isEmpty ? '' : ': ${f.reason}'}');
}
}
/// Forgets what no longer matters: performing marks after a minute (a
/// swap that never landed may be tried again), local picks no longer
/// queued.
void _prune(StateBroadcast r) {
final now = DateTime.now();
_performing.removeWhere((_, at) => now.difference(at) > const Duration(minutes: 1));
final keys = {for (final q in r.queue) q.key};
_local.removeWhere((k, v) => !keys.contains(k) && now.difference(v.at) > const Duration(minutes: 1));
final ids = {for (final q in r.queue) q.id};
_skipped.removeWhere((id) => !ids.contains(id));
}
/// [item] should go up now: by this app when it is the one that can;
/// nobody can: the lowest member takes it off with the reason.
void _bringUp(QueueItem item, StateBroadcast r) {
final perf = queuePerformer(item, r.members);
if (perf.id == null) {
final why = perf.why;
if (why != null && queueJanitor(r.members) == _me && _skipped.add(item.id)) {
coord.sync.sendQueue(QueueIntent.remove(item.id, why: why));
}
return;
}
if (perf.id == _me) _perform(item);
}
/// The media finished with Up next waiting: the card, and with
/// auto-advance on the countdown, for its first item.
void _atEnd(StateBroadcast r) {
// Up next goes before the next episode, even one already counting.
if (coord.autoAdvance.peek() != null) coord.cancelAutoAdvance();
final head = r.queue.first;
final perf = queuePerformer(head, r.members);
if (perf.id == null && perf.why != null) {
// Taken off by [_bringUp]'s rule; the next state shows the next one.
_bringUp(head, r);
return;
}
final c = countdown.peek();
if (c != null && c.mediaId == r.mediaId) {
if (c.item.id != head.id || c.item.thumb != head.thumb) {
countdown.value = UpNextCountdown(mediaId: c.mediaId, item: head, secondsLeft: c.secondsLeft);
}
return;
}
final auto = AppSettings.instance.autoAdvance.peek() && _cancelledFor != r.mediaId;
countdown.value = UpNextCountdown(mediaId: r.mediaId, item: head, secondsLeft: auto ? autoAdvanceSecs : null);
_timer?.cancel();
_timer = auto ? Timer.periodic(const Duration(seconds: 1), (_) => _tick()) : null;
}
void _tick() {
final c = countdown.peek();
final r = coord.serverState.peek();
final left = c?.secondsLeft;
if (c == null || left == null || r == null || r.mediaId != c.mediaId || r.transport != 'finished') {
_stopCountdown();
return;
}
if (left > 1) {
countdown.value = UpNextCountdown(mediaId: c.mediaId, item: c.item, secondsLeft: left - 1);
return;
}
_timer?.cancel();
_timer = null;
_cancelledFor = c.mediaId;
countdown.value = UpNextCountdown(mediaId: c.mediaId, item: c.item);
// Decided on the latest state: only the app that can puts it up.
final head = r.queue.firstOrNull;
if (head != null && queuePerformer(head, r.members).id == _me) _perform(head);
}
void _stopCountdown() {
_timer?.cancel();
_timer = null;
countdown.value = null;
}
/// Puts [item] up from here (once per item while it is in flight).
/// When it cannot play after all, it is taken off with the reason.
void _perform(QueueItem item) {
if (_performing.containsKey(item.id)) return;
_performing[item.id] = DateTime.now();
unawaited(() async {
String? error;
try {
error = await _swapTo(item);
} catch (e) {
error = '$e';
}
if (error != null) {
debugPrint('queue: ${item.id} cannot go up: $error');
coord.sync.sendQueue(QueueIntent.remove(item.id, why: "couldn't play it ($error)"));
}
}());
}
Future<String?> _swapTo(QueueItem item) async {
switch (item.how) {
case QueueHow.jellyfin:
final ref = item.media.jellyfin;
final pick = ref == null ? null : pickForRef(coord.jellyfin, ref);
if (pick == null) return 'no Jellyfin account here for it';
coord.swapJellyfin(pick, queueId: item.id);
return null;
case QueueHow.direct:
final url = item.media.url;
if (url == null) return 'no link';
// Each viewer opens it: one computer's probe does not decide.
return coord.swapLink(url, relay: LinkRelay.direct, anyway: true, queueId: item.id);
case QueueHow.server:
coord.swapQueued(HelloMedia(name: item.title, size: 0, duration: 0, relay: const MediaRelay(fetch: true)),
item.id,
art: thumbs.bytesFor(item.thumb));
return null;
case QueueHow.through:
case QueueHow.file:
final local = _local[item.key];
if (local == null) return "it isn't on this computer any more";
// Only this app can pass it on, and a member sitting out has
// nothing open: back first.
if (coord.selfOut.peek()) coord.joinBack();
return coord.swapPicked(local.pick, queueId: item.id);
}
}
// ------------------------------------------------------------ warming
/// How long a warm result is kept, when it is resolved again, and when a
/// failed one is tried again.
static const _warmKeep = Duration(minutes: 35);
static const _warmRefresh = Duration(minutes: 30);
static const _warmRetry = Duration(minutes: 2);
String _warmKey = '';
String? _warmUrl;
Timer? _warmTimer;
int _warmGen = 0;
// What the preload player should hold once the media playing has
// started (null: nothing); [_preloadFresh]: open it again (a link's
// refresh).
({String url, Map<String, String>? headers})? _preloadWant;
bool _preloadFresh = false;
String _preloadKey = '';
bool _preloadBusy = false;
bool _preloadOk = true;
int _preloadGen = 0;
StateBroadcast? _lastState;
bool _ownAccount(String serverId) =>
coord.jellyfin.account(serverId) != null && coord.jellyfin.statusOf(serverId) != JellyfinAccountStatus.expired;
/// What this app gets ready: Up next's first item, else (Up next empty)
/// the first continuation episode (only here when this app plays the
/// series with its own account). Every app does, each its own copy (see
/// [_WarmTarget]); while sitting out only the probe of a link this app
/// puts up (its swap reads it). Null: nothing.
_WarmTarget? _warmTarget(StateBroadcast? r) {
final t = _warmTargetHere(r);
if (t == null || !coord.selfOut.peek()) return t;
return t.probe == null ? null : _WarmTarget(t.id, probe: t.probe);
}
_WarmTarget? _warmTargetHere(StateBroadcast? r) {
if (r == null) return null;
final head = r.queue.firstOrNull;
if (head != null) {
final performer = queuePerformer(head, r.members).id == _me;
final local = _local[head.key]?.pick;
// The server's warm relay of the item, once bytes can flow: what
// everyone without their own copy preloads.
final relay = head.media.relay;
final warmUrl = relay != null && relay.live ? coord.warmRelayUrl(head, relay) : null;
switch (head.how) {
case QueueHow.jellyfin:
final ref = head.media.jellyfin;
if (ref == null) return null;
if (_ownAccount(ref.serverId)) return _WarmTarget(head.id, ref: ref);
// No account here: the server's login passes it on (`/j`), else
// the warm relay a member with an account feeds.
final open = r.relayNext ? coord.jellyfinRelayUrl(ref) : warmUrl;
return open == null ? null : _WarmTarget(head.id, open: open);
case QueueHow.direct:
final url = head.media.url;
if (url == null) return null;
return _WarmTarget(head.id, probe: performer ? url : null, open: url);
case QueueHow.through:
// Only its adder has the link (and feeds the warm relay).
if (!performer) return warmUrl == null ? null : _WarmTarget(head.id, open: warmUrl);
final url = local is LinkMedia ? local.url : null;
return _WarmTarget(head.id, probe: url, open: url);
case QueueHow.file:
if (!performer) return warmUrl == null ? null : _WarmTarget(head.id, open: warmUrl);
// The host's own file plays from its `/m` URL once it is up.
return _WarmTarget(head.id, open: local is FileMedia && coord.hostFile == null ? local.path : null);
case QueueHow.server:
// The server fetches it into its warm relay; everyone preloads that.
if (warmUrl != null) return _WarmTarget(head.id, open: warmUrl);
return performer ? _WarmTarget(head.id) : null;
}
}
final next = shownContinuation.peek().firstOrNull;
if (next == null) return null;
return _WarmTarget('next:${next.item.id}', ref: mediaForPick(next.pick).jellyfin);
}
/// Keeps the one item that plays next ready on this app, so a skip or
/// the end of what plays starts it at once: a link probed when this app
/// puts it up (`AppYtDlp.probe`, kept while it stays first), a Jellyfin
/// item resolved with this app's account
/// ([SessionCoordinator.warmJellyfin]), and then opened in the hidden
/// preload player ([SessionCoordinator.preloadMedia]) once the media
/// playing has started. Done as soon as it is first, again before that
/// runs out, dropped when it is no longer first.
void _warm(StateBroadcast? r) {
_feedWarm(r);
final t = _warmTarget(r);
final key = t?.key ?? '';
if (key != _warmKey) {
_dropWarm(r);
_warmKey = key;
if (t == null) {
if (warm.peek() != null) warm.value = null;
} else if (t.empty) {
warm.value = (id: t.id, state: WarmState.ready);
} else {
warm.value = (id: t.id, state: WarmState.warming);
_resolveWarm(t, fresh: false);
}
}
_syncPreload(r);
}
// The warm relay this app feeds (`<queue id>|<token>`), started once.
String _fedWarm = '';
/// Feeds the server's warm relay of Up next's first item from here when
/// it names this member as its source (a link passed on, a file), or a
/// Jellyfin item this app puts up with its own account when the server
/// asks for a source; see [SessionCoordinator.feedWarm]. The server
/// drops the relay (and with it the source) once the item is no longer
/// first.
void _feedWarm(StateBroadcast? r) {
final head = r?.queue.firstOrNull;
final relay = head?.media.relay;
if (r == null || head == null || relay == null || relay.fetch) return;
final key = '${head.id}|${coord.sync.token}';
if (key == _fedWarm) return;
switch (head.how) {
case QueueHow.through:
case QueueHow.file:
if (relay.source != _me) return;
final pick = _local[head.key]?.pick;
if (pick is FileMedia) {
_fedWarm = key;
coord.feedWarm(head.id, RelayMedia.single(FileRelayBytes(File(pick.path))));
} else if (pick is LinkMedia) {
_fedWarm = key;
unawaited(() async {
try {
// The probe [_resolveWarm] keeps: no second yt-dlp run.
final probe = await coord.ytDlp.probe(pick.url, keep: _warmKeep).timeout(const Duration(seconds: 60));
final y = await coord.ytDlp.ytDlp;
if (_fedWarm != key) return;
coord.feedWarm(head.id, LinkRelayMedia(Uri.parse(pick.url), ytDlp: y, probe: probe));
} catch (e) {
debugPrint('queue: feeding ${head.id}: $e');
}
}());
}
case QueueHow.jellyfin:
final ref = head.media.jellyfin;
if (ref == null || (relay.source.isNotEmpty && relay.source != _me)) return;
if (queuePerformer(head, r.members).id != _me) return;
// Resolved with this app's account by [_resolveWarm].
final w = coord.warmJellyfin;
if (w == null || !sameJellyfinItem(w.ref, ref)) return;
_fedWarm = key;
coord.feedWarm(
head.id, RelayMedia.single(HttpRelayBytes(Uri.parse(w.plan.url), headers: w.plan.headers)));
case QueueHow.direct:
case QueueHow.server:
return;
}
}
void _dropWarm(StateBroadcast? r) {
_warmTimer?.cancel();
_warmTimer = null;
_warmGen++;
_preloadWant = null;
final url = _warmUrl;
_warmUrl = null;
if (url != null) coord.ytDlp.release(url);
// Kept while it is what just went up: that open takes it.
final w = coord.warmJellyfin;
if (w != null && !sameJellyfinItem(w.ref, refOfStateMedia(r?.media))) coord.warmJellyfin = null;
}
void _resolveWarm(_WarmTarget t, {required bool fresh}) {
final gen = ++_warmGen;
unawaited(() async {
var ok = true;
({String url, Map<String, String>? headers})? open;
try {
final probe = t.probe, ref = t.ref, url = t.open;
if (probe != null) {
_warmUrl = probe;
final p = await coord.ytDlp.probe(probe, keep: _warmKeep, fresh: fresh).timeout(const Duration(seconds: 60));
ok = p.problem == null;
}
if (ref != null) {
final plan = await resolveJellyfinRef(coord.jellyfin, ref, relay: false, relayUrl: null);
if (gen != _warmGen) return;
ok = plan is OwnPlay;
if (plan is OwnPlay) {
coord.warmJellyfin = (ref: ref, plan: plan, until: DateTime.now().add(_warmKeep));
open = (url: plan.url, headers: plan.headers);
_feedWarm(coord.serverState.peek());
}
} else if (url != null && ok) {
open = (url: url, headers: null);
}
} catch (e) {
ok = false;
debugPrint('queue: getting ${t.id} ready: $e');
}
if (gen != _warmGen) return;
_preloadWant = ok ? open : null;
// A refresh opens a link again (a page's stream URLs expire); the
// same Jellyfin stream stays as it is.
_preloadFresh = fresh && ok && t.ref == null && open != null;
final waits = ok && open != null && coord.playback.preloadEnabled;
warm.value = (id: t.id, state: !ok ? WarmState.failed : (waits ? WarmState.warming : WarmState.ready));
_syncPreload(coord.serverState.peek());
_warmTimer = Timer(ok ? _warmRefresh : _warmRetry, () => _resolveWarm(t, fresh: true));
}());
}
/// Puts [_preloadWant] in the preload player, or drops what it holds;
/// only once the media playing has started (its own open goes first,
/// and a preload a swap is about to take is never replaced before), at
/// once while sitting out (nothing preloads then).
void _syncPreload(StateBroadcast? r) {
// The newest state seen here: the coordinator's lags behind while the
// same `state` is still being handed to it.
final seen = _lastState;
if (r == null || (seen != null && seen.mediaId > r.mediaId)) r = seen;
if (r == null) return;
final out = coord.selfOut.peek();
if (!out && r.media != null && coord.settledMediaId.peek() != r.mediaId) return;
final want = out ? null : _preloadWant;
final playback = coord.playback;
if (want == null) {
_preloadKey = '';
_preloadGen++;
if (playback.preloadUrl != null) unawaited(playback.dropPreload());
return;
}
if (!playback.preloadEnabled) return;
final key = '${want.url}|${want.headers}';
final fresh = _preloadFresh;
// Asked for already: in flight, there, or failed (tried again with the
// next refresh). One that went away (Reload video) is asked again.
if (!fresh && key == _preloadKey && (_preloadBusy || !_preloadOk || playback.preloadUrl == want.url)) return;
_preloadFresh = false;
_preloadKey = key;
_preloadBusy = true;
final gen = ++_preloadGen;
final id = warm.peek()?.id;
unawaited(coord.preloadMedia(want.url, headers: want.headers, fresh: fresh).then((ok) {
if (gen != _preloadGen) return;
_preloadBusy = false;
_preloadOk = ok;
// A preload that fails leaves the usual open: nothing to say.
if (!ok) debugPrint('queue: preload of $id failed');
final w = warm.peek();
if (w != null && w.id == id && w.state == WarmState.warming) {
warm.value = (id: w.id, state: WarmState.ready);
}
}));
}
// -------------------------------------------------------- continuation
/// The next few episodes after the one playing (own account only).
Future<void> _loadContinuation(EpisodeLink? next, JellyfinPlan? plan) async {
if (next == null || plan is! OwnPlay) {
_continuationFor = '';
if (continuation.peek().isNotEmpty) continuation.value = const [];
return;
}
final key = '${plan.account.serverId}/${next.item.id}';
if (key == _continuationFor) return;
_continuationFor = key;
continuation.value = [next];
final seriesId = next.item.seriesId;
if (seriesId == null) return;
try {
final list = await coord.jellyfin.cached(
plan.account.serverId, 'episodes:$seriesId', () => plan.client.episodes(seriesId));
if (_continuationFor != key) return;
final more = [next];
var at = next.item;
while (more.length < 4) {
final after = episodeAfter(list, at.id);
if (after == null) break;
more.add(EpisodeLink(after, pickFor(plan.account, after)));
at = after;
}
continuation.value = more;
} catch (e) {
debugPrint('queue: continuation of $seriesId: $e');
}
}
}
/// What [QueueController] gets ready for one item on this app: [probe] a
/// link (the app that puts it up), resolve [ref] with this app's own
/// account, and preload [open] (or the resolved stream). None of it:
/// nothing to do here, and it is ready ([empty]).
class _WarmTarget {
final String id;
final String? probe;
final JellyfinRef? ref;
final String? open;
const _WarmTarget(this.id, {this.probe, this.ref, this.open});
bool get empty => probe == null && ref == null && open == null;
String get key => '$id|$probe|$open|${ref?.serverId}/${ref?.itemId}/${ref?.mediaSourceId}';
}
@@ -0,0 +1,76 @@
import 'package:sync_relay_core/core.dart';
import '../jellyfin/session_media.dart' show compareMemberIds;
// Who puts an Up next item up. Pure: only what every client sees in
// `state` decides, so all clients agree without a wire message (like the
// Jellyfin auto-advance swapper).
/// Who puts [item] up: [id] a member id; null with [why] when nobody here
/// can (the item is skipped), null without [why] when nobody is around
/// right now (nothing happens).
typedef QueuePerformer = ({String? id, String? why});
/// - A link passed on by its adder, or the adder's file: only the adder
/// (their app has the file, the cookies). Gone: skipped.
/// - Jellyfin: the adder when it plays that server with its own account,
/// else the lowest member id that does (`jf[serverId] == true`, not
/// stale, not sitting out). Nobody: skipped.
/// - A link everyone opens, or one the server fetches: the adder when
/// present (not stale or sitting out), else the lowest such member id.
QueuePerformer queuePerformer(QueueItem item, List<MemberView> members) {
MemberView? adder;
for (final m in members) {
if (m.id == item.by) adder = m;
}
bool active(MemberView m) => !m.stale && !m.out;
if (item.how.adderOnly) {
if (adder == null) return (id: null, why: '${_name(item)} left');
return (id: adder.id, why: null);
}
final ref = item.media.jellyfin;
if (item.how == QueueHow.jellyfin && ref != null) {
bool can(MemberView m) => active(m) && m.jf[ref.serverId] == true;
if (adder != null && can(adder)) return (id: adder.id, why: null);
final id = autoAdvanceLowest(members.where(can));
return id == null ? (id: null, why: 'nobody here has that Jellyfin server') : (id: id, why: null);
}
if (adder != null && active(adder)) return (id: adder.id, why: null);
return (id: autoAdvanceLowest(members.where(active)), why: null);
}
String _name(QueueItem item) => item.byName.isEmpty ? 'who added it' : item.byName;
/// The lowest member id of [members] ("m2" before "m10"), or null.
String? autoAdvanceLowest(Iterable<MemberView> members) {
final ids = [for (final m in members) m.id]..sort(compareMemberIds);
return ids.isEmpty ? null : ids.first;
}
/// Who takes an item nobody can put up off the queue (`remove` with why):
/// the lowest member id that is not stale.
String? queueJanitor(List<MemberView> members) => autoAdvanceLowest(members.where((m) => !m.stale));
/// How [item] plays, in a few words for its row: "through Foxyz" (you:
/// "through you"), "server fetches", "everyone fetches", "Jellyfin",
/// "Foxyz's file".
String queueHowLabel(QueueItem item, {required String selfId}) {
final mine = item.by == selfId && selfId.isNotEmpty;
final who = mine ? 'you' : _name(item);
return switch (item.how) {
QueueHow.through => 'through $who',
QueueHow.file => mine ? 'your file' : "$who's file",
QueueHow.server => 'server fetches',
QueueHow.direct => 'everyone fetches',
QueueHow.jellyfin => 'Jellyfin',
};
}
/// "1:23:45" / "23:45" for [secs]; empty when unknown.
String queueClock(double secs) {
if (secs <= 0) return '';
final s = secs.round();
final h = s ~/ 3600, m = (s % 3600) ~/ 60, r = s % 60;
String two(int v) => v.toString().padLeft(2, '0');
return h > 0 ? '$h:${two(m)}:${two(r)}' : '$m:${two(r)}';
}
File diff suppressed because it is too large. Load diff
+391
View File
@@ -0,0 +1,391 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui' show Color;
import 'package:path_provider/path_provider.dart';
import 'package:sync_relay_core/core.dart';
// AniList airing schedule (public GraphQL, no login): every episode airing
// in a time range, with covers, banners and popularity; and a title search
// (cover + banner of a show, for the media art of files and links).
//
// AniList allows ~90 requests/min (sometimes degraded to 30), so the raw
// pages are cached: in memory for 10 min (tab switches) and on disk for an
// hour (restarts), keyed by range start. A 429 waits out Retry-After once;
// if AniList still refuses (or is unreachable), stale cached data is
// returned with its fetch time.
final _log = createLogger('anilist');
const aniListDefaultUrl = 'https://graphql.anilist.co';
/// One episode airing, as AniList announces it.
class Airing {
final int anilistId;
/// English title, else romaji.
final String title;
final String titleRomaji;
final int episode;
/// UTC.
final DateTime airingAt;
final String? cover;
final String? banner;
/// The cover's dominant color, if AniList has one.
final Color? color;
final int popularity;
const Airing({
required this.anilistId,
required this.title,
required this.titleRomaji,
required this.episode,
required this.airingAt,
this.cover,
this.banner,
this.color,
this.popularity = 0,
});
/// Null for adult entries, music videos and malformed rows.
static Airing? fromJson(Object? j) {
if (j is! Map) return null;
final m = j['media'], at = j['airingAt'], ep = j['episode'];
if (m is! Map || at is! int || ep is! int) return null;
final id = m['id'];
if (id is! int || m['isAdult'] == true || m['format'] == 'MUSIC') return null;
final t = m['title'] is Map ? m['title'] as Map : const {};
final romaji = t['romaji'] is String ? t['romaji'] as String : '';
final english = t['english'] is String ? t['english'] as String : '';
final cov = m['coverImage'] is Map ? m['coverImage'] as Map : const {};
return Airing(
anilistId: id,
title: english.isNotEmpty ? english : (romaji.isNotEmpty ? romaji : '?'),
titleRomaji: romaji,
episode: ep,
airingAt: DateTime.fromMillisecondsSinceEpoch(at * 1000, isUtc: true),
cover: cov['large'] is String ? cov['large'] as String : null,
banner: m['bannerImage'] is String ? m['bannerImage'] as String : null,
color: parseHexColor(cov['color']),
popularity: m['popularity'] is int ? m['popularity'] as int : 0,
);
}
@override
String toString() => 'Airing($anilistId "$title" ep $episode @ $airingAt)';
}
/// An anime found by title ([AniListClient.searchMedia]).
class AniListMedia {
final int id;
/// English title, else romaji.
final String title;
/// Cover (portrait) URL: extraLarge, else large.
final String? cover;
/// The smaller cover, for when [cover] is too big to send on.
final String? coverSmall;
final String? banner;
const AniListMedia({required this.id, required this.title, this.cover, this.coverSmall, this.banner});
static AniListMedia? fromJson(Object? m) {
if (m is! Map || m['id'] is! int) return null;
final t = m['title'] is Map ? m['title'] as Map : const {};
final romaji = t['romaji'] is String ? t['romaji'] as String : '';
final english = t['english'] is String ? t['english'] as String : '';
final cov = m['coverImage'] is Map ? m['coverImage'] as Map : const {};
final large = cov['large'] is String ? cov['large'] as String : null;
return AniListMedia(
id: m['id'] as int,
title: english.isNotEmpty ? english : romaji,
cover: cov['extraLarge'] is String ? cov['extraLarge'] as String : large,
coverSmall: large,
banner: m['bannerImage'] is String ? m['bannerImage'] as String : null,
);
}
}
/// "#e4a15d" -> opaque [Color]; anything else -> null.
Color? parseHexColor(Object? s) {
if (s is! String || s.length != 7 || !s.startsWith('#')) return null;
final v = int.tryParse(s.substring(1), radix: 16);
return v == null ? null : Color(0xff000000 | v);
}
class AniListException implements Exception {
final int? statusCode;
final String message;
const AniListException(this.message, {this.statusCode});
bool get isRateLimited => statusCode == 429;
@override
String toString() => 'AniListException: $message';
}
/// The airings of one range. [staleSince] is set when AniList refused and
/// older cached data (fetched then) is returned instead.
class AniListSchedule {
final List<Airing> airings;
final DateTime fetchedAt;
final DateTime? staleSince;
const AniListSchedule(this.airings, this.fetchedAt, {this.staleSince});
bool get isStale => staleSince != null;
}
class _Cached {
final DateTime fetched;
final int from;
final int to;
final List<Object?> raw;
const _Cached(this.fetched, this.from, this.to, this.raw);
}
class AniListClient {
AniListClient({
String url = aniListDefaultUrl,
HttpClient? http,
Future<Directory?> Function()? cacheDir,
this.memoryTtl = const Duration(minutes: 10),
this.diskTtl = const Duration(hours: 1),
Future<void> Function(Duration)? delay,
DateTime Function()? now,
String? userAgent,
}) : url = Uri.parse(url),
_http = http ?? HttpClient(),
_ownsHttp = http == null,
_cacheDir = cacheDir ?? _defaultCacheDir,
_delay = delay ?? Future<void>.delayed,
_now = now ?? DateTime.now,
userAgent = userAgent ?? 'watch-together/$appVersion';
final Uri url;
final Duration memoryTtl;
final Duration diskTtl;
/// AniList 403s some default agents.
final String userAgent;
final HttpClient _http;
final bool _ownsHttp;
final Future<Directory?> Function() _cacheDir;
final Future<void> Function(Duration) _delay;
final DateTime Function() _now;
/// Pages per range at most (50 airings each).
static const maxPages = 40;
static const perPage = 50;
static const query = r'''
query($from:Int,$to:Int,$page:Int){Page(page:$page,perPage:50){pageInfo{hasNextPage}
airingSchedules(airingAt_greater:$from,airingAt_lesser:$to,sort:TIME){airingAt episode
media{id title{romaji english} coverImage{large color} bannerImage isAdult popularity format}}}}''';
final _mem = <int, _Cached>{};
final _inflight = <String, Future<AniListSchedule>>{};
static Future<Directory?> _defaultCacheDir() async {
try {
return await getApplicationCacheDirectory();
} catch (_) {
return null;
}
}
/// Every airing in [from, to), cached; see the file comment. [force]
/// skips the fresh caches (stale data is still the fallback).
Future<AniListSchedule> schedule(DateTime from, DateTime to, {bool force = false}) {
final f = from.millisecondsSinceEpoch ~/ 1000, t = to.millisecondsSinceEpoch ~/ 1000;
final k = '$f-$t-$force';
return _inflight[k] ??= _schedule(f, t, force).whenComplete(() {
_inflight.remove(k);
});
}
Future<AniListSchedule> _schedule(int from, int to, bool force) async {
final now = _now();
final mem = _mem[from];
final memHit = mem != null && mem.to == to ? mem : null;
if (!force && memHit != null && now.difference(memHit.fetched) < memoryTtl) {
return _result(memHit);
}
final disk = await _readDisk(from, to);
if (!force && disk != null && now.difference(disk.fetched) < diskTtl) {
_mem[from] = disk;
return _result(disk);
}
try {
final raw = await _fetch(from, to);
final c = _mem[from] = _Cached(_now(), from, to, raw);
await _writeDisk(c);
return _result(c);
} catch (e) {
final stale = [memHit, disk].whereType<_Cached>().fold<_Cached?>(
null, (a, b) => a == null || b.fetched.isAfter(a.fetched) ? b : a);
if (stale == null) rethrow;
_log.warning('AniList failed, using cache from ${stale.fetched}: $e');
return _result(stale, stale: true);
}
}
AniListSchedule _result(_Cached c, {bool stale = false}) => AniListSchedule(
[for (final r in c.raw) ?Airing.fromJson(r)],
c.fetched,
staleSince: stale ? c.fetched : null,
);
Future<List<Object?>> _fetch(int from, int to) async {
final out = <Object?>[];
for (var page = 1; page <= maxPages; page++) {
final data = await _page(from, to, page);
final rows = data['airingSchedules'];
if (rows is List) out.addAll(rows);
final info = data['pageInfo'];
if (info is! Map || info['hasNextPage'] != true) break;
}
return out;
}
Future<Map> _page(int from, int to, int page) async {
final (data, status, j) = await _graphql(query, {'from': from - 1, 'to': to, 'page': page});
final p = data?['Page'];
if (p is! Map) {
throw AniListException('AniList: bad response${_errorText(j)}', statusCode: status);
}
return p;
}
/// One GraphQL request: `data` (null when missing), status and body. A
/// 429 waits out Retry-After once; other non-200s throw.
Future<(Map?, int, Object?)> _graphql(String query, Map<String, Object?> variables) async {
for (var attempt = 0;; attempt++) {
final (status, body, retryAfter) = await _post(jsonEncode({'query': query, 'variables': variables}));
if (status == 429 && attempt == 0) {
final wait = retryAfter ?? 60;
_log.info('AniList rate limited, retrying in ${wait}s');
await _delay(Duration(seconds: wait.clamp(1, 65)));
continue;
}
Object? j;
try {
j = jsonDecode(body);
} catch (_) {}
if (status != 200) {
throw AniListException('AniList HTTP $status${_errorText(j)}', statusCode: status);
}
final data = j is Map && j['data'] is Map ? j['data'] as Map : null;
return (data, status, j);
}
}
static const searchQuery = r'''
query($search:String){Media(search:$search,type:ANIME,isAdult:false){id title{romaji english}
coverImage{extraLarge large} bannerImage}}''';
final _search = <String, Future<AniListMedia?>>{};
/// AniList's best anime match for [title], or null when there is none.
/// One request per title for this client's life (a failure is not kept,
/// so a later call asks again).
Future<AniListMedia?> searchMedia(String title) {
final key = title.trim();
if (key.isEmpty) return Future.value(null);
return _search[key] ??= _searchMedia(key).catchError((Object e) {
_search.remove(key);
throw e;
});
}
Future<AniListMedia?> _searchMedia(String title) async {
try {
final (data, _, _) = await _graphql(searchQuery, {'search': title});
return AniListMedia.fromJson(data?['Media']);
} on AniListException catch (e) {
// No match is a 404 with "Not Found." in errors.
if (e.statusCode == 404) return null;
rethrow;
}
}
static String _errorText(Object? j) {
final errs = j is Map ? j['errors'] : null;
if (errs is! List || errs.isEmpty) return '';
final m = errs.first is Map ? (errs.first as Map)['message'] : null;
return m is String ? ': $m' : '';
}
Future<(int, String, int?)> _post(String body) async {
try {
final req = await _http.postUrl(url).timeout(const Duration(seconds: 15));
req.headers
..contentType = ContentType.json
..set(HttpHeaders.acceptHeader, 'application/json')
..set(HttpHeaders.userAgentHeader, userAgent);
req.write(body);
final res = await req.close().timeout(const Duration(seconds: 20));
final text = await res.transform(const Utf8Decoder(allowMalformed: true)).join();
return (
res.statusCode,
text,
int.tryParse(res.headers.value(HttpHeaders.retryAfterHeader) ?? ''),
);
} on TimeoutException {
throw const AniListException('AniList timed out');
} on IOException catch (e) {
throw AniListException('AniList unreachable: $e');
}
}
// ---------------------------------------------------------------- disk
Future<File?> _file(int from) async {
final dir = await _cacheDir();
return dir == null ? null : File('${dir.path}/anilist-schedule-$from.json');
}
Future<_Cached?> _readDisk(int from, int to) async {
try {
final f = await _file(from);
if (f == null || !await f.exists()) return null;
final j = jsonDecode(await f.readAsString());
if (j is! Map || j['from'] != from || j['to'] != to) return null;
final fetched = j['fetched'], raw = j['raw'];
if (fetched is! int || raw is! List) return null;
return _Cached(DateTime.fromMillisecondsSinceEpoch(fetched), from, to, raw);
} catch (_) {
return null;
}
}
Future<void> _writeDisk(_Cached c) async {
try {
final f = await _file(c.from);
if (f == null) return;
await f.parent.create(recursive: true);
await f.writeAsString(jsonEncode({
'fetched': c.fetched.millisecondsSinceEpoch,
'from': c.from,
'to': c.to,
'raw': c.raw,
}));
// Older ranges are useless once a day has passed.
await for (final e in f.parent.list()) {
final name = e.uri.pathSegments.last;
if (e is File &&
e.path != f.path &&
name.startsWith('anilist-schedule-') &&
_now().difference(await e.lastModified()) > const Duration(days: 1)) {
await e.delete();
}
}
} catch (e) {
_log.warning('AniList cache write failed: $e');
}
}
/// Drops the memory cache (the disk cache stays as the stale fallback).
void clearMemory() => _mem.clear();
void close() {
if (_ownsHttp) _http.close(force: true);
}
}
@@ -0,0 +1,751 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:sync_relay_core/core.dart';
import '../jellyfin/accounts.dart';
// AnimeFlow app API: what the media server tracks, has, downloads and was
// asked for. Authenticated with the user's own Jellyfin access token
// (X-Emby-Token) on the Jellyfin server next to it.
//
// Errors are JSON {"detail","code"} ([AnimeFlowException]); nginx in front
// may answer 429 with HTML (5 req/s, burst 20), retried with backoff.
// /schedule, /series, /series/{id} and /requests send weak ETags: the last
// body is kept and revalidated with If-None-Match. Times are UTC ISO
// ("2026-10-08T19:36:32Z"). Scopes can change at runtime, so /me is cached
// for a minute at most.
final _log = createLogger('animeflow');
const animeFlowDefaultBase = 'https://anime.senkochan.moe/api/app/v1';
// ---------------------------------------------------------------- errors
enum AnimeFlowErrorCode {
missingToken('missing_token'),
badToken('bad_token'),
revoked('revoked'),
missingScope('missing_scope'),
ownAccount('own_account'),
jellyfinUnreachable('jellyfin_unreachable'),
searchUnavailable('search_unavailable'),
invalidRequest('invalid_request'),
invalidTime('invalid_time'),
invalidRange('invalid_range'),
notFound('not_found'),
seriesNotFound('series_not_found'),
episodeNotFound('episode_not_found'),
requestNotFound('request_not_found'),
userNotFound('user_not_found'),
alreadyTracked('already_tracked'),
alreadyAvailable('already_available'),
alreadyRequested('already_requested'),
invalidState('invalid_state'),
/// nginx 429 (client side, no JSON body).
rateLimited('rate_limited'),
/// No answer: DNS, connect, timeout (client side).
network('network'),
/// Anything not in this list.
unknown('unknown');
const AnimeFlowErrorCode(this.wire);
final String wire;
static AnimeFlowErrorCode parse(Object? s) =>
values.where((c) => c.wire == s).firstOrNull ?? unknown;
/// Best guess from the status alone (body was not JSON).
static AnimeFlowErrorCode forStatus(int status) => switch (status) {
401 => badToken,
403 => missingScope,
404 => notFound,
409 => invalidState,
400 => invalidRequest,
429 => rateLimited,
503 => jellyfinUnreachable,
_ => unknown,
};
}
class AnimeFlowException implements Exception {
/// HTTP status; 0 when no answer came.
final int statusCode;
final AnimeFlowErrorCode code;
final String detail;
const AnimeFlowException(this.statusCode, this.code, this.detail);
/// Sign in again (token missing, wrong or expired).
bool get isUnauthorized => statusCode == 401;
/// Revoked, missing scope or own account.
bool get isForbidden => statusCode == 403;
bool get isNotFound => statusCode == 404;
bool get isConflict => statusCode == 409;
bool get isUnavailable => statusCode == 503 || code == AnimeFlowErrorCode.network;
bool get isRateLimited => code == AnimeFlowErrorCode.rateLimited;
@override
String toString() => 'AnimeFlowException($statusCode ${code.wire}): $detail';
}
// ---------------------------------------------------------------- models
DateTime? _time(Object? v) => v is String ? DateTime.tryParse(v)?.toUtc() : null;
int? _int(Object? v) => v is int ? v : (v is num ? v.toInt() : null);
String? _str(Object? v) => v is String ? v : null;
List<Map> _items(Object? j) =>
j is Map && j['items'] is List ? (j['items'] as List).whereType<Map>().toList() : const [];
/// Formats [d] as AnimeFlow wants it: "2026-10-08T19:36:32Z".
String animeFlowTime(DateTime d) => '${d.toUtc().toIso8601String().split('.').first.replaceAll('Z', '')}Z';
enum AnimeFlowScope {
view,
request,
manage;
static Set<AnimeFlowScope> parse(Object? j) => {
if (j is List)
for (final s in j) ...values.where((v) => v.name == s),
};
}
class AnimeFlowMe {
final String userId;
final String name;
final Set<AnimeFlowScope> scopes;
const AnimeFlowMe({required this.userId, required this.name, required this.scopes});
bool can(AnimeFlowScope s) => scopes.contains(s);
static AnimeFlowMe fromJson(Object? j) {
final m = j is Map ? j : const {};
return AnimeFlowMe(
userId: _str(m['user_id']) ?? '',
name: _str(m['name']) ?? '',
scopes: AnimeFlowScope.parse(m['scopes']),
);
}
}
enum EpisodeStatus {
upcoming,
missing,
downloading,
available,
unmonitored,
failed;
/// Unknown values read as upcoming.
static EpisodeStatus parse(Object? s) => values.where((v) => v.name == s).firstOrNull ?? upcoming;
}
/// A series on Jellyfin (`GET /library`).
class AnimeFlowLibraryItem {
final String jellyfinSeriesId;
final String name;
/// Set when AnimeFlow tracks it.
final int? seriesId;
/// AniList entry per TVDB season (the first, when a season spans more).
final Map<int, int> anilistIds;
/// Every AniList entry per season with its episode run (`anilist_map`):
/// a season AniList splits (one cour as two entries) has several rows.
final List<AniListRun> anilistMap;
const AnimeFlowLibraryItem({
required this.jellyfinSeriesId,
required this.name,
this.seriesId,
this.anilistIds = const {},
this.anilistMap = const [],
});
static AnimeFlowLibraryItem fromJson(Map j) => AnimeFlowLibraryItem(
jellyfinSeriesId: _str(j['jellyfin_series_id']) ?? '',
name: _str(j['name']) ?? '',
seriesId: _int(j['series_id']),
anilistIds: {
for (final e in ((j['anilist_ids'] as Map?) ?? const {}).entries)
if (int.tryParse('${e.key}') case final season? when _int(e.value) != null) season: _int(e.value)!,
},
anilistMap: [
for (final r in (j['anilist_map'] as List?) ?? const [])
if (r is Map && _int(r['season']) != null && _int(r['anilist_id']) != null)
AniListRun(
season: _int(r['season'])!,
anilistId: _int(r['anilist_id'])!,
firstEpisode: _int(r['first_episode']) ?? 1,
offset: _int(r['offset']) ?? 0,
),
],
);
}
/// One AniList entry's run within a TVDB season: TVDB episode e (from
/// [firstEpisode] to the next run of that season) is AniList episode
/// e - [offset].
class AniListRun {
final int season;
final int anilistId;
final int firstEpisode;
final int offset;
const AniListRun({required this.season, required this.anilistId, this.firstEpisode = 1, this.offset = 0});
}
class AnimeFlowEpisode {
/// 0 = specials.
final int season;
final int episode;
final int? absolute;
final String? title;
/// Date-only: midnight Japan time of the broadcast day. Compare it as a
/// Japan day (`sameAirDay` in schedule_model.dart), never a local one.
final DateTime? airDate;
/// The AniList entry of this episode's season (one AnimeFlow series may
/// span several AniList entries).
final int? anilistId;
/// The episode number within [anilistId] when AnimeFlow knows it (split
/// cours, absolute numbering); prefer it over [episode] for matching.
final int? anilistEpisode;
final EpisodeStatus status;
/// 0..1, only while downloading.
final double? progress;
/// Why it failed; for a missing one with rejected releases, a
/// ready-to-show sentence ("2 release(s) found, rejected: …").
final String? reason;
/// Last search for a monitored, not yet downloaded episode.
final DateTime? lastSearchedAt;
const AnimeFlowEpisode({
required this.season,
required this.episode,
this.absolute,
this.title,
this.airDate,
this.anilistId,
this.anilistEpisode,
required this.status,
this.progress,
this.reason,
this.lastSearchedAt,
});
static AnimeFlowEpisode fromJson(Map j) => AnimeFlowEpisode(
season: _int(j['season']) ?? 0,
episode: _int(j['episode']) ?? 0,
absolute: _int(j['absolute']),
title: _str(j['episode_title']),
airDate: _time(j['air_date']),
// The episode's own entry (a split season rolls into its sequel),
// else the series' one.
anilistId: _int(j['episode_anilist_id']) ?? _int(j['anilist_id']),
anilistEpisode: _int(j['anilist_episode']),
status: EpisodeStatus.parse(j['status']),
progress: (j['progress'] as num?)?.toDouble(),
reason: _str(j['reason']),
lastSearchedAt: _time(j['last_searched_at']),
);
}
/// A /schedule row: one episode plus its series.
class AnimeFlowScheduleItem {
final AnimeFlowEpisode episode;
final int seriesId;
final String title;
final int? anidbId;
final int? tvdbId;
final String? jellyfinSeriesId;
const AnimeFlowScheduleItem({
required this.episode,
required this.seriesId,
required this.title,
this.anidbId,
this.tvdbId,
this.jellyfinSeriesId,
});
static AnimeFlowScheduleItem fromJson(Map j) => AnimeFlowScheduleItem(
episode: AnimeFlowEpisode.fromJson(j),
seriesId: _int(j['series_id']) ?? 0,
title: _str(j['title']) ?? '',
anidbId: _int(j['anidb_id']),
tvdbId: _int(j['tvdb_id']),
jellyfinSeriesId: _str(j['jellyfin_series_id']),
);
}
class AnimeFlowScheduleWindow {
final DateTime? from;
final DateTime? to;
final List<AnimeFlowScheduleItem> items;
const AnimeFlowScheduleWindow({this.from, this.to, required this.items});
}
enum SeriesState {
releasing,
released;
static SeriesState parse(Object? s) => s == 'released' ? released : releasing;
}
class AnimeFlowSeries {
final int seriesId;
final String title;
final int? anidbId;
final int? tvdbId;
/// The series' (first) AniList entry; later seasons may have others, see
/// [AnimeFlowEpisode.anilistId].
final int? anilistId;
final String? jellyfinSeriesId;
final SeriesState state;
final bool monitored;
final int episodesTotal;
final int episodesAvailable;
final DateTime? nextAirDate;
/// Only from [AnimeFlowClient.seriesDetail].
final List<AnimeFlowEpisode> episodes;
const AnimeFlowSeries({
required this.seriesId,
required this.title,
this.anidbId,
this.tvdbId,
this.anilistId,
this.jellyfinSeriesId,
this.state = SeriesState.releasing,
this.monitored = false,
this.episodesTotal = 0,
this.episodesAvailable = 0,
this.nextAirDate,
this.episodes = const [],
});
static AnimeFlowSeries fromJson(Map j) => AnimeFlowSeries(
seriesId: _int(j['series_id']) ?? 0,
title: _str(j['title']) ?? '',
anidbId: _int(j['anidb_id']),
tvdbId: _int(j['tvdb_id']),
anilistId: _int(j['anilist_id']),
jellyfinSeriesId: _str(j['jellyfin_series_id']),
state: SeriesState.parse(j['state']),
monitored: j['monitored'] == true,
episodesTotal: _int(j['episodes_total']) ?? 0,
episodesAvailable: _int(j['episodes_available']) ?? 0,
nextAirDate: _time(j['next_air_date']),
episodes: [
if (j['episodes'] is List)
for (final e in (j['episodes'] as List).whereType<Map>()) AnimeFlowEpisode.fromJson(e),
],
);
}
class AnimeFlowSearchHit {
final int anidbId;
final String title;
final int? anilistId;
/// Set when AnimeFlow already tracks it.
final int? seriesId;
const AnimeFlowSearchHit({required this.anidbId, required this.title, this.anilistId, this.seriesId});
static AnimeFlowSearchHit fromJson(Map j) => AnimeFlowSearchHit(
anidbId: _int(j['anidb_id']) ?? 0,
title: _str(j['title']) ?? '',
anilistId: _int(j['anilist_id']),
seriesId: _int(j['series_id']),
);
}
enum RequestKind {
series,
season,
episode;
static RequestKind parse(Object? s) => values.where((v) => v.name == s).firstOrNull ?? series;
}
enum RequestStatus {
/// Briefly, before auto-approval or a manager decides.
pending,
approved,
searching,
done,
rejected,
failed;
static RequestStatus parse(Object? s) => values.where((v) => v.name == s).firstOrNull ?? pending;
}
class AnimeFlowRequest {
final int id;
final RequestKind kind;
final RequestStatus status;
final String title;
final int? anidbId;
final int? seriesId;
final int? season;
final int? episode;
final String? reason;
final DateTime? createdAt;
final DateTime? updatedAt;
/// Only with scope=all.
final String? userId;
final String? userName;
const AnimeFlowRequest({
required this.id,
required this.kind,
required this.status,
required this.title,
this.anidbId,
this.seriesId,
this.season,
this.episode,
this.reason,
this.createdAt,
this.updatedAt,
this.userId,
this.userName,
});
static AnimeFlowRequest fromJson(Map j) => AnimeFlowRequest(
id: _int(j['id']) ?? 0,
kind: RequestKind.parse(j['kind']),
status: RequestStatus.parse(j['status']),
title: _str(j['title']) ?? '',
anidbId: _int(j['anidb_id']),
seriesId: _int(j['series_id']),
season: _int(j['season']),
episode: _int(j['episode']),
reason: _str(j['reason']),
createdAt: _time(j['created_at']),
updatedAt: _time(j['updated_at']),
userId: _str(j['user_id']),
userName: _str(j['user_name']),
);
}
class AnimeFlowUser {
/// The Jellyfin user id.
final String userId;
final String name;
final Set<AnimeFlowScope> scopes;
final bool revoked;
final DateTime? lastSeenAt;
const AnimeFlowUser({
required this.userId,
required this.name,
required this.scopes,
this.revoked = false,
this.lastSeenAt,
});
static AnimeFlowUser fromJson(Map j) => AnimeFlowUser(
userId: _str(j['user_id']) ?? '',
name: _str(j['name']) ?? '',
scopes: AnimeFlowScope.parse(j['scopes']),
revoked: j['revoked'] == true,
lastSeenAt: _time(j['last_seen_at']),
);
}
// ---------------------------------------------------------------- client
class _Etagged {
final String etag;
final Object? body;
const _Etagged(this.etag, this.body);
}
class AnimeFlowClient {
/// [token] wins over [tokenProvider]; with neither, every call fails with
/// [AnimeFlowErrorCode.missingToken].
AnimeFlowClient({
String baseUrl = animeFlowDefaultBase,
this._token,
this._tokenProvider,
HttpClient? http,
Future<void> Function(Duration)? delay,
DateTime Function()? now,
this.meTtl = const Duration(seconds: 60),
String? userAgent,
}) : baseUrl = baseUrl.endsWith('/') ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl,
_http = http ?? HttpClient(),
_ownsHttp = http == null,
_delay = delay ?? Future<void>.delayed,
_now = now ?? DateTime.now,
userAgent = userAgent ?? 'watch-together/$appVersion';
/// Authenticated with the linked Jellyfin account on the same domain
/// (see [tokenFor]), looked up on every call so a re-link applies.
factory AnimeFlowClient.fromAccounts(
JellyfinAccounts accounts, {
String baseUrl = animeFlowDefaultBase,
HttpClient? http,
}) =>
AnimeFlowClient(
baseUrl: baseUrl,
http: http,
tokenProvider: () async {
await accounts.ensureLoaded(validate: false);
return tokenFor(accounts.accounts, baseUrl);
},
);
final String baseUrl;
final Duration meTtl;
final String userAgent;
final String? _token;
final Future<String?> Function()? _tokenProvider;
final HttpClient _http;
final bool _ownsHttp;
final Future<void> Function(Duration) _delay;
final DateTime Function() _now;
/// Request bodies above this are refused by the server.
static const maxBodyBytes = 16 * 1024;
/// Retries after an nginx 429 (backoff 1 s, 2 s unless Retry-After).
static const rateLimitRetries = 2;
/// The longest /schedule window.
static const maxScheduleRange = Duration(days: 45);
String? _lastToken;
final _etags = <String, _Etagged>{};
({DateTime at, AnimeFlowMe me})? _me;
/// The token of the linked Jellyfin account whose host shares
/// [animeFlowBase]'s parent domain (anime.example.org -> example.org,
/// jellyfin.example.org, ...).
static String? tokenFor(Iterable<JellyfinAccount> accounts, String animeFlowBase) {
final host = Uri.tryParse(animeFlowBase)?.host.toLowerCase() ?? '';
if (host.isEmpty) return null;
final labels = host.split('.');
final parent = labels.length > 2 ? labels.skip(1).join('.') : host;
bool matches(String url) {
final h = Uri.tryParse(url)?.host.toLowerCase() ?? '';
return h.isNotEmpty && (h == parent || h.endsWith('.$parent'));
}
for (final a in accounts) {
if (a.urls.any(matches)) return a.token;
}
return null;
}
Future<String> _authToken() async {
final t = _token ?? await _tokenProvider?.call();
if (t == null || t.isEmpty) {
throw const AnimeFlowException(
401, AnimeFlowErrorCode.missingToken, 'No linked Jellyfin account for AnimeFlow');
}
if (t != _lastToken) {
// Another user: nothing cached belongs to them.
_lastToken = t;
_etags.clear();
_me = null;
}
return t;
}
Uri _uri(String path, [Map<String, String>? query]) =>
Uri.parse('$baseUrl$path').replace(queryParameters: query);
/// Sends one call; JSON body or null (204). [etag]: revalidate the last
/// body of this URL.
Future<Object?> _call(String method, String path,
{Map<String, String>? query, Object? body, bool etag = false}) async {
final token = await _authToken();
final uri = _uri(path, query);
final key = uri.toString();
List<int>? bytes;
if (body != null) {
bytes = utf8.encode(jsonEncode(body));
if (bytes.length > maxBodyBytes) {
throw const AnimeFlowException(400, AnimeFlowErrorCode.invalidRequest, 'Request body too large');
}
}
for (var attempt = 0;; attempt++) {
final cached = etag ? _etags[key] : null;
final (status, text, retryAfter, tag) = await _send(method, uri, token, bytes, cached?.etag);
if (status == 304 && cached != null) return cached.body;
if (status >= 200 && status < 300) {
final j = text.trim().isEmpty ? null : _decode(text, status);
if (etag && tag != null && tag.isNotEmpty) _etags[key] = _Etagged(tag, j);
return j;
}
final err = _error(status, text);
if (err.isRateLimited && attempt < rateLimitRetries) {
final wait = retryAfter != null ? Duration(seconds: retryAfter.clamp(1, 30)) : Duration(seconds: 1 << attempt);
_log.info('AnimeFlow rate limited on $path, retrying in ${wait.inSeconds}s');
await _delay(wait);
continue;
}
throw err;
}
}
static Object? _decode(String text, int status) {
try {
return jsonDecode(text);
} catch (_) {
throw AnimeFlowException(status, AnimeFlowErrorCode.unknown, 'Response is not JSON');
}
}
static AnimeFlowException _error(int status, String text) {
Object? j;
try {
j = jsonDecode(text);
} catch (_) {}
if (j is Map && j['code'] is String) {
final code = AnimeFlowErrorCode.parse(j['code']);
return AnimeFlowException(status, code == AnimeFlowErrorCode.unknown ? AnimeFlowErrorCode.forStatus(status) : code,
_str(j['detail']) ?? 'HTTP $status');
}
// Not ours: nginx (HTML 429/502), a proxy, ...
return AnimeFlowException(status, AnimeFlowErrorCode.forStatus(status),
status == 429 ? 'Too many requests, try again shortly' : 'HTTP $status');
}
Future<(int, String, int?, String?)> _send(
String method, Uri uri, String token, List<int>? body, String? ifNoneMatch) async {
try {
final req = await _http.openUrl(method, uri).timeout(const Duration(seconds: 15));
req.headers
..set(HttpHeaders.acceptHeader, 'application/json')
..set(HttpHeaders.userAgentHeader, userAgent)
..set('X-Emby-Token', token);
if (ifNoneMatch != null) req.headers.set(HttpHeaders.ifNoneMatchHeader, ifNoneMatch);
if (body != null) {
req.headers.contentType = ContentType.json;
req.contentLength = body.length;
req.add(body);
}
final res = await req.close().timeout(const Duration(seconds: 20));
final text = await res.transform(const Utf8Decoder(allowMalformed: true)).join();
return (
res.statusCode,
text,
int.tryParse(res.headers.value(HttpHeaders.retryAfterHeader) ?? ''),
res.headers.value(HttpHeaders.etagHeader),
);
} on TimeoutException {
throw const AnimeFlowException(0, AnimeFlowErrorCode.network, 'AnimeFlow timed out');
} on IOException catch (e) {
throw AnimeFlowException(0, AnimeFlowErrorCode.network, 'AnimeFlow unreachable: $e');
}
}
// ------------------------------------------------------------ endpoints
/// Who the token belongs to and what they may do. Cached for [meTtl]
/// (scopes change at runtime).
Future<AnimeFlowMe> me({bool force = false}) async {
await _authToken();
final c = _me;
if (!force && c != null && _now().difference(c.at) < meTtl) return c.me;
final me = AnimeFlowMe.fromJson(await _call('GET', '/me'));
_me = (at: _now(), me: me);
return me;
}
/// Episodes airing in [from, to) (at most 45 days) [view].
Future<AnimeFlowScheduleWindow> schedule(DateTime from, DateTime to) async {
if (!from.isBefore(to) || to.difference(from) > maxScheduleRange) {
throw const AnimeFlowException(400, AnimeFlowErrorCode.invalidRange, 'from must be before to, at most 45 days');
}
final j = await _call('GET', '/schedule',
query: {'from': animeFlowTime(from), 'to': animeFlowTime(to)}, etag: true);
final m = j is Map ? j : const {};
return AnimeFlowScheduleWindow(
from: _time(m['from']),
to: _time(m['to']),
items: [for (final e in _items(j)) AnimeFlowScheduleItem.fromJson(e)],
);
}
/// Every tracked series [view].
Future<List<AnimeFlowSeries>> series() async =>
[for (final s in _items(await _call('GET', '/series', etag: true))) AnimeFlowSeries.fromJson(s)];
/// Every series on Jellyfin (as the caller sees it), tracked by
/// AnimeFlow or not [view]. Empty on servers without the endpoint.
Future<List<AnimeFlowLibraryItem>> library() async {
try {
return [for (final s in _items(await _call('GET', '/library', etag: true))) AnimeFlowLibraryItem.fromJson(s)];
} on AnimeFlowException catch (e) {
if (e.isNotFound) return const [];
rethrow;
}
}
/// One series with all its episodes [view].
Future<AnimeFlowSeries> seriesDetail(int seriesId) async {
final j = await _call('GET', '/series/$seriesId', etag: true);
return AnimeFlowSeries.fromJson(j is Map ? j : const {});
}
/// Titles that can be requested [request].
Future<List<AnimeFlowSearchHit>> search(String q) async => [
for (final h in _items(await _call('GET', '/search', query: {'q': q}))) AnimeFlowSearchHit.fromJson(h),
];
/// Asks for a whole new series by its AniDB id [request].
Future<AnimeFlowRequest> requestSeries(int anidbId) => _request({'anidb_id': anidbId});
/// Asks for one season of a tracked series [request].
Future<AnimeFlowRequest> requestSeason(int seriesId, int season) =>
_request({'series_id': seriesId, 'season': season});
/// Asks for one episode of a tracked series [request].
Future<AnimeFlowRequest> requestEpisode(int seriesId, int season, int episode) =>
_request({'series_id': seriesId, 'season': season, 'episode': episode});
Future<AnimeFlowRequest> _request(Map<String, Object> body) async {
final j = await _call('POST', '/requests', body: body);
return AnimeFlowRequest.fromJson(j is Map ? j : const {});
}
/// Own requests, newest first [view]; [all] = everyone's, with the user
/// [manage].
Future<List<AnimeFlowRequest>> requests({bool all = false}) async => [
for (final r in _items(await _call('GET', '/requests', query: all ? {'scope': 'all'} : null, etag: true)))
AnimeFlowRequest.fromJson(r),
];
/// [manage]. Returns the updated request when the server sends one.
Future<AnimeFlowRequest?> approve(int requestId) async =>
_maybeRequest(await _call('POST', '/requests/$requestId/approve'));
/// [manage].
Future<AnimeFlowRequest?> reject(int requestId, {String? reason}) async => _maybeRequest(
await _call('POST', '/requests/$requestId/reject', body: {'reason': ?reason}));
static AnimeFlowRequest? _maybeRequest(Object? j) =>
j is Map && j['id'] != null ? AnimeFlowRequest.fromJson(j) : null;
/// [manage].
Future<List<AnimeFlowUser>> users() async =>
[for (final u in _items(await _call('GET', '/users'))) AnimeFlowUser.fromJson(u)];
/// Sets a user's scopes (and revocation) [manage]. Returns the updated
/// user when the server sends one.
Future<AnimeFlowUser?> setScopes(String jellyfinUserId, Set<AnimeFlowScope> scopes, {bool? revoked}) async {
final j = await _call('PUT', '/users/${Uri.encodeComponent(jellyfinUserId)}/scopes', body: {
'scopes': [for (final s in AnimeFlowScope.values) if (scopes.contains(s)) s.name],
'revoked': ?revoked,
});
_me = null; // could be our own
return j is Map && j['user_id'] != null ? AnimeFlowUser.fromJson(j) : null;
}
/// Forgets cached bodies and /me (e.g. on a manual refresh).
void clearCache() {
_etags.clear();
_me = null;
}
void close() {
if (_ownsHttp) _http.close(force: true);
}
}
@@ -0,0 +1,457 @@
import 'dart:async';
import 'anilist.dart';
import 'animeflow.dart';
// The Schedule tab's data: AniList says what airs when, AnimeFlow says
// what the media server has of it. [matchSchedule] merges the two and
// [ScheduleRepository] loads a window of both, degrading to AniList alone
// when AnimeFlow is unreachable or not linked.
enum ScheduleStatus {
available,
downloading,
upcoming,
/// AnimeFlow's `missing`: aired, monitored, no release grabbed yet;
/// AnimeFlow grabs it by itself once one appears (nothing to request).
/// Also an aired episode of a tracked series AnimeFlow has no row for.
missing,
failed,
/// AnimeFlow does not monitor it: the only status worth a Request.
unmonitored,
/// AnimeFlow does not track this series (or is unreachable).
notTracked,
/// Not tracked by AnimeFlow, the series is on Jellyfin (`/library`) and
/// so is the episode ([confirmOnJellyfin]; [matchSchedule] alone sets
/// it from the series).
inLibrary,
/// Not tracked, the series is on Jellyfin, the episode aired but is not
/// there yet.
notOnJellyfin,
/// Not tracked, the series is on Jellyfin, the episode aired; whether
/// it is there is unknown (no linked account, lookup failed).
aired;
static ScheduleStatus of(EpisodeStatus s) => switch (s) {
EpisodeStatus.available => available,
EpisodeStatus.downloading => downloading,
EpisodeStatus.upcoming => upcoming,
EpisodeStatus.missing => missing,
EpisodeStatus.failed => failed,
EpisodeStatus.unmonitored => unmonitored,
};
}
/// One airing with what AnimeFlow knows about it.
class ScheduleEntry {
final Airing airing;
final ScheduleStatus status;
/// 0..1 while downloading.
final double? progress;
/// Why it failed, or (missing) why the releases found were rejected.
final String? reason;
/// AnimeFlow's last search for it (missing).
final DateTime? lastSearchedAt;
/// AnimeFlow tracks the series.
final bool tracked;
/// AnimeFlow's series id (requests).
final int? seriesId;
/// The AnimeFlow season of the matched episode (requests); untracked
/// on Jellyfin: the TVDB season of the AniList entry.
final int? season;
/// The TVDB episode number in [season]: tracked, AnimeFlow's own;
/// untracked on Jellyfin, AniList episode + the `anilist_map` run's
/// offset.
final int? jellyfinEpisode;
/// Play target once available.
final String? jellyfinSeriesId;
/// This user's progress on the Jellyfin episode ([confirmOnJellyfin]);
/// null when it is not on Jellyfin or was not looked up.
final EpisodeWatch? watch;
/// The series is on Jellyfin (tracked or not).
bool get onJellyfin => jellyfinSeriesId != null;
const ScheduleEntry({
required this.airing,
required this.status,
this.progress,
this.reason,
this.lastSearchedAt,
this.tracked = false,
this.seriesId,
this.season,
this.jellyfinEpisode,
this.jellyfinSeriesId,
this.watch,
});
ScheduleEntry copyWith({ScheduleStatus? status, EpisodeWatch? watch}) => ScheduleEntry(
airing: airing,
status: status ?? this.status,
progress: progress,
reason: reason,
lastSearchedAt: lastSearchedAt,
tracked: tracked,
seriesId: seriesId,
season: season,
jellyfinEpisode: jellyfinEpisode,
jellyfinSeriesId: jellyfinSeriesId,
watch: watch ?? this.watch,
);
@override
String toString() => 'ScheduleEntry(${airing.anilistId}#${airing.episode} $status)';
}
/// Without AnimeFlow: future airings are upcoming, past ones unknown.
ScheduleEntry untrackedEntry(Airing a, DateTime now) => ScheduleEntry(
airing: a,
status: a.airingAt.isAfter(now) ? ScheduleStatus.upcoming : ScheduleStatus.notTracked,
);
/// This user's state of a Jellyfin episode (its `UserData`).
class EpisodeWatch {
final bool played;
/// 0..1: the resume point of a partly watched episode (0 = none).
final double progress;
const EpisodeWatch({this.played = false, this.progress = 0});
@override
bool operator ==(Object other) => other is EpisodeWatch && other.played == played && other.progress == progress;
@override
int get hashCode => Object.hash(played, progress);
@override
String toString() => 'EpisodeWatch(${played ? 'played' : '$progress'})';
}
/// One episode Jellyfin has of a series, as the schedule needs it.
class JellyfinEpisodeRef {
final int season;
final int episode;
/// Jellyfin's date-only premiere date.
final DateTime? premiere;
final EpisodeWatch watch;
const JellyfinEpisodeRef(this.season, this.episode, {this.premiere, this.watch = const EpisodeWatch()});
}
// Air dates (AnimeFlow's `air_date`, Jellyfin's premiere date) are
// date-only: the Japanese broadcast day, sent as midnight Japan time (or
// midnight UTC by older servers). Both land on the right day read as a
// Japan (UTC+9, no DST) calendar day, never as a local one: midnight JST
// is the evening before in Europe and America.
const _jst = Duration(hours: 9);
/// The Japan calendar day of [t].
DateTime _jstDay(DateTime t) {
final j = t.toUtc().add(_jst);
return DateTime.utc(j.year, j.month, j.day);
}
String _dayKey(DateTime d) => '${d.year}-${d.month}-${d.day}';
/// The Japan days whose air date fits a broadcast at [airingAt]: its own
/// and, for a late-night slot (before 05:00 JST, "25:30"), the evening
/// before, which listings often give.
List<DateTime> _airDays(DateTime airingAt) {
final j = airingAt.toUtc().add(_jst);
final d = DateTime.utc(j.year, j.month, j.day);
return [d, if (j.hour < 5) DateTime.utc(j.year, j.month, j.day - 1)];
}
/// The date-only [airDate] is the broadcast day of [airingAt] (see
/// [_airDays]).
bool sameAirDay(DateTime airDate, DateTime airingAt) => _airDays(airingAt).contains(_jstDay(airDate));
class _SeriesRef {
final int seriesId;
final String? jellyfinSeriesId;
_SeriesRef(this.seriesId, this.jellyfinSeriesId);
}
/// Merges AniList [airings] with AnimeFlow's [schedule] rows and tracked
/// [series]. Per airing: the episode with the same AniList id (that
/// episode's season's entry) and episode number, else absolute number,
/// else the same local air day; unmatched airings of a tracked AniList id
/// are missing once aired, upcoming before; the rest are not tracked.
List<ScheduleEntry> matchSchedule(
List<Airing> airings, {
required List<AnimeFlowScheduleItem> schedule,
List<AnimeFlowSeries> series = const [],
List<AnimeFlowLibraryItem> library = const [],
DateTime? now,
}) {
final at = now ?? DateTime.now();
// AniList id -> (Jellyfin series, its TVDB season, TVDB - AniList
// episode offset) from /library.
final lib = <int, (String, int, int)>{};
for (final item in library) {
if (item.jellyfinSeriesId.isEmpty) continue;
for (final r in item.anilistMap) {
lib[r.anilistId] ??= (item.jellyfinSeriesId, r.season, r.offset);
}
for (final e in item.anilistIds.entries) {
lib[e.value] ??= (item.jellyfinSeriesId, e.key, 0);
}
}
final refs = <int, _SeriesRef>{};
for (final s in series) {
final id = s.anilistId;
if (id != null) refs[id] ??= _SeriesRef(s.seriesId, s.jellyfinSeriesId);
}
// AnimeFlow's AniList episode number wins outright: TVDB numbering runs
// on across split entries (JoJo S6E4 is "2nd & 3rd STAGE" ep 3), so a TVDB
// or absolute number, or an air day, is only a guess for AniList entries
// AnimeFlow gives no AniList numbers for.
final byAni = <String, AnimeFlowScheduleItem>{};
final byEp = <String, AnimeFlowScheduleItem>{};
final byAbs = <String, AnimeFlowScheduleItem>{};
final byDay = <String, AnimeFlowScheduleItem>{};
final numbered = <int>{};
for (final it in schedule) {
final e = it.episode, id = e.anilistId;
if (id == null) continue;
refs[id] ??= _SeriesRef(it.seriesId, it.jellyfinSeriesId);
if (e.anilistEpisode != null) {
numbered.add(id);
byAni.putIfAbsent('$id#${e.anilistEpisode}', () => it);
}
byEp.putIfAbsent('$id#${e.episode}', () => it);
if (e.absolute != null) byAbs.putIfAbsent('$id#${e.absolute}', () => it);
if (e.airDate != null) byDay.putIfAbsent('$id@${_dayKey(_jstDay(e.airDate!))}', () => it);
}
return [
for (final a in airings)
() {
final id = a.anilistId;
final ref = refs[id];
final it = numbered.contains(id)
? byAni['$id#${a.episode}']
: byEp['$id#${a.episode}'] ??
byAbs['$id#${a.episode}'] ??
_airDays(a.airingAt).map((d) => byDay['$id@${_dayKey(d)}']).nonNulls.firstOrNull;
if (it != null) {
final e = it.episode;
return ScheduleEntry(
airing: a,
status: ScheduleStatus.of(e.status),
progress: e.status == EpisodeStatus.downloading ? e.progress : null,
reason: e.status == EpisodeStatus.failed || e.status == EpisodeStatus.missing ? e.reason : null,
lastSearchedAt: e.lastSearchedAt,
tracked: true,
seriesId: it.seriesId,
season: e.season,
jellyfinEpisode: e.episode,
jellyfinSeriesId: it.jellyfinSeriesId ?? ref?.jellyfinSeriesId ?? lib[id]?.$1,
);
}
if (ref == null) {
final onJf = lib[id];
if (onJf == null) return ScheduleEntry(airing: a, status: ScheduleStatus.notTracked);
return ScheduleEntry(
airing: a,
status: a.airingAt.isAfter(at) ? ScheduleStatus.upcoming : ScheduleStatus.inLibrary,
season: onJf.$2,
jellyfinEpisode: a.episode + onJf.$3,
jellyfinSeriesId: onJf.$1,
);
}
return ScheduleEntry(
airing: a,
status: a.airingAt.isAfter(at) ? ScheduleStatus.upcoming : ScheduleStatus.missing,
tracked: true,
seriesId: ref.seriesId,
jellyfinSeriesId: ref.jellyfinSeriesId ?? lib[id]?.$1,
);
}(),
];
}
/// The episodes [confirmOnJellyfin] looks up: on Jellyfin, untracked
/// ([ScheduleStatus.inLibrary]) or available through AnimeFlow.
bool needsJellyfinLookup(ScheduleEntry e) =>
e.jellyfinSeriesId != null && (e.status == ScheduleStatus.inLibrary || e.status == ScheduleStatus.available);
/// Checks [matchSchedule]'s entries against the episodes Jellyfin has:
/// [episodes] maps a Jellyfin series id to its episodes. An
/// [ScheduleStatus.inLibrary] one found stays inLibrary, absent becomes
/// [ScheduleStatus.notOnJellyfin], a series missing from [episodes] (not
/// looked up) [ScheduleStatus.aired]. Found episodes (inLibrary or
/// available) get this user's [ScheduleEntry.watch]: (season, episode),
/// else the one premiered that day.
List<ScheduleEntry> confirmOnJellyfin(List<ScheduleEntry> entries, Map<String, List<JellyfinEpisodeRef>> episodes) => [
for (final e in entries)
if (!needsJellyfinLookup(e))
e
else
switch ((episodes[e.jellyfinSeriesId], e.status)) {
(null, ScheduleStatus.inLibrary) => e.copyWith(status: ScheduleStatus.aired),
(null, _) => e,
(final have?, _) => switch (_episodeOf(have, e)) {
final x? => e.copyWith(watch: x.watch),
null when e.status == ScheduleStatus.inLibrary => e.copyWith(status: ScheduleStatus.notOnJellyfin),
null => e,
},
},
];
JellyfinEpisodeRef? _episodeOf(List<JellyfinEpisodeRef> have, ScheduleEntry e) {
final byNumber = have.where((x) => x.season == e.season && x.episode == e.jellyfinEpisode).firstOrNull;
if (byNumber != null || !e.tracked) return byNumber;
// AnimeFlow's numbering missed (specials, renumbered seasons): the
// episode premiered that day, when only one did.
return have.where((x) => x.premiere != null && sameAirDay(x.premiere!, e.airing.airingAt)).singleOrNull;
}
/// The episodes Jellyfin has of a series, as the linked account sees
/// them; null when it cannot tell (no linked account for that server).
typedef JellyfinEpisodeLookup = Future<List<JellyfinEpisodeRef>?> Function(String jellyfinSeriesId);
/// One loaded window. Errors are display text; [entries] is empty only
/// when AniList failed with nothing cached.
class ScheduleSnapshot {
final DateTime from;
final DateTime to;
/// Sorted by air time.
final List<ScheduleEntry> entries;
/// AniList failed and nothing was cached.
final String? aniListError;
/// AniList failed; [entries] come from the cache fetched then.
final DateTime? aniListStaleSince;
/// AnimeFlow is unreachable, refused or not linked: statuses are
/// AniList-only (upcoming / not tracked).
final String? animeFlowError;
/// The structured AnimeFlow failure behind [animeFlowError].
final AnimeFlowException? animeFlowException;
/// Who we are on AnimeFlow (null when it failed).
final AnimeFlowMe? me;
const ScheduleSnapshot({
required this.from,
required this.to,
required this.entries,
this.aniListError,
this.aniListStaleSince,
this.animeFlowError,
this.animeFlowException,
this.me,
});
bool get hasAnimeFlow => me != null;
}
/// Loads AniList and AnimeFlow for a window and merges them.
class ScheduleRepository {
ScheduleRepository({required this.aniList, this.animeFlow, this.jellyfinEpisodes, DateTime Function()? now})
: _now = now ?? DateTime.now;
final AniListClient aniList;
/// Null = not configured (AniList only).
final AnimeFlowClient? animeFlow;
/// Confirms "on Jellyfin" and this user's watched state per episode
/// ([confirmOnJellyfin]); null = never confirmed (aired entries read
/// [ScheduleStatus.aired], nothing shows as watched).
final JellyfinEpisodeLookup? jellyfinEpisodes;
final DateTime Function() _now;
/// Last week's Monday 00:00 local .. 4 weeks later.
static ({DateTime from, DateTime to}) defaultWindow(DateTime now) {
final l = now.toLocal();
final from = DateTime(l.year, l.month, l.day - (l.weekday - 1) - 7);
return (from: from, to: DateTime(from.year, from.month, from.day + 28));
}
Future<ScheduleSnapshot> load({DateTime? from, DateTime? to, bool force = false}) async {
final w = defaultWindow(_now());
final start = from ?? w.from, end = to ?? w.to;
final flowF = _animeFlow(start, end, force);
final AniListSchedule ani;
try {
ani = await aniList.schedule(start, end, force: force);
} catch (e) {
unawaited(flowF.then((_) {}, onError: (_) {}));
return ScheduleSnapshot(from: start, to: end, entries: const [], aniListError: _text(e));
}
final now = _now();
final flow = await flowF;
final airings = [...ani.airings]..sort((a, b) => a.airingAt.compareTo(b.airingAt));
final entries = flow.error == null
? await _confirm(
matchSchedule(airings, schedule: flow.schedule, series: flow.series, library: flow.library, now: now))
: [for (final a in airings) untrackedEntry(a, now)];
return ScheduleSnapshot(
from: start,
to: end,
entries: entries,
aniListStaleSince: ani.staleSince,
animeFlowError: flow.error == null ? null : _text(flow.error!),
animeFlowException: flow.error is AnimeFlowException ? flow.error as AnimeFlowException : null,
me: flow.me,
);
}
/// Looks up the episodes of every series an entry on Jellyfin is on
/// ([needsJellyfinLookup]), in parallel, one request per series; a
/// failed or impossible lookup leaves that series out.
Future<List<ScheduleEntry>> _confirm(List<ScheduleEntry> entries) async {
final lookup = jellyfinEpisodes;
final ids = {
for (final e in entries)
if (needsJellyfinLookup(e)) e.jellyfinSeriesId!,
};
final found = <String, List<JellyfinEpisodeRef>>{};
if (lookup != null) {
await Future.wait([
for (final id in ids)
lookup(id).then((eps) {
if (eps != null) found[id] = eps;
}, onError: (_) {}),
]);
}
return confirmOnJellyfin(entries, found);
}
Future<({AnimeFlowMe? me, List<AnimeFlowScheduleItem> schedule, List<AnimeFlowSeries> series, List<AnimeFlowLibraryItem> library, Object? error})>
_animeFlow(DateTime from, DateTime to, bool force) async {
final c = animeFlow;
if (c == null) {
return (me: null, schedule: const <AnimeFlowScheduleItem>[], series: const <AnimeFlowSeries>[],
library: const <AnimeFlowLibraryItem>[],
error: 'AnimeFlow is not configured');
}
try {
if (force) c.clearCache();
final me = await c.me();
final (sched, series) = await (c.schedule(from, to), c.series()).wait;
// The library only adds "on Jellyfin" for untracked shows: its
// failure never fails the schedule.
List<AnimeFlowLibraryItem> library = const [];
try {
library = await c.library();
} catch (_) {}
return (me: me, schedule: sched.items, series: series, library: library, error: null);
} on ParallelWaitError catch (e) {
final errs = e.errors as (AsyncError?, AsyncError?);
return (
me: null,
schedule: const <AnimeFlowScheduleItem>[],
series: const <AnimeFlowSeries>[],
library: const <AnimeFlowLibraryItem>[],
error: (errs.$1 ?? errs.$2)!.error,
);
} catch (e) {
return (me: null, schedule: const <AnimeFlowScheduleItem>[], series: const <AnimeFlowSeries>[], library: const <AnimeFlowLibraryItem>[], error: e);
}
}
static String _text(Object e) => switch (e) {
AnimeFlowException(:final code, :final detail) => switch (code) {
AnimeFlowErrorCode.missingToken => 'Link the Jellyfin server to see AnimeFlow statuses',
AnimeFlowErrorCode.badToken => 'AnimeFlow: sign in to Jellyfin again',
AnimeFlowErrorCode.revoked => 'AnimeFlow: access revoked',
AnimeFlowErrorCode.rateLimited => 'AnimeFlow: too many requests, try again shortly',
_ => 'AnimeFlow: $detail',
},
AniListException(:final message) => message,
_ => '$e',
};
}
@@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import '../../shell/avatar.dart';
import '../animeflow.dart';
// The Schedule page's side sheet: AnimeFlow requests (everyone's for the
// owner, who can retry failed ones and decline the rest; own otherwise).
Color requestColor(RequestStatus s) => switch (s) {
RequestStatus.pending || RequestStatus.searching => Colors.amber,
RequestStatus.approved => Colors.lightBlueAccent,
RequestStatus.rejected || RequestStatus.failed => Colors.redAccent,
RequestStatus.done => Colors.greenAccent,
};
String requestWhat(AnimeFlowRequest r) => switch (r.kind) {
RequestKind.series => r.title,
RequestKind.season => '${r.title} · season ${r.season ?? '?'}',
RequestKind.episode => '${r.title} · S${r.season ?? '?'}E${r.episode ?? '?'}',
};
class RequestsSheet extends StatelessWidget {
/// Scope manage: everyone's requests, Retry and Decline.
final bool owner;
/// Null while loading.
final List<AnimeFlowRequest>? requests;
final String? error;
/// Our own name, for requests without a user name.
final String selfName;
/// Requests with a call in flight.
final Set<int> busy;
final void Function(AnimeFlowRequest r) onRetry;
final void Function(AnimeFlowRequest r) onDecline;
final VoidCallback onClose;
const RequestsSheet({
super.key,
required this.owner,
required this.requests,
required this.selfName,
required this.onRetry,
required this.onDecline,
required this.onClose,
this.error,
this.busy = const {},
});
Widget _tile(BuildContext context, AnimeFlowRequest r) {
final scheme = Theme.of(context).colorScheme;
final who = r.userName ?? (selfName.isEmpty ? 'you' : selfName);
final canRetry = owner && r.status == RequestStatus.failed;
final canDecline =
owner &&
(r.status == RequestStatus.pending ||
r.status == RequestStatus.approved ||
r.status == RequestStatus.searching);
final inFlight = busy.contains(r.id);
return Padding(
key: ValueKey('request-${r.id}'),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StatusAvatar(
info: AvatarInfo(name: who, color: colorForName(who)),
size: 32,
ring: false,
badges: false,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(requestWhat(r), style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 2),
Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(who, style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12)),
Pill(r.status.name, requestColor(r.status)),
],
),
if (r.reason != null && r.reason!.isNotEmpty)
Text(r.reason!, style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 11)),
if (canRetry || canDecline)
Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
children: [
if (canRetry)
FilledButton.tonal(
key: ValueKey('retry-${r.id}'),
onPressed: inFlight ? null : () => onRetry(r),
child: const Text('Retry'),
),
if (canDecline)
TextButton(
key: ValueKey('decline-${r.id}'),
onPressed: inFlight ? null : () => onDecline(r),
child: const Text('Decline'),
),
if (inFlight) ...[
const SizedBox(width: 8),
const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2)),
],
],
),
),
],
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final reqs = requests;
return Material(
color: scheme.surfaceContainerLow,
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 10, 8, 16),
children: [
Row(
children: [
Text(
owner ? 'Requests' : 'My requests',
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
),
const Spacer(),
IconButton(
key: const ValueKey('requests-close'),
onPressed: onClose,
tooltip: 'Close',
icon: const Icon(Icons.close, size: 18),
),
],
),
Text(
owner
? 'Everyone\'s requests. AnimeFlow approves them automatically; you can decline or retry.'
: 'AnimeFlow downloads requests automatically; they appear on Jellyfin when done.',
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 12),
if (error != null)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(error!, style: TextStyle(color: scheme.error, fontSize: 12)),
),
if (reqs == null && error == null)
const Padding(
padding: EdgeInsets.only(top: 24),
child: Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))),
)
else if (reqs != null && reqs.isEmpty)
Padding(
padding: const EdgeInsets.only(top: 24),
child: Center(
child: Text('No requests yet', style: TextStyle(color: scheme.onSurfaceVariant)),
),
),
for (final r in reqs ?? const <AnimeFlowRequest>[]) _tile(context, r),
],
),
);
}
}
@@ -0,0 +1,169 @@
import 'package:sync_relay_core/core.dart';
import '../../jellyfin/accounts.dart';
import '../../jellyfin/format.dart' show pickFor, progressOf;
import '../../screens/home/picked_media.dart';
import '../anilist.dart';
import '../animeflow.dart';
import '../schedule_model.dart';
// What the Schedule page's buttons do beyond one client call: friendly
// AnimeFlow errors, finding AnimeFlow's own numbering of an airing, the
// AniDB id of an untracked AniList entry, and the Jellyfin episode to put
// up.
/// A failure meant for the user as is.
class ScheduleActionException implements Exception {
final String message;
const ScheduleActionException(this.message);
@override
String toString() => message;
}
/// [e] as one short sentence for a toast.
String friendlyError(Object e) => switch (e) {
ScheduleActionException(:final message) => message,
AnimeFlowException(:final code, :final detail) => switch (code) {
AnimeFlowErrorCode.alreadyRequested => 'Already requested',
AnimeFlowErrorCode.alreadyTracked => 'AnimeFlow already tracks this series',
AnimeFlowErrorCode.alreadyAvailable => 'Already on Jellyfin',
AnimeFlowErrorCode.missingScope => 'The owner has not allowed you to do that on AnimeFlow',
AnimeFlowErrorCode.revoked => 'Your AnimeFlow access was revoked',
AnimeFlowErrorCode.missingToken => 'Link the Jellyfin server to use AnimeFlow',
AnimeFlowErrorCode.badToken => 'AnimeFlow: sign in to Jellyfin again',
AnimeFlowErrorCode.ownAccount => 'You cannot change your own account',
AnimeFlowErrorCode.searchUnavailable => 'AniDB search is unavailable right now',
AnimeFlowErrorCode.seriesNotFound => 'AnimeFlow does not know this series',
AnimeFlowErrorCode.episodeNotFound => 'AnimeFlow does not know this episode',
AnimeFlowErrorCode.requestNotFound => 'That request is gone',
AnimeFlowErrorCode.notFound => 'AnimeFlow: not found',
AnimeFlowErrorCode.invalidState => 'That cannot be done in its current state',
AnimeFlowErrorCode.rateLimited => 'AnimeFlow is busy, try again shortly',
AnimeFlowErrorCode.network => 'AnimeFlow is unreachable',
AnimeFlowErrorCode.jellyfinUnreachable => 'AnimeFlow cannot reach Jellyfin right now',
_ => 'AnimeFlow: $detail',
},
JellyfinException(:final message) => 'Jellyfin: $message',
_ => '$e',
};
/// AnimeFlow's episode for [e] (its season and numbering), from the
/// series detail: same AniList id and episode, else absolute number, else
/// the same air day ([sameAirDay]). Null when the entry is not tracked or nothing fits.
Future<AnimeFlowEpisode?> flowEpisodeFor(AnimeFlowClient client, ScheduleEntry e) async {
final id = e.seriesId;
if (id == null) return null;
final eps = (await client.seriesDetail(id)).episodes;
final a = e.airing;
final mine = eps.where((x) => x.anilistId == null || x.anilistId == a.anilistId).toList();
return mine.where((x) => x.anilistId == a.anilistId && x.episode == a.episode).firstOrNull ??
mine.where((x) => x.absolute == a.episode).firstOrNull ??
mine.where((x) => x.airDate != null && sameAirDay(x.airDate!, a.airingAt)).firstOrNull;
}
/// The (season, episode) AnimeFlow uses for [e]; falls back to the
/// matched season and AniList's number when the detail has nothing.
Future<({int season, int episode})> flowNumbering(AnimeFlowClient? client, ScheduleEntry e) async {
AnimeFlowEpisode? ep;
if (client != null) {
try {
ep = await flowEpisodeFor(client, e);
} on AnimeFlowException catch (err) {
if (err.isUnauthorized || err.isForbidden) rethrow;
}
}
if (ep != null) return (season: ep.season, episode: ep.episode);
return (season: e.season ?? 1, episode: e.jellyfinEpisode ?? e.airing.episode);
}
/// The AniDB id of an untracked AniList entry, from AnimeFlow's search:
/// the hit with the same AniList id, searched by English then romaji
/// title. Otherwise every hit seen, for the user to choose from.
Future<({AnimeFlowSearchHit? match, List<AnimeFlowSearchHit> hits})> findOnAniDb(
AnimeFlowClient client,
Airing a,
) async {
final hits = <AnimeFlowSearchHit>[];
final queries = {a.title, if (a.titleRomaji.isNotEmpty) a.titleRomaji};
for (final q in queries) {
final found = await client.search(q);
final match = found.where((h) => h.anilistId == a.anilistId).firstOrNull;
if (match != null) return (match: match, hits: found);
for (final h in found) {
if (hits.every((x) => x.anidbId != h.anidbId)) hits.add(h);
}
}
return (match: null, hits: hits);
}
/// The linked Jellyfin account AnimeFlow's series ids belong to: the one
/// AnimeFlow authenticates with, else the default.
JellyfinAccount? jellyfinAccountFor(JellyfinAccounts accounts, AnimeFlowClient? flow) {
if (flow != null) {
final token = AnimeFlowClient.tokenFor(accounts.accounts, flow.baseUrl);
final a = accounts.accounts.where((a) => token != null && a.token == token).firstOrNull;
if (a != null) return a;
}
return accounts.defaultAccount;
}
/// The episodes Jellyfin has of [jellyfinSeriesId] (numbers, premiere
/// date, this user's watched state), seen by the account AnimeFlow's ids
/// belong to ([jellyfinAccountFor]): the user's own; null without a
/// linked account. One request per series, memoised a few minutes
/// ([JellyfinAccounts.cached]; a stop report drops it,
/// [JellyfinAccounts.watched]).
Future<List<JellyfinEpisodeRef>?> jellyfinScheduleEpisodes(
JellyfinAccounts accounts,
AnimeFlowClient? flow,
String jellyfinSeriesId,
) async {
await accounts.ensureLoaded(validate: false);
final account = jellyfinAccountFor(accounts, flow);
final client = account == null ? null : accounts.clientFor(account.serverId);
if (account == null || client == null) return null;
return accounts.cached(
account.serverId,
'schedule-episodes:$jellyfinSeriesId',
() async => [
for (final x in await client.episodes(jellyfinSeriesId))
if (x.parentIndexNumber != null && x.indexNumber != null)
JellyfinEpisodeRef(
x.parentIndexNumber!,
x.indexNumber!,
premiere: x.premiereDate,
watch: EpisodeWatch(played: x.userData.played, progress: x.userData.played ? 0 : progressOf(x)),
),
],
);
}
/// The Jellyfin episode of an available [e] as a media pick. Throws
/// [ScheduleActionException] (or a client error) when it cannot be found.
Future<JellyfinMedia> resolveJellyfinEpisode(JellyfinAccounts accounts, AnimeFlowClient? flow, ScheduleEntry e) async {
final seriesId = e.jellyfinSeriesId;
if (seriesId == null) throw const ScheduleActionException('This series is not on Jellyfin yet');
await accounts.ensureLoaded(validate: false);
final account = jellyfinAccountFor(accounts, flow);
final client = account == null ? null : accounts.clientFor(account.serverId);
if (account == null || client == null) {
throw const ScheduleActionException('Link the Jellyfin server first (Settings)');
}
final n = await flowNumbering(flow, e);
final List<JellyfinItem> eps;
try {
eps = await client.episodes(seriesId);
} on JellyfinException catch (err) {
if (err.isUnauthorized) accounts.markExpired(account.serverId);
rethrow;
}
final a = e.airing;
final item =
eps.where((x) => x.parentIndexNumber == n.season && x.indexNumber == n.episode).firstOrNull ??
eps.where((x) => x.premiereDate != null && sameAirDay(x.premiereDate!, a.airingAt)).singleOrNull ??
eps.where((x) => x.indexNumber == a.episode).singleOrNull;
if (item == null) {
throw ScheduleActionException('Episode ${a.episode} of ${a.title} is not on Jellyfin yet');
}
return pickFor(account, item);
}
@@ -0,0 +1,457 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../schedule_model.dart';
// One day of the Schedule board and its airing cards: cover poster on the
// left (air time on it, download progress or this user's watch progress
// under it, a check once watched), title, episode and status on the
// right, one full-width action below. Compact mode is a slim row.
/// What a card's action does, decided by the page.
enum CardAction {
none,
putUp,
/// Available, nobody else watching (offline or solo): plays it here.
play,
request,
requestSeries,
requested,
}
String _two(int n) => n.toString().padLeft(2, '0');
String clockText(DateTime t) {
final l = t.toLocal();
return '${_two(l.hour)}:${_two(l.minute)}';
}
String countdownText(DateTime at, DateTime now) {
final d = at.difference(now);
if (d.isNegative) return 'aired';
if (d.inDays > 0) return 'in ${d.inDays}d ${d.inHours % 24}h';
if (d.inHours > 0) return 'in ${d.inHours}h ${d.inMinutes % 60}m';
return 'in ${d.inMinutes}m';
}
/// Aired, tracked, no release yet: AnimeFlow downloads it by itself.
bool _waiting(ScheduleEntry e, DateTime now) =>
e.status == ScheduleStatus.missing ||
(e.status == ScheduleStatus.upcoming && e.tracked && !e.airing.airingAt.isAfter(now));
const waitingHint = 'Aired in Japan; downloads automatically when a release appears';
String _ago(DateTime t, DateTime now) {
final m = now.difference(t).inMinutes;
return m < 1 ? 'just now' : m < 60 ? '$m min ago' : '${m ~/ 60} h ago';
}
/// Status line: text, colour, icon.
(String, Color, IconData) statusBadge(ScheduleEntry e, DateTime now) => _waiting(e, now)
? e.reason != null
// Releases exist but AnimeFlow rejected them (the hint says why).
? ('release rejected', Colors.orangeAccent, Icons.block)
: (
e.lastSearchedAt == null ? 'waiting for release' : 'waiting · searched ${_ago(e.lastSearchedAt!, now)}',
Colors.amber.shade200,
Icons.hourglass_top,
)
: switch (e.status) {
ScheduleStatus.available => ('on Jellyfin', Colors.greenAccent, Icons.check_circle),
ScheduleStatus.downloading => (
e.progress == null ? 'downloading' : 'downloading ${(e.progress! * 100).round()}%',
Colors.lightBlueAccent,
Icons.downloading,
),
ScheduleStatus.upcoming => (countdownText(e.airing.airingAt, now), Colors.amber, Icons.schedule),
ScheduleStatus.missing => ('waiting for release', Colors.amber.shade200, Icons.hourglass_top),
ScheduleStatus.failed => (
'failed${e.reason == null ? '' : ' · ${e.reason}'}',
Colors.redAccent,
Icons.error_outline,
),
ScheduleStatus.unmonitored => ('not monitored', Colors.grey, Icons.visibility_off_outlined),
ScheduleStatus.notTracked => ('not in AnimeFlow', Colors.grey, Icons.public),
ScheduleStatus.inLibrary => ('on Jellyfin', Colors.greenAccent, Icons.video_library_outlined),
ScheduleStatus.notOnJellyfin => ('aired · not on Jellyfin yet', Colors.grey, Icons.hourglass_empty),
ScheduleStatus.aired => ('aired', Colors.grey, Icons.done),
};
/// What the status line means, when its text alone does not say.
String? statusHint(ScheduleEntry e, DateTime now) => !_waiting(e, now)
? null
: e.reason != null
? '${e.reason}${e.lastSearchedAt == null ? '' : ' · searched ${_ago(e.lastSearchedAt!, now)}'}'
: waitingHint;
class DayColumn extends StatelessWidget {
final DateTime day;
final bool today, past;
final List<Widget> cards;
final bool compact;
const DayColumn({
super.key,
required this.day,
required this.today,
required this.past,
required this.cards,
required this.compact,
});
static const _wd = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
color: today ? scheme.primary.withValues(alpha: 0.06) : scheme.surfaceContainerLow.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(14),
border: today ? Border.all(color: scheme.primary.withValues(alpha: 0.5)) : null,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 8),
child: Row(
children: [
Text(
today ? 'Today' : _wd[day.weekday - 1],
style: TextStyle(fontWeight: FontWeight.w700, color: today ? scheme.primary : null),
),
const SizedBox(width: 6),
Text('${day.day}.${day.month}.', style: TextStyle(color: scheme.onSurfaceVariant)),
const Spacer(),
if (cards.isNotEmpty)
Text('${cards.length}', style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12)),
],
),
),
Expanded(
child: Opacity(
opacity: past ? 0.85 : 1,
child: cards.isEmpty
? Center(
child: Text('—', style: TextStyle(color: scheme.onSurfaceVariant)),
)
// The board's mouse drag is sideways only: a column scrolls by
// wheel (or touch), never by dragging the mouse.
: ScrollConfiguration(
behavior: ScrollConfiguration.of(context)
.copyWith(dragDevices: {...PointerDeviceKind.values}..remove(PointerDeviceKind.mouse)),
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 10),
itemCount: cards.length,
separatorBuilder: (_, _) => SizedBox(height: compact ? 6 : 10),
itemBuilder: (context, i) => cards[i],
),
),
),
),
],
),
);
}
}
class AiringCard extends StatelessWidget {
final ScheduleEntry entry;
final DateTime now;
final bool compact;
final CardAction action;
/// Why [action] is disabled (no request scope, not on Jellyfin, ...);
/// null = enabled.
final String? disabledHint;
/// A call for this card is in flight.
final bool busy;
final VoidCallback onAction;
/// Right-click ([context]: the card): the page's menu; null: none.
final void Function(BuildContext context)? onMenu;
const AiringCard({
super.key,
required this.entry,
required this.now,
required this.compact,
required this.action,
required this.onAction,
this.disabledHint,
this.busy = false,
this.onMenu,
});
String get _id => '${entry.airing.anilistId}-${entry.airing.episode}';
Widget _image(String? url, Widget fallback) =>
url == null ? fallback : Image.network(url, fit: BoxFit.cover, errorBuilder: (_, _, _) => fallback);
static Widget _hinted(String? hint, Widget child) => hint == null ? child : Tooltip(message: hint, child: child);
bool get _played => entry.watch?.played ?? false;
/// A watched episode's cover is a little dimmer.
Widget _dimmed(Widget cover) => _played ? Opacity(opacity: 0.6, child: cover) : cover;
/// This user's state of the episode on Jellyfin: the library cards'
/// green check once watched, else a thin bar to the resume point.
List<Widget> _watchMarks({bool small = false}) {
final w = entry.watch;
if (w == null) return const [];
if (w.played) {
final size = small ? 16.0 : 22.0;
return [
Positioned(
top: small ? 2 : 4,
right: small ? 2 : 4,
child: Tooltip(
message: 'Watched',
child: Container(
key: ValueKey('watched-$_id'),
constraints: BoxConstraints(minWidth: size, minHeight: size),
alignment: Alignment.center,
decoration: BoxDecoration(color: const Color(0xCC1B5E20), borderRadius: BorderRadius.circular(size / 2)),
child: Icon(Icons.check, size: small ? 11 : 14, color: Colors.white),
),
),
),
];
}
if (w.progress <= 0) return const [];
return [
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Tooltip(
message: '${(w.progress * 100).round()}% watched',
child: LinearProgressIndicator(
key: ValueKey('watch-progress-$_id'),
value: w.progress,
minHeight: small ? 2 : 3,
backgroundColor: Colors.black54,
),
),
),
];
}
Widget? _button(BuildContext context) {
final enabled = disabledHint == null && !busy;
final spinner = const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2));
Widget? b = switch (action) {
CardAction.none => null,
CardAction.putUp || CardAction.play => FilledButton.tonalIcon(
onPressed: enabled ? onAction : null,
icon: busy ? spinner : const Icon(Icons.play_arrow, size: 18),
label: Text(action == CardAction.play ? 'Play' : 'Put it up'),
),
CardAction.request || CardAction.requestSeries => OutlinedButton.icon(
onPressed: enabled ? onAction : null,
icon: busy ? spinner : const Icon(Icons.download, size: 18),
label: Text(action == CardAction.request ? 'Request' : 'Request series'),
),
CardAction.requested => const OutlinedButton(onPressed: null, child: Text('Requested')),
};
if (b == null) return null;
b = KeyedSubtree(key: ValueKey('action-$_id'), child: b);
return disabledHint == null ? b : Tooltip(message: disabledHint!, child: b);
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final a = entry.airing;
final (label, color, icon) = statusBadge(entry, now);
final hint = statusHint(entry, now);
final time = clockText(a.airingAt);
final tintColor = a.color ?? Colors.blueGrey;
final tint = DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [tintColor.withValues(alpha: 0.85), tintColor.withValues(alpha: 0.35)],
),
),
);
final tap = (action == CardAction.putUp || action == CardAction.play) && disabledHint == null && !busy
? onAction
: null;
final onMenu = this.onMenu;
final menu = onMenu == null || busy ? null : () => onMenu(context);
if (compact) {
final requestable = action == CardAction.request || action == CardAction.requestSeries;
return Material(
key: ValueKey('card-$_id'),
color: scheme.surfaceContainer,
borderRadius: BorderRadius.circular(10),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: tap,
onSecondaryTap: menu,
child: Padding(
padding: const EdgeInsets.all(6),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: SizedBox(
width: 34,
height: 48,
child: Stack(
fit: StackFit.expand,
children: [_dimmed(_image(a.cover, tint)), ..._watchMarks(small: true)],
),
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
a.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13),
),
Text('$time · E${a.episode}', style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 11)),
],
),
),
if (requestable)
Tooltip(
message: disabledHint ?? (action == CardAction.request ? 'Request' : 'Request series'),
child: IconButton(
key: ValueKey('action-$_id'),
onPressed: disabledHint == null && !busy ? onAction : null,
visualDensity: VisualDensity.compact,
iconSize: 18,
icon: Icon(Icons.download, color: color),
),
)
else
Tooltip(
message: hint == null ? label : '$label: $hint',
child: Icon(icon, size: 16, color: color),
),
],
),
),
),
);
}
final button = _button(context);
// The cover poster leads: it is what people recognise at a glance.
return Material(
key: ValueKey('card-$_id'),
color: scheme.surfaceContainer,
borderRadius: BorderRadius.circular(12),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: tap,
onSecondaryTap: menu,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
width: 84,
height: 120,
child: Stack(
fit: StackFit.expand,
children: [
_dimmed(_image(a.cover, tint)),
Positioned(
left: 4,
top: 4,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(5),
),
child: Text(
time,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12),
),
),
),
..._watchMarks(),
if (entry.status == ScheduleStatus.downloading)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: LinearProgressIndicator(
value: entry.progress,
minHeight: 3,
color: Colors.lightBlueAccent,
),
),
],
),
),
),
const SizedBox(width: 10),
Expanded(
child: SizedBox(
height: 120,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
a.title,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600, height: 1.25),
),
const SizedBox(height: 2),
Text(
'Episode ${a.episode}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12),
),
const Spacer(),
_hinted(
hint,
Row(
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 4),
Expanded(
child: Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: color, fontSize: 12),
),
),
],
),
),
],
),
),
),
],
),
if (button != null) ...[const SizedBox(height: 8), SizedBox(width: double.infinity, child: button)],
],
),
),
),
);
}
}
@@ -0,0 +1,607 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../jellyfin/accounts.dart';
import '../../screens/home/picked_media.dart';
import '../../shell/avatar.dart';
import '../../shell/motion.dart';
import '../../widgets/app_menu.dart';
import '../anilist.dart';
import '../animeflow.dart';
import '../schedule_model.dart';
import 'requests_sheet.dart';
import 'schedule_actions.dart';
import 'schedule_card.dart';
// The Schedule tab: one column per day from last week's Monday to four
// weeks out (AniList), each airing with what AnimeFlow has of it, and the
// one thing to do about it (put it up, request it; right-click an
// available one to queue it). Refreshes every minute while visible; ETags
// keep that cheap. Episodes on Jellyfin show this user's watched state,
// reloaded after a playback stops (JellyfinAccounts.watched).
enum ScheduleFilter { jellyfin, all }
class SchedulePage extends StatefulWidget {
/// Nobody else watches (offline or solo): available episodes say Play
/// (offline that starts a solo session).
final bool solo;
/// Puts a Jellyfin episode up in the server; returns an error to show.
final Future<String?> Function(JellyfinMedia pick) onPutUp;
/// Adds an episode to Up next; null: no server, the cards have no menu.
final AddToQueue? onAddToQueue;
final void Function(String message) onToast;
/// Null = the page makes (and closes) its own.
final ScheduleRepository? repository;
/// Linked Jellyfin servers for Put it up; null = [JellyfinAccounts.instance].
final JellyfinAccounts? accounts;
const SchedulePage({
super.key,
this.solo = false,
required this.onPutUp,
this.onAddToQueue,
required this.onToast,
this.repository,
this.accounts,
});
static const columnWidth = 252.0;
static const columnGap = 12.0;
static const refreshEvery = Duration(seconds: 60);
/// "All airing" hides entries below this AniList popularity unless
/// "Niche too" is on (tracked ones always show).
static const nicheBelow = 5000;
static const sheetWidth = 360.0;
@override
State<SchedulePage> createState() => _SchedulePageState();
}
class _SchedulePageState extends State<SchedulePage> {
late final ScheduleRepository _repo;
late final bool _ownsRepo;
ScheduleSnapshot? _snap;
bool _refreshing = false;
int _gen = 0;
Timer? _timer;
StreamSubscription<String>? _watchedSub;
final _scroll = ScrollController();
bool _jumped = false;
ScheduleFilter _filter = ScheduleFilter.jellyfin;
bool _compact = false;
bool _niche = false;
bool _sheet = false;
/// The sheet stays built while it slides out.
bool _sheetBuilt = false;
/// Requested in this run: "anilistId#episode" or "series:anilistId".
final _requested = <String>{};
/// Cards with a call in flight (same keys as [_requested], "play:...").
final _busy = <String>{};
List<AnimeFlowRequest>? _requests;
String? _requestsError;
final _reqBusy = <int>{};
JellyfinAccounts get _accounts => widget.accounts ?? JellyfinAccounts.instance;
AnimeFlowClient? get _flow => _repo.animeFlow;
AnimeFlowMe? get _me => _snap?.me;
bool get _owner => _me?.can(AnimeFlowScope.manage) ?? false;
@override
void initState() {
super.initState();
_ownsRepo = widget.repository == null;
_repo = widget.repository ?? _ownRepo();
_load();
_timer = Timer.periodic(SchedulePage.refreshEvery, (_) {
if (mounted && !_refreshing && TickerMode.valuesOf(context).enabled) _load();
});
// Back from watching: the cards' watched marks follow (the stop report
// already dropped the cached episode lists).
_watchedSub = _accounts.watched.listen((_) {
if (mounted) _load();
});
}
ScheduleRepository _ownRepo() {
final flow = AnimeFlowClient.fromAccounts(_accounts);
return ScheduleRepository(
aniList: AniListClient(),
animeFlow: flow,
jellyfinEpisodes: (id) => jellyfinScheduleEpisodes(_accounts, flow, id),
);
}
@override
void dispose() {
_timer?.cancel();
_watchedSub?.cancel();
_scroll.dispose();
if (_ownsRepo) {
_repo.aniList.close();
_repo.animeFlow?.close();
}
super.dispose();
}
void _toast(String s) {
if (mounted) widget.onToast(s);
}
Future<void> _load({bool force = false}) async {
final gen = ++_gen;
if (_snap != null) setState(() => _refreshing = true);
final snap = await _repo.load(force: force);
if (!mounted || gen != _gen) return;
final first = _snap == null;
setState(() {
_snap = snap;
_refreshing = false;
// Without AnimeFlow nothing is "on Jellyfin": start on everything.
if (first && !snap.hasAnimeFlow) _filter = ScheduleFilter.all;
});
if (!_jumped && snap.entries.isNotEmpty) {
_jumped = true;
WidgetsBinding.instance.addPostFrameCallback((_) => _jumpToday());
}
await _loadRequests();
}
Future<void> _loadRequests() async {
final c = _flow, me = _me;
if (c == null || me == null) {
if (_requests != null || _requestsError != null) {
setState(() {
_requests = null;
_requestsError = null;
});
}
return;
}
try {
final reqs = await c.requests(all: me.can(AnimeFlowScope.manage));
if (mounted) {
setState(() {
_requests = reqs;
_requestsError = null;
});
}
} catch (e) {
if (mounted) setState(() => _requestsError = friendlyError(e));
}
}
// ------------------------------------------------------------- layout
int _todayIndex(DateTime from) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final f = DateTime(from.year, from.month, from.day);
return (today.difference(f).inHours / 24).round();
}
void _jumpToday({bool animate = false}) {
final snap = _snap;
if (!_scroll.hasClients || snap == null) return;
final i = _todayIndex(snap.from.toLocal());
final x = (i * (SchedulePage.columnWidth + SchedulePage.columnGap))
.clamp(0.0, _scroll.position.maxScrollExtent)
.toDouble();
animate ? _scroll.animateTo(x, duration: easeTime, curve: ease) : _scroll.jumpTo(x);
}
bool _shows(ScheduleEntry e) => switch (_filter) {
ScheduleFilter.jellyfin => e.tracked || e.onJellyfin,
ScheduleFilter.all => _niche || e.tracked || e.airing.popularity >= SchedulePage.nicheBelow,
};
static String _key(ScheduleEntry e) =>
e.tracked ? '${e.airing.anilistId}#${e.airing.episode}' : 'series:${e.airing.anilistId}';
static const _active = {RequestStatus.pending, RequestStatus.approved, RequestStatus.searching};
bool _isRequested(ScheduleEntry e) {
if (_requested.contains(_key(e))) return true;
final a = e.airing;
for (final r in _requests ?? const <AnimeFlowRequest>[]) {
if (!_active.contains(r.status)) continue;
if (e.tracked) {
if (r.kind == RequestKind.episode && r.seriesId == e.seriesId && r.episode == a.episode) return true;
if (r.kind == RequestKind.season && r.seriesId == e.seriesId && r.season == e.season) return true;
} else if (r.kind == RequestKind.series && (r.title == a.title || r.title == a.titleRomaji)) {
return true;
}
}
return false;
}
(CardAction, String?) _actionFor(ScheduleEntry e) {
if (e.status == ScheduleStatus.available || e.status == ScheduleStatus.inLibrary) {
final put = widget.solo ? CardAction.play : CardAction.putUp;
if (e.jellyfinSeriesId == null) return (put, 'Not linked to a Jellyfin series yet');
return (put, null);
}
final me = _me;
final CardAction kind;
if (e.tracked) {
// Only an unmonitored episode is worth asking for: AnimeFlow grabs
// a missing one (no release yet) by itself once a release appears,
// and retries a failed one.
kind = e.status == ScheduleStatus.unmonitored ? CardAction.request : CardAction.none;
} else {
// On Jellyfin already (owner added it by hand): nothing to request.
kind = me != null && !e.onJellyfin ? CardAction.requestSeries : CardAction.none;
}
if (kind == CardAction.none) return (kind, null);
if (_isRequested(e)) return (CardAction.requested, null);
if (me == null || !me.can(AnimeFlowScope.request)) {
return (kind, 'Requests need the owner to allow them for you on AnimeFlow');
}
return (kind, null);
}
// ------------------------------------------------------------- actions
Future<void> _onAction(ScheduleEntry e, CardAction action) async {
switch (action) {
case CardAction.putUp:
case CardAction.play:
return _putUp(e);
case CardAction.request:
case CardAction.requestSeries:
return _request(e);
default:
return;
}
}
Future<void> _putUp(ScheduleEntry e) async {
final key = 'play:${_key(e)}';
if (_busy.contains(key)) return;
setState(() => _busy.add(key));
try {
final pick = await resolveJellyfinEpisode(_accounts, _flow, e);
final err = await widget.onPutUp(pick);
if (err != null) _toast(err);
} catch (err) {
_toast(friendlyError(err));
} finally {
if (mounted) setState(() => _busy.remove(key));
}
}
/// Right-click on an available episode ([context]: its card): Put it
/// up, Add to queue, Play next.
Future<void> _menu(BuildContext context, ScheduleEntry e) async {
final add = widget.onAddToQueue;
if (add == null) return;
final pick = await showAppMenu<int>(context, items: [
AppMenuItem(value: 0, label: widget.solo ? 'Play' : 'Put it up'),
const AppMenuItem(value: 1, label: 'Add to queue'),
const AppMenuItem(value: 2, label: 'Play next'),
]);
if (pick == null || !mounted) return;
if (pick == 0) return _putUp(e);
final key = 'play:${_key(e)}';
if (_busy.contains(key)) return;
setState(() => _busy.add(key));
try {
final media = await resolveJellyfinEpisode(_accounts, _flow, e);
final err = await add(media, next: pick == 2);
if (err != null) _toast(err);
} catch (err) {
_toast(friendlyError(err));
} finally {
if (mounted) setState(() => _busy.remove(key));
}
}
Future<void> _request(ScheduleEntry e) async {
final c = _flow;
if (c == null) return;
final key = _key(e);
if (_busy.contains(key)) return;
setState(() => _busy.add(key));
final a = e.airing;
try {
final seriesId = e.seriesId;
if (e.tracked && seriesId != null) {
final n = await flowNumbering(c, e);
await c.requestEpisode(seriesId, n.season, n.episode);
_requested.add(key);
_toast('Requested ${a.title}, episode ${a.episode}');
} else {
final found = await findOnAniDb(c, a);
final hit = found.match ?? (mounted ? await _chooseHit(a, found.hits) : null);
if (hit == null) return;
await c.requestSeries(hit.anidbId);
_requested.add(key);
_toast('Requested ${hit.title}');
}
unawaited(_loadRequests());
} on AnimeFlowException catch (err) {
if (err.code == AnimeFlowErrorCode.alreadyRequested) _requested.add(key);
_toast(friendlyError(err));
} catch (err) {
_toast(friendlyError(err));
} finally {
if (mounted) setState(() => _busy.remove(key));
}
}
/// No search hit has [a]'s AniList id: let the user pick one (or learn
/// that AniDB does not know it).
Future<AnimeFlowSearchHit?> _chooseHit(Airing a, List<AnimeFlowSearchHit> hits) => showDialog<AnimeFlowSearchHit>(
context: context,
builder: (context) {
final scheme = Theme.of(context).colorScheme;
return AlertDialog(
key: const ValueKey('anidb-choose'),
title: Text(hits.isEmpty ? 'Not found on AniDB' : 'Which one is "${a.title}"?'),
content: SizedBox(
width: 420,
child: hits.isEmpty
? Text(
'AnimeFlow can only add series AniDB knows, and its search found nothing for this title.',
style: TextStyle(color: scheme.onSurfaceVariant),
)
: ListView(
shrinkWrap: true,
children: [
for (final h in hits)
ListTile(
key: ValueKey('anidb-${h.anidbId}'),
title: Text(h.title),
subtitle: Text('AniDB ${h.anidbId}${h.seriesId != null ? ' · already on AnimeFlow' : ''}'),
onTap: () => Navigator.of(context).pop(h),
),
],
),
),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(hits.isEmpty ? 'OK' : 'Cancel')),
],
);
},
);
Future<void> _decide(AnimeFlowRequest r, {required bool approve}) async {
final c = _flow;
if (c == null || _reqBusy.contains(r.id)) return;
setState(() => _reqBusy.add(r.id));
try {
approve ? await c.approve(r.id) : await c.reject(r.id);
_toast(approve ? 'Retrying ${r.title}' : 'Declined ${r.title}');
await _loadRequests();
} catch (err) {
_toast(friendlyError(err));
} finally {
if (mounted) setState(() => _reqBusy.remove(r.id));
}
}
// ------------------------------------------------------------- build
Widget _header(BuildContext context) {
final snap = _snap;
final me = _me;
final stale = snap?.aniListStaleSince;
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
child: Wrap(
spacing: 12,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
SegmentedButton<ScheduleFilter>(
key: const ValueKey('schedule-filter'),
showSelectedIcon: false,
segments: const [
ButtonSegment(
value: ScheduleFilter.jellyfin,
icon: Icon(Icons.video_library_outlined),
label: Text('On Jellyfin'),
),
ButtonSegment(value: ScheduleFilter.all, icon: Icon(Icons.public), label: Text('All airing')),
],
selected: {_filter},
onSelectionChanged: (s) => setState(() => _filter = s.first),
),
FilterChip(
key: const ValueKey('schedule-compact'),
label: const Text('Compact'),
selected: _compact,
onSelected: (v) => setState(() => _compact = v),
),
if (_filter == ScheduleFilter.all)
FilterChip(
key: const ValueKey('schedule-niche'),
label: const Text('Niche too'),
selected: _niche,
onSelected: (v) => setState(() => _niche = v),
),
TextButton.icon(
key: const ValueKey('schedule-today'),
onPressed: () => _jumpToday(animate: true),
icon: const Icon(Icons.today, size: 18),
label: const Text('Today'),
),
IconButton(
key: const ValueKey('schedule-refresh'),
tooltip: 'Refresh',
onPressed: _refreshing || snap == null ? null : () => _load(force: true),
icon: _refreshing
? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.refresh, size: 20),
),
Tooltip(
message: me == null ? (snap?.animeFlowError ?? 'AnimeFlow is not available') : '',
child: OutlinedButton.icon(
key: const ValueKey('schedule-requests'),
onPressed: me == null
? null
: () => setState(() {
_sheet = !_sheet;
if (_sheet) _sheetBuilt = true;
}),
icon: const Icon(Icons.inbox_outlined, size: 18),
label: Text(_owner ? 'Requests' : 'My requests'),
),
),
if (stale != null)
Pill(
key: const ValueKey('schedule-stale'),
'AniList busy · showing data from ${clockText(stale)}',
Colors.amber,
),
if (me != null)
Pill(
key: const ValueKey('schedule-animeflow'),
'AnimeFlow · ${me.name} · ${[for (final s in AnimeFlowScope.values)
if (me.can(s)) s.name].join(', ')}',
Colors.greenAccent.shade400,
)
else if (snap?.animeFlowError != null)
Pill(key: const ValueKey('schedule-animeflow'), snap!.animeFlowError!, Colors.orangeAccent),
],
),
);
}
Widget _board(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final snap = _snap;
if (snap == null) return const Center(child: CircularProgressIndicator());
if (snap.aniListError != null && snap.entries.isEmpty) {
return Center(
key: const ValueKey('schedule-error'),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.cloud_off, color: scheme.error),
const SizedBox(height: 8),
Text(
'Could not load the schedule: ${snap.aniListError}',
textAlign: TextAlign.center,
style: TextStyle(color: scheme.error),
),
const SizedBox(height: 12),
FilledButton.tonal(
onPressed: _refreshing ? null : () => _load(force: true),
child: const Text('Try again'),
),
],
),
),
);
}
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final from = snap.from.toLocal();
final start = DateTime(from.year, from.month, from.day);
final to = snap.to.toLocal();
final count = (DateTime(to.year, to.month, to.day).difference(start).inHours / 24).round().clamp(1, 60);
final days = [for (var d = 0; d < count; d++) DateTime(start.year, start.month, start.day + d)];
final byDay = <DateTime, List<ScheduleEntry>>{};
for (final e in snap.entries) {
if (!_shows(e)) continue;
final l = e.airing.airingAt.toLocal();
(byDay[DateTime(l.year, l.month, l.day)] ??= []).add(e);
}
return HScroll(
controller: _scroll,
builder: (c) => ListView.separated(
key: const ValueKey('schedule-board'),
controller: c,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.fromLTRB(20, 4, 20, 16),
itemCount: days.length,
separatorBuilder: (_, _) => const SizedBox(width: SchedulePage.columnGap),
itemBuilder: (context, i) {
final d = days[i];
final items = byDay[d] ?? const <ScheduleEntry>[];
return SizedBox(
key: ValueKey('day-${d.year}-${d.month}-${d.day}'),
width: SchedulePage.columnWidth,
child: DayColumn(
day: d,
today: d == today,
past: d.isBefore(today),
compact: _compact,
cards: [for (final e in items) _card(e, now)],
),
);
},
),
);
}
Widget _card(ScheduleEntry e, DateTime now) {
final (action, hint) = _actionFor(e);
final busy = _busy.contains(action == CardAction.putUp || action == CardAction.play ? 'play:${_key(e)}' : _key(e));
return AiringCard(
entry: e,
now: now,
compact: _compact,
action: action,
disabledHint: hint,
busy: busy,
onAction: () => _onAction(e, action),
onMenu: widget.onAddToQueue != null && (action == CardAction.putUp || action == CardAction.play) && hint == null
? (context) => _menu(context, e)
: null,
);
}
@override
Widget build(BuildContext context) => Row(
children: [
Expanded(
child: Column(
children: [
_header(context),
Expanded(child: _board(context)),
],
),
),
ClipRect(
child: AnimatedContainer(
duration: easeTime,
curve: ease,
width: _sheet ? SchedulePage.sheetWidth : 0,
child: OverflowBox(
alignment: Alignment.centerLeft,
minWidth: SchedulePage.sheetWidth,
maxWidth: SchedulePage.sheetWidth,
child: _sheetBuilt && _me != null
? RequestsSheet(
key: const ValueKey('requests-sheet'),
owner: _owner,
requests: _requests,
error: _requestsError,
selfName: _me?.name ?? '',
busy: _reqBusy,
onRetry: (r) => _decide(r, approve: true),
onDecline: (r) => _decide(r, approve: false),
onClose: () => setState(() => _sheet = false),
)
: const SizedBox.shrink(),
),
),
),
],
);
}
@@ -1,359 +0,0 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import 'package:window_manager/window_manager.dart';
import '../session/session_coordinator.dart';
import '../state/session_state.dart';
import '../widgets/avatar_pipeline.dart';
import '../widgets/media_input.dart';
import '../widgets/user_section.dart';
import '../widgets/window_buttons.dart';
class ConnectScreen extends StatefulWidget {
const ConnectScreen({super.key});
@override
State<ConnectScreen> createState() => _ConnectScreenState();
}
class _ConnectScreenState extends State<ConnectScreen> {
late final TextEditingController _host;
late final TextEditingController _port;
late final TextEditingController _secret;
late final TextEditingController _name;
late final TextEditingController _media;
late final void Function() _stopKickWatch;
String? _error;
String? _notice;
bool _busy = false;
bool _dialogOpen = false;
int _attempt = 0;
String? _lastKickShown;
@override
void initState() {
super.initState();
final f = SessionState.instance.lastForm;
_host = TextEditingController(text: f.host);
_port = TextEditingController(text: f.port);
_secret = TextEditingController(text: f.secret);
_name = TextEditingController(text: f.name);
_media = TextEditingController(text: f.linkUrl.isNotEmpty ? f.linkUrl : f.mediaPath);
_stopKickWatch = effect(() {
final kick = SessionState.instance.coordinator.kickReason.value;
if (kick == null || kick.isEmpty) {
_lastKickShown = null;
return;
}
if (kick == _lastKickShown) return;
_lastKickShown = kick;
final reason = kick;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _showKickedDialog(reason);
});
});
}
@override
void dispose() {
_stopKickWatch();
_host.dispose();
_port.dispose();
_secret.dispose();
_name.dispose();
_media.dispose();
super.dispose();
}
String _actionable(Object e) {
if (e is FormatException) return e.message;
return 'check the form and try again';
}
Future<ConnectParams> _buildParams(ConnectMode mode) async {
final port = int.tryParse(_port.text.trim());
if (port == null || port <= 0 || port > 65535) {
throw const FormatException('port must be 1–65535');
}
if (_secret.text.isEmpty) {
throw const FormatException('room password required');
}
if (_name.text.trim().isEmpty) {
throw const FormatException('display name required');
}
String avatar = '';
try {
avatar = await encodeAvatar(SessionState.instance.lastForm.avatarPath);
} on FormatException catch (e) {
throw FormatException(e.message);
} catch (_) {
throw const FormatException('could not process that avatar — pick a PNG, JPG or WebP photo');
}
final parsed = parseMediaInput(_media.text);
if (mode == ConnectMode.host && parsed.mediaPath.isNotEmpty && !await File(parsed.mediaPath).exists()) {
throw const FormatException('media file not found — check the path or pick again with Browse');
}
return ConnectParams(
host: _host.text.trim().isEmpty ? '127.0.0.1' : _host.text.trim(),
port: port,
secret: _secret.text,
name: _name.text.trim(),
avatar: avatar,
mediaPath: mode == ConnectMode.host ? parsed.mediaPath : '',
linkUrl: mode == ConnectMode.host ? parsed.linkUrl : '',
);
}
Future<void> _submit(ConnectMode mode) async {
final myAttempt = ++_attempt;
final session = SessionState.instance;
setState(() {
_error = null;
_notice = null;
_busy = true;
});
late final ConnectParams params;
try {
params = await _buildParams(mode);
} catch (e) {
if (!mounted || myAttempt != _attempt) return;
setState(() {
_error = _actionable(e);
_busy = false;
});
return;
}
final parsed = parseMediaInput(_media.text);
final form = SessionState.instance.lastForm
..host = _host.text.trim()
..port = _port.text.trim()
..secret = _secret.text
..name = _name.text.trim()
..mediaPath = parsed.mediaPath
..linkUrl = parsed.linkUrl;
try {
if (mode == ConnectMode.host) {
await session.host(params, form);
} else {
await session.join(params, form);
}
} catch (e) {
if (!mounted || myAttempt != _attempt) return;
final kick = session.coordinator.kickReason.peek();
setState(() => _busy = false);
if (kick != null && kick.isNotEmpty) {
await _showKickedDialog(kick);
} else {
await _showConnectionDialog('$e');
}
return;
}
if (myAttempt != _attempt) {
try {
await session.leave();
await session.stopServer();
} catch (_) {}
return;
}
if (mounted) setState(() => _busy = false);
}
Future<void> _cancel() async {
_attempt++;
try {
await SessionState.instance.coordinator.cancelConnect();
await SessionState.instance.stopServer();
} catch (_) {}
if (!mounted) return;
setState(() {
_busy = false;
_notice = 'connection cancelled';
});
}
Future<void> _showKickedDialog(String reason) async {
if (!mounted || _dialogOpen) return;
_dialogOpen = true;
try {
await showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('You were removed'),
content: Text(reason.isEmpty ? 'The host removed you from the room.' : reason),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
} finally {
_dialogOpen = false;
}
}
Future<void> _showConnectionDialog(String message) async {
if (!mounted || _dialogOpen) return;
_dialogOpen = true;
try {
await showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Could not connect'),
content: Text(message),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
} finally {
_dialogOpen = false;
}
}
Future<void> _browse(TextEditingController target, {required String dialogTitle, List<String>? allowedExtensions}) async {
try {
final files = await FilePicker.pickFiles(
dialogTitle: dialogTitle,
type: allowedExtensions == null ? FileType.any : FileType.custom,
allowedExtensions: allowedExtensions,
);
if (files.isEmpty) return;
final path = files.first.path;
if (path != null && mounted) setState(() => target.text = path);
} catch (e) {
if (!mounted) return;
setState(() => _notice = 'file dialog unavailable (${e.runtimeType}) — paste the path manually');
}
}
@override
Widget build(BuildContext context) {
final session = SessionState.instance;
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: DragToMoveArea(
child: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => session.route.value = AppRoute.welcome),
title: SignalBuilder(builder: (context) => Text(session.connectMode.value == ConnectMode.host ? 'Host room' : 'Join room')),
actions: const [WindowButtons()],
),
),
),
body: Stack(
alignment: AlignmentGeometry.center,
children: [
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20),
child: Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: ConnectProfileCard(name: _name),
),
),
),
),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(24),
children: [
if (_error != null)
Card(
child: Padding(padding: const EdgeInsets.all(12), child: Text(_error!)),
),
if (_notice != null) Text(_notice!),
SignalBuilder(
builder: (context) {
final mode = session.connectMode.value;
return SegmentedButton<ConnectMode>(
segments: const [
ButtonSegment(value: ConnectMode.host, label: Text('Host')),
ButtonSegment(value: ConnectMode.join, label: Text('Join')),
],
selected: {mode},
onSelectionChanged: (s) => session.connectMode.value = s.first,
);
},
),
const SizedBox(height: 16),
SignalBuilder(
builder: (context) {
if (session.connectMode.value == ConnectMode.host) {
return const SizedBox.shrink();
}
return TextField(
controller: _host,
decoration: const InputDecoration(labelText: 'Host address'),
);
},
),
TextField(
controller: _port,
decoration: const InputDecoration(labelText: 'Port'),
keyboardType: TextInputType.number,
),
TextField(
controller: _secret,
decoration: const InputDecoration(labelText: 'Room password'),
obscureText: true,
),
const SizedBox(height: 8),
SignalBuilder(
builder: (context) {
if (session.connectMode.value == ConnectMode.host) {
return Row(
children: [
Expanded(
child: TextField(
controller: _media,
decoration: const InputDecoration(labelText: 'Media (file path or http(s) URL)'),
),
),
TextButton(
onPressed: () => _browse(
_media,
dialogTitle: 'Pick media file',
allowedExtensions: const ['mp4', 'mkv', 'webm', 'avi', 'mov', 'm4v', 'ts'],
),
child: const Text('Browse'),
),
],
);
}
return const SizedBox.shrink();
},
),
const SizedBox(height: 16),
SignalBuilder(
builder: (context) {
final mode = session.connectMode.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
onPressed: _busy ? null : () => _submit(mode),
child: Text(
_busy
? 'Connecting…'
: mode == ConnectMode.host
? 'Start hosting'
: 'Join',
),
),
if (_busy) TextButton(onPressed: _cancel, child: const Text('Cancel')),
],
);
},
),
const SizedBox(height: 12),
const Text('Host opens ports and serves. Clients open nothing.', textAlign: TextAlign.center),
],
),
),
],
),
);
}
}
@@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import '../../platform/app_log.dart';
import '../../shell/mark_problem.dart';
import '../../state/session_state.dart';
import '../../state/settings.dart';
import '../../update/update_controller.dart';
import '../../widgets/app_menu.dart';
import '../../widgets/copy_open.dart';
import 'settings_page.dart';
import 'ui.dart';
import 'update_indicator.dart';
/// Name, version, update state, the source link and the logs (Settings →
/// About).
class AboutPanel extends StatelessWidget {
final UpdateController? updates;
const AboutPanel({super.key, this.updates});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return SettingsPanel(
title: 'About',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(appName, style: t.titleLarge),
const SizedBox(height: 4),
SelectableText('Version ${displayVersion()}',
style: t.bodyMedium?.copyWith(color: Colors.grey)),
const SizedBox(height: 4),
UpdateStatusLine(controller: updates),
const SizedBox(height: kGap),
Row(
children: [
Expanded(child: SelectableText(repoUrl, style: t.bodySmall)),
IconButton(
tooltip: ReuseText.copyLink,
icon: const Icon(Icons.copy, size: 18),
onPressed: () => copyToClipboard(repoUrl, done: 'Link copied'),
),
FilledButton.tonalIcon(
onPressed: () => openLink(repoUrl),
icon: const Icon(Icons.open_in_new, size: 18),
label: const Text('Source'),
),
],
),
const SizedBox(height: kGap),
Text('Something went wrong? Copy diagnostics and send them to whoever helps you: '
'versions and the last hours of the log, passwords, tokens and cookies left out. '
'In a session it can add the server\'s session log, everyone\'s lines merged. '
'Mark a problem (Ctrl+Shift+B) when it happens so the spot is easy to find.',
style: t.bodySmall?.copyWith(color: Colors.grey)),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
Builder(
builder: (context) => FilledButton.tonalIcon(
key: const ValueKey('copy-diagnostics'),
onPressed: () => _copyDiagnostics(context),
icon: const Icon(Icons.bug_report_outlined, size: 18),
label: const Text('Copy diagnostics'),
),
),
const MarkProblemButton(outlined: true),
OutlinedButton.icon(
onPressed: appLogDir == null ? null : () => _openLogs(),
icon: const Icon(Icons.folder_open, size: 18),
label: const Text('Open logs folder'),
),
],
),
const SizedBox(height: 8),
SignalBuilder(
builder: (context) => SwitchListTile(
key: const ValueKey('share-diagnostics'),
contentPadding: EdgeInsets.zero,
title: const Text('Share diagnostics with the server'),
subtitle: const Text('In a session, this app\'s log goes to the server\'s session log, '
'merged with everyone\'s, so a problem can be read afterwards. Never chat; '
'passwords and tokens left out. Off: only a problem you mark is sent.'),
value: AppSettings.instance.shareDiagnostics.value,
onChanged: AppSettings.instance.setShareDiagnostics,
),
),
],
),
);
}
/// In a session it asks whether to add the server's session log.
static Future<void> _copyDiagnostics(BuildContext context) async {
var withServer = false;
if (SessionState.instance.route.peek() == AppRoute.session) {
final pick = await showAppMenu<bool>(context, items: const [
AppMenuItem(value: false, label: "This app's log"),
AppMenuItem(value: true, label: "This app's and the server's session log"),
]);
if (pick == null) return;
withServer = pick;
}
final text = await diagnosticsText(sessionLog: withServer);
final saved = await saveDiagnostics(text);
await copyToClipboard(text,
done: saved == null ? 'Diagnostics copied' : 'Diagnostics copied (also saved in the logs folder)');
}
static Future<void> _openLogs() async {
final dir = appLogDir;
if (dir == null) return;
// The file manager opens a folder path the way a browser opens a link.
if (!await openInBrowser(Uri.directory(dir).toString())) {
await copyToClipboard(dir, done: "Couldn't open it — folder path copied");
}
}
}
@@ -0,0 +1,146 @@
import 'package:flutter/foundation.dart';
import '../../session/session_coordinator.dart';
import '../../state/session_state.dart';
import 'profile.dart';
/// What a card shows under its fields after a failed attempt.
class CardError {
final String title;
final String message;
const CardError(this.title, this.message);
}
/// Host/Join submit state for the strip's two connect cards. One attempt at a time;
/// [cancel] abandons it via [SessionState.cancelPending].
class ConnectFlow extends ChangeNotifier {
ConnectFlow({SessionState? session}) : _session = session ?? SessionState.instance;
final SessionState _session;
ConnectMode? busy;
final errors = <ConnectMode, CardError?>{};
final notices = <ConnectMode, String?>{};
int _attempt = 0;
bool _disposed = false;
@override
void dispose() {
_disposed = true;
super.dispose();
}
void _notify() {
if (!_disposed) notifyListeners();
}
void showError(ConnectMode mode, CardError error) {
errors[mode] = error;
notices[mode] = null;
_notify();
}
void clearError(ConnectMode mode) {
if (errors[mode] == null && notices[mode] == null) return;
errors[mode] = null;
notices[mode] = null;
_notify();
}
/// Validates and builds the params; throws [FormatException] with a
/// user-facing message. [avatar] is the base64 picture (the profile's).
/// Hosting puts nothing up: what to watch is picked in the library.
static ConnectParams buildParams(ConnectMode mode, ConnectForm f, {String avatar = ''}) {
final rawPort = mode == ConnectMode.host ? f.hostPort : f.port;
final port = int.tryParse(rawPort.trim());
if (port == null || port <= 0 || port > 65535) {
throw const FormatException('port must be 1–65535');
}
if (f.name.trim().isEmpty) {
throw const FormatException('set your display name first (click your avatar at the top left)');
}
if (mode == ConnectMode.join && f.host.trim().isEmpty) {
throw const FormatException('enter the server address');
}
return ConnectParams(
host: f.host.trim(),
port: port,
secret: mode == ConnectMode.host ? f.hostSecret : f.secret,
name: f.name.trim(),
avatar: avatar,
);
}
/// [edit] fills the card's fields into a copy of the last form, so the
/// other card's saved values survive.
Future<void> submit(ConnectMode mode, void Function(ConnectForm f) edit) async {
if (busy != null) return;
final myAttempt = ++_attempt;
final form = _session.lastForm.copy();
edit(form);
form.name = form.name.trim();
form.host = form.host.trim();
busy = mode;
errors[mode] = null;
notices[mode] = null;
_notify();
ConnectParams params;
try {
final profile = Profile.instance;
await profile.ensureLoaded();
params = buildParams(mode, form, avatar: profile.avatarBase64());
} catch (e) {
if (myAttempt != _attempt) return;
busy = null;
errors[mode] = CardError('Check the form',
e is FormatException ? e.message : 'check the form and try again');
_notify();
return;
}
final fromSolo = mode == ConnectMode.host && _session.solo.value;
try {
if (mode == ConnectMode.host) {
await _session.host(params, form);
} else {
await _session.join(params, form);
}
} catch (e) {
if (myAttempt != _attempt) return;
busy = null;
errors[mode] = CardError('Could not connect', '$e');
_notify();
return;
}
if (myAttempt != _attempt) {
// Cancelled after it got through: back out (an Invite: back to solo).
try {
if (fromSolo) {
await _session.cancelInvite();
} else {
await _session.leave();
await _session.stopServer();
}
} catch (_) {}
return;
}
busy = null;
_notify();
await _session.rememberServer(RecentServer(
hosted: mode == ConnectMode.host,
host: mode == ConnectMode.host ? '' : form.host,
port: mode == ConnectMode.host ? form.hostPort.trim() : form.port.trim(),
secret: mode == ConnectMode.host ? form.hostSecret : form.secret,
));
}
Future<void> cancel() async {
final mode = busy;
_attempt++;
try {
await _session.cancelPending();
} catch (_) {}
busy = null;
if (mode != null) notices[mode] = 'Cancelled';
_notify();
}
}
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import '../../state/session_state.dart';
import 'connect_flow.dart';
import 'ui.dart';
/// The submit button for one [ConnectMode], swapped for a progress bar and
/// Cancel while that attempt runs. Disabled while the other mode is busy.
class ConnectButton extends StatelessWidget {
final ConnectFlow flow;
final ConnectMode mode;
final String label;
final VoidCallback onSubmit;
final bool expand;
const ConnectButton({
super.key,
required this.flow,
required this.mode,
required this.label,
required this.onSubmit,
this.expand = false,
});
@override
Widget build(BuildContext context) {
final busyHere = flow.busy == mode;
if (busyHere) {
return SizedBox(
width: expand ? null : 220,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const LinearProgressIndicator(),
const SizedBox(height: 4),
Row(
children: [
Expanded(
child: Text(mode == ConnectMode.host ? 'Starting server…' : 'Connecting…',
style: Theme.of(context).textTheme.bodySmall),
),
TextButton(onPressed: flow.cancel, child: const Text('Cancel')),
],
),
],
),
);
}
final button = FilledButton(
onPressed: flow.busy != null ? null : onSubmit,
child: Text(label),
);
return expand ? SizedBox(width: double.infinity, child: button) : button;
}
}
/// Last error or notice for [mode], or nothing.
class ConnectFeedback extends StatelessWidget {
final ConnectFlow flow;
final ConnectMode mode;
const ConnectFeedback({super.key, required this.flow, required this.mode});
@override
Widget build(BuildContext context) {
final error = flow.errors[mode];
final notice = flow.notices[mode];
if (error != null) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: ErrorBox(title: error.title, message: error.message),
);
}
if (notice != null) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Text(notice,
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey)),
);
}
return const SizedBox.shrink();
}
}
@@ -0,0 +1,77 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:sync_relay_core/core.dart';
/// Fetches a URL body; injectable so tests fake the server's `/health`.
typedef HealthGetter = Future<String> Function(Uri uri);
const healthPollEvery = Duration(seconds: 10);
const _timeout = Duration(seconds: 4);
/// What a recent-server tile shows: unknown until the first probe answers.
class ServerHealth {
final bool online;
final String name;
final int? proto;
final int viewers;
const ServerHealth.offline()
: online = false,
name = '',
proto = null,
viewers = 0;
const ServerHealth.online({required this.name, this.proto, this.viewers = 0})
: online = true;
/// True when the server speaks another protocol than this build.
bool get needsUpdate =>
online && proto != null && proto != RelayConstants.protoVersion;
/// Lenient parse of `GET /health`; anything unexpected reads as offline.
static ServerHealth parse(String body) {
try {
final j = jsonDecode(body);
if (j is! Map || j['ok'] != true) return const ServerHealth.offline();
final name = j['name'], proto = j['proto'], viewers = j['viewers'];
return ServerHealth.online(
name: name is String ? name.trim() : '',
proto: proto is num ? proto.toInt() : null,
viewers: viewers is num ? viewers.toInt() : 0,
);
} catch (_) {
return const ServerHealth.offline();
}
}
}
Uri healthUri(String host, String port) {
final h = host.trim();
// Bare IPv6 literals need brackets in a URL.
final authority = h.contains(':') && !h.startsWith('[') ? '[$h]' : h;
return Uri.parse('http://$authority:${port.trim()}/health');
}
Future<ServerHealth> probeHealth(String host, String port, HealthGetter get) async {
try {
return ServerHealth.parse(await get(healthUri(host, port)).timeout(_timeout));
} catch (_) {
return const ServerHealth.offline();
}
}
/// Plain dart:io GET; non-200 throws.
Future<String> defaultHealthGet(Uri uri) async {
final client = HttpClient()..connectionTimeout = _timeout;
try {
final req = await client.getUrl(uri);
final res = await req.close().timeout(_timeout);
if (res.statusCode != 200) {
await res.drain<void>();
throw HttpException('status ${res.statusCode}', uri: uri);
}
return await res.transform(utf8.decoder).join().timeout(_timeout);
} finally {
client.close(force: true);
}
}
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import '../../jellyfin/browse_scope.dart' show JellyfinSelection;
import '../../widgets/media_input.dart';
import 'picked_media.dart';
export 'picked_media.dart';
/// The Library's sources.
enum MediaKind { jellyfin, link, file }
/// State of the Library tab, owned by the shell so it survives rebuilds
/// and tab switches: the source shown, the link field and its relay mode,
/// the selected Jellyfin card (details panel) and the last Jellyfin pick.
class MediaPickerController extends ChangeNotifier {
MediaPickerController(
{String mediaPath = '', String linkUrl = '', JellyfinMedia? jellyfin, MediaKind? source})
: link = TextEditingController(text: linkUrl),
file = TextEditingController(text: mediaPath),
_jellyfin = jellyfin,
_selected = jellyfin == null ? null : JellyfinSelection(jellyfin.serverId, jellyfin.itemId),
_source = source ?? MediaKind.jellyfin {
link.addListener(notifyListeners);
file.addListener(notifyListeners);
}
final TextEditingController link;
final TextEditingController file;
MediaKind _source;
// Through me unless picked otherwise (Library → Link).
LinkRelay _linkRelay = LinkRelay.me;
JellyfinMedia? _jellyfin;
JellyfinSelection? _selected;
LinkRelay get linkRelay => _linkRelay;
set linkRelay(LinkRelay v) {
if (v == _linkRelay) return;
_linkRelay = v;
notifyListeners();
}
/// The last Jellyfin item picked (restored from the last put-up).
JellyfinMedia? get jellyfin => _jellyfin;
/// Picks a Jellyfin item: selects its card and switches to Jellyfin.
void pickJellyfin(JellyfinMedia? media) {
_jellyfin = media;
_selected = media == null ? null : JellyfinSelection(media.serverId, media.itemId);
_source = MediaKind.jellyfin;
notifyListeners();
}
/// The selected Jellyfin card (details panel open), or null.
JellyfinSelection? get selected => _selected;
void select(JellyfinSelection? s) {
if (s == _selected && s?.item == _selected?.item) return;
_selected = s;
notifyListeners();
}
MediaKind get source => _source;
set source(MediaKind v) {
if (v == _source) return;
_source = v;
notifyListeners();
}
/// What the Link or File source holds; for Jellyfin the last pick.
PickedMedia? get selection {
switch (_source) {
case MediaKind.link:
final url = link.text.trim();
return url.isEmpty ? null : LinkMedia(url, relay: _linkRelay);
case MediaKind.file:
final path = file.text.trim();
return path.isEmpty ? null : FileMedia(path);
case MediaKind.jellyfin:
return _jellyfin;
}
}
/// Drops pasted text where it belongs: http(s) into Link, else File.
void pasteText(String text) {
final parsed = parseMediaInput(text);
if (parsed.linkUrl.isNotEmpty) {
link.text = parsed.linkUrl;
source = MediaKind.link;
} else if (parsed.mediaPath.isNotEmpty) {
file.text = parsed.mediaPath;
source = MediaKind.file;
}
}
@override
void dispose() {
link.dispose();
file.dispose();
super.dispose();
}
}
@@ -0,0 +1,128 @@
/// What the media picker selected. Null selection = nothing (hosting
/// without media is allowed).
sealed class PickedMedia {
const PickedMedia();
/// One line for summaries ("Playing: …").
String get label;
}
/// How the others get a link: each fetches it ([direct]), the server
/// fetches it once for everyone ([server]), or this computer does and
/// passes it on through the server ([me]; for links only you can reach).
enum LinkRelay { direct, server, me }
/// An http(s) link.
final class LinkMedia extends PickedMedia {
final String url;
final LinkRelay relay;
/// Put it up even though this computer's probe found a problem
/// (`LinkProbe.problem`): the server or a friend may still open it.
final bool anyway;
const LinkMedia(this.url, {this.relay = LinkRelay.direct, this.anyway = false});
@override
String get label {
final uri = Uri.tryParse(url);
final segs = uri?.pathSegments.where((s) => s.isNotEmpty).toList() ?? const [];
if (segs.isEmpty) return uri?.host.isNotEmpty == true ? uri!.host : url;
try {
return Uri.decodeComponent(segs.last);
} catch (_) {
return segs.last;
}
}
@override
bool operator ==(Object other) => other is LinkMedia && other.url == url && other.relay == relay;
@override
int get hashCode => Object.hash(url, relay);
@override
String toString() => 'LinkMedia($url)';
}
/// A file on this computer; the others stream it from here (the host's
/// app serves it itself, anyone else's goes through the server's relay).
final class FileMedia extends PickedMedia {
final String path;
const FileMedia(this.path);
@override
String get label =>
path.split(RegExp(r'[/\\]')).lastWhere((p) => p.isNotEmpty, orElse: () => path);
@override
bool operator ==(Object other) => other is FileMedia && other.path == path;
@override
int get hashCode => path.hashCode;
@override
String toString() => 'FileMedia($path)';
}
/// A movie or episode on a linked Jellyfin server. [serverId] keys the
/// account (`JellyfinAccounts.clientFor`); [baseUrl] is the address it was
/// picked through. No credentials in here: each side authorises its own.
final class JellyfinMedia extends PickedMedia {
final String serverId;
final String baseUrl;
final String itemId;
/// Null = the item's default source.
final String? mediaSourceId;
final String title;
/// "Series · S1E6" for episodes, the year for movies; may be empty.
final String subtitle;
final Duration? runtime;
const JellyfinMedia({
required this.serverId,
required this.baseUrl,
required this.itemId,
this.mediaSourceId,
required this.title,
this.subtitle = '',
this.runtime,
});
@override
String get label => subtitle.isEmpty ? title : '$title — $subtitle';
Map<String, Object?> toJson() => {
'serverId': serverId,
'baseUrl': baseUrl,
'itemId': itemId,
'mediaSourceId': ?mediaSourceId,
'title': title,
'subtitle': subtitle,
if (runtime != null) 'runtimeMs': runtime!.inMilliseconds,
};
static JellyfinMedia? fromJson(Object? j) {
if (j is! Map) return null;
final serverId = j['serverId'], baseUrl = j['baseUrl'], itemId = j['itemId'];
if (serverId is! String || baseUrl is! String || itemId is! String) return null;
final ms = j['runtimeMs'];
return JellyfinMedia(
serverId: serverId,
baseUrl: baseUrl,
itemId: itemId,
mediaSourceId: j['mediaSourceId'] is String ? j['mediaSourceId'] as String : null,
title: j['title'] is String ? j['title'] as String : '',
subtitle: j['subtitle'] is String ? j['subtitle'] as String : '',
runtime: ms is int ? Duration(milliseconds: ms) : null,
);
}
@override
bool operator ==(Object other) =>
other is JellyfinMedia &&
other.serverId == serverId &&
other.itemId == itemId &&
other.mediaSourceId == mediaSourceId;
@override
int get hashCode => Object.hash(serverId, itemId, mediaSourceId);
@override
String toString() => 'JellyfinMedia($serverId/$itemId "$title")';
}
/// Adds a pick to Up next (with [next]: right after what plays); returns
/// an error text, or null when sent. Null where there is no server.
typedef AddToQueue = Future<String?> Function(PickedMedia media, {bool next});
@@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import '../../state/connect_prefs.dart';
import '../../state/session_state.dart';
import '../../widgets/avatar_pipeline.dart';
/// Colours offered in the profile card; the first is the default.
const profileColors = <Color>[
Colors.deepPurple,
Colors.indigo,
Colors.teal,
Colors.pink,
Colors.orange,
Colors.blueGrey,
];
const _keyPicture = 'avatarPng';
const _keyColor = 'profileColor';
/// Sends a mid-session name/picture change (null = unchanged, empty
/// avatar = removed). Swappable so tests can watch it.
typedef ProfilePush = void Function({String? name, String? avatar});
/// The one place a profile change reaches the server.
void pushProfileToSession({String? name, String? avatar}) {
final session = SessionState.instance;
if (session.route.peek() != AppRoute.session) return;
session.coordinator.updateProfile(name: name, avatar: avatar);
}
/// Display name, picture and colour of this app's user. Backed by
/// [SessionState.lastForm] (name) and the connect-form box (the cropped
/// 256 px PNG and the colour); every change is persisted, and while in a
/// server it is pushed through [push] so everyone sees it at once.
class Profile {
static final Profile instance = Profile._();
Profile._();
final name = signal('');
/// The cropped PNG; null shows the initial on [color].
final picture = signal<Uint8List?>(null);
final color = signal<Color>(profileColors.first);
final error = signal<String?>(null);
ProfilePush push = pushProfileToSession;
bool _loaded = false;
Timer? _nameTimer;
/// Reads the stored profile once (the app shell starts it). Old installs
/// stored a picture path: it is shrunk through [encodeAvatar] once and
/// kept as bytes. A caller during the first read does not wait for it
/// (no shared Future: tests run each in its own zone).
Future<void> ensureLoaded() async {
if (_loaded) return;
_loaded = true;
await _load();
}
Future<void> _load() async {
final form = SessionState.instance.lastForm;
name.value = form.name;
try {
final box = connectFormBoxIfOpen() ?? await openConnectFormBox();
final png = box.get(_keyPicture);
final c = box.get(_keyColor);
if (c is int) color.value = Color(c);
if (png is Uint8List && png.isNotEmpty) {
picture.value = png;
} else if (form.avatarPath.isNotEmpty) {
final b64 = await encodeAvatar(form.avatarPath);
if (b64.isNotEmpty) picture.value = base64Decode(b64);
form.avatarPath = '';
await _save();
}
} catch (e) {
// No store (tests) or an unreadable old picture: start without.
debugPrint('profile load: $e');
}
}
/// The picture as sent in hello and profile updates; '' when none.
String avatarBase64() {
final png = picture.peek();
return png == null ? '' : avatarWireBase64(png);
}
void setName(String value) {
final v = value.trim();
if (v.isEmpty || v == name.peek()) return;
name.value = v;
SessionState.instance.lastForm.name = v;
unawaited(_save());
// Typing renames once it settles, not on every key.
_nameTimer?.cancel();
_nameTimer = Timer(const Duration(milliseconds: 600), () => push(name: name.peek()));
}
Future<void> setPicture(Uint8List? png) async {
picture.value = png;
error.value = null;
await _save();
try {
push(avatar: png == null ? '' : avatarWireBase64(png));
} on FormatException catch (e) {
error.value = e.message;
}
}
Future<void> setColor(Color c) async {
color.value = c;
await _save();
}
Future<void> _save() async {
try {
await SessionState.instance.saveForm();
final box = connectFormBoxIfOpen() ?? await openConnectFormBox();
final png = picture.peek();
await box.putAll({
_keyPicture: png ?? Uint8List(0),
_keyColor: color.peek().toARGB32(),
});
} catch (_) {
error.value = 'could not save profile — try again';
}
}
}
@@ -0,0 +1,290 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';
import '../../state/settings.dart';
import '../../update/update_controller.dart';
import 'ui.dart';
import 'update_indicator.dart';
/// Session keys: Space and Tab anywhere in a server (`AppKeys`), the
/// rest by the session player's key handler (while it fills the window).
const sessionShortcuts = [
('Space', 'Toggle ready'),
('← / →', 'Seek back / forward 10 s'),
('↑ / ↓, wheel', 'Volume up / down (up to 150 %)'),
('Shift+→', 'Skip 1:30 (for everyone)'),
('PgUp / PgDn', 'Previous / next chapter'),
('F', 'Toggle fullscreen'),
('Tab', 'Open / close chat & activity'),
('Ctrl+R', 'Reload the video'),
('> or Shift+N', 'Put up the next Jellyfin episode'),
('< or Shift+P', 'Put up the previous Jellyfin episode'),
('Ctrl+1…6', 'Shaders: Anime4K mode A, B, C, A+A, B+B, C+A'),
('Ctrl+0', 'Shaders off'),
('Esc', 'Close the panel or fullscreen, shrink the player, then ask to leave'),
('Ctrl+Shift+B', 'Mark a problem in the session log (anywhere)'),
];
/// One titled card of the Settings tab (rounded Material, so list tiles
/// paint their ripple on it).
class SettingsPanel extends StatelessWidget {
final String title;
final Widget child;
const SettingsPanel({super.key, required this.title, required this.child});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: kGap),
child: Material(
color: Theme.of(context).colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.fromLTRB(18, 14, 18, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w700)),
const SizedBox(height: 6),
child,
],
),
),
),
);
}
}
/// A tinted hint card at the top of a Settings page.
class SettingsHintCard extends StatelessWidget {
final IconData icon;
final String text;
const SettingsHintCard({super.key, required this.icon, required this.text});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.only(bottom: kGap),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Icon(icon, color: scheme.primary),
const SizedBox(width: 12),
Expanded(child: Text(text)),
],
),
);
}
}
/// The .glsl files of the "Custom shaders" preset, in mpv order.
class CustomShaderFiles extends StatelessWidget {
const CustomShaderFiles({super.key});
Future<void> _addFiles() async {
final List<PlatformFile> files;
try {
files = await FilePicker.pickFiles(
dialogTitle: 'Pick mpv GLSL shaders',
type: FileType.custom,
allowedExtensions: const ['glsl', 'hook'],
);
} catch (_) {
return;
}
final s = AppSettings.instance;
final current = s.customShaders.peek();
final added = [
for (final f in files)
if (f.path case final p? when !current.contains(p)) p,
];
if (added.isNotEmpty) s.setCustomShaders([...current, ...added]);
}
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
final muted = TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant);
return SignalBuilder(builder: (context) {
final custom = s.customShaders.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text('Your own mpv shaders for the "Custom shaders" choice; they run in this order.',
style: muted),
),
TextButton.icon(
onPressed: _addFiles,
icon: const Icon(Icons.add, size: 18),
label: const Text('Add .glsl files'),
),
],
),
if (custom.isEmpty)
Text('None picked.', style: muted)
else
for (final (i, path) in custom.indexed)
Row(
children: [
Expanded(
child: Tooltip(
message: path,
child: Text(path.split(RegExp(r'[/\\]')).last, overflow: TextOverflow.ellipsis),
),
),
IconButton(
tooltip: 'Remove',
icon: const Icon(Icons.close, size: 18),
onPressed: () => s.setCustomShaders([...custom]..removeAt(i)),
),
],
),
],
);
});
}
}
class UpdateSettings extends StatelessWidget {
final UpdateController? controller;
const UpdateSettings({super.key, this.controller});
@override
Widget build(BuildContext context) {
final c = controller ?? UpdateController.instance;
final s = AppSettings.instance;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(child: UpdateStatusLine(controller: c)),
ValueListenableBuilder(
valueListenable: c.status,
builder: (context, st, _) {
if (st.phase == UpdatePhase.idle || st.manifest == null) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilledButton.tonal(
onPressed: () => showUpdateDialog(context, controller: c),
child: const Text('Show update'),
),
);
},
),
ValueListenableBuilder(
valueListenable: updateCheck,
builder: (context, check, _) => OutlinedButton(
onPressed: check == UpdateCheck.checking ? null : () => runUpdateCheck(c, true),
child: const Text('Check now'),
),
),
],
),
const SizedBox(height: 8),
SignalBuilder(
builder: (context) => SwitchListTile(
key: const ValueKey('auto-check-updates'),
contentPadding: EdgeInsets.zero,
title: const Text('Check for updates on start'),
subtitle: const Text('Looks for a new version when the app opens; nothing installs without asking'),
value: s.autoCheckUpdates.value,
onChanged: s.setAutoCheckUpdates,
),
),
],
);
}
}
class AutoAdvanceSetting extends StatelessWidget {
const AutoAdvanceSetting({super.key});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(
builder: (context) => SwitchListTile(
key: const ValueKey('auto-advance'),
contentPadding: EdgeInsets.zero,
title: const Text('Play the next episode automatically'),
subtitle: const Text('When a Jellyfin episode ends, a 10 s countdown puts up the next one; '
'anyone can cancel it'),
value: s.autoAdvance.value,
onChanged: s.setAutoAdvance,
),
);
}
}
class HideReReleasesSetting extends StatelessWidget {
const HideReReleasesSetting({super.key});
@override
Widget build(BuildContext context) {
final s = AppSettings.instance;
return SignalBuilder(
builder: (context) => SwitchListTile(
key: const ValueKey('hide-re-releases'),
contentPadding: EdgeInsets.zero,
title: const Text('Hide re-releases in Latest'),
subtitle: const Text('Skips episodes added more than 30 days after they aired '
'(re-uploads, upgrades); also in each Latest row\'s ⋮ menu'),
value: s.hideReReleases.value,
onChanged: s.setHideReReleases,
),
);
}
}
class ShortcutList extends StatelessWidget {
const ShortcutList({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('While watching:', style: TextStyle(color: Colors.grey)),
const SizedBox(height: 8),
for (final (key, what) in sessionShortcuts)
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 96,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(6),
),
child: Text(key,
style: const TextStyle(fontFamily: 'monospace', fontSize: 13)),
),
),
),
Expanded(child: Text(what)),
],
),
),
],
);
}
}
+127
View File
@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart' show appVersion, isDevBuild;
/// Product name (working title). Change it here only.
const appName = 'Starsync';
const repoUrl = 'https://git.senkochan.moe/Foxyz/sync_replay_project';
/// The pubspec's version: the Flutter tool defines it for every build and
/// run (`flutter run` too).
const _pubspecVersion = String.fromEnvironment('FLUTTER_BUILD_NAME');
/// The short commit a packaged build was made from (`tool/package_linux.sh`;
/// `-dirty` with uncommitted changes); empty otherwise.
const _gitCommit = String.fromEnvironment('GIT_COMMIT');
/// The version to show: a release's own ("1.1.2"), a test build's with its
/// commit ("1.1.3-dev.12 · 1a2b3c4"), and for a dev run (no `APP_VERSION`)
/// the pubspec's with "dev" ("1.1.2 · dev run").
String displayVersion({String version = appVersion, String pubspec = _pubspecVersion, String commit = _gitCommit}) {
if (version == '0.0.0-dev') return pubspec.isEmpty ? 'dev run' : '$pubspec · dev run';
return isDevBuild(version) && commit.isNotEmpty ? '$version · $commit' : version;
}
const kRadius = 12.0;
const kGap = 16.0;
/// Rounded panel used by every Settings section.
class Panel extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final bool enabled;
const Panel(
{super.key,
required this.child,
this.padding = const EdgeInsets.all(20),
this.enabled = true});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
// Material (not a DecoratedBox) so ink from tiles/chips inside shows.
final panel = Material(
color: scheme.surfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(kRadius),
side: BorderSide(color: scheme.outlineVariant.withValues(alpha: 0.5)),
),
clipBehavior: Clip.antiAlias,
child: Padding(padding: padding, child: child),
);
if (enabled) return panel;
return Opacity(opacity: 0.5, child: IgnorePointer(child: panel));
}
}
/// Small grey heading inside a panel.
class SectionLabel extends StatelessWidget {
final String text;
final Widget? trailing;
const SectionLabel(this.text, {super.key, this.trailing});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Expanded(
child: Text(text, style: Theme.of(context).textTheme.titleMedium),
),
?trailing,
],
),
);
}
}
/// Outlined text field style (settings, Jellyfin sign-in).
InputDecoration fieldDecoration(String label, {String? hint, Widget? suffix}) =>
InputDecoration(
labelText: label,
hintText: hint,
isDense: true,
suffixIcon: suffix,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
);
/// Widest a single text field gets, however wide the window.
const kFieldMaxWidth = 720.0;
/// Inline failure under a connect form: bold title, selectable message.
class ErrorBox extends StatelessWidget {
final String title;
final String message;
const ErrorBox({super.key, required this.title, required this.message});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: scheme.errorContainer.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(8),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.error_outline, size: 18, color: scheme.error),
const SizedBox(width: 8),
Expanded(
child: SelectableText.rich(
TextSpan(children: [
TextSpan(
text: '$title\n',
style: const TextStyle(fontWeight: FontWeight.w600)),
TextSpan(text: message),
]),
style: const TextStyle(fontSize: 13),
),
),
],
),
);
}
}
@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import '../../update/update_controller.dart';
import '../../widgets/update_banner.dart';
import 'ui.dart';
/// Compact "Update vX" pill for the party strip; hidden until there is
/// something to install. Click opens the full [UpdateBanner].
class UpdateIndicator extends StatelessWidget {
final UpdateController? controller;
const UpdateIndicator({super.key, this.controller});
@override
Widget build(BuildContext context) {
final c = controller ?? UpdateController.instance;
return ValueListenableBuilder<UpdateStatus>(
valueListenable: c.status,
builder: (context, s, _) {
final m = s.manifest;
if (s.phase == UpdatePhase.idle || m == null) {
return const SizedBox.shrink();
}
final scheme = Theme.of(context).colorScheme;
final busy = s.phase == UpdatePhase.downloading ||
s.phase == UpdatePhase.applying ||
s.phase == UpdatePhase.restarting;
final label = switch (s.phase) {
UpdatePhase.downloading =>
'Updating ${((s.progress ?? 0) * 100).round()}%',
UpdatePhase.applying => 'Installing…',
UpdatePhase.restarting => 'Restarting…',
UpdatePhase.failed => 'Update failed',
_ => m.isRequired ? 'Required update' : 'Update v${m.version}',
};
return Tooltip(
message: 'Show update',
child: FilledButton.tonalIcon(
style: FilledButton.styleFrom(
backgroundColor: m.isRequired || s.phase == UpdatePhase.failed
? scheme.errorContainer
: null,
visualDensity: VisualDensity.compact,
),
onPressed: () => showUpdateDialog(context, controller: c),
icon: busy
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.system_update_alt, size: 18),
label: Text(label),
),
);
},
);
}
}
/// The existing update card in a dialog; closes itself when the card
/// would render nothing (e.g. "Later").
Future<void> showUpdateDialog(BuildContext context, {UpdateController? controller}) {
final c = controller ?? UpdateController.instance;
return showDialog<void>(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(kRadius)),
child: SizedBox(
width: 420,
child: ValueListenableBuilder<UpdateStatus>(
valueListenable: c.status,
builder: (context, s, _) {
if (s.phase == UpdatePhase.idle || s.manifest == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) Navigator.of(context).maybePop();
});
}
return Padding(
padding: const EdgeInsets.all(8),
child: UpdateBanner(controller: c),
);
},
),
),
),
);
}
enum UpdateCheck { notRun, checking, done }
/// Progress of this run's update check, for Settings/About wording.
final updateCheck = ValueNotifier(UpdateCheck.notRun);
/// [force] re-checks even if this process already checked ("Check now").
Future<void> runUpdateCheck([UpdateController? controller, bool force = false]) async {
final c = controller ?? UpdateController.instance;
updateCheck.value = UpdateCheck.checking;
try {
await (force ? c.checkNow() : c.checkOnce());
} finally {
updateCheck.value = UpdateCheck.done;
}
}
/// One-line update state: "Up to date", "Update v1.2 available", ...
class UpdateStatusLine extends StatelessWidget {
final UpdateController? controller;
const UpdateStatusLine({super.key, this.controller});
@override
Widget build(BuildContext context) {
final c = controller ?? UpdateController.instance;
return ListenableBuilder(
listenable: Listenable.merge([c.status, c.lastCheck, updateCheck]),
builder: (context, _) {
final s = c.status.value;
final m = s.manifest;
final String text;
if (c.skipDevBuilds && c.currentVersion.endsWith('-dev')) {
text = 'Dev build — update checks are off';
} else if (s.phase != UpdatePhase.idle && m != null) {
text = s.phase == UpdatePhase.failed
? 'Update to v${m.version} failed'
: 'Update v${m.version} available';
} else {
text = switch (updateCheck.value) {
UpdateCheck.notRun => 'Not checked yet',
UpdateCheck.checking => 'Checking…',
UpdateCheck.done => c.lastCheck.value ?? 'Not checked yet',
};
}
return Text(text,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: Colors.grey));
},
);
}
}
@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import '../../jellyfin/session_media.dart';
import '../../session/session_coordinator.dart' show AutoAdvance, autoAdvanceSecs;
import '../../widgets/app_menu.dart' show AppGlass;
import 'player_controls.dart' show FloatingPill;
// Jellyfin pieces of the session player. Plain widgets fed by the
// coordinator's signals, so tests can pump them without the player.
/// Previous / next episode in the video controls. Nothing when there is
/// no such episode or this viewer cannot put it up (no account).
class EpisodeSkipButton extends StatelessWidget {
final EpisodeLink? episode;
final bool next;
final VoidCallback onPressed;
final FocusNode? focusNode;
const EpisodeSkipButton(
{super.key, required this.episode, required this.next, required this.onPressed, this.focusNode});
@override
Widget build(BuildContext context) {
final e = episode;
if (e == null) return const SizedBox.shrink();
return Tooltip(
message: '${next ? 'Next' : 'Previous'}: ${e.label} (${next ? '> or Shift+N' : '< or Shift+P'})',
child: IconButton(
key: ValueKey(next ? 'episode-next' : 'episode-previous'),
focusNode: focusNode,
color: Colors.white,
iconSize: 22,
visualDensity: VisualDensity.compact,
icon: Icon(next ? Icons.skip_next_rounded : Icons.skip_previous_rounded),
onPressed: onPressed,
),
);
}
}
/// Floating "Next episode ›" from the ED (or the last two minutes), while
/// the controls are up (`PillGate`): faint at rest, lit on hover.
class NextEpisodePill extends StatelessWidget {
final EpisodeLink episode;
final VoidCallback onPressed;
const NextEpisodePill({super.key, required this.episode, required this.onPressed});
@override
Widget build(BuildContext context) => FloatingPill(
label: 'Next episode',
tooltip: 'Next: ${episode.label}',
tapKey: const ValueKey('next-episode-pill'),
onPressed: onPressed,
);
}
/// At the end of an episode: "Next: S1E7 title" with Play now, and while
/// the auto-advance countdown runs, the seconds left and Cancel.
class EndOfEpisodeCard extends StatelessWidget {
final EpisodeLink next;
final AutoAdvance? countdown;
final VoidCallback onPlayNow;
final VoidCallback onCancel;
const EndOfEpisodeCard({
super.key,
required this.next,
required this.countdown,
required this.onPlayNow,
required this.onCancel,
});
@override
Widget build(BuildContext context) {
final c = countdown;
// The same card as Up next's (`UpNextCard`): the app's glass, 400 wide.
return AppGlass(
key: const ValueKey('end-of-episode'),
child: SizedBox(
width: 400,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(c == null ? 'Up next' : 'Next episode in ${c.secondsLeft} s',
style: const TextStyle(color: Colors.white60, fontSize: 12)),
const SizedBox(height: 2),
Text('Next: ${next.label}',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600, height: 1.25)),
if (c != null) ...[
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: TweenAnimationBuilder<double>(
tween: Tween(end: c.secondsLeft / autoAdvanceSecs),
duration: const Duration(seconds: 1),
builder: (context, v, _) =>
LinearProgressIndicator(value: v, minHeight: 3, backgroundColor: const Color(0x1FFFFFFF)),
),
),
],
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (c != null)
TextButton(
key: const ValueKey('auto-advance-cancel'),
onPressed: onCancel,
child: const Text('Cancel')),
const SizedBox(width: 8),
FilledButton.icon(
key: const ValueKey('play-next'),
onPressed: onPlayNow,
icon: const Icon(Icons.skip_next_rounded, size: 18),
label: Text(c == null ? 'Play next' : 'Play now'),
),
],
),
],
),
),
),
);
}
}
/// This viewer cannot play the Jellyfin media: say why and offer to link
/// an account (never a silent stall).
class JellyfinBlockedCard extends StatelessWidget {
final BlockedPlay blocked;
final VoidCallback onLinkAccount;
final VoidCallback onRetry;
const JellyfinBlockedCard(
{super.key, required this.blocked, required this.onLinkAccount, required this.onRetry});
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return Card(
key: const ValueKey('jellyfin-blocked'),
color: Colors.black.withValues(alpha: 0.85),
child: Padding(
padding: const EdgeInsets.all(20),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 440),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.lock_outline, color: Colors.orange, size: 32),
const SizedBox(height: 10),
Text(blocked.message, textAlign: TextAlign.center, style: t.titleSmall),
const SizedBox(height: 6),
Text(
'Whoever put it up passes it on automatically while their app is open; '
'if they left, ask them to put it up again. Or link your own account in Integrations.',
textAlign: TextAlign.center, style: t.bodySmall?.copyWith(color: Colors.grey)),
const SizedBox(height: 14),
Wrap(
spacing: 8,
children: [
FilledButton(
key: const ValueKey('link-jellyfin-account'),
onPressed: onLinkAccount,
child: const Text('Link a Jellyfin account')),
TextButton(onPressed: onRetry, child: const Text('Try again')),
],
),
],
),
),
),
);
}
}
@@ -0,0 +1,621 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:sync_relay_core/core.dart' show JellyfinStreamType;
import '../../jellyfin/track_prefs.dart' show MpvTrack;
import '../../playback/media_marks.dart';
import '../../widgets/app_menu.dart' show AppGlass;
// Session control-bar pieces built on the playback signals (chapters,
// tracks, skip offers): the seek bar with chapter marks, the chapter and
// track menus, the 1:30 skip button and the floating skip pill. Plain
// widgets fed by values and callbacks, so tests pump them without a player.
/// Keeps media_kit's desktop controls shown, as a mouse hover would.
/// While a seek bar drag runs no hover events arrive, and the controls
/// would hide (and unmount the bar) mid-drag. A guarded dynamic call on
/// the private controls state; nothing happens outside those controls.
void keepVideoControlsAwake(BuildContext context) {
context.visitAncestorElements((e) {
if (e is StatefulElement &&
e.state.runtimeType.toString() == '_MaterialDesktopVideoControlsState') {
try {
(e.state as dynamic).onHover();
} catch (_) {}
return false;
}
return true;
});
}
/// The seek bar: played / buffered track, a diamond at each chapter start
/// (not the one at 0), and on hover the time with the chapter title above
/// it. Like media_kit's bar it reports a seek on every drag move and on
/// release ([onSeek]); the session routes those through the user-seek path
/// (debounced, everyone follows).
class ChapterSeekBar extends StatefulWidget {
final Duration position;
final Duration duration;
final Duration buffer;
final List<MediaChapter> chapters;
final ValueChanged<Duration> onSeek;
/// Any pointer activity on the bar (keeps the controls up).
final VoidCallback? onActivity;
const ChapterSeekBar({
super.key,
required this.position,
required this.duration,
this.buffer = Duration.zero,
this.chapters = const [],
required this.onSeek,
this.onActivity,
});
static const height = 28.0;
@override
State<ChapterSeekBar> createState() => _ChapterSeekBarState();
}
class _ChapterSeekBarState extends State<ChapterSeekBar> {
double? _hoverX;
bool _dragging = false;
double _dragFraction = 0;
double _fraction(Duration d) {
final total = widget.duration.inMicroseconds;
if (total <= 0) return 0;
return (d.inMicroseconds / total).clamp(0.0, 1.0);
}
Duration _at(double fraction) => widget.duration * fraction.clamp(0.0, 1.0);
void _seekTo(double x, double width) {
if (widget.duration <= Duration.zero || width <= 0) return;
_dragFraction = (x / width).clamp(0.0, 1.0);
widget.onSeek(_at(_dragFraction));
}
@override
Widget build(BuildContext context) {
final primary = Theme.of(context).colorScheme.primary;
return LayoutBuilder(builder: (context, constraints) {
final width = constraints.maxWidth;
final active = _hoverX != null || _dragging;
final barHeight = active ? 5.0 : 3.0;
final played = _dragging ? _dragFraction : _fraction(widget.position);
const mid = ChapterSeekBar.height / 2;
final hoverX = _hoverX;
final hoverAt = hoverX == null || width <= 0 ? null : _at(hoverX / width);
final hoverChapter = hoverAt == null ? null : chapterIndexAt(widget.chapters, hoverAt);
final hoverTitle = hoverChapter == null ? '' : widget.chapters[hoverChapter].title;
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (e) => setState(() => _hoverX = e.localPosition.dx.clamp(0, width)),
onHover: (e) {
setState(() => _hoverX = e.localPosition.dx.clamp(0, width));
widget.onActivity?.call();
},
onExit: (_) => setState(() => _hoverX = null),
child: Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (e) {
widget.onActivity?.call();
setState(() {
_dragging = true;
_dragFraction = width <= 0 ? 0 : (e.localPosition.dx / width).clamp(0.0, 1.0);
});
},
onPointerMove: (e) {
widget.onActivity?.call();
setState(() => _hoverX = e.localPosition.dx.clamp(0, width));
_seekTo(e.localPosition.dx, width);
},
onPointerUp: (e) {
_seekTo(e.localPosition.dx, width);
setState(() => _dragging = false);
widget.onActivity?.call();
},
onPointerCancel: (_) => setState(() => _dragging = false),
child: SizedBox(
key: const ValueKey('seek-bar'),
width: width,
height: ChapterSeekBar.height,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
left: 0,
right: 0,
top: mid - barHeight / 2,
height: barHeight,
child: Container(color: Colors.white24),
),
Positioned(
left: 0,
top: mid - barHeight / 2,
height: barHeight,
width: width * _fraction(widget.buffer),
child: Container(color: Colors.white24),
),
if (hoverX != null && !_dragging)
Positioned(
left: 0,
top: mid - barHeight / 2,
height: barHeight,
width: hoverX,
child: Container(color: Colors.white24),
),
Positioned(
left: 0,
top: mid - barHeight / 2,
height: barHeight,
width: width * played,
child: Container(color: primary),
),
for (final c in widget.chapters)
if (c.start > Duration.zero && widget.duration > Duration.zero)
Positioned(
key: ValueKey('chapter-mark-${c.index}'),
left: width * _fraction(c.start) - 4,
top: mid - 4,
width: 8,
height: 8,
child: IgnorePointer(
child: Transform.rotate(
angle: math.pi / 4,
child: Container(
margin: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.black54, width: 0.8),
),
),
),
),
),
if (active)
Positioned(
left: width * played - 6,
top: mid - 6,
width: 12,
height: 12,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(color: primary, shape: BoxShape.circle),
),
),
),
if (hoverAt != null)
Positioned(
// Kept clear of the window edges (the tip is centred).
left: hoverX!.clamp(math.min(60.0, width / 2), math.max(width - 60, width / 2)),
bottom: ChapterSeekBar.height - 2,
child: IgnorePointer(
child: FractionalTranslation(
translation: const Offset(-0.5, 0),
child: _HoverTip(title: hoverTitle, time: formatClock(hoverAt)),
),
),
),
],
),
),
),
);
});
}
}
class _HoverTip extends StatelessWidget {
final String title;
final String time;
const _HoverTip({required this.title, required this.time});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxWidth: 320),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(6),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (title.isNotEmpty)
Text(title,
key: const ValueKey('seek-hover-chapter'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600)),
Text(time,
key: const ValueKey('seek-hover-time'),
style: const TextStyle(color: Colors.white70, fontSize: 11)),
],
),
);
}
}
/// [ChapterSeekBar] on a live player: position, duration and buffer from
/// its streams. Seeks go to `player.seek`, which the session's
/// `SyncPlayer` turns into user seeks.
class PlayerSeekBar extends StatefulWidget {
final Player player;
final List<MediaChapter> chapters;
const PlayerSeekBar({super.key, required this.player, required this.chapters});
@override
State<PlayerSeekBar> createState() => _PlayerSeekBarState();
}
class _PlayerSeekBarState extends State<PlayerSeekBar> {
final List<StreamSubscription<Object>> _subs = [];
late Duration _position, _duration, _buffer;
@override
void initState() {
super.initState();
_listen();
}
@override
void didUpdateWidget(PlayerSeekBar old) {
super.didUpdateWidget(old);
if (!identical(old.player, widget.player)) {
_cancel();
_listen();
}
}
void _listen() {
final p = widget.player;
_position = p.state.position;
_duration = p.state.duration;
_buffer = p.state.buffer;
void set(VoidCallback f) {
if (mounted) setState(f);
}
_subs.addAll([
p.stream.position.listen((d) => set(() => _position = d)),
p.stream.duration.listen((d) => set(() => _duration = d)),
p.stream.buffer.listen((d) => set(() => _buffer = d)),
]);
}
void _cancel() {
for (final s in _subs) {
s.cancel();
}
_subs.clear();
}
@override
void dispose() {
_cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChapterSeekBar(
position: _position,
duration: _duration,
buffer: _buffer,
chapters: widget.chapters,
onSeek: (d) => widget.player.seek(d),
onActivity: () => keepVideoControlsAwake(context),
);
}
}
RelativeRect _menuPosition(BuildContext context) {
final button = context.findRenderObject() as RenderBox;
final overlay = Navigator.of(context).overlay!.context.findRenderObject() as RenderBox;
return RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset.zero, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
}
/// Control-bar icon with a small count in the corner.
class _BadgedIcon extends StatelessWidget {
final IconData icon;
final int count;
const _BadgedIcon(this.icon, this.count);
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Icon(icon),
if (count > 0)
Positioned(
right: -7,
top: -5,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0.5),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(999),
),
child: Text('$count',
style: const TextStyle(color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold)),
),
),
],
);
}
}
/// Chapter list for the controls: current chapter checked, a pick seeks
/// to its start. Nothing when the file has no chapters.
class ChaptersControlButton extends StatelessWidget {
final List<MediaChapter> chapters;
final int? current;
final ValueChanged<MediaChapter> onPick;
final FocusNode? focusNode;
final ValueChanged<bool>? onMenuOpen;
const ChaptersControlButton({
super.key,
required this.chapters,
required this.current,
required this.onPick,
this.focusNode,
this.onMenuOpen,
});
@override
Widget build(BuildContext context) {
if (chapters.isEmpty) return const SizedBox.shrink();
return Tooltip(
message: 'Chapters (PgUp / PgDn)',
child: IconButton(
key: const ValueKey('chapters-button'),
focusNode: focusNode,
color: Colors.white,
iconSize: 20,
visualDensity: VisualDensity.compact,
icon: _BadgedIcon(Icons.format_list_bulleted, chapters.length),
onPressed: () => _open(context),
),
);
}
Future<void> _open(BuildContext context) async {
final position = _menuPosition(context);
onMenuOpen?.call(true);
final pick = await showMenu<MediaChapter>(
context: context,
position: position,
popUpAnimationStyle: AnimationStyle.noAnimation,
constraints: const BoxConstraints(minWidth: 240, maxWidth: 420),
items: [
for (final c in chapters)
CheckedPopupMenuItem(
value: c,
checked: c.index == current,
height: 36,
child: Row(
children: [
Expanded(
child: Text(c.title.isEmpty ? 'Chapter ${c.index + 1}' : c.title,
maxLines: 1, overflow: TextOverflow.ellipsis),
),
const SizedBox(width: 12),
Text(formatClock(c.start), style: const TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
],
);
onMenuOpen?.call(false);
if (pick != null) onPick(pick);
}
}
/// A track pick from [TrackMenuButton]: audio id, subtitle id, or off.
typedef TrackPick = ({int? aid, int? sid, bool subtitlesOff});
/// Audio or subtitle menu for the controls bar (the count of tracks on the
/// icon): "Japanese · E-AC3 2.0 · title", mpv's selection checked,
/// subtitles with Off. The choice is remembered for the next Jellyfin item
/// (per viewer).
class TrackMenuButton extends StatelessWidget {
final JellyfinStreamType type;
/// All tracks; only those of [type] are listed.
final List<MpvTrack> tracks;
final void Function(TrackPick pick) onPick;
final FocusNode? focusNode;
final ValueChanged<bool>? onMenuOpen;
const TrackMenuButton({
super.key,
required this.type,
required this.tracks,
required this.onPick,
this.focusNode,
this.onMenuOpen,
});
bool get _audio => type == JellyfinStreamType.audio;
@override
Widget build(BuildContext context) {
final list = tracks.where((t) => t.type == type).toList();
return Tooltip(
message: _audio ? 'Audio' : 'Subtitles',
child: IconButton(
key: ValueKey(_audio ? 'audio-button' : 'subtitles-button'),
focusNode: focusNode,
color: Colors.white,
iconSize: 20,
visualDensity: VisualDensity.compact,
icon: _BadgedIcon(_audio ? Icons.audiotrack_outlined : Icons.subtitles_outlined, list.length),
onPressed: () => _open(context, list),
),
);
}
Future<void> _open(BuildContext context, List<MpvTrack> list) async {
final position = _menuPosition(context);
onMenuOpen?.call(true);
final pick = await showMenu<TrackPick>(
context: context,
position: position,
popUpAnimationStyle: AnimationStyle.noAnimation,
constraints: const BoxConstraints(minWidth: 280, maxWidth: 460),
items: [
if (!_audio)
CheckedPopupMenuItem(
value: (aid: null, sid: null, subtitlesOff: true),
checked: !list.any((t) => t.selected),
height: 36,
child: const Text('Off'),
),
if (_audio && list.isEmpty)
const PopupMenuItem(enabled: false, height: 32, child: Text('None')),
for (final t in list)
CheckedPopupMenuItem(
value: _audio
? (aid: t.id, sid: null, subtitlesOff: false)
: (aid: null, sid: t.id, subtitlesOff: false),
checked: t.selected,
height: 36,
child: Text(t.label, maxLines: 1, overflow: TextOverflow.ellipsis),
),
],
);
onMenuOpen?.call(false);
if (pick != null) onPick(pick);
}
}
/// "1:30" in the controls: skip forward [fallbackSkip] (Shift+Right).
class SkipForwardButton extends StatelessWidget {
final VoidCallback onPressed;
final FocusNode? focusNode;
const SkipForwardButton({super.key, required this.onPressed, this.focusNode});
@override
Widget build(BuildContext context) {
return Tooltip(
message: 'Skip 1:30 for everyone (Shift+Right)',
child: TextButton.icon(
key: const ValueKey('skip-forward-button'),
focusNode: focusNode,
style: TextButton.styleFrom(
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: onPressed,
icon: const Icon(Icons.fast_forward_rounded, size: 16),
label: const Text('1:30', style: TextStyle(fontSize: 12)),
),
);
}
}
/// The player's action pill (Next episode, Skip OP / ED / 1:30): the
/// status pill's glass ([AppGlass.pill]) at [height], faint at rest, lit
/// on hover. Lives in the player's action area, outside the auto-hiding
/// controls (also in fullscreen); `PillGate` shows it with them.
class FloatingPill extends StatefulWidget {
/// Every action pill's height: the status pill's without avatars.
static const height = 44.0;
final String label;
final String tooltip;
final IconData icon;
final VoidCallback onPressed;
/// Key of the tap target (tests find the pill by it).
final Key? tapKey;
const FloatingPill({
super.key,
required this.label,
required this.tooltip,
required this.onPressed,
this.icon = Icons.chevron_right_rounded,
this.tapKey,
});
@override
State<FloatingPill> createState() => _FloatingPillState();
}
class _FloatingPillState extends State<FloatingPill> {
bool _hover = false;
@override
Widget build(BuildContext context) {
const fade = Duration(milliseconds: 150);
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _hover = true),
onExit: (_) => setState(() => _hover = false),
child: Tooltip(
message: widget.tooltip,
child: AnimatedOpacity(
duration: fade,
opacity: _hover ? 1 : 0.45,
child: GestureDetector(
key: widget.tapKey,
onTap: widget.onPressed,
child: AppGlass.pill(
child: AnimatedContainer(
duration: fade,
height: FloatingPill.height,
padding: const EdgeInsets.only(left: 16, right: 12),
color: _hover ? Colors.white12 : Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.label,
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)),
const SizedBox(width: 6),
Icon(widget.icon, color: Colors.white, size: 18),
],
),
),
),
),
),
),
);
}
}
/// "Skip OP" / "Skip ED" / "Skip 1:30" for a [SkipOffer].
class SkipPill extends StatelessWidget {
final SkipOffer offer;
final VoidCallback onPressed;
const SkipPill({super.key, required this.offer, required this.onPressed});
static String labelFor(SkipKind kind) => switch (kind) {
SkipKind.opening => 'Skip OP',
SkipKind.ending => 'Skip ED',
SkipKind.fixed => 'Skip 1:30',
};
@override
Widget build(BuildContext context) {
return FloatingPill(
label: labelFor(offer.kind),
tooltip: offer.kind == SkipKind.fixed
? 'Skip 1:30 for everyone (Shift+Right)'
: 'Skip to ${formatClock(offer.target)} for everyone',
icon: Icons.fast_forward_rounded,
tapKey: const ValueKey('skip-pill'),
onPressed: onPressed,
);
}
}
@@ -1,941 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart';
import '../session/health.dart';
import '../state/session_state.dart';
import '../update/updater.dart';
import '../widgets/dialogs.dart';
import '../widgets/member_avatar.dart';
import '../widgets/window_buttons.dart';
import '../widgets/window_drag_layer.dart';
class SessionScreen extends StatefulWidget {
const SessionScreen({super.key});
@override
State<SessionScreen> createState() => _SessionScreenState();
}
class _SessionScreenState extends State<SessionScreen> {
VideoController? _video;
final _videoKey = GlobalKey<VideoState>();
final _sheetOpen = signal(false);
final _confirmLeave = signal(false);
final _isFullscreen = signal(false);
DateTime? _lastKeyAt;
final _chat = TextEditingController();
final _chatFocus = FocusNode();
final _swapLink = TextEditingController();
late final void Function() _stopKickWatch;
late final void Function() _stopSheetWatch;
late final FocusNode _noFocus;
@override
void initState() {
super.initState();
final session = SessionState.instance;
_noFocus = FocusNode(skipTraversal: true, canRequestFocus: false);
_video = VideoController(session.coordinator.playback.player);
HardwareKeyboard.instance.addHandler(_globalKey);
_stopKickWatch = effect(() {
if (session.coordinator.kickReason.value != null) {
session.route.value = AppRoute.connect;
}
});
_stopSheetWatch = effect(() {
if (!_sheetOpen.value) {
FocusManager.instance.primaryFocus?.unfocus();
}
});
}
void _toggleFullscreen() {
if (_isFullscreen.peek()) {
if (mounted) {
Navigator.of(context, rootNavigator: true).maybePop();
}
} else {
_videoKey.currentState?.enterFullscreen();
}
}
@override
void dispose() {
HardwareKeyboard.instance.removeHandler(_globalKey);
_stopKickWatch();
_stopSheetWatch();
_chat.dispose();
_chatFocus.dispose();
_swapLink.dispose();
_noFocus.dispose();
super.dispose();
}
bool get _editing {
var n = FocusManager.instance.primaryFocus;
while (n != null) {
final w = n.context?.widget;
if (w is EditableText) return true;
n = n.parent;
}
return false;
}
bool _globalKey(KeyEvent event) {
if (event is! KeyDownEvent) return false;
if (SessionState.instance.route.peek() != AppRoute.session) return false;
if (_editing) return false;
final now = DateTime.now().toUtc();
if (_lastKeyAt != null && now.difference(_lastKeyAt!).inMilliseconds < 350) {
return false;
}
final coord = SessionState.instance.coordinator;
if (event.logicalKey == LogicalKeyboardKey.space) {
_lastKeyAt = now;
coord.toggleReady();
return true;
}
if (event.logicalKey == LogicalKeyboardKey.tab) {
_lastKeyAt = now;
_sheetOpen.value = !_sheetOpen.peek();
coord.markInput();
return true;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
_lastKeyAt = now;
coord.nudge(10);
return true;
}
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
_lastKeyAt = now;
coord.nudge(-10);
return true;
}
if (event.logicalKey == LogicalKeyboardKey.escape) {
if (_sheetOpen.peek()) {
_sheetOpen.value = false;
return true;
}
if (_confirmLeave.peek()) {
_confirmLeave.value = false;
return true;
}
if (_isFullscreen.peek()) {
if (mounted) {
Navigator.of(context, rootNavigator: true).maybePop();
}
return true;
}
_lastKeyAt = now;
_confirmLeave.value = true;
return true;
}
if (event.logicalKey == LogicalKeyboardKey.keyF) {
_lastKeyAt = now;
if (_isFullscreen.peek()) {
if (mounted) {
Navigator.of(context, rootNavigator: true).maybePop();
}
} else {
_videoKey.currentState?.enterFullscreen();
}
return true;
}
return false;
}
@override
Widget build(BuildContext context) {
final coord = SessionState.instance.coordinator;
final controlsTheme = MaterialDesktopVideoControlsThemeData(
playAndPauseOnTap: false,
modifyVolumeOnScroll: false,
keyboardShortcuts: {},
visibleOnMount: true,
hideMouseOnControlsRemoval: true,
controlsHoverDuration: const Duration(seconds: 3),
topButtonBar: [
const Spacer(),
MaterialDesktopCustomButton(
icon: const Icon(Icons.group),
onPressed: () {
_sheetOpen.value = true;
coord.markInput();
},
),
SignalBuilder(
builder: (context) => WindowButtons(focusNode: _noFocus, fullscreen: _isFullscreen.value, onToggleFullscreen: _toggleFullscreen),
),
],
bottomButtonBar: [
const MaterialDesktopVolumeButton(),
_ReadyControlButton(focusNode: _noFocus),
_MarkAllReadyControlButton(focusNode: _noFocus),
const MaterialDesktopPositionIndicator(),
const Spacer(),
const MaterialDesktopFullscreenButton(),
],
);
return FocusTraversalGroup(
descendantsAreTraversable: false,
child: Scaffold(
backgroundColor: Colors.black,
body: _video == null
? const Center(
child: Text('player starting…', style: TextStyle(color: Colors.white)),
)
: MaterialDesktopVideoControlsTheme(
normal: controlsTheme,
fullscreen: controlsTheme,
child: Video(
key: _videoKey,
controller: _video!,
subtitleViewConfiguration: const SubtitleViewConfiguration(visible: false),
controls: _sessionControls,
onEnterFullscreen: () async {
_isFullscreen.value = true;
await defaultEnterNativeFullscreen();
},
onExitFullscreen: () async {
_isFullscreen.value = false;
await defaultExitNativeFullscreen();
},
),
),
),
);
}
Widget _sessionControls(VideoState state) {
return Stack(
children: [
MaterialDesktopVideoControls(state),
_healthOverlay(),
_waitingOverlay(),
_toastsOverlay(),
_sheetOverlay(),
_leaveOverlay(),
Positioned.fill(
child: SignalBuilder(
builder: (context) {
final sheetOpen = _sheetOpen.value;
final confirm = _confirmLeave.value;
return WindowDragLayer(
enabled: !confirm,
exclusions: [
(s) => Rect.fromLTWH(0, s.height - 120, s.width, 120),
if (sheetOpen) (s) => Rect.fromLTWH(s.width - 380, 0, 380, s.height),
],
);
},
),
),
],
);
}
Widget _healthOverlay() {
final coord = SessionState.instance.coordinator;
return Positioned(
top: 12,
left: 12,
child: SignalBuilder(
builder: (context) {
final h = coord.health.value;
final starve = coord.starving.value;
final retrying = coord.reconnecting.value;
final kicked = coord.kickReason.value;
final unwell = h.severity == HealthSeverity.warn || h.severity == HealthSeverity.error || retrying || starve != null;
if (!unwell) return const SizedBox.shrink();
final color = switch (h.severity) {
HealthSeverity.ok => Colors.green,
HealthSeverity.info => Colors.grey,
HealthSeverity.warn => Colors.orange,
HealthSeverity.error => Colors.red,
};
final IconData icon;
if (retrying) {
icon = Icons.sync;
} else if (h.severity == HealthSeverity.error) {
icon = Icons.error_outline;
} else if (h.severity == HealthSeverity.warn) {
icon = Icons.warning_amber_outlined;
} else {
icon = Icons.hourglass_empty;
}
final text = [h.message, if (starve != null) starve, if (retrying) 'reconnecting…'].join(' · ');
final canRetry = !retrying && kicked == null && (h.severity == HealthSeverity.error || h.severity == HealthSeverity.warn);
return Container(
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.65), borderRadius: BorderRadius.circular(999)),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 8),
Flexible(
child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 12)),
),
if (retrying)
TextButton(
style: TextButton.styleFrom(foregroundColor: Colors.white, tapTargetSize: MaterialTapTargetSize.shrinkWrap),
onPressed: () => coord.cancelReconnect(),
child: const Text('Cancel', style: TextStyle(fontSize: 12)),
),
if (canRetry)
TextButton(
style: TextButton.styleFrom(foregroundColor: Colors.white, tapTargetSize: MaterialTapTargetSize.shrinkWrap),
onPressed: () => coord.retry(),
child: const Text('Retry', style: TextStyle(fontSize: 12)),
),
],
),
);
},
),
);
}
Widget _waitingOverlay() {
final coord = SessionState.instance.coordinator;
return SignalBuilder(
builder: (context) {
final r = coord.room.value;
final waiting = [
for (final m in r?.members ?? [])
if (!m.ready) m,
];
final show = r != null && r.transport == 'held' && waiting.isNotEmpty;
final selfReady = coord.selfMember.value?.ready ?? false;
return Positioned.fill(
child: Center(
child: Visibility(
visible: show,
maintainState: true,
maintainAnimation: true,
maintainSize: true,
child: Column(
mainAxisSize: .min,
spacing: 12,
children: [
IgnorePointer(
child: Container(
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.7), borderRadius: BorderRadiusGeometry.circular(20)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: .min,
spacing: 10,
crossAxisAlignment: .center,
children: [
Text("Waiting for", style: TextTheme.of(context).labelLarge),
Wrap(
alignment: WrapAlignment.center,
spacing: -20,
runSpacing: 8,
children: [
for (final m in waiting)
Opacity(
opacity: m.stale ? 0.5 : 1.0,
child: MemberAvatar(avatarBase64: m.avatar, name: m.name, size: 96),
),
],
),
],
),
),
),
),
if (!selfReady)
FilledButton(
focusNode: _noFocus,
onPressed: () => coord.toggleReady(),
child: const Text("I'm Ready"),
),
],
),
),
),
);
},
);
}
Widget _toastsOverlay() {
final coord = SessionState.instance.coordinator;
return Positioned(
right: 12,
top: 56,
child: IgnorePointer(
child: SignalBuilder(
builder: (context) {
final items = coord.toasts.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [for (final t in items.reversed.take(5)) _ToastCard(key: ValueKey(t.id), text: t.text)],
);
},
),
),
);
}
Widget _sheetOverlay() {
return SignalBuilder(
builder: (context) {
final open = _sheetOpen.value;
return ExcludeFocus(
excluding: !open,
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: IgnorePointer(
ignoring: !open,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: open ? 1 : 0,
child: GestureDetector(
onTap: () => _sheetOpen.value = false,
child: Container(color: Colors.black54),
),
),
),
),
Positioned(
top: 20,
bottom: 20,
right: 0,
child: IgnorePointer(
ignoring: !open,
child: AnimatedSlide(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
offset: open ? Offset.zero : const Offset(1, 0),
child: SizedBox(
width: 380,
height: double.infinity,
child: _SheetPanel(
chat: _chat,
chatFocus: _chatFocus,
swapLink: _swapLink,
onClose: () => _sheetOpen.value = false,
onLeave: () {
_sheetOpen.value = false;
_confirmLeave.value = true;
},
),
),
),
),
),
],
),
);
},
);
}
Widget _leaveOverlay() {
final session = SessionState.instance;
return SignalBuilder(
builder: (context) {
if (!_confirmLeave.value) return const SizedBox.shrink();
return Positioned.fill(
child: GestureDetector(
onTap: () => _confirmLeave.value = false,
child: Container(
color: Colors.black54,
child: Center(
child: GestureDetector(
onTap: () {},
child: AlertDialog(
title: const Text('Leave room?'),
content: const Text('You can rejoin right away.'),
actions: [
TextButton(onPressed: () => _confirmLeave.value = false, child: const Text('Cancel')),
TextButton(
onPressed: () {
_confirmLeave.value = false;
session.leave();
},
child: const Text('Leave'),
),
],
),
),
),
),
),
);
},
);
}
}
class _ReadyControlButton extends StatelessWidget {
final FocusNode focusNode;
const _ReadyControlButton({required this.focusNode});
@override
Widget build(BuildContext context) {
return SignalBuilder(
builder: (context) {
final ready = SessionState.instance.coordinator.selfMember.value?.ready ?? false;
return TextButton(
focusNode: focusNode,
style: TextButton.styleFrom(
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () => SessionState.instance.coordinator.toggleReady(),
child: Text(ready ? 'Ready ✓' : 'Not ready', style: const TextStyle(fontSize: 12)),
);
},
);
}
}
class _MarkAllReadyControlButton extends StatelessWidget {
final FocusNode focusNode;
const _MarkAllReadyControlButton({required this.focusNode});
@override
Widget build(BuildContext context) {
return TextButton(
focusNode: focusNode,
style: TextButton.styleFrom(
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () => SessionState.instance.coordinator.forcePlayAll(),
child: const Text('Mark everyone ready', style: TextStyle(fontSize: 12)),
);
}
}
class _ToastCard extends StatefulWidget {
final String text;
const _ToastCard({super.key, required this.text});
@override
State<_ToastCard> createState() => _ToastCardState();
}
class _ToastCardState extends State<_ToastCard> {
bool _shown = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) setState(() => _shown = true);
});
}
@override
Widget build(BuildContext context) {
return AnimatedSlide(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
offset: _shown ? Offset.zero : const Offset(1, 0),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: _shown ? 1 : 0,
child: Card(
child: Padding(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Text(widget.text)),
),
),
);
}
}
class _SheetPanel extends StatelessWidget {
final TextEditingController chat;
final FocusNode chatFocus;
final TextEditingController swapLink;
final VoidCallback onClose;
final VoidCallback onLeave;
const _SheetPanel({required this.chat, required this.chatFocus, required this.swapLink, required this.onClose, required this.onLeave});
@override
Widget build(BuildContext context) {
final coord = SessionState.instance.coordinator;
return Material(
borderRadius: BorderRadius.horizontal(left: .circular(20)),
elevation: 8,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
child: SignalBuilder(
builder: (context) {
final r = coord.room.value;
final log = coord.feedLog.value;
final theme = Theme.of(context);
final sectionStyle = theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold);
final members = r?.members ?? const <MemberView>[];
final readyCount = members.where((m) => m.ready).length;
final playing = r?.transport == 'playing';
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Room', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(width: 8),
_StatusChip(playing: playing, hasRoom: r != null),
],
),
const SizedBox(height: 2),
Text(
r == null ? 'connecting…' : '${members.length} member${members.length == 1 ? '' : 's'} · $readyCount ready',
style: theme.textTheme.bodySmall,
),
],
),
),
IconButton(icon: const Icon(Icons.close), onPressed: onClose),
],
),
const Divider(height: 20),
Text('Users (${members.length})', style: sectionStyle),
const SizedBox(height: 2),
for (final m in members) _MemberRow(member: m, isSelf: m.id == coord.sync.memberId),
const Divider(height: 20),
Text('Activity', style: sectionStyle),
const SizedBox(height: 6),
if (log.isEmpty) Text('No events yet', style: theme.textTheme.bodySmall),
Expanded(
child: ListView.builder(
reverse: true,
itemBuilder: (context, index) => _ActivityRow(item: log.reversed.toList()[index]),
itemCount: log.length,
),
),
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: TextField(
controller: chat,
focusNode: chatFocus,
decoration: const InputDecoration(labelText: 'Chat message', border: OutlineInputBorder(), isDense: true),
onSubmitted: (v) {
coord.sendChat(v);
chat.clear();
chatFocus.requestFocus();
},
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.send),
onPressed: () {
coord.sendChat(chat.text);
chat.clear();
chatFocus.requestFocus();
},
),
],
),
const SizedBox(height: 12),
Text('Share a link', style: sectionStyle),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: TextField(
controller: swapLink,
decoration: const InputDecoration(labelText: 'Swap to link URL', border: OutlineInputBorder(), isDense: true),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.swap_horiz),
onPressed: () {
coord.swapLink(swapLink.text.trim());
swapLink.clear();
},
),
],
),
const Divider(height: 20),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(foregroundColor: theme.colorScheme.error),
icon: const Icon(Icons.logout),
label: const Text('Leave room'),
onPressed: onLeave,
),
),
const SizedBox(height: 8),
const Center(
child: Text('v$appVersion', style: TextStyle(fontSize: 11, color: Colors.grey)),
),
],
);
},
),
),
);
}
}
class _StatusChip extends StatelessWidget {
final bool playing;
final bool hasRoom;
const _StatusChip({required this.playing, required this.hasRoom});
@override
Widget build(BuildContext context) {
final Color dot;
final String label;
if (!hasRoom) {
dot = Colors.grey;
label = '…';
} else if (playing) {
dot = Colors.green;
label = 'Playing';
} else {
dot = Colors.orange;
label = 'Waiting';
}
return Container(
decoration: BoxDecoration(color: Colors.white10, borderRadius: BorderRadius.circular(999)),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 7,
height: 7,
decoration: BoxDecoration(shape: BoxShape.circle, color: dot),
),
const SizedBox(width: 6),
Text(label, style: Theme.of(context).textTheme.labelSmall),
],
),
);
}
}
class _MemberRow extends StatelessWidget {
final MemberView member;
final bool isSelf;
const _MemberRow({required this.member, required this.isSelf});
@override
Widget build(BuildContext context) {
final coord = SessionState.instance.coordinator;
final theme = Theme.of(context);
final status = _memberStatus(member);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
MemberAvatar(avatarBase64: member.avatar, name: member.name, size: 32),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isSelf ? '${member.name} (you)' : member.name,
style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(shape: BoxShape.circle, color: status.$2),
),
const SizedBox(width: 6),
Text(
status.$1,
style: theme.textTheme.bodySmall?.copyWith(color: theme.textTheme.bodySmall?.color?.withValues(alpha: 0.75)),
),
],
),
],
),
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, size: 18),
padding: EdgeInsets.zero,
onSelected: (v) async {
if (v == 'force') {
coord.forceTarget(member.id, !member.ready);
} else if (v == 'kick') {
final ok = await showKickConfirmDialog(context, member.name);
if (ok == true) coord.kick(member.id);
}
},
itemBuilder: (_) => [
PopupMenuItem(value: 'force', child: Text(member.ready ? 'Force unready' : 'Force ready')),
PopupMenuItem(
value: 'kick',
child: Text('Kick', style: TextStyle(color: theme.colorScheme.error)),
),
],
),
],
),
);
}
}
(String, Color) _memberStatus(MemberView m) {
if (m.stale) return ('Stale', Colors.orange);
if (!m.ready) return ('Not ready', Colors.grey);
if (m.buffered < RelayConstants.minBufferSecs) return ('Buffering ${m.buffered.toStringAsFixed(0)}s', Colors.orange);
return ('Ready', Colors.green);
}
class _ActivityRow extends StatelessWidget {
final FeedMessage item;
const _ActivityRow({required this.item});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final time = _clock(item.at);
final subtle = theme.textTheme.bodySmall?.color?.withValues(alpha: 0.6) ?? Colors.grey;
if (item.action == 'chat') {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.chat_bubble_outline, size: 15, color: Colors.grey),
const SizedBox(width: 8),
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${item.actor} ',
style: theme.textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold),
),
TextSpan(text: item.reason, style: theme.textTheme.bodySmall),
],
),
),
),
const SizedBox(width: 8),
Text(time, style: theme.textTheme.labelSmall?.copyWith(color: subtle)),
],
),
);
}
if (item.actor == 'server') {
final style = _serverFeedStyle(item);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(style.$1, size: 15, color: style.$2),
const SizedBox(width: 8),
Expanded(child: Text(style.$3, style: theme.textTheme.bodySmall)),
const SizedBox(width: 8),
Text(time, style: theme.textTheme.labelSmall?.copyWith(color: subtle)),
],
),
);
}
final style = _feedStyle(item, theme);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(style.$1, size: 15, color: style.$2),
const SizedBox(width: 8),
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.actor,
style: theme.textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold),
),
TextSpan(text: ' ${style.$3}', style: theme.textTheme.bodySmall),
],
),
),
),
const SizedBox(width: 8),
Text(time, style: theme.textTheme.labelSmall?.copyWith(color: subtle)),
],
),
);
}
}
(IconData, Color, String) _feedStyle(FeedMessage f, ThemeData theme) {
switch (f.action) {
case 'join':
return (Icons.person_add, Colors.green, 'joined');
case 'leave':
final reason = f.reason == 'silent too long' ? ' · timed out' : (f.reason.isEmpty ? '' : ' · ${f.reason}');
return (Icons.logout, Colors.grey, 'left$reason');
case 'ready':
return (Icons.check_circle, Colors.green, 'is ready');
case 'unready':
return (Icons.radio_button_unchecked, Colors.grey, "isn't ready");
case 'force-ready':
return (Icons.how_to_reg, Colors.orange, 'marked ${f.target ?? ''} ready');
case 'force-unready':
return (Icons.how_to_reg, Colors.orange, 'marked ${f.target ?? ''} not ready');
case 'force-play':
return (Icons.play_arrow, Colors.green, 'started playback for everyone');
case 'force-hold':
return (Icons.pause, Colors.orange, 'held playback for everyone');
case 'seek':
return (Icons.fast_forward, theme.colorScheme.primary, 'seeked');
case 'swap':
return (Icons.movie, theme.colorScheme.primary, 'changed the media');
case 'stale':
return (Icons.cloud_off, Colors.orange, 'lost signal');
case 'kick':
final reason = f.reason.isEmpty ? '' : ' · ${f.reason}';
return (Icons.gavel, Colors.red, 'kicked ${f.target ?? ''}$reason');
default:
final target = f.target == null || f.target!.isEmpty ? '' : ' ${f.target}';
return (Icons.info_outline, Colors.grey, '${f.action}$target');
}
}
(IconData, Color, String) _serverFeedStyle(FeedMessage f) {
switch (f.action) {
case 'resume':
return (Icons.play_arrow, Colors.green, 'Playback resumed');
default:
return (Icons.pause, Colors.orange, 'Playback paused · waiting for readiness');
}
}
String _clock(DateTime at) {
final l = at.toLocal();
String two(int n) => n.toString().padLeft(2, '0');
return '${two(l.hour)}:${two(l.minute)}';
}
@@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
import '../state/session_state.dart';
import '../update/updater.dart';
import '../widgets/window_chrome_bar.dart';
class WelcomeScreen extends StatelessWidget {
const WelcomeScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Column(
children: [
WindowChromeBar(title: 'Watch Together'),
Expanded(child: _WelcomeBody()),
],
),
);
}
}
class _WelcomeBody extends StatelessWidget {
const _WelcomeBody();
@override
Widget build(BuildContext context) {
final session = SessionState.instance;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Watch Together', style: TextStyle(fontSize: 24)),
Text('v$appVersion', style: const TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 8),
const Text(
'Set your name + avatar on the next screen.',
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => session.goConnect(ConnectMode.host),
child: const Text('Host'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () => session.goConnect(ConnectMode.join),
child: const Text('Join'),
),
],
),
const SizedBox(height: 12),
const Text(
'Host opens ports and serves. Clients open nothing.',
textAlign: TextAlign.center,
),
],
),
),
);
}
}
+309
View File
@@ -0,0 +1,309 @@
import 'package:sync_relay_core/core.dart';
import '../jellyfin/session_media.dart';
// Who fetches the current media and how it spreads, from the state alone:
// the members card's source line and each member's route and buffer.
/// "4.1 MB/s", "850 kB/s".
String formatRate(int bytesPerSec) {
if (bytesPerSec >= 1000000) {
final mb = bytesPerSec / 1e6;
return '${mb.toStringAsFixed(mb >= 10 ? 0 : 1)} MB/s';
}
return '${(bytesPerSec / 1000).round()} kB/s';
}
/// How the current media reaches the party.
enum DeliveryKind {
/// A link every app opens itself.
link,
/// The hosting app's file, read by everyone from its server.
hostFile,
/// A file the dedicated server was started with.
serverFile,
/// A member's machine passes it on through the server.
memberRelay,
/// The server reads the link itself.
serverFetch,
/// Jellyfin: own accounts, the server's login for the rest.
jellyfin,
}
/// How a member gets the bytes.
enum DeliveryRoute {
/// It serves them (a relay source, the host's file).
source,
/// It reads them from the server.
server,
/// It fetches them itself (a link, its own Jellyfin account).
direct,
}
enum BufferHealth { good, low, starving }
/// The relay source's condition.
enum SourceState {
ok,
/// Measured slower than the media's bitrate.
slow,
/// Its channel is down (or the server has not read the link yet).
waiting,
/// It left the server.
left,
}
/// Members' names by id, kept after they leave: "Foxyz left" needs the
/// name the state no longer has.
class MemberNames {
final _names = <String, String>{};
void observe(StateBroadcast r) {
if (_names.length > 256) _names.clear();
for (final m in r.members) {
_names[m.id] = m.name;
}
}
String? operator [](String id) => _names[id];
}
/// The member [m]'s route and buffer.
class MemberDelivery {
/// Null when unknown (a Jellyfin member that has not reported yet).
final DeliveryRoute? route;
final DeliveryKind kind;
/// Bytes per second the server sends it lately.
final int rate;
/// Null when it does not count: sitting out, reconnecting, cannot play.
final BufferHealth? health;
final double buffered;
/// The media is a Jellyfin item (direct = its own account).
final bool jellyfin;
const MemberDelivery({
required this.route,
required this.kind,
this.jellyfin = false,
required this.rate,
required this.health,
required this.buffered,
});
/// "via server · 3.2 MB/s", "direct", "passes it on"; empty when unknown.
String get short => switch (route) {
DeliveryRoute.source => kind == DeliveryKind.hostFile ? 'hosts the file' : 'passes it on',
DeliveryRoute.server => rate > 0 ? 'via server · ${formatRate(rate)}' : 'via server',
DeliveryRoute.direct => jellyfin ? 'own Jellyfin account' : 'direct',
null => '',
};
/// [short] and the buffer: "via server · 3.2 MB/s · 45 s buffered".
String get detail => [
if (short.isNotEmpty) short,
if (health != null) health == BufferHealth.starving ? 'buffer empty' : '${buffered.round()} s buffered',
].join(' · ');
}
/// The current media's path for everyone: what the members card's source
/// line says.
class Delivery {
final DeliveryKind kind;
/// The member serving it ([DeliveryKind.memberRelay], [DeliveryKind.hostFile]); empty otherwise.
final String sourceId;
final String sourceName;
final bool sourceIsSelf;
final SourceState state;
/// `media.relay.rate` / `need` (0 unknown).
final int rate, need;
/// Members (not sitting out, not the source) reading from the server,
/// and fetching it themselves.
final int viaServer, direct;
const Delivery({
required this.kind,
this.sourceId = '',
this.sourceName = '',
this.sourceIsSelf = false,
this.state = SourceState.ok,
this.rate = 0,
this.need = 0,
this.viaServer = 0,
this.direct = 0,
});
bool get relayed => kind == DeliveryKind.memberRelay || kind == DeliveryKind.serverFetch;
/// Rate against bitrate, both known: 1 = exactly what the media needs.
double? get headroom => rate > 0 && need > 0 ? rate / need : null;
String get title {
final who = sourceIsSelf ? 'You' : sourceName;
switch (kind) {
case DeliveryKind.link:
return 'Everyone plays the link directly';
case DeliveryKind.serverFile:
return 'The server has the file';
case DeliveryKind.hostFile:
return sourceIsSelf ? 'You host the file' : '$who hosts the file';
case DeliveryKind.jellyfin:
return viaServer == 0 ? 'Everyone streams it from Jellyfin' : 'From Jellyfin';
case DeliveryKind.serverFetch:
return switch (state) {
SourceState.waiting => 'The server is getting the video…',
SourceState.slow => 'The server fetches it slower than it plays',
_ => 'The server fetches it',
};
case DeliveryKind.memberRelay:
return switch (state) {
SourceState.left => '$sourceName left: playing what the server has',
SourceState.waiting => sourceIsSelf ? 'Reconnecting you to the server…' : 'Waiting for $who to reconnect',
SourceState.slow => sourceIsSelf ? 'Your upload is slower than the video' : "$who's upload is slower than the video",
SourceState.ok => sourceIsSelf ? 'You pass it on through the server' : '$who passes it on through the server',
};
}
}
/// "3 through the server · 4.1 MB/s, needs 2.3 MB/s"; empty when
/// there is nothing to add.
String get detail {
final parts = <String>[
if (direct > 0 && (kind == DeliveryKind.jellyfin || kind == DeliveryKind.memberRelay))
'$direct with their own account',
if (viaServer > 0 && kind != DeliveryKind.link) '$viaServer through the server',
if (rate > 0 && state != SourceState.left) need > 0 ? '${formatRate(rate)}, needs ${formatRate(need)}' : formatRate(rate),
];
return parts.join(' · ');
}
/// The hover text: what the numbers mean.
String get explain => switch (kind) {
DeliveryKind.memberRelay || DeliveryKind.serverFetch => [
if (rate > 0)
'${kind == DeliveryKind.serverFetch ? 'The server gets' : sourceIsSelf ? 'You send' : '$sourceName sends'} '
'${formatRate(rate)} while the server asks for more.',
if (need > 0) 'The video averages ${formatRate(need)}.',
if (state == SourceState.slow) 'Slower than the video: the others will pause to buffer.',
if (rate == 0) 'The speed shows once bytes have flowed for a few seconds.',
].join(' '),
DeliveryKind.hostFile || DeliveryKind.serverFile => 'Everyone else reads the file from the server.',
DeliveryKind.jellyfin => 'Members with an account on that Jellyfin stream it themselves; '
'the rest through the server.',
DeliveryKind.link => 'Each app opens the link itself; the server only keeps time.',
};
}
/// The current media's [Delivery]; null with nothing up or nobody else in
/// the server. [names] remembers a source that left.
Delivery? deliveryOf(StateBroadcast r, {required String selfId, MemberNames? names}) {
final media = r.media;
if (media == null || r.members.length < 2) return null;
final rel = media['relay'];
final relay = rel is Map ? MediaRelay.fromJson(Map<String, Object?>.from(rel)) : null;
final ref = refOfStateMedia(media);
final url = media['url'];
final kind = relay != null
? (relay.fetch ? DeliveryKind.serverFetch : DeliveryKind.memberRelay)
: ref != null
? DeliveryKind.jellyfin
: url is String && url.isNotEmpty
? DeliveryKind.link
: r.members.any((m) => m.host)
? DeliveryKind.hostFile
: DeliveryKind.serverFile;
final sourceId = switch (kind) {
DeliveryKind.memberRelay => relay!.source,
DeliveryKind.hostFile => r.members.firstWhere((m) => m.host).id,
_ => '',
};
final source = r.members.where((m) => m.id == sourceId).firstOrNull;
var via = 0, direct = 0;
for (final m in r.members) {
if (m.out || m.id == sourceId) continue;
switch (routeOf(m, kind, sourceId, ref, r.relay)) {
case DeliveryRoute.server:
via++;
case DeliveryRoute.direct:
direct++;
default:
break;
}
}
// A Jellyfin item that everyone streams with their own account: the
// relay stands by, nothing goes through it.
if (kind == DeliveryKind.memberRelay && ref != null && via == 0 && source != null) {
return Delivery(kind: DeliveryKind.jellyfin, direct: direct);
}
final state = relay == null
? SourceState.ok
: kind == DeliveryKind.memberRelay && source == null
? SourceState.left
: !relay.live
? SourceState.waiting
: relay.slow
? SourceState.slow
: SourceState.ok;
return Delivery(
kind: kind,
sourceId: sourceId,
sourceName: source?.name ?? names?[sourceId] ?? 'The member passing it on',
sourceIsSelf: sourceId.isNotEmpty && sourceId == selfId,
state: state,
rate: relay?.rate ?? 0,
need: relay?.need ?? 0,
viaServer: via,
direct: direct,
);
}
/// How [m] gets media of [kind] served by [sourceId]. Jellyfin: by its
/// last `report.jf` for [ref]'s server ([jellyfinRelay]: the server can
/// relay it); null before it reported.
DeliveryRoute? routeOf(MemberView m, DeliveryKind kind, String sourceId, JellyfinRef? ref, bool jellyfinRelay) {
if (sourceId.isNotEmpty && m.id == sourceId) return DeliveryRoute.source;
switch (kind) {
case DeliveryKind.link:
return DeliveryRoute.direct;
case DeliveryKind.hostFile:
case DeliveryKind.serverFile:
case DeliveryKind.serverFetch:
return DeliveryRoute.server;
case DeliveryKind.memberRelay:
case DeliveryKind.jellyfin:
final own = ref == null ? null : m.jf[ref.serverId];
if (own == true) return DeliveryRoute.direct;
if (kind == DeliveryKind.memberRelay) return DeliveryRoute.server;
return own == false && jellyfinRelay ? DeliveryRoute.server : null;
}
}
/// [m]'s route and buffer for [d] (from [deliveryOf] on [r]).
MemberDelivery memberDeliveryOf(MemberView m, StateBroadcast r, Delivery d) {
final counts = !m.out && !m.stale && m.error.isEmpty;
final need = r.bufferNeed;
final ref = refOfStateMedia(r.media);
final health = !counts
? null
: m.buffered >= need
? BufferHealth.good
: r.transport == 'playing' && m.buffered < 1
? BufferHealth.starving
: BufferHealth.low;
return MemberDelivery(
route: m.out ? null : routeOf(m, d.kind, d.sourceId, ref, r.relay),
kind: d.kind,
jellyfin: ref != null,
rate: m.rate,
health: health,
buffered: m.buffered,
);
}
/// What the source is told once per media when it is too slow; null
/// while it keeps up (or this member is not the source).
String? slowSourceWarning(StateBroadcast r, String selfId) {
final rel = r.media?['relay'];
if (rel is! Map || selfId.isEmpty) return null;
final relay = MediaRelay.fromJson(Map<String, Object?>.from(rel));
if (relay.source != selfId || !relay.slow) return null;
return 'Your upload (${formatRate(relay.rate)}) is slower than this video needs '
'(${formatRate(relay.need)}): the others will pause to buffer now and then.';
}
Loaded 100 of 328 files, more files were not shown because too many files have changed in this diff. Show more