Skip to content

Commit 0dfdab9

Browse files
authored
Merge pull request #17 from mjc/mjc/videos-page-performance
Improve videos page responsiveness and filter behavior
2 parents 51d36c2 + c9a54b2 commit 0dfdab9

7 files changed

Lines changed: 222 additions & 187 deletions

File tree

lib/reencodarr/media.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2353,7 +2353,6 @@ defmodule Reencodarr.Media do
23532353

23542354
case Flop.validate_and_run(base_query, flop_params, for: Video) do
23552355
{:ok, {videos, meta}} ->
2356-
videos = Repo.preload(videos, :chosen_vmaf)
23572356
{videos, meta}
23582357

23592358
{:error, _meta} ->

lib/reencodarr/videos/state.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ defmodule Reencodarr.Videos.State do
33

44
alias Reencodarr.Media
55

6-
@spec load(map()) :: map()
7-
def load(assigns) do
6+
@spec load(map(), keyword()) :: map()
7+
def load(assigns, opts \\ []) do
8+
include_state_counts? = Keyword.get(opts, :include_state_counts, true)
9+
810
{videos, meta} =
911
Media.list_videos_paginated(
1012
page: assigns.page,
@@ -17,13 +19,20 @@ defmodule Reencodarr.Videos.State do
1719
sort_dir: assigns.sort_dir
1820
)
1921

22+
state_counts =
23+
if include_state_counts? do
24+
Media.count_videos_by_state()
25+
else
26+
Map.get(assigns, :state_counts, %{})
27+
end
28+
2029
%{
2130
videos: videos,
2231
meta: meta,
2332
total: meta.total_count || 0,
2433
page: meta.current_page || assigns.page,
2534
per_page: meta.page_size || assigns.per_page,
26-
state_counts: Media.count_videos_by_state()
35+
state_counts: state_counts
2736
}
2837
end
2938
end

lib/reencodarr_web/live/dashboard_live.ex

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -620,13 +620,24 @@ defmodule ReencodarrWeb.DashboardLive do
620620
<% end %>
621621
</div>
622622
<% else %>
623-
<!-- Idle: Show compact status -->
623+
<!-- Idle/Paused: Show compact status and allow resume when paused -->
624624
<div class="text-sm text-gray-400">
625625
<span>Queue: {@queue_count}</span>
626626
<%= if @status == :idle do %>
627627
<span class="ml-2">• Idle</span>
628628
<% end %>
629629
</div>
630+
631+
<%= if @status == :paused do %>
632+
<div class="mt-2">
633+
<.active_job_controls
634+
status={@status}
635+
suspend_event="suspend_crf_search"
636+
resume_event="resume_crf_search"
637+
fail_event="fail_crf_search"
638+
/>
639+
</div>
640+
<% end %>
630641
<% end %>
631642
632643
<!-- Always show next-up videos -->
@@ -730,13 +741,24 @@ defmodule ReencodarrWeb.DashboardLive do
730741
/>
731742
</div>
732743
<% else %>
733-
<!-- Idle: Show compact status -->
744+
<!-- Idle/Paused: Show compact status and allow resume when paused -->
734745
<div class="text-sm text-gray-400">
735746
<span>Queue: {@queue_count}</span>
736747
<%= if @status == :idle do %>
737748
<span class="ml-2">• Idle</span>
738749
<% end %>
739750
</div>
751+
752+
<%= if @status == :paused do %>
753+
<div class="mt-2">
754+
<.active_job_controls
755+
status={@status}
756+
suspend_event="suspend_encode"
757+
resume_event="resume_encode"
758+
fail_event="fail_encode"
759+
/>
760+
</div>
761+
<% end %>
740762
<% end %>
741763
742764
<!-- Always show next-up videos -->

lib/reencodarr_web/live/videos_live.ex

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ defmodule ReencodarrWeb.VideosLive do
7777
|> assign(filters)
7878
|> then(fn s ->
7979
if connected?(s) and s.assigns.loaded_once and filters_changed?,
80-
do: load_data(s),
80+
do: load_data(s, include_state_counts: false),
8181
else: s
8282
end)
8383

@@ -114,33 +114,20 @@ defmodule ReencodarrWeb.VideosLive do
114114
# ---------------------------------------------------------------------------
115115

116116
@impl true
117-
def handle_event("search", %{"search" => q}, socket) do
118-
{:noreply, push_patch(socket, to: patch_path(socket.assigns, search: q, page: 1))}
119-
end
120-
121-
@impl true
122-
def handle_event("filter_state", %{"state" => state}, socket) do
123-
{:noreply,
124-
push_patch(socket,
125-
to: patch_path(socket.assigns, state: nilify_empty(state), page: 1)
126-
)}
127-
end
117+
def handle_event("set_filters", params, socket) do
118+
search = Map.get(params, "search", socket.assigns.search)
119+
state = params |> Map.get("state") |> nilify_empty()
120+
service = params |> Map.get("service") |> nilify_empty()
121+
hdr = params |> Map.get("hdr") |> nilify_empty() |> parse_hdr_param() |> hdr_to_param()
128122

