"""Utility Functions for tracks.""" import unicodedata from datetime import datetime from backend import features from backend.constants import track_field as tf from backend.constants import track_role from backend.constants.validation import PRODUCER_REQUIRED_START_DATE from backend.models import track_sample from backend.utils import product_utils from backend.utils import validation as validation_utils def unpack_work_and_movement(track): """Unpack the track work and movement field from the track_name. Args: track (dict): Track dictionary Returns: tuple: Tuple with work and movement """ work, _, movement = track[tf.TRACK_NAME].partition(': ') return work.strip(), movement.strip() def get_track_artists_name_ver_artists_tpl(track): """Gets a tuple with the name, version and artists of the track. Args: track (dict): Track dictionary Returns: tuple: Tuple with artists """ track_performers_tuple = validation_utils.transform_to_performers_tuple(track[tf.ARTISTS]) if features.is_bypass_name_validation_if_diff_explicit_enabled(): return (track[tf.TRACK_NAME], track[tf.VERSION], track_performers_tuple, track[tf.EXPLICIT]) else: return (track[tf.TRACK_NAME], track[tf.VERSION], track_performers_tuple,) def get_track_artists_by_role(track, role_type): """Filters a track's artists by role type. Args: track (dict): Track dictionary role_type (str): The role_type to filter on Returns: list: filtered artists """ track_role_artists = [] keys_to_check = [] for key in tf.TRACK_CLASSICAL_ARTISTS: if key in track: keys_to_check.append(key) if tf.PRIMARY_ARTIST in track: keys_to_check.append(tf.PRIMARY_ARTIST) # If no specific keys found, use ARTISTS as fallback if not keys_to_check and tf.ARTISTS in track: keys_to_check.append(tf.ARTISTS) # Filter artists from all selected keys for key in keys_to_check: for artist in track[key]: if artist[tf.TYPE] == role_type: track_role_artists.append(artist) return track_role_artists def has_role(track, role_type): """Check if track has specified role. Args: track (dict): Track dictionary role_type (str): Role type to check for Returns: bool """ if role_type in track_role.ARTISTS: return any(get_track_artists_by_role(track, role_type)) elif role_type in track_role.PUBLISHERS: return bool(track[tf.PUBLISHERS]) elif role_type in track_role.WRITERS: return bool(track[tf.WRITERS]) return False def add_track_samples_to_response(track_response): """Add track samples data to tracks response object. Args: track_response (object): Track Response object to update Returns: Response: An updated Response obj with lyrics """ if not track_response: return track_response tuid = track_response.message['tuid'] res = track_sample.get_track_samples(tuid) if not res: return res else: track_response.message[tf.TRACK_SAMPLES] = res.message return track_response def get_track_featuring_artist_names(track): """Get featuring artist names on a particular track. Args: track (object): Track object Returns: list: List of track level featuring artists """ track_level_feat_artists = [] track_level_artists = track[tf.ARTISTS] track_level_feat_artists = product_utils.get_featuring_artists(track_level_artists, 'type') track_has_feat_artists_corrections = False track_level_feat_artists_corrections = [] for key in track: if key == tf.FEATURING: track_has_feat_artists_corrections = True for artist in track[tf.FEATURING]: track_level_feat_artists_corrections.append(artist[tf.NAME]) if track_has_feat_artists_corrections: return track_level_feat_artists_corrections return track_level_feat_artists def get_track_primary_artist_names(track): """Get primary artist names on a particular track. Args: track (object): Track object Returns: list: List of track level primary artists """ track_level_primary_artists = [] track_level_artists = track[tf.ARTISTS] track_level_primary_artists = product_utils.get_primary_artists(track_level_artists, 'type') track_level_primary_artists_corrections = [] if tf.PRIMARY_ARTIST in track: track_level_primary_artists_corrections = product_utils.get_primary_artists(track[tf.PRIMARY_ARTIST], 'type') if track_level_primary_artists_corrections: return track_level_primary_artists_corrections return track_level_primary_artists def get_track_artists_by_key(key, track): """Get artists by key on a particular track. Args: key (str): artist type key track (object): Track object Returns: list: List of track level artists """ track_level_artists_by_key = [] track_level_artists = track[tf.ARTISTS] track_level_artists_by_key = product_utils.get_artists_by_key(key, track_level_artists, 'type') track_has_artists_corrections = False track_level_artist_corrections = [] for track_key in track: if track_key == key: track_has_artists_corrections = True for artist in track[key]: track_level_artist_corrections.append(artist[tf.NAME]) if track_has_artists_corrections: return track_level_artist_corrections return track_level_artists_by_key def unicode_normalize_compare(s1, s2): s1_nfc = unicodedata.normalize('NFC', _cast_none_to_empty_str(s1)) s2_nfc = unicodedata.normalize('NFC', _cast_none_to_empty_str(s2)) return s1_nfc == s2_nfc def unicode_normalize_values(values): return [unicodedata.normalize('NFC', _cast_none_to_empty_str(v)) for v in values] def _cast_none_to_empty_str(s): if s is None: return '' return s def get_artists_with_corrections(track): """Build a merged artist list for a track, respecting ChainMap corrections. Args: track (dict): track dict, possibly wrapped in a ChainMap. Returns: list: merged list of artist dicts. """ track_artist_correction = [] all_corrected_types = set() for key in tf.ARTIST_TYPES: if key in track: track_artist_correction.extend(track[key]) all_corrected_types.add(key) if len(track[key]) > 0: all_corrected_types.add(track[key][0][tf.TYPE]) track_artist_non_correction = [ artist for artist in track.get(tf.ARTISTS, []) if artist[tf.TYPE] not in all_corrected_types ] merged = track_artist_correction + track_artist_non_correction return merged def is_producer_required_track(product_release_date): """Determine if a producer is required for a track based on release date. Args: product_release_date (str): The release date of the product in 'YYYY-MM-DD' format. Returns: bool: True if a producer is required, False otherwise. """ return ( bool(product_release_date) and datetime.strptime( product_release_date, '%Y-%m-%d' ) >= datetime.strptime(PRODUCER_REQUIRED_START_DATE, '%Y-%m-%d') )