metadata: add ability to search exif metadata.
Usage: * https://danbooru.donmai.us/media_metadata?search[has_metadata]=true * https://danbooru.donmai.us/media_metadata?search[has_metadata]=false * https://danbooru.donmai.us/media_metadata?search[metadata_has_key]=GIF:GIFVersion * https://danbooru.donmai.us/media_metadata?search[metadata][GIF:GIFVersion]=89a * https://danbooru.donmai.us/media_metadata?search[metadata][GIF:GIFVersion]&search[metadata][GIF:BackgroundColor]=0
This commit is contained in:
@@ -125,6 +125,30 @@ module Searchable
|
|||||||
where_operator(qualified_column, *range)
|
where_operator(qualified_column, *range)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param attr [String] the name of the JSON field
|
||||||
|
# @param hash [Hash] the hash of values it should contain
|
||||||
|
def where_json_contains(attr, hash)
|
||||||
|
# XXX Hack to transform strings to numbers. Needed to match numeric JSON
|
||||||
|
# values when given string input values from an URL.
|
||||||
|
hash = hash.transform_values do |value|
|
||||||
|
if Integer(value, exception: false)
|
||||||
|
value.to_i
|
||||||
|
elsif Float(value, exception: false)
|
||||||
|
value.to_f
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
where("#{qualified_column_for(attr)} @> :hash", hash: hash.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param attr [String] the name of the JSON field
|
||||||
|
# @param hash [String] the key it should contain
|
||||||
|
def where_json_has_key(attr, key)
|
||||||
|
where("#{qualified_column_for(attr)} ? :key", key: key)
|
||||||
|
end
|
||||||
|
|
||||||
def search_boolean_attribute(attr, params)
|
def search_boolean_attribute(attr, params)
|
||||||
if params[attr].present?
|
if params[attr].present?
|
||||||
boolean_attribute_matches(attr, params[attr])
|
boolean_attribute_matches(attr, params[attr])
|
||||||
@@ -218,6 +242,8 @@ module Searchable
|
|||||||
search_inet_attribute(name, params)
|
search_inet_attribute(name, params)
|
||||||
when :enum
|
when :enum
|
||||||
search_enum_attribute(name, params)
|
search_enum_attribute(name, params)
|
||||||
|
when :jsonb
|
||||||
|
search_jsonb_attribute(name, params)
|
||||||
when :array
|
when :array
|
||||||
search_array_attribute(name, subtype, params)
|
search_array_attribute(name, subtype, params)
|
||||||
else
|
else
|
||||||
@@ -406,6 +432,26 @@ module Searchable
|
|||||||
relation
|
relation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_jsonb_attribute(name, params)
|
||||||
|
relation = all
|
||||||
|
|
||||||
|
if params[name].present?
|
||||||
|
relation = relation.where_json_contains(:metadata, params[name])
|
||||||
|
end
|
||||||
|
|
||||||
|
if params["#{name}_has_key"]
|
||||||
|
relation = relation.where_json_has_key(:metadata, params["#{name}_has_key"])
|
||||||
|
end
|
||||||
|
|
||||||
|
if params["has_#{name}"].to_s.truthy?
|
||||||
|
relation = relation.where.not(name => "{}")
|
||||||
|
elsif params["has_#{name}"].to_s.falsy?
|
||||||
|
relation = relation.where(name => "{}")
|
||||||
|
end
|
||||||
|
|
||||||
|
relation
|
||||||
|
end
|
||||||
|
|
||||||
def search_array_attribute(name, type, params)
|
def search_array_attribute(name, type, params)
|
||||||
relation = all
|
relation = all
|
||||||
singular_name = name.to_s.singularize
|
singular_name = name.to_s.singularize
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class UploadService
|
|||||||
|
|
||||||
process_resizes(upload, file, original_post_id)
|
process_resizes(upload, file, original_post_id)
|
||||||
|
|
||||||
MediaAsset.create_from_media_file!(media_file)
|
MediaAsset.create!(file: media_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
def automatic_tags(media_file)
|
def automatic_tags(media_file)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
class MediaAsset < ApplicationRecord
|
class MediaAsset < ApplicationRecord
|
||||||
has_one :media_metadata, dependent: :destroy
|
has_one :media_metadata, dependent: :destroy
|
||||||
|
|
||||||
def self.create_from_media_file!(media_file)
|
|
||||||
create!(
|
|
||||||
md5: media_file.md5,
|
|
||||||
file_ext: media_file.file_ext,
|
|
||||||
file_size: media_file.file_size,
|
|
||||||
image_width: media_file.width,
|
|
||||||
image_height: media_file.height,
|
|
||||||
media_metadata: MediaMetadata.new(metadata: media_file.metadata),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.search(params)
|
def self.search(params)
|
||||||
q = search_attributes(params, :id, :created_at, :updated_at, :md5, :file_ext, :file_size, :image_width, :image_height)
|
q = search_attributes(params, :id, :created_at, :updated_at, :md5, :file_ext, :file_size, :image_width, :image_height)
|
||||||
q = q.apply_default_order(params)
|
q = q.apply_default_order(params)
|
||||||
q
|
q
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def file=(file_or_path)
|
||||||
|
media_file = file_or_path.is_a?(MediaFile) ? file_or_path : MediaFile.open(file_or_path)
|
||||||
|
|
||||||
|
self.md5 = media_file.md5
|
||||||
|
self.file_ext = media_file.file_ext
|
||||||
|
self.file_size = media_file.file_size
|
||||||
|
self.image_width = media_file.width
|
||||||
|
self.image_height = media_file.height
|
||||||
|
self.media_metadata = MediaMetadata.new(file: media_file)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ class MediaMetadata < ApplicationRecord
|
|||||||
belongs_to :media_asset
|
belongs_to :media_asset
|
||||||
|
|
||||||
def self.search(params)
|
def self.search(params)
|
||||||
q = search_attributes(params, :id, :created_at, :updated_at, :media_asset_id)
|
q = search_attributes(params, :id, :created_at, :updated_at, :media_asset, :metadata)
|
||||||
q = q.apply_default_order(params)
|
q = q.apply_default_order(params)
|
||||||
q
|
q
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def file=(file_or_path)
|
||||||
|
self.metadata = MediaFile.open(file_or_path).metadata
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,26 @@ class MediaMetadataControllerTest < ActionDispatch::IntegrationTest
|
|||||||
|
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "searching" do
|
||||||
|
setup do
|
||||||
|
@jpg = create(:media_metadata, file: "test/files/test.jpg")
|
||||||
|
@gif = create(:media_metadata, file: "test/files/test.gif")
|
||||||
|
@png = create(:media_metadata, file: "test/files/test.png")
|
||||||
|
end
|
||||||
|
|
||||||
|
should respond_to_search(has_metadata: true).with { [@png, @gif, @jpg] }
|
||||||
|
|
||||||
|
should respond_to_search(metadata_has_key: "File:ColorComponents").with { [@jpg] }
|
||||||
|
should respond_to_search(metadata: { "File:ColorComponents": 3 }).with { [@jpg] }
|
||||||
|
should respond_to_search(metadata: { "File:ColorComponents": "3" }).with { [@jpg] }
|
||||||
|
|
||||||
|
should respond_to_search(metadata_has_key: "GIF:GIFVersion").with { [@gif] }
|
||||||
|
should respond_to_search(metadata: { "GIF:GIFVersion": "89a" }).with { [@gif] }
|
||||||
|
|
||||||
|
should respond_to_search(metadata_has_key: "PNG:ColorType").with { [@png] }
|
||||||
|
should respond_to_search(metadata: { "PNG:ColorType": "RGB" }).with { [@png] }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user