import json import yaml import dataclasses from dataclasses import dataclass from collections import OrderedDict from typing import Optional, Dict, List def map_ditem(d): if type(d) != dict: return d do = OrderedDict() for k, v in d.items(): if type(v) == dict: do[k] = map_ditem(v) elif type(v) == list: do[k] = [map_ditem(itm) for itm in v] elif v is not None: do[k] = v return do class ExtendedEncoder(json.JSONEncoder): def default(self, o): if dataclasses.is_dataclass(o): return map_ditem(dataclasses.asdict(o)) return super().default(o) @dataclass class GenreMetadata: name: str description: Optional[str] = None parent: Optional[str] = None language: Optional[str] = None country: Optional[str] = None region: Optional[str] = None localized_name: Optional[bool] = None wikipedia_url: Optional[str] = None wikidata_id: Optional[int] = None rank: Optional[int] = None playlists: Optional[Dict[str, str]] = None alias: Optional[str] = None deprecated: Optional[bool] = None metagenre: Optional[bool] = None # Packaged genre metadata @dataclass class GenreMetadataDB: name: Optional[str] = None parent: Optional[str] = None language: Optional[str] = None country: Optional[str] = None region: Optional[str] = None rank: Optional[int] = None playlists: Optional[Dict[str, str]] = None alias: Optional[str] = None deprecated: Optional[bool] = None metagenre: Optional[bool] = None @classmethod def conv(cls, md: GenreMetadata, name: Optional[str] = None): if md.alias: return cls(alias=md.alias) return cls(name=name or md.name, parent=md.parent, language=md.language, country=md.country, region=md.region, rank=md.rank, playlists=md.playlists, deprecated=md.deprecated, metagenre=md.metagenre) # Genre metadata from tree @dataclass class GenreMetadataTree: id: str name: Optional[str] = None language: Optional[str] = None country: Optional[str] = None region: Optional[str] = None rank: Optional[int] = None playlists: Optional[Dict[str, str]] = None metagenre: Optional[bool] = None children: Optional[List['GenreMetadataTree']] = None @classmethod def conv(cls, genre_id: str, md: GenreMetadata): if md.alias: return cls(genre_id, alias=md.alias) return cls(genre_id, name=md.name, language=md.language, country=md.country, region=md.region, rank=md.rank, playlists=md.playlists, metagenre=md.metagenre) def __lt__(self, other): self.id.__lt__(other.id) def load_genre_dict(d: dict) -> Dict[str, GenreMetadata]: return {k: GenreMetadata(**v) for k, v in d.items()} def store_pack_json(path, data, sort=False): with open(path, "w") as f: json.dump(data, f, sort_keys=sort, ensure_ascii=False, cls=ExtendedEncoder) def store_genres_json(path, genre_data: Dict[str, GenreMetadata], indent=2): with open(path, "w") as f: json.dump(genre_data, f, sort_keys=True, ensure_ascii=False, indent=indent, cls=ExtendedEncoder) if indent: f.write("\n") def store_genres_yaml(path, genre_data: Dict[str, GenreMetadata]): mapped_data = { k: map_ditem(dataclasses.asdict(v)) for k, v in genre_data.items() } yaml.add_representer( OrderedDict, lambda dumper, data: dumper.represent_mapping( 'tag:yaml.org,2002:map', data.items())) with open(path, "w") as f: yaml.dump(mapped_data, f, sort_keys=True, allow_unicode=True)