"""Image Location Model. Model representing the location of a remote image. """ import hashlib from sqlalchemy import bindparam, text from assets import config from assets.connectors import mysql def get_image_filename(entity_id: int, image_type: str, image_format: str) -> str: """Get image filename based on well-formed path structure. Args: entity_id (int): Unique identifier of the product or artist. image_type (str): Type of entity (e.g. "product" or "artist"). image_format (str): Whether this is for a thumbnail or cover. Returns: str: Path for the image. """ product_hash = hashlib.md5() product_hash.update(str(entity_id).encode("utf8")) return "{image_type}/{image_format}/{md5_hash}.jpg".format( image_type=image_type, image_format=image_format, md5_hash=str(product_hash.hexdigest()), ) def get_image_filenames( entity_ids: list[int], image_type: str, image_format: str ) -> list[str]: """Get image filenames for list of images. Args: entity_ids (list[int]): List of product or artist identifiers. image_type (str): Type of entity (e.g. "product" or "artist"). image_format (str): Whether this is for a thumbnail or cover. Returns: list: Paths for the images. """ return [ get_image_filename(entity_id, image_type, image_format) for entity_id in entity_ids ] def get_image_location_by_filename(filename: str, cdn: str | None = None) -> str: """Get image location for a specified filename. Gets the location for an image from the configured CDN. Args: filename (str): The path and filename for the image. cdn (str): The CDN URL where images can be access from. Returns: str: Location for the file on the cdn. """ return _cdn_filename(filename, cdn) def get_image_location_by_filenames( filenames: list[str], cdn: str | None = None ) -> list[str]: """Get image locations for specified filenames. Gets the locations for images from the configured CDN. Args: filenames (list[str]): The paths and filenames for the images. cdn (str): The CDN URL where images can be access from. Returns: list: Locations for the files on the cdn. """ return [_cdn_filename(filename, cdn) for filename in filenames] def _cdn_filename(filename: str, cdn: str | None = None) -> str: """Get filename on cdn.""" if cdn is None: cdn = config.CDN_URL return "{cdn}/{filename}".format(cdn=cdn, filename=filename) # The subquery ensures we receive the latest asset_upload for older content where previous # upload attempts to same entity (track or artwork) are not marked as deleted. BULK_IMAGE_LOCATION_SQL = """ SELECT au.product_id, af.filename FROM asset_upload au LEFT JOIN asset_final af ON af.asset_upload_id = au.id INNER JOIN ( SELECT MAX(au.id) as max_id FROM asset_upload au WHERE au.api_version = 2 AND au.deleted = 0 AND au.product_id IN :product_ids AND au.track_unique_id = 0 AND au.is_correction IN :is_correction_values GROUP BY au.product_id ORDER BY au.id DESC ) mru_asset on au.id = mru_asset.max_id WHERE af.asset_type = 'JPG' AND af.asset_subtype = :image_format; """ def get_image_locations( product_ids: list[int], image_format: str, fallback: bool = True, omit_corrections: bool = False, ) -> dict[int, str]: """Query for successfully ingested artworks in the v2 flow, and fall back on v1 for the missing ones. Args: entities (list[dict): List of dict of product_ids and UPCs. image_format (str): the cover type: cover, large_cover, xlarge_cover.' fallback (bool): fallback to v1 image indicator omit_corrections (bool): omit error corrections indicator. Returns: dict: a dict of product_ids to artwork urls. """ if not product_ids: return {} is_correction_filter = ["0"] if not omit_corrections: is_correction_filter.append("1") with mysql.au_db_session(read_only=True) as session: # fetch all v2 assets that have been successfully ingested. results = session.execute( text(BULK_IMAGE_LOCATION_SQL) .bindparams( bindparam("product_ids", expanding=True), ) .bindparams( bindparam("is_correction_values", expanding=True), ), { "product_ids": product_ids, "is_correction_values": is_correction_filter, "image_format": image_format, }, ) rows = results.mappings().all() v2_images = { row.product_id: "{}{}".format( config.CDN_URL, row.filename.split("images")[1] ) for row in rows } if not fallback: return v2_images # add placeholder images v2_images.update( dict.fromkeys( set(product_ids) - set(v2_images.keys()), f"{config.CDN_URL}/placeholders/{image_format}.png", ) ) return v2_images def get_v1_images_locations( entity_ids: list[int], image_type: str, image_format: str ) -> dict[int, str]: """Reusable method for formatting v1 URLs for cover and large_cover only . Args: entity_ids: (list[int]): List of dict of product_ids. image_type (str): Type of entity (e.g. "product" or "artist"). image_format: (str): the cover type: cover, large_cover.' Returns: dict: a dict of product_ids to artwork urls. """ v1_filenames = get_image_filenames( entity_ids=entity_ids, image_type=image_type, image_format=image_format ) image_locations = get_image_location_by_filenames( filenames=v1_filenames, cdn=config.CDN_URL ) v1_images = dict(zip(entity_ids, image_locations, strict=False)) return v1_images