Photoshopを自動化してBarChartRaceの挿絵画像を作るプログラムを作った

ランキング1位の曲を取得する

このような(タイトル, アーティスト名, Spotifyのページ, 画像のリンク)の情報を持ったTSVを用意する

title	artist	table_img_url	detail_url	img_url
Starboy	The Weeknd, Daft Punk	https://i.scdn.co/image/ab67616d000048510c8599cbde51245c128bcea9	https://open.spotify.com/track/5aAx2yezTd8zXrkmtKl66Z	https://i.scdn.co/image/ab67616d00001e020c8599cbde51245c128bcea9
Shape of You	Ed Sheeran	https://i.scdn.co/image/ab67616d00004851ba5db46f4b838ef6027e6f96	https://open.spotify.com/track/7qiZfU4dY1lWllzX7mPBI3	https://i.scdn.co/image/ab67616d00001e02ba5db46f4b838ef6027e6f96
HUMBLE.	Kendrick Lamar	https://i.scdn.co/image/ab67616d000048518b52c6b9bc4e43d873869699	https://open.spotify.com/track/7KXjTSCq5nL1LoYtL7XAwS	https://i.scdn.co/image/ab67616d00001e028b52c6b9bc4e43d873869699
Despacito - Remix	Luis Fonsi, Daddy Yankee, Justin Bieber	https://i.scdn.co/image/ab67616d00004851d8559d0280ffe2a2af6dacdf	https://open.spotify.com/track/5CtI0qwDJkDQGwXD1H1cLb	https://i.scdn.co/image/ab67616d00001e02d8559d0280ffe2a2af6dacdf
Despacito (Featuring Daddy Yankee)	Luis Fonsi	https://i.scdn.co/image/ab67616d00004851a5971936e3b8d91f8b616b17	https://open.spotify.com/track/4aWmUDTfIPGksMNLV2rQP2	https://i.scdn.co/image/ab67616d00001e02a5971936e3b8d91f8b616b17
Mi Gente	J Balvin, Willy William	https://i.scdn.co/image/ab67616d00004851996bd1ea86bd3815eedfeef5	https://open.spotify.com/track/2rb5MvYT7ZIxbKW5hfcHx8	https://i.scdn.co/image/ab67616d00001e02d8559d0280ffe2a2af6dacdf
Look What You Made Me Do	Taylor Swift	https://i.scdn.co/image/ab67616d00004851836f1bcbfbd095a859cd5b15	https://open.spotify.com/track/2VjtYe7gpfUi2OkGxR2O2z	https://i.scdn.co/image/ab67616d00001e02836f1bcbfbd095a859cd5b15
Too Good At Goodbyes - Edit	Sam Smith	https://i.scdn.co/image/ab67616d000048510a5b25f2ae0690285b4b1508	https://open.spotify.com/track/0mel2N9Ws9r4yLQn5QE21Y	https://i.scdn.co/image/ab67616d00001e020a5b25f2ae0690285b4b1508
rockstar	Post Malone, 21 Savage	https://i.scdn.co/image/ab67616d00004851d3884f06c92d826ecd87a319	https://open.spotify.com/track/1OmcAT5Y8eg5bUPv9qJT4R	https://i.scdn.co/image/ab67616d00001e02d3884f06c92d826ecd87a319
画像をダウンロードする

curlで画像をダウンロードする

import os
import csv
import subprocess

if __name__ == '__main__':
    path = 'X:\\Adobe\\PremierePro\\Spotify\\global\\top_music_global.tsv'
    save_dir_path = 'X:\\Adobe\\PremierePro\\Spotify\\global\\images\\Jackets'
    with open(path, 'r') as f:
        reader = csv.reader(f, delimiter='\t')
        _ = next(reader)
        for row in reader:
            title, artist, table_img_url, detail_url, img_url = row
            file_name = img_url.split('/')[-1]
            save_path = os.path.join(save_dir_path, file_name) + '.jpg'
            print(f'curl -o {save_path} {img_url}')
            subprocess.run(['curl', '-o', f'{save_path}', f'{img_url}'])
Photoshopで画像を生成

(タイトル, アーティスト, 画像)から挿絵画像を生成する
本来PNGだけで十分だけど、「文字が長すぎて1つの画像だけ枠からはみ出している」のような状況のときに手作業で修正しやすいようにPSDも出力している