129-
@impl true
130-
def handle_event("filter_service", %{"service" => svc}, socket) do
131-
{:noreply,
132-
push_patch(socket,
133-
to: patch_path(socket.assigns, service: nilify_empty(svc), page: 1)
134-
)}
135-
end
136-
137-
@impl true
138-
def handle_event("filter_hdr", %{"hdr" => hdr}, socket) do
139123
{:noreply,
140124
push_patch(socket,
141125
to:
142126
patch_path(socket.assigns,
143-
hdr: hdr_to_param(parse_hdr_param(nilify_empty(hdr))),
127+
search: search,
128+
state: state,
129+
service: service,
130+
hdr: hdr,
144131
page: 1
145132
)
146133
)}
@@ -281,8 +268,8 @@ defmodule ReencodarrWeb.VideosLive do
281268
def handle_event("reset_selected", _params, socket) do
282269
ids = MapSet.to_list(socket.assigns.selected)
283270
Enum.each(ids, &reset_video_by_id/1)
284-
285271
socket = socket |> assign(selected: MapSet.new()) |> load_data()
272+
286273
{:noreply, put_flash(socket, :info, "Reset #{length(ids)} video(s) to needs_analysis")}
287274
end
288275

@@ -299,7 +286,7 @@ defmodule ReencodarrWeb.VideosLive do
299286
put_flash(socket, :error, "No selected videos were eligible for queue prioritization")}
300287

301288
{:ok, count} ->
302-
socket = socket |> assign(selected: MapSet.new()) |> load_data()
289+
socket = socket |> assign(selected: MapSet.new())
303290
{:noreply, put_flash(socket, :info, "Prioritized #{count} video(s)")}
304291

305292
{:error, _reason} ->
@@ -345,7 +332,7 @@ defmodule ReencodarrWeb.VideosLive do
345332
with {:ok, id} <- Parsers.parse_integer_exact(id_str),
346333
{:ok, count} <- Media.prioritize_video(id),
347334
true <- count > 0 do
348-
{:noreply, socket |> put_flash(:info, "Prioritized video") |> load_data()}
335+
{:noreply, socket |> put_flash(:info, "Prioritized video")}
349336
else
350337
{:ok, 0} -> {:noreply, put_flash(socket, :error, "Video is not currently queueable")}
351338
false -> {:noreply, put_flash(socket, :error, "Video is not currently queueable")}
@@ -356,20 +343,21 @@ defmodule ReencodarrWeb.VideosLive do
356343

357344
@impl true
358345
def handle_event("fail_video", %{"id" => id_str}, socket) do
359-
result =
360-
with {:ok, id} <- Parsers.parse_integer_exact(id_str) do
361-
fail_video_by_id(id)
362-
end
346+
case Parsers.parse_integer_exact(id_str) do
347+
{:ok, id} ->
348+
case fail_video_by_id(id) do
349+
:ok ->
350+
{:noreply, socket |> put_flash(:info, "Job stopped") |> load_data()}
363351

364-
case result do
365-
:ok ->
366-
{:noreply, socket |> put_flash(:info, "Job stopped") |> load_data()}
352+
{:error, :active_mismatch} ->
353+
{:noreply, socket |> put_flash(:error, "That video is not the active job")}
367354

368-
{:error, :active_mismatch} ->
369-
{:noreply, socket |> put_flash(:error, "That video is not the active job") |> load_data()}
355+
_ ->
356+
{:noreply, socket |> put_flash(:error, "Unable to stop job")}
357+
end
370358

371359
_ ->
372-
{:noreply, socket |> put_flash(:error, "Unable to stop job") |> load_data()}
360+
{:noreply, socket |> put_flash(:error, "Unable to stop job")}
373361
end
374362
end
375363

@@ -398,8 +386,7 @@ defmodule ReencodarrWeb.VideosLive do
398386
|> put_flash(
399387
:info,
400388
"Prioritized #{count} #{Path.basename(season_dir)} video(s)"
401-
)
402-
|> load_data()}
389+
)}
403390

404391
{:error, _reason} ->
405392
{:noreply, put_flash(socket, :error, "Failed to prioritize season videos")}
@@ -442,8 +429,7 @@ defmodule ReencodarrWeb.VideosLive do
442429
{:noreply,
443430
socket
444431
|> assign(:expanded_bad_forms, List.delete(socket.assigns.expanded_bad_forms, id))
445-
|> put_flash(:info, "Marked as bad")
446-
|> load_data()}
432+
|> put_flash(:info, "Marked as bad")}
447433
else
448434
:not_found -> {:noreply, put_flash(socket, :error, "Video not found")}
449435
{:error, _} -> {:noreply, put_flash(socket, :error, "Mark bad failed")}
@@ -455,18 +441,22 @@ defmodule ReencodarrWeb.VideosLive do
455441
# Private helpers
456442
# ---------------------------------------------------------------------------
457443

