如何使用 Python 构建分层列表制作器

2023年 7月 14日 53.2k 0

你好 Python 爱好者!你想提升你的 Python 和 API 技能,同时构建一些真正有用的东西吗?那么你来对地方了。

本实践教程展示了如何利用 Python 的功能在终端内编写交互式分层列表生成器。

我们将在此过程中使用一些有用的 Python 库来构建一个实用的工具,让你可以在几秒钟内以引人入胜且高效的方式对你喜爱的专辑进行排名和组织。

项目概况

分层列表是用于根据喜欢对对象进行排名的分类工具。它们用于音乐、电影和其他领域。该项目中的专辑分层列表根据你的个人选择将记录分配到不同的级别。

本分步指南利用了Rich、PyLast、Pillow和Pick 等 Python 库的强大功能,在终端内创建分层列表生成器。

考虑轻松地将你的专辑分为不同的级别,例如“S 级”表示一直以来的最爱,“B 级”表示那些未被发现的宝石。你将可以根据自己的喜好完全控制音乐收藏的组织方式。

2023-07-09_23h22_17.png
在此项目结束时,你可以导出所有分层列表。下面是它可能的样子的示例。这可以为你选择的任何艺术家完成。

MAC-DEMACO-层列表最终项目成果

获取你的 LastFM API 密钥

LastFM是一个音乐数据库和在线平台,提供复杂的音乐推荐系统和 API。它允许开发人员从他们的数据库访问和下载数据。

这是必要的步骤,因为 CLI 应用程序从 LastFM API 请求专辑元数据和封面。

首先,你需要创建一个LastFM 开发者帐户。

图片7切勿共享 API 凭据。使用环境变量来存储它们。

接下来,复制 API 密钥和共享密钥。将它们设置为环境变量。

在 Windows 上:

setx LASTFM_API_KEY "your_api_key"setx LASTFM_API_SECRET "your_api_secret"

在 Linux/MacOS 上:

export LASTFM_API_KEY="your_api_key"export LASTFM_API_SECRET="your_api_secret"

导入模块

以下是启动项目所需安装的模块:

  • json:对来自 API 的 JSON 响应进行编码和解码。
  • os:文件和目录操作。
  • datetime:日期和时间的格式化和数学运算。
  • io:内存中字节数据的类似流的接口。
  • typing:类型提示以提高可读性
  • pylast:围绕 LastFM API 的 Python 包装器库。
  • requests:使用在线服务和 API 发出 HTTP 请求。
  • pick:交互式选择菜单,用于直接从终端中的列表中进行选择。
  • PIL:图像处理和操作(例如绘图、调整大小和保存)
  • rich:可爱的终端格式。

使用 pip(Python 包管理器)安装它们。

pip install pylast requests pick Pillow rich

现在设置已完成,启动代码编辑器,让我们开始构建。

import jsonimport osfrom datetime import datetimefrom io import BytesIOfrom typing import Listimport pylastimport requestsfrom pick import pickfrom PIL import Image, ImageDraw, ImageFontfrom rich import printfrom rich.panel import Panelfrom rich.table import Table

通过交互式菜单启动

