Fix #4442: Autotag image metadata.
Autotag `greyscale`, `non-repeating_animation`, and `exif_rotation`. Note that this does not detect all (or even most) greyscale images. Artists often save greyscale images as RGB instead of as greyscale.
This commit is contained in:
@@ -30,4 +30,48 @@ class MediaAsset < ApplicationRecord
|
||||
def is_animated_png?
|
||||
file_ext == "png" && metadata.fetch("PNG:AnimationFrames", 1) > 1
|
||||
end
|
||||
end
|
||||
|
||||
# @see https://exiftool.org/TagNames/JPEG.html
|
||||
# @see https://exiftool.org/TagNames/PNG.html
|
||||
# @see https://danbooru.donmai.us/posts?tags=exif:File:ColorComponents=1
|
||||
# @see https://danbooru.donmai.us/posts?tags=exif:PNG:ColorType=Grayscale
|
||||
def is_greyscale?
|
||||
metadata["File:ColorComponents"] == 1 ||
|
||||
metadata["PNG:ColorType"] == "Grayscale" ||
|
||||
metadata["PNG:ColorType"] == "Grayscale with Alpha"
|
||||
|
||||
# Not always accurate:
|
||||
# metadata["ICC-header:ColorSpaceData"] == "GRAY" ||
|
||||
# metadata["XMP-photoshop:ColorMode"] == "Grayscale" ||
|
||||
# metadata["XMP-photoshop:ICCProfileName"] == "EPSON Gray - Gamma 2.2" ||
|
||||
# metadata["XMP-photoshop:ICCProfileName"] == "Gray Gamma 2.2"
|
||||
end
|
||||
|
||||
# https://exiftool.org/TagNames/EXIF.html
|
||||
def is_rotated?
|
||||
metadata["IFD0:Orientation"].in?(["Rotate 90 CW", "Rotate 270 CW", "Rotate 180"]) ||
|
||||
metadata["IFD1:Orientation"].in?(["Rotate 90 CW", "Rotate 270 CW", "Rotate 180"])
|
||||
end
|
||||
|
||||
# Some animations technically have a finite loop count, but loop for hundreds
|
||||
# or thousands of times. Only count animations with a low loop count as non-repeating.
|
||||
def is_non_repeating_animation?
|
||||
loop_count.in?(0..10)
|
||||
end
|
||||
|
||||
# @see https://exiftool.org/TagNames/GIF.html
|
||||
# @see https://exiftool.org/TagNames/PNG.html
|
||||
# @see https://danbooru.donmai.us/posts?tags=-exif:GIF:AnimationIterations=Infinite+animated_gif
|
||||
# @see https://danbooru.donmai.us/posts?tags=-exif:PNG:AnimationPlays=inf+animated_png
|
||||
def loop_count
|
||||
return Float::INFINITY if metadata["GIF:AnimationIterations"] == "Infinite"
|
||||
return Float::INFINITY if metadata["PNG:AnimationPlays"] == "inf"
|
||||
return metadata["GIF:AnimationIterations"] if metadata["GIF:AnimationIterations"].present?
|
||||
return metadata["PNG:AnimationPlays"] if metadata["PNG:AnimationPlays"].present?
|
||||
|
||||
# If the AnimationIterations tag isn't present, then it's counted as a loop count of 0.
|
||||
return 0 if is_animated_gif? && metadata["GIF:AnimationIterations"].nil?
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -498,7 +498,7 @@ class Post < ApplicationRecord
|
||||
end
|
||||
|
||||
def add_automatic_tags(tags)
|
||||
tags -= %w[incredibly_absurdres absurdres highres lowres huge_filesize flash video ugoira animated_gif animated_png]
|
||||
tags -= %w[incredibly_absurdres absurdres highres lowres huge_filesize flash video ugoira animated_gif animated_png exif_rotation non-repeating_animation]
|
||||
|
||||
if image_width >= 10_000 || image_height >= 10_000
|
||||
tags << "incredibly_absurdres"
|
||||
@@ -543,6 +543,10 @@ class Post < ApplicationRecord
|
||||
tags << "animated_gif" if media_asset.is_animated_gif?
|
||||
tags << "animated_png" if media_asset.is_animated_png?
|
||||
|
||||
tags << "greyscale" if media_asset.is_greyscale?
|
||||
tags << "exif_rotation" if media_asset.is_rotated?
|
||||
tags << "non-repeating_animation" if media_asset.is_non_repeating_animation?
|
||||
|
||||
tags
|
||||
end
|
||||
|
||||
|
||||
BIN
test/files/test-animated-86x52-loop-1.gif
Normal file
BIN
test/files/test-animated-86x52-loop-1.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
test/files/test-animated-86x52-loop-2.gif
Normal file
BIN
test/files/test-animated-86x52-loop-2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
test/files/test-rotation-90cw.jpg
Normal file
BIN
test/files/test-rotation-90cw.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1318,6 +1318,38 @@ class PostTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
context "a greyscale image missing the greyscale tag" do
|
||||
should "automatically add the greyscale tag" do
|
||||
@media_asset = MediaAsset.create!(file: "test/files/test-grey-no-profile.jpg")
|
||||
@post.update!(md5: @media_asset.md5)
|
||||
@post.reload.update!(tag_string: "tagme")
|
||||
assert_equal("greyscale tagme", @post.tag_string)
|
||||
end
|
||||
end
|
||||
|
||||
context "an exif-rotated image missing the exif_rotation tag" do
|
||||
should "automatically add the exif_rotation tag" do
|
||||
@media_asset = MediaAsset.create!(file: "test/files/test-rotation-90cw.jpg")
|
||||
@post.update!(md5: @media_asset.md5)
|
||||
@post.reload.update!(tag_string: "tagme")
|
||||
assert_equal("exif_rotation tagme", @post.tag_string)
|
||||
end
|
||||
end
|
||||
|
||||
context "a non-repeating GIF missing the non-repeating_animation tag" do
|
||||
should "automatically add the non-repeating_animation tag" do
|
||||
@media_asset = MediaAsset.create!(file: "test/files/test-animated-86x52-loop-1.gif")
|
||||
@post.update!(md5: @media_asset.md5)
|
||||
@post.reload.update!(tag_string: "tagme")
|
||||
assert_equal("animated animated_gif non-repeating_animation tagme", @post.tag_string)
|
||||
|
||||
@media_asset = MediaAsset.create!(file: "test/files/test-animated-86x52-loop-2.gif")
|
||||
@post.update!(md5: @media_asset.md5)
|
||||
@post.reload.update!(tag_string: "tagme")
|
||||
assert_equal("animated animated_gif non-repeating_animation tagme", @post.tag_string)
|
||||
end
|
||||
end
|
||||
|
||||
should "have an array representation of its tags" do
|
||||
post = FactoryBot.create(:post)
|
||||
post.reload
|
||||
|
||||
Reference in New Issue
Block a user