コードはこんな感じ

import csv
from typing import Iterator, NamedTuple, List, Any
import unicodedata
from photoshop import Session
import os

class Music(NamedTuple):
    title: str
    artist: str
    table_img_url: str
    detail_url: str
    img_url: str

# tsv to List
def read_tsv(path: str, has_header: bool = False) -> Iterator[List[Any]]:
    with open(path, 'r', encoding='utf-8') as f:
        reader = csv.reader(f, delimiter='\t')
        if has_header:
            next(reader)
        for row in reader:
            yield row

# replace text
def replace_text(cursor_layer, layer_name: str, text: str) -> None:
    if cursor_layer.name == layer_name:
        tmp = cursor_layer.name
        cursor_layer.textItem.contents = text
        cursor_layer.name = tmp

# replace image
def replace_image(ps, cursor_layer, layer_name, path) -> None:
    if cursor_layer.name == layer_name:
        tmp = cursor_layer.name
        replace_contents = ps.app.stringIDToTypeID('placedLayerReplaceContents')
        desc = ps.ActionDescriptor
        desc.putPath(ps.app.charIDToTypeID('null'), path)
        ps.app.executeAction(replace_contents, desc)
        cursor_layer.name = tmp

def replace_text_with_changing_size(cursor_layer, layer_name: str, text: str) -> None:
    if cursor_layer.name == layer_name:
        tmp = cursor_layer.name
        cursor_layer.textItem.size = 16
        cursor_layer.textItem.contents = text
        cursor_layer.name = tmp
        return True
    else:
        return False

# save
def save_doc(doc, options, save_path):
    doc.saveAs(save_path, options, asCopy=True)

# replace name
def replace_name(name):
    if "(" in name:
        name = name.split("(")[0]
    name = name.replace("'", "")
    name = name.replace("’", "")
    name = name.replace('"', '')
    name = name.replace(",", ".")
    normalized = unicodedata.normalize("NFD", name)
    new_name = u"".join([c for c in normalized if not unicodedata.combining(c)])
    return new_name


if __name__ == '__main__':

    DEFAULT_FONT_SIZE = 18

    cc = 'global'

    # template の psd のファイル
    template_psd_path = f'X:\\Adobe\\PremierePro\\Spotify\\global\\images\\template.psd'
    # 情報源の tsv
    tsv_path = f'X:\\Adobe\\PremierePro\\Spotify\\{cc}\\top_music_{cc}.tsv'
    # psd, png の保存先
    save_dir = f'X:\\Adobe\\PremierePro\\Spotify\\{cc}\\images\\canvases'
    # 素材画像
    images_dir = f'X:\\Adobe\\PremierePro\\Spotify\\global\\images\\Jackets_resized'
    
    musics = []

    # 情報の読み取り
    for info in read_tsv(tsv_path, True):
        print(info)
        title, artist, table_img_url, detail_url, img_url = info
        musics.append(Music(title, artist, table_img_url, detail_url, img_url))
    
    with Session(template_psd_path, action='open') as ps:
        doc = ps.active_document
        layers = doc.artLayers

        # each articles 
        for i, info in enumerate(musics):
            
            # if i<58: continue
            # if info.name != 'Russell Westbrook':continue

            print(f'{i:02}', info)


            # 文字サイズを変更したかのフラッグ
            flag = False

            # each layer
            for layer in layers:
                
                # title
                if '(' in info.title:
                    title = info.title.replace('(', '\r(')
                else:
                    title = info.title
                replace_text(layer, 'title', title)

                # artist
                replace_text(layer, 'artist', info.artist)
                
                # replace image
                file_name = info.img_url.split('/')[-1] + '.jpg'
                path = os.path.join(images_dir, file_name)
                print(path)
                replace_image(ps, layer, 'image', path)

            # save
            save_name = f'{i:02}__{info.title}'.replace(':', '_').replace('"', '')
            options = ps.PhotoshopSaveOptions()
            save_path = os.path.join(save_dir, save_name + '.psd')
            save_doc(doc, options, save_path)

            options = ps.PNGSaveOptions()
            save_doc(doc, options, save_path.replace('.psd', '.png'))


            # 文字サイズをもとに戻す
            if flag:
                for layer in layers:
                    if layer.name == 'name':
                        layer.textItem.size = DEFAULT_FONT_SIZE
                        flag = False