BETA: accurate color extractor

This commit is contained in:
Claudio Ortolina
2026-03-03 15:41:24 +00:00
parent d838c29207
commit 1d3361c425
8 changed files with 51 additions and 2 deletions
@@ -0,0 +1,17 @@
defmodule MusicLibrary.Colors.KMeansExtractor do
@moduledoc """
Extracts dominant colors from images using K-Means clustering via the dominant_colors library.
"""
alias Vix.Vips.Image
def extract_dominant_colors(image_data, num_colors \\ 5) do
with {:ok, dir} <- Briefly.create(type: :directory),
path = dir <> "/temp_image.jpg",
{:ok, image} <- Image.new_from_buffer(image_data),
:ok <- Image.write_to_file(image, path),
{:ok, colors} <- DominantColors.dominant_colors(path) do
{:ok, Enum.take(colors, num_colors)}
end
end
end
+1 -1
View File
@@ -375,7 +375,7 @@ defmodule MusicLibrary.Records do
def create_record(attrs \\ %{}) do
with {:ok, record} <- do_create_record(attrs) do
extract_colors_async(record, :fast)
extract_colors_async(record, :accurate)
generate_embedding_async(record)
record
+4 -1
View File
@@ -2,7 +2,7 @@ defmodule MusicLibrary.Worker.ExtractColors do
use Oban.Worker, queue: :heavy_writes, max_attempts: 3
alias MusicLibrary.{Assets, Records}
alias MusicLibrary.Colors.{ColorFrequencyExtractor, EdgeWeightedExtractor}
alias MusicLibrary.Colors.{ColorFrequencyExtractor, EdgeWeightedExtractor, KMeansExtractor}
@impl Oban.Worker
def perform(%Oban.Job{args: %{"id" => record_id, "method" => method}}) do
@@ -21,4 +21,7 @@ defmodule MusicLibrary.Worker.ExtractColors do
defp extract_colors(image_data, :slow),
do: EdgeWeightedExtractor.extract_dominant_colors(image_data)
defp extract_colors(image_data, :accurate),
do: KMeansExtractor.extract_dominant_colors(image_data)
end
@@ -195,6 +195,21 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
{gettext("Extract colors (slow)")}
</.dropdown_link>
<.dropdown_link
id={"actions-#{@record.id}-extract-colors-accurate"}
phx-click={
JS.push("extract_colors", value: %{id: @record.id, method: :accurate})
}
>
<.icon
name="hero-paint-brush"
class="h-4 w-4 mr-1 phx-click-loading:animate-shake"
aria-hidden="true"
data-slot="icon"
/>
{gettext("Extract colors (accurate)")}
</.dropdown_link>
<.dropdown_separator />
<.dropdown_link
id={"actions-#{@record.id}-delete"}
+2
View File
@@ -96,6 +96,8 @@ defmodule MusicLibrary.MixProject do
# Image manipulation
{:vix, "~> 0.38.0"},
{:dominant_colors, "~> 0.1.4"},
{:briefly, "~> 0.5.0"},
# Notes
{:earmark, "~> 1.4"},
+2
View File
@@ -1,6 +1,7 @@
%{
"bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"},
"benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"},
"briefly": {:hex, :briefly, "0.5.1", "ee10d48da7f79ed2aebdc3e536d5f9a0c3e36ff76c0ad0d4254653a152b13a8a", [:mix], [], "hexpm", "bd684aa92ad8b7b4e0d92c31200993c4bc1469fc68cd6d5f15144041bd15cb57"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
@@ -11,6 +12,7 @@
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dominant_colors": {:hex, :dominant_colors, "0.1.4", "afdc765c5156ad3eb9016827e86061f778c5b1d4d879977317be80f32be375ba", [:mix], [{:rustler, "~> 0.30.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "8196358f6491a051e84460c622bcbe01ef1daae9e539e13f9b304e3379f09e8b"},
"earmark": {:hex, :earmark, "1.4.48", "5f41e579d85ef812351211842b6e005f6e0cef111216dea7d4b9d58af4608434", [:mix], [], "hexpm", "a461a0ddfdc5432381c876af1c86c411fd78a25790c75023c7a4c035fdc858f9"},
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
+5
View File
@@ -2063,3 +2063,8 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Failed to store cover image"
msgstr ""
#: lib/music_library_web/live/collection_live/show.ex
#, elixir-autogen, elixir-format
msgid "Extract colors (accurate)"
msgstr ""
+5
View File
@@ -2063,3 +2063,8 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy
msgid "Failed to store cover image"
msgstr ""
#: lib/music_library_web/live/collection_live/show.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Extract colors (accurate)"
msgstr ""