458-
defp load_data(socket) do
444+
defp load_data(socket, opts \\ []) do
459445
page_state =
460-
VideosState.load(%{
461-
page: socket.assigns.page,
462-
per_page: socket.assigns.per_page,
463-
state_filter: socket.assigns.state_filter,
464-
service_filter: socket.assigns.service_filter,
465-
hdr_filter: socket.assigns.hdr_filter,
466-
search: socket.assigns.search,
467-
sort_by: socket.assigns.sort_by,
468-
sort_dir: socket.assigns.sort_dir
469-
})
446+
VideosState.load(
447+
%{
448+
state_counts: socket.assigns.state_counts,
449+
page: socket.assigns.page,
450+
per_page: socket.assigns.per_page,
451+
state_filter: socket.assigns.state_filter,
452+
service_filter: socket.assigns.service_filter,
453+
hdr_filter: socket.assigns.hdr_filter,
454+
search: socket.assigns.search,
455+
sort_by: socket.assigns.sort_by,
456+
sort_dir: socket.assigns.sort_dir
457+
},
458+
opts
459+
)
470460

471461
assign_changed(socket, Map.put(page_state, :loading, false))
472462
end
@@ -750,18 +740,17 @@ defmodule ReencodarrWeb.VideosLive do
750740
<!-- Toolbar -->
751741
<div class="bg-gray-800 rounded-lg border border-gray-700 p-3">
752742
<div class="flex flex-wrap gap-3 items-center">
753-
<form phx-change="search" class="flex-1 min-w-[180px]">
754-
<input
755-
type="text"
756-
name="search"
757-
value={@search}
758-
placeholder="Search by path..."
759-
phx-debounce="300"
760-
class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg px-3 py-2 text-sm focus:ring-purple-500 focus:border-purple-500 placeholder-gray-400"
761-
/>
762-
</form>
763-
764-
<form phx-change="filter_state">
743+
<form id="videos-filters" phx-change="set_filters" class="contents">
744+
<div class="flex-1 min-w-[180px]">
745+
<input
746+
type="text"
747+
name="search"
748+
value={@search}
749+
placeholder="Search by path..."
750+
phx-debounce="300"
751+
class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg px-3 py-2 text-sm focus:ring-purple-500 focus:border-purple-500 placeholder-gray-400"
752+
/>
753+
</div>
765754
<select
766755
name="state"
767756
value={@state_filter || ""}
@@ -772,9 +761,6 @@ defmodule ReencodarrWeb.VideosLive do
772761
<option value={s}>{s}</option>
773762
<% end %>
774763
</select>
775-
</form>
776-
777-
<form phx-change="filter_service">
778764
<select
779765
name="service"
780766
value={@service_filter || ""}
@@ -784,9 +770,6 @@ defmodule ReencodarrWeb.VideosLive do
784770
<option value="sonarr">Sonarr (TV)</option>
785771
<option value="radarr">Radarr (Movies)</option>
786772
</select>
787-
</form>
788-
789-
<form phx-change="filter_hdr">
790773
<select
791774
name="hdr"
792775
value={hdr_to_param(@hdr_filter) || ""}

nix/package.nix

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,38 @@ in
4848
beamPackages.mixRelease rec {
4949
pname = "reencodarr";
5050
version = "0.1.0";
51-
src = lib.cleanSource ../.;
51+
src = lib.cleanSourceWith {
52+
src = ../.;
53+
54+
filter = path: type:
55+
let
56+
root = toString ../.;
57+
pathStr = toString path;
58+
relPath =
59+
if pathStr == root
60+
then ""
61+
else lib.removePrefix "${root}/" pathStr;
62+
63+
excludedDirs = [
64+
"_build"
65+
"deps"
66+
"node_modules"
67+
"logs"
68+
".elixir_ls"
69+
".direnv"
70+
".git"
71+
];
72+
73+
isExcludedDir = dir: lib.hasInfix "/${dir}/" "/${relPath}/";
74+
75+
excluded =
76+
lib.any isExcludedDir excludedDirs
77+
|| builtins.match "priv/reencodarr_(dev|prod|test).*\\.db(-shm|-wal|-journal)?"
78+
relPath
79+
!= null;
80+
in
81+
lib.cleanSourceFilter path type && !excluded;
82+
};
5283

5384
mixFodDeps = beamPackages.fetchMixDeps {
5485
pname = "${pname}-mix-deps";

0 commit comments

Comments
 (0)