From e0b5382c3f380eee978a6ed703271087845682d5 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Sat, 24 Feb 2024 16:12:02 -0800 Subject: [PATCH] Update `SwfAsset#image_url` to use all the latest tech I think there's no call sites for these anymore, so now I can start repurposing these methods for the new API endpoints I'm planning! :3 Now, `SwfAsset#image_url` approximately matches Impress 2020 logic: use the thumbnail PNG from the manifest if one exists, or the Impress 2020 converter for canvas movies, or the old AWS copy generated by gnash if necessary, or return nil. --- app/models/alt_style.rb | 2 +- app/models/swf_asset.rb | 123 ++++++++++++++++++++++++++++------------ 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/app/models/alt_style.rb b/app/models/alt_style.rb index 3115c6d8..75bfb0b9 100644 --- a/app/models/alt_style.rb +++ b/app/models/alt_style.rb @@ -38,7 +38,7 @@ class AltStyle < ApplicationRecord swf_asset = swf_assets.first return nil if swf_asset.nil? - swf_asset.html5_image_url + swf_asset.image_url end def biology=(biology) diff --git a/app/models/swf_asset.rb b/app/models/swf_asset.rb index 8f2878df..fbd39ea6 100644 --- a/app/models/swf_asset.rb +++ b/app/models/swf_asset.rb @@ -1,3 +1,4 @@ +require 'addressable/template' require 'async' require 'async/barrier' require 'async/semaphore' @@ -10,12 +11,6 @@ class SwfAsset < ApplicationRecord # Used in `item_is_body_specific?`. (TODO: Could we refactor this out?) attr_accessor :item - - IMAGE_SIZES = { - :small => [150, 150], - :medium => [300, 300], - :large => [600, 600] - } belongs_to :zone has_many :parent_swf_asset_relationships @@ -29,28 +24,14 @@ class SwfAsset < ApplicationRecord scope :biology_assets, -> { where(:type => PetState::SwfAssetType) } scope :object_assets, -> { where(:type => Item::SwfAssetType) } - PARTITION_COUNT = 3 - PARTITION_DIGITS = 3 - PARTITION_ID_LENGTH = PARTITION_COUNT * PARTITION_DIGITS - def partition_path - (remote_id / 10**PARTITION_DIGITS).to_s.rjust(PARTITION_ID_LENGTH, '0').tap do |id_str| - PARTITION_COUNT.times do |n| - id_str.insert(PARTITION_ID_LENGTH - (n * PARTITION_DIGITS), '/') - end - end - end - - def image_version - converted_at.to_i - end - - def image_url(size=IMAGE_SIZES[:large]) - size_key = size.join('x') - image_dir = "#{self['type']}/#{partition_path}#{self.remote_id}" - - "https://impress-asset-images.openneo.net/#{image_dir}/#{size_key}.png?" + - "#{image_version}" - end + CANVAS_MOVIE_IMAGE_URL_TEMPLATE = Addressable::Template.new( + Rails.configuration.impress_2020_origin + + "/api/assetImage{?libraryUrl,size}" + ) + LEGACY_IMAGE_URL_TEMPLATE = Addressable::Template.new( + "https://aws.impress-asset-images.openneo.net/{type}" + + "/{id1}/{id2}/{id3}/{id}/{size}x{size}.png?v2-{time}" + ) def as_json(options={}) super({ @@ -63,13 +44,14 @@ class SwfAsset < ApplicationRecord { swf: url, png: image_url, + svg: manifest_asset_urls[:svg], manifest: manifest_url, } end def manifest raise "manifest_url is blank" if manifest_url.blank? - NeopetsMediaArchive.load_json(manifest_url) + @manifest ||= NeopetsMediaArchive.load_json(manifest_url) end def preload_manifest @@ -82,18 +64,89 @@ class SwfAsset < ApplicationRecord return {} if manifest_url.nil? begin - # Organize the asset URLs by file extension, grab the ones we want, and - # convert them from paths to full URLs. - manifest["cpmanifest"]["assets"][0]["asset_data"]. - to_h { |a| [a["file_ext"].to_sym, a] }. - slice(:png, :svg, :js) - .transform_values { |a| (MANIFEST_BASE_URL + a["url"]).to_s } + # Organize the asset URLs by file extension, convert them from paths to + # full URLs, and grab the ones we want. + assets_by_ext = manifest["cpmanifest"]["assets"][0]["asset_data"]. + group_by { |a| a["file_ext"].to_sym }. + transform_values do |assets| + assets.map { |a| (MANIFEST_BASE_URL + a["url"]).to_s } + end + + if assets_by_ext[:js].present? + # If a JS asset is present, assume any other assets are supporting + # assets, and skip them. (e.g. if there's a PNG, it's likely to be an + # "atlas" file used in the animation, rather than a thumbnail.) + # + # NOTE: We take the last one, because sometimes there are multiple JS + # assets in the same manifest, and earlier ones are broken and later + # ones are fixed. I don't know the logic exactly, but that's what we've + # seen! + { js: assets_by_ext[:js].last } + else + # Otherwise, return the last PNG and the last SVG, arbitrarily. + # (There's probably only one of each! I'm just going by the same logic + # we've seen in the JS library case, that later entries are more likely + # to be correct.) + { png: assets_by_ext[:png].last, svg: assets_by_ext[:svg].last } + end rescue StandardError => error Rails.logger.error "Could not read URLs from manifest: #{error.full_message}" return {} end end + def image_url + # Use the PNG image from the manifest, if one exists. + return manifest_asset_urls[:png] if manifest_asset_urls[:png].present? + + # Or, if this is a canvas movie, let Impress 2020 generate a PNG for us. + return canvas_movie_image_url if manifest_asset_urls[:js].present? + + # Otherwise, if we don't have the manifest or it doesn't have the files we + # need, fall back to the Classic DTI image storage, which was generated + # from the SWFs via an old version of gnash (or sometimes manually + # overridden). It's less accurate, but well-tested to generally work okay, + # and it's the only image we have for assets not yet converted to HTML5. + # + # NOTE: We've stopped generating these images for new assets! This is + # mainly for old assets not yet converted to HTML5. + # + # NOTE: If you're modeling from a fresh development database, `has_image?` + # might be false even though we *do* have a saved copy of the image + # available in production. But if you're using the public modeling + # data exported from production, then this check should be fine! + # + # TODO: Rename `has_image?` to `has_legacy_image?`. + return legacy_image_url if has_image? + + # Otherwise, there's no image URL. + nil + end + + def canvas_movie_image_url + return nil unless manifest_asset_urls[:js] + + CANVAS_MOVIE_IMAGE_URL_TEMPLATE.expand( + libraryUrl: manifest_asset_urls[:js], + size: 600, + ).to_s + end + + def legacy_image_url + return nil unless has_image? + + padded_id = remote_id.to_s.rjust(12, "0") + LEGACY_IMAGE_URL_TEMPLATE.expand( + type: type, + id1: padded_id[0...3], + id2: padded_id[3...6], + id3: padded_id[6...9], + id: remote_id, + size: "600", + time: converted_at.to_i, + ).to_s + end + def html5_image_url manifest_asset_urls[:png] end