mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
reverse search
This commit is contained in:
parent
109f3f781a
commit
85b1f2b1ec
11 changed files with 214 additions and 28 deletions
|
@ -30,7 +30,7 @@ defmodule Philomena.DuplicateReports do
|
|||
where: it.ne >= ^(intensities.ne - dist) and it.ne <= ^(intensities.ne + dist),
|
||||
where: it.sw >= ^(intensities.sw - dist) and it.sw <= ^(intensities.sw + dist),
|
||||
where: it.se >= ^(intensities.se - dist) and it.se <= ^(intensities.se + dist),
|
||||
where: i.image_aspect_ratio >= ^(aspect_ratio - aspect_dist) and i.image_aspect_ratio >= ^(aspect_ratio + aspect_dist),
|
||||
where: i.image_aspect_ratio >= ^(aspect_ratio - aspect_dist) and i.image_aspect_ratio <= ^(aspect_ratio + aspect_dist),
|
||||
limit: 20
|
||||
end
|
||||
|
||||
|
|
|
@ -6,31 +6,40 @@ defmodule Philomena.Processors do
|
|||
alias Philomena.Mime
|
||||
alias Philomena.Sha512
|
||||
|
||||
@mimes %{
|
||||
"image/gif" => "image/gif",
|
||||
"image/jpeg" => "image/jpeg",
|
||||
"image/png" => "image/png",
|
||||
"image/svg+xml" => "image/svg+xml",
|
||||
"video/webm" => "video/webm",
|
||||
"image/svg" => "image/svg+xml",
|
||||
"audio/webm" => "video/webm"
|
||||
}
|
||||
def mimes(type) do
|
||||
%{
|
||||
"image/gif" => "image/gif",
|
||||
"image/jpeg" => "image/jpeg",
|
||||
"image/png" => "image/png",
|
||||
"image/svg+xml" => "image/svg+xml",
|
||||
"video/webm" => "video/webm",
|
||||
"image/svg" => "image/svg+xml",
|
||||
"audio/webm" => "video/webm"
|
||||
}
|
||||
|> Map.get(type)
|
||||
end
|
||||
|
||||
@analyzers %{
|
||||
"image/gif" => Philomena.Analyzers.Gif,
|
||||
"image/jpeg" => Philomena.Analyzers.Jpeg,
|
||||
"image/png" => Philomena.Analyzers.Png,
|
||||
"image/svg+xml" => Philomena.Analyzers.Svg,
|
||||
"video/webm" => Philomena.Analyzers.Webm
|
||||
}
|
||||
def analyzers(type) do
|
||||
%{
|
||||
"image/gif" => Philomena.Analyzers.Gif,
|
||||
"image/jpeg" => Philomena.Analyzers.Jpeg,
|
||||
"image/png" => Philomena.Analyzers.Png,
|
||||
"image/svg+xml" => Philomena.Analyzers.Svg,
|
||||
"video/webm" => Philomena.Analyzers.Webm
|
||||
}
|
||||
|> Map.get(type)
|
||||
end
|
||||
|
||||
@processors %{
|
||||
"image/gif" => Philomena.Processors.Gif,
|
||||
"image/jpeg" => Philomena.Processors.Jpeg,
|
||||
"image/png" => Philomena.Processors.Png,
|
||||
"image/svg+xml" => Philomena.Processors.Svg,
|
||||
"video/webm" => Philomena.Processors.Webm
|
||||
}
|
||||
def processors(type) do
|
||||
%{
|
||||
"image/gif" => Philomena.Processors.Gif,
|
||||
"image/jpeg" => Philomena.Processors.Jpeg,
|
||||
"image/png" => Philomena.Processors.Png,
|
||||
"image/svg+xml" => Philomena.Processors.Svg,
|
||||
"video/webm" => Philomena.Processors.Webm
|
||||
}
|
||||
|> Map.get(type)
|
||||
end
|
||||
|
||||
@versions [
|
||||
thumb_tiny: {50, 50},
|
||||
|
@ -47,8 +56,8 @@ defmodule Philomena.Processors do
|
|||
with upload when not is_nil(upload) <- params["image"],
|
||||
file <- upload.path,
|
||||
{:ok, mime} <- Mime.file(file),
|
||||
mime <- @mimes[mime],
|
||||
analyzer when not is_nil(analyzer) <- @analyzers[mime],
|
||||
mime <- mimes(mime),
|
||||
analyzer when not is_nil(analyzer) <- analyzers(mime),
|
||||
analysis <- analyzer.analyze(file),
|
||||
changes <- analysis_to_changes(analysis, file, upload.filename)
|
||||
do
|
||||
|
@ -73,9 +82,9 @@ defmodule Philomena.Processors do
|
|||
|
||||
mime = image.image_mime_type
|
||||
file = image_file(image)
|
||||
analyzer = @analyzers[mime]
|
||||
analyzer = analyzers(mime)
|
||||
analysis = analyzer.analyze(file)
|
||||
processor = @processors[mime]
|
||||
processor = processors(mime)
|
||||
process = processor.process(analysis, file, @versions)
|
||||
|
||||
apply_edit_script(image, process)
|
||||
|
|
|
@ -21,6 +21,12 @@ defmodule Philomena.Processors.Gif do
|
|||
%{replace_original: optimize(file)}
|
||||
end
|
||||
|
||||
def intensities(analysis, file) do
|
||||
{:ok, intensities} = Intensities.file(preview(analysis.duration, file))
|
||||
intensities
|
||||
end
|
||||
|
||||
|
||||
defp optimize(file) do
|
||||
optimized = Briefly.create!(extname: ".gif")
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@ defmodule Philomena.Processors.Jpeg do
|
|||
|
||||
def post_process(_analysis, _file), do: %{}
|
||||
|
||||
def intensities(_analysis, file) do
|
||||
{:ok, intensities} = Intensities.file(file)
|
||||
intensities
|
||||
end
|
||||
|
||||
|
||||
defp strip(file) do
|
||||
stripped = Briefly.create!(extname: ".jpg")
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@ defmodule Philomena.Processors.Png do
|
|||
%{replace_original: optimize(file)}
|
||||
end
|
||||
|
||||
def intensities(_analysis, file) do
|
||||
{:ok, intensities} = Intensities.file(file)
|
||||
intensities
|
||||
end
|
||||
|
||||
|
||||
defp optimize(file) do
|
||||
optimized = Briefly.create!(extname: ".png")
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@ defmodule Philomena.Processors.Svg do
|
|||
|
||||
def post_process(_analysis, _file), do: %{}
|
||||
|
||||
def intensities(_analysis, file) do
|
||||
{:ok, intensities} = Intensities.file(preview(file))
|
||||
intensities
|
||||
end
|
||||
|
||||
|
||||
defp preview(file) do
|
||||
preview = Briefly.create!(extname: ".png")
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@ defmodule Philomena.Processors.Webm do
|
|||
|
||||
def post_process(_analysis, _file), do: %{}
|
||||
|
||||
def intensities(analysis, file) do
|
||||
{:ok, intensities} = Intensities.file(preview(analysis.duration, file))
|
||||
intensities
|
||||
end
|
||||
|
||||
|
||||
defp preview(duration, file) do
|
||||
preview = Briefly.create!(extname: ".png")
|
||||
|
||||
|
|
65
lib/philomena_web/controllers/search/reverse_controller.ex
Normal file
65
lib/philomena_web/controllers/search/reverse_controller.ex
Normal file
|
@ -0,0 +1,65 @@
|
|||
defmodule PhilomenaWeb.Search.ReverseController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Processors
|
||||
alias Philomena.DuplicateReports
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
plug PhilomenaWeb.ScraperPlug, [params_key: "image", params_name: "image"] when action in [:create]
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, "index.html", images: nil)
|
||||
end
|
||||
|
||||
def create(conn, %{"image" => image_params}) do
|
||||
images =
|
||||
image_params["image"].path
|
||||
|> mime()
|
||||
|> analyze()
|
||||
|> intensities()
|
||||
|> case do
|
||||
:error ->
|
||||
[]
|
||||
|
||||
{analysis, intensities} ->
|
||||
{width, height} = analysis.dimensions
|
||||
aspect = width / height
|
||||
dist = normalize_dist(image_params)
|
||||
|
||||
DuplicateReports.duplicates_of(intensities, aspect, dist, dist)
|
||||
|> preload(:tags)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
conn
|
||||
|> render("index.html", images: images)
|
||||
end
|
||||
|
||||
defp mime(file) do
|
||||
{:ok, mime} = Philomena.Mime.file(file)
|
||||
|
||||
{mime, file}
|
||||
end
|
||||
|
||||
defp analyze({mime, file}) do
|
||||
case Processors.analyzers(mime) do
|
||||
nil -> :error
|
||||
a -> {a.analyze(file), mime, file}
|
||||
end
|
||||
end
|
||||
|
||||
defp intensities(:error), do: :error
|
||||
defp intensities({analysis, mime, file}) do
|
||||
{analysis, Processors.processors(mime).intensities(analysis, file)}
|
||||
end
|
||||
|
||||
# The distance metric is taxicab distance, not Euclidean,
|
||||
# because this is more efficient to index.
|
||||
defp normalize_dist(%{"distance" => distance}) do
|
||||
distance
|
||||
|> String.to_float()
|
||||
|> max(0.01)
|
||||
|> min(1.0)
|
||||
end
|
||||
end
|
|
@ -98,6 +98,9 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/autocomplete", AutocompleteController, only: [:show], singleton: true
|
||||
end
|
||||
resources "/tags", TagController, only: [:index, :show]
|
||||
scope "/search", Search, as: :search do
|
||||
resources "/reverse", ReverseController, only: [:index, :create]
|
||||
end
|
||||
resources "/search", SearchController, only: [:index]
|
||||
resources "/forums", ForumController, only: [:index, :show] do
|
||||
resources "/topics", TopicController, only: [:show] do
|
||||
|
|
74
lib/philomena_web/templates/search/reverse/index.html.slime
Normal file
74
lib/philomena_web/templates/search/reverse/index.html.slime
Normal file
|
@ -0,0 +1,74 @@
|
|||
h1 Reverse Search
|
||||
|
||||
= form_for :image, Routes.search_reverse_path(@conn, :create), [multipart: true], fn f ->
|
||||
p
|
||||
' Basic image similarity search. Finds uploaded images similar to the one
|
||||
' provided based on simple intensities and uses the median frame of GIFs;
|
||||
' very low contrast images (such as sketches) will produce poor results
|
||||
' and, regardless of contrast, results may include seemingly random images
|
||||
' that look very different.
|
||||
|
||||
.image-other
|
||||
#js-image-upload-previews
|
||||
p Upload a file from your computer, or provide a link to the page containing the image and click Fetch.
|
||||
.field
|
||||
= file_input f, :image, class: "input js-scraper"
|
||||
|
||||
.field.field--inline
|
||||
= url_input f, :scraper_url, class: "input input--wide js-scraper", placeholder: "Link a deviantART page, a Tumblr post, or the image directly"
|
||||
button.button.button--separate-left#js-scraper-preview type="button" title="Fetch the image at the specified URL" data-disable-with="Fetch"
|
||||
' Fetch
|
||||
|
||||
.field-error-js.hidden.js-scraper
|
||||
|
||||
h4 Optional settings
|
||||
|
||||
.field
|
||||
= label f, :distance, "Match distance (suggested values: between 0.2 and 0.5)"
|
||||
br
|
||||
= number_input f, :distance, value: 0.25, min: 0, max: 1, step: 0.01, class: "input"
|
||||
|
||||
.field
|
||||
= submit "Reverse Search", class: "button"
|
||||
|
||||
= cond do
|
||||
- is_nil(@images) ->
|
||||
|
||||
- Enum.any?(@images) ->
|
||||
h2 Results
|
||||
|
||||
table
|
||||
tr
|
||||
th
|
||||
th Image
|
||||
th
|
||||
|
||||
= for match <- @images do
|
||||
tr
|
||||
td
|
||||
h3 = link "##{match.id}", to: Routes.image_path(@conn, :show, match)
|
||||
p
|
||||
= if match.source_url not in [nil, ""] do
|
||||
span.source_url
|
||||
= link "Source", to: match.source_url
|
||||
- else
|
||||
' Unknown source
|
||||
|
||||
td
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: match, size: :thumb, conn: @conn
|
||||
|
||||
td
|
||||
h3
|
||||
= match.image_width
|
||||
| x
|
||||
=> match.image_height
|
||||
' -
|
||||
=> round(match.image_size / 1024)
|
||||
' KiB
|
||||
|
||||
= render PhilomenaWeb.TagView, "_tag_list.html", tags: Tag.display_order(match.tags)
|
||||
|
||||
- true ->
|
||||
h2 Results
|
||||
p
|
||||
' We couldn't find any images matching this in our image database.
|
5
lib/philomena_web/views/search/reverse_view.ex
Normal file
5
lib/philomena_web/views/search/reverse_view.ex
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule PhilomenaWeb.Search.ReverseView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
alias Philomena.Tags.Tag
|
||||
end
|
Loading…
Reference in a new issue