这是一个基于 CLI 的应用程序。因此,你所做的任何选择都将直接在终端内做出。启动屏幕上向用户提供两个选择:

  • 创建分层列表: 输入列表名称和艺术家。该应用程序将从 LastFM API 获取元数据和专辑封面并将其保存到 JSON 文件中。
  • 将分层列表导出到图像: 使用 Pandas 将收集的 JSON 数据导出到漂亮的 PNG/JPG 图像。该图像将具有行和列来指示层和专辑。
  • 首先,我们向用户展示一个交互式菜单:

    图12选择模块在终端中呈现一个选项选择菜单。使用箭头键导航并按 Enter 键确认。

    忽略前四个选项,因为它们超出了本演练的范围。你可以只使用该pass语句而不是调用这些函数来防止任何错误。

    为此,你需要在文件末尾编写以下驱动程序代码。

    LASTFM_API_KEY = os.environ.get("LASTFM_API_KEY")LASTFM_API_SECRET = os.environ.get("LASTFM_API_SECRET")network = pylast.LastFMNetwork(api_key=LASTFM_API_KEY, api_secret=LASTFM_API_SECRET)def start():    
        global network
        startup_question = "What Do You Want To Do?"
        options = ["Rate by Album", "Rate Songs", "See Albums Rated", "See Songs Rated", "Make a Tier List", "See Created Tier Lists", "EXIT"]
        selected_option, index = pick(options, startup_question, indicator="→")
        
        if index == 0:
            rate_by_album()
        elif index == 1:
            rate_by_song()
        elif index == 2:
            see_albums_rated()
        elif index == 3:
            see_songs_rated()
        elif index == 4:
            create_tier_list()
        elif index == 5:
            see_tier_lists()
        elif index == 6:
            exit()start()
    

    如上面的代码所示,该os.environ.get() 函数检索你在上一节中设置的环境变量的值。

    network可能是最重要的变量。它附加了很多方法。这些方法包括:

    • 获取艺术家的专辑
    • 获取有关艺术家的元数据
    • 获取有关专辑的元数据
    • 获取专辑封面
    • 通过检查 200(正常)响应状态进行错误验证。

    然后,start() 启动应用程序,使用该功能提出启动问题pick,存储用户选择,并根据所选选项执行各种操作。

    该pick方法接受以下参数:

    • options:可供选择的选项列表。这些将是专辑列表。
    • title:向用户显示的标题或问题。层列表名称。
    • multiselect:指示是否可以选择多个选项的标志。多选或单选。
    • indicator:用于指示所选选项的符号或字符。
    • min_selection_count:必须选择的最少选项数。该选项仅允许一项选择,即默认值。

    注意:下面的所有代码都必须放在驱动程序代码之上。 ****我们将定义几个函数,每个选项一个。

    如何在 JSON 中保存状态

    即使应用程序架构发生变化,JSON 文件也易于使用和维护。这就是你将以 JSON 格式存储层列表数据的原因。它是一种持久存储方法,允许你更新专辑和歌曲评级以及等级列表,即使程序重新运行也是如此。

    你肯定不希望应用程序重新启动时丢失用户数据吗?因此,需要保存状态。大多数时候它是一个数据库。但为了简单起见,我们使用 JSON 来存储和检索用户数据。

    def load_or_create_json() -> None:
        if os.path.exists("albums.json"):
            with open("albums.json") as f:
                ratings = json.load(f)
        else:
            # create a new json file with empty dict
            with open("albums.json", "w") as f:
                ratings = {"album_ratings": [], "song_ratings": [], "tier_lists": []}
                json.dump(ratings, f)
    

    此自定义函数要么加载现有的 JSON 文件,要么生成一个(如果不存在)。它保证应用程序有一个用于存储和检索专辑和歌曲评级以及等级列表的文件。

    如果该文件不存在,它将以写入模式创建一个名为“albums.json”的新文件。然后将该ratings变量初始化为包含空列表的字典。json.dump() 将字典的内容ratings写入 JSON 文件。

    如何编写实用函数

    菜单驱动编程中的实用程序或辅助函数执行与菜单选项相关的常见任务或操作。这些函数是可重用和模块化的,使代码更有组织性并且更易于维护。示例包括:

    • 显示菜单
    • 输入验证
    • 数据持久化
    • 格式化和显示
    • 错误处理
    • 常见操作。

    这些函数处理多个菜单选项所需的常见任务,提高代码的可重用性并减少冗余。将这些功能封装在菜单逻辑中有助于维护代码流,并方便测试、调试和将来的修改。

    将它们视为桥梁,帮助更好地连接两个功能并隔离可即时使用的琐碎逻辑。该项目依赖于两个辅助函数。

    从列表中删除专辑

    首先,我们将编写一个函数来从列表中删除所选专辑,以防止不同层之间的重复。看起来是这样的:

    def create_tier_list_helper(albums_to_rank, tier_name):
        # if there are no more albums to rank, return an empty list
        if not albums_to_rank:
            return []
        
        question = f"Select the albums you want to rank in  {tier_name}"
        tier_picks = pick(options=albums_to_rank, title=question, multiselect=True, indicator="→", min_selection_count=0)
        tier_picks = [x[0] for x in tier_picks]
        
        for album in tier_picks:
            albums_to_rank.remove(album)
        return tier_picks
    

    该函数将被该方法调用,create_tier_list直到所有层都被循环通过

    这允许用户对某些层内的相册进行排名,并有助于创建层列表。

    它需要两个参数:albums_to_rank和tier_name。如果没有更多专辑可供排名,该函数将生成一个空列表。用户可以从要排名的相册中选择要评分的相册、将它们保存在层选择中、删除它们以及返回层选择列表。

    返回值tier_picks是一个Python列表。

    返回所选专辑的封面

    接下来,编写一个返回用户选择的专辑封面的函数。它看起来是这样的:

    def get_album_cover(artist, album):
        album = network.get_album(artist, album)
        album_cover = album.get_cover_image()
        # check if it is a valid url
        try:
            response = requests.get(album_cover)
            if response.status_code != 200:
                album_cover = "https://community.mp3tag.de/uploads/default/original/2X/a/acf3edeb055e7b77114f9e393d1edeeda37e50c9.png"
        except:
            album_cover = "https://community.mp3tag.de/uploads/default/original/2X/a/acf3edeb055e7b77114f9e393d1edeeda37e50c9.png"
        return album_cover
    

    如果服务器上存在资源,则获取专辑封面,否则返回后备图像

    这将通过 LastFM API 检索指定艺术家的专辑封面图像和专辑名称。它使用 HTTP 请求验证来自 API 答案的封面图像 URL。

    如果 URL 正确,则返回专辑封面。否则,默认情况下会提供专辑封面的后备占位符图像。  

    你之前创建的对象network有几个方便的方法。第一行获取专辑对象,然后直接通过 LastFM 获取该对象的封面图像。

    如何将分层列表数据添加到 JSON

    一旦用户从菜单中选择“创建层级列表”选项,脚本就会向他们显示可用的层级,并要求他们输入有效的艺术家和层级列表的名称,以便将其存储在 JSON 文件中。

    图片 16选择“创建层列表”选项后,脚本将验证艺术家使用 LastFM API 返回的元数据。

    使用该network对象来验证艺术家是否存在。如果是,请请求该艺术家的所有专辑。使用这些专辑填充列表并将 设为option该列表,以便它显示在 S 层的选项中。

    在下图中,(x) 标记表示用户已选择将该特定专辑纳入 S 层。

    图33这是提示用户选择要移动到 S 层的相册。使用箭头键导航以从列表中选择零个、一个或多个专辑。

    用户选择这些相册后,你希望序列化此列表并将其放入 JSON 文件中,稍后将使用该文件生成实际图像。该 JSON 文件需要有数据定义。

    想想数据库如何拥有模式。它们有描述数据性质和格式的表、列和行。

    同样,我们将定义 JSON 文件的架构来存储所有这些层列表选项。每个层列表对象包含以下属性:

    • tier_list_name:为层列表指定的名称。
    • artist:为其创建等级列表的艺术家的姓名。
    • s_tier, a_tier, b_tier, c_tier, d_tier, e_tier:保存每个层的专辑及其相应封面的数组。专辑表示为具有“album”和“cover_art”属性的对象。
    • time:创建时间戳。
    • 每个层数组包含一个或多个专辑对象,其中“album”代表专辑名称,“cover_art”代表专辑名称

    这是示例 JSON 架构。一旦用户在终端中做出选择,与此类似的包含层列表数据的序列化 Python 对象将被写入 JSON 文件。

    {
      "tier_lists": [
            {
                "tier_list_name": "THE WEEKND RANKED",
                "artist": "the weeknd",
                "s_tier": [
                    {
                        "album": "After Hours",
                        "cover_art": "https://lastfm.freetls.fastly.net/i/u/300x300/7d957bd27dd562bee7aaa89eafa0bbe6.jpg"
                    }
                ],
                "a_tier": [
                    {
                        "album": "Kiss Land",
                        "cover_art": "https://lastfm.freetls.fastly.net/i/u/300x300/01ad150445023de653c50dbbc3e10dbc.jpg"
                    },
                    {
                        "album": "Echoes of Silence",
                        "cover_art": "https://lastfm.freetls.fastly.net/i/u/300x300/4f257619898b44b7a8f95431045e9ffe.png"
                    }
                ],
                "b_tier": [],
                "c_tier": [],
                "d_tier": [],
                "e_tier": [
                    {
                        "album": "I Feel It Coming",
                        "cover_art": "https://lastfm.freetls.fastly.net/i/u/300x300/974deeb8c348d0ad0c0fa10941dd67e8.jpg"
                    }
                ],
                "time": "2023-04-23 23:56:14.652417"
            }
        ]}
    

    当用户继续创建层列表时,你希望动态写入此 JSON 文件。也就是说,它应该继续增长和扩展以适应所有专辑封面。下面的代码正是这样做的:

    def create_tier_list():
        load_or_create_json()
        with open("albums.json") as f:
            album_file = json.load(f)
        print("TIERS - S, A, B, C, D, E")
        question = "Which artist do you want to make a tier list for?"
        artist = input(question).strip().lower()
        
        try:
            get_artist = network.get_artist(artist)
            artist = get_artist.get_name()
            albums_to_rank = get_album_list(artist)
            
            # keep only the album name by splitting the string at the first - and removing the first element
            albums_to_rank = [x.split(" - ", 1)[1] for x in albums_to_rank[1:]]
            question = "What do you want to call this tier list?"
            tier_list_name = input(question).strip()
            # repeat until the user enters at least one character
            while not tier_list_name:
                print("Please enter at least one character")
                tier_list_name = input(question).strip()
            # S TIER
            question = "Select the albums you want to rank in S Tier:"
            s_tier_picks = create_tier_list_helper(albums_to_rank, "S Tier")
            s_tier_covers = [get_album_cover(artist, album) for album in s_tier_picks]
            s_tier = [{"album":album,"cover_art": cover} for album, cover in zip(s_tier_picks, s_tier_covers)]
            
            # A TIER
            question = "Select the albums you want to rank in A Tier:"
            a_tier_picks = create_tier_list_helper(albums_to_rank, "A Tier")
            a_tier_covers = [get_album_cover(artist, album) for album in a_tier_picks]
            a_tier = [{"album":album,"cover_art": cover} for album, cover in zip(a_tier_picks, a_tier_covers)]
                
            # B TIER
            question = "Select the albums you want to rank in B Tier:"
            b_tier_picks = create_tier_list_helper(albums_to_rank, "B Tier")
            b_tier_covers = [get_album_cover(artist, album) for album in b_tier_picks]
            b_tier = [{"album":album,"cover_art": cover} for album, cover in zip(b_tier_picks, b_tier_covers)]
            
            # C TIER
            question = "Select the albums you want to rank in C Tier:"
            c_tier_picks = create_tier_list_helper(albums_to_rank, "C Tier")
            c_tier_covers = [get_album_cover(artist, album) for album in c_tier_picks]
            c_tier = [{"album":album,"cover_art": cover} for album, cover in zip(c_tier_picks, c_tier_covers)]
                
            # D TIER
            question = "Select the albums you want to rank in D Tier:"
            d_tier_picks = create_tier_list_helper(albums_to_rank, "D Tier")
            d_tier_covers = [get_album_cover(artist, album) for album in d_tier_picks] 
            d_tier = [{"album":album,"cover_art": cover} for album, cover in zip(d_tier_picks, d_tier_covers)]
            # E TIER
            question = "Select the albums you want to rank in E Tier:"
            e_tier_picks = create_tier_list_helper(albums_to_rank, "E Tier")
            e_tier_covers = [get_album_cover(artist, album) for album in e_tier_picks]
            e_tier = [{"album":album,"cover_art": cover} for album, cover in zip(e_tier_picks, e_tier_covers)]
            
            # check if all tiers are empty and if so, exit
            if not any([s_tier_picks, a_tier_picks, b_tier_picks, c_tier_picks, d_tier_picks, e_tier_picks]):
                print("All tiers are empty. Exiting...")
                return
            
            
            # # add the albums that were picked to the tier list
            tier_list = {
                "tier_list_name": tier_list_name,
                "artist": artist,
                "s_tier": s_tier, 
                "a_tier": a_tier,
                "b_tier": b_tier,
                "c_tier": c_tier,
                "d_tier": d_tier,
                "e_tier": e_tier,
                "time": str(datetime.now())
            }
            
            # add the tier list to the json file
            album_file["tier_lists"].append(tier_list)
            
            # save the json file
            with open("albums.json", "w") as f:
                json.dump(album_file, f, indent=4)
                
            return
        
        except pylast.PyLastError:
            print("❌[b red] Artist not found [/b red]")
    

    这是用于为相册创建层级列表并将其存储在albums.json. 这是其中发生的事情:

    • 用户输入艺术家的姓名并从 LastFM API 检索信息。
    • 接下来,为他们要创建的层列表提供名称。
    • 对于每个层(S、A、B、C、D、E),使用你之前编写的辅助函数选择要在该层内排名的专辑。
    • 通过 检索每个所选专辑的专辑封面艺术get_album_cover() ,并且所选专辑及其相应的封面艺术作为字典存储在相应的层列表中。
    • 如果所有层都为空,则该函数退出。JSON 文件中未写入任何内容。
    • 否则,层列表将添加到保存在当前工作目录(与 Python 脚本相同的路径)中的 JSON 文件中。

    图15现在,这是下一层(A 层)的选择。我们在前面的选项中选择的专辑不再出现,这意味着它们已经被选择了。

    如何使用枕头进行视觉变换

    现在你已经拥有了层级列表的所有 JSON 数据,你希望将所有数据导出到图像,以便可以与朋友共享或将其发布到网络上。但你应该怎么做呢?让我们来分解一下:

    首先,你需要确定层数。然后,确定层列表网格和专辑封面方块的位置和大小。

    在这里,你需要考虑动态宽度和高度偏移。你应该如何防止图像溢出、添加新行或保持最小高度?

    这一切都与图像画布有关。枕头是一个很好的选择。你可以根据用户输入和选择动态调整、调整和扩展所有图像以及背景画布的尺寸。

    图像34用 Pillow 制作的层级列表模板。请参阅下面的代码进行解释。

    解决这个问题最合乎逻辑的方法是将层列表对象传递给函数并让它循环遍历所有层。在每一层内,让它循环所有记录并添加一个项目。如果专辑封面超过最大宽度,请添加新行以使其不会溢出。继续此操作,直到处理完每一层中的所有专辑。中提琴!

    def image_generator(file_name, data):
        # return if the file already exists
        if os.path.exists(file_name):
            return
        
        # Set the image size and font
        image_width = 1920
        image_height = 5000
        font = ImageFont.truetype("arial.ttf", 15)
        tier_font = ImageFont.truetype("arial.ttf", 30)
        
        # Make a new image with the size and background color black
        image = Image.new("RGB", (image_width, image_height), "black")
        text_cutoff_value = 20
        #Initialize variables for row and column positions
        row_pos = 0
        col_pos = 0
        increment_size = 200
        
        """S Tier"""
        # leftmost side - make a square with text inside the square and fill color
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="red")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "S Tier", font=tier_font, fill="white")
            col_pos += increment_size
            
        for album in data["s_tier"]:
            # Get the cover art
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))
        
        	# Resize the cover art
            cover_art = cover_art.resize((increment_size, increment_size))
            
            # Paste the cover art onto the base image
            image.paste(cover_art, (col_pos, row_pos))
            
            # Draw the album name on the image with the font size 10 and background color white
            draw = ImageDraw.Draw(image)
            # Get the album name
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            # Increment the column position
            col_pos += 200
            # check if the column position is greater than the image width
            if col_pos > image_width - increment_size:
                # add a new row
                row_pos += increment_size + 50
                col_pos = 0 
        # add a new row to separate the tiers
        row_pos += increment_size + 50
        col_pos = 0
        """A TIER"""
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="orange")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "A Tier", font=tier_font, fill="white")
            col_pos += increment_size
            
        for album in data["a_tier"]:
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))
            cover_art = cover_art.resize((increment_size, increment_size))
            image.paste(cover_art, (col_pos, row_pos))
            draw = ImageDraw.Draw(image)
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            col_pos += 200
            if col_pos > image_width - increment_size:
                row_pos += increment_size + 50
                col_pos = 0 
        row_pos += increment_size + 50
        col_pos = 0
        
        """B TIER"""
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="yellow")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "B Tier", font=tier_font, fill="black")
            col_pos += increment_size
            
        for album in data["b_tier"]:
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))
            cover_art = cover_art.resize((increment_size, increment_size))
            image.paste(cover_art, (col_pos, row_pos))
            draw = ImageDraw.Draw(image)
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            col_pos += 200
            if col_pos > image_width - increment_size:
                # add a new row
                row_pos += increment_size + 50
                col_pos = 0
        
        row_pos += increment_size + 50
        col_pos = 0
        
        """C TIER"""
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="green")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "C Tier", font=tier_font, fill="black")
            col_pos += increment_size
            
        for album in data["c_tier"]:
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))       
            cover_art = cover_art.resize((increment_size, increment_size))
            image.paste(cover_art, (col_pos, row_pos))
            draw = ImageDraw.Draw(image)
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            col_pos += 200
            if col_pos > image_width - increment_size:
                row_pos += increment_size + 50
                col_pos = 0
        
        row_pos += increment_size + 50
        col_pos = 0
       
        """D TIER"""
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="blue")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "D Tier", font=tier_font, fill="black")
            col_pos += increment_size
            
        for album in data["d_tier"]:
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))
            cover_art = cover_art.resize((increment_size, increment_size))
            image.paste(cover_art, (col_pos, row_pos))        
            draw = ImageDraw.Draw(image)
            
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            col_pos += 200
            if col_pos > image_width - increment_size:
                # add a new row
                row_pos += increment_size + 50
                col_pos = 0
        
        row_pos += increment_size + 50
        col_pos = 0
        """E TIER"""
        if col_pos == 0:
            draw = ImageDraw.Draw(image)
            draw.rectangle((col_pos, row_pos, col_pos + increment_size, row_pos + increment_size), fill="pink")
            draw.text((col_pos + (increment_size//3), row_pos+(increment_size//3)), "E Tier", font=tier_font, fill="black")
            col_pos += increment_size
            
        for album in data["e_tier"]:
            
            response = requests.get(album["cover_art"])
            cover_art = Image.open(BytesIO(response.content))
            cover_art = cover_art.resize((increment_size, increment_size))    
            image.paste(cover_art, (col_pos, row_pos))
            draw = ImageDraw.Draw(image)
            name = album["album"]
            if len(name) > text_cutoff_value:
                name = f"{name[:text_cutoff_value]}..."
            draw.text((col_pos, row_pos + increment_size), name, font=font, fill="white")
            col_pos += 200
            if col_pos > image_width - increment_size:
                row_pos += increment_size + 50
                col_pos = 0
        
        row_pos += increment_size + 50
        col_pos = 0
    	image = image.crop((0, 0, image_width, row_pos))
        image.save(f"{file_name}")
    

    首先,通过两个参数(file name和data),这个自定义函数负责将我们存储的所有 JSON 数据转换为组织良好的层列表图像。

    它确定指定的文件是否file name存在,如果存在则返回 true。如果你已经使用该名称创建了层列表,则这可以节省计算量。

    ​你可以看到它指定了用于构建层列表视觉效果的图像大小和字体,生成具有黑色背景的新图像,定义行和列位置的变量,并设置增量大小。

    该函数生成层列表的 S 层部分,生成一个方框,其中的文本填充为红色。

    在检索 S 层中每个专辑的封面图形后,一旦封面艺术被缩放并放置在图像上,专辑标题就会使用给定的字体绘制在图像上。如果列位置大于图像宽度,则会添加新行。

    对 A、B、C、D 和 E 层重复此过程,每个层都有其颜色。如果图片文件尚不存在,则保存生成的图像。

    简而言之,这会将所有专辑封面放置在每层内的行和列中,并根据需要引入新行以适应图像的宽度。动态宽度和高度偏移是为了宽度和高度的自然增长而设置的。

    GRIMES 等级列表---最喜欢的专辑整个图像是通过 Pillow 库处理 JSON 文件中的数据生成的。首先,将层设置到画布的左边缘,然后将选定的专辑放置在画布上。任何溢出都可以通过在层列表下方添加一行来解决。 

    如何导出创建的图像

    你快到了。最后一个函数将层列表对象数据传递给之前定义的函数,以使用 Pillow 渲染图像。

    将其视为两个功能之间的连接纽带。它只是在 CLI 中打印成功或失败消息,让用户了解图像生成状态。

    def see_tier_lists():
        load_or_create_json()
        with open("albums.json", "r") as f:
            data = json.load(f)
        if not data["tier_lists"]:
            print("❌ [b red]No tier lists have been created yet![/b red]")
            return
        
        for key in data["tier_lists"]:
            image_generator(f"{key['tier_list_name']}.png", key)
            print(f"✅ [b green]CREATED[/b green] {key['tier_list_name']} tier list.")
            
        print("✅ [b green]DONE[/b green]. Check the directory for the tier lists.")    
        return
    

    图17让用户知道图像是在当前目录中渲染的。 

    要点

    本教程演示了使用 Python 和 Pillow 库将 JSON 数据转换为交互式层列表图形的方法。通过结合图像处理和 API 数据检索,生成有吸引力的专辑排名表示。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论