Pythonを使って動画をAA化できた

# 経緯
数日前に[アスキーアート化されるプリクラの記事](https://qiita.com/ShiodaKosuke/items/8080c28e0155c46e9f1d)を拝見しました。
それを見て、「これ動画をAA化できたら面白いんじゃね？」と思い、Python初学者がインターネットとChatGPTの力を借りながら完成までこぎつけたコードの備忘録です。

# 完成品
完成したものを使用して作成できるものの例が[こちら](https://youtube.com/shorts/R1D8zXv6TAc?feature=share)になります。（youtubeShortに上げました）
option.txtの中身を編集したのちに順番に実行することで動画が出力されます。

<details><summary>動画を指定フレームずつ画像に変換</summary>

```python:convert_frame.py
import cv2
import sys
import os
from tqdm import tqdm
def read_init_file(init_file_path):
    values = {}
    with open(init_file_path, 'r') as file:
        lines = file.readlines()
        for line in lines:
            parts = line.strip().split(',')
            if len(parts) == 2:
                key = parts[0].strip()
                value = parts[1].strip()
                values[key] = value
    return values

# ファイルパスから各値を取得
init_file_path = 'option.txt'
init_values = read_init_file(init_file_path)

# 各値へのアクセス例
video_path = init_values.get('DataPath', '')
txt_folder = init_values.get('TxtFolder', '')
image_folder = init_values.get('imageFolder', '')

# 新しい関数: 画像のコントラストを調整
def adjust_contrast(image):
    # ガンマ補正を使用して画像のコントラストを調整
    gamma = 1.6  # ガンマ値を調整してコントラストを変更
    adjusted_image = cv2.convertScaleAbs(image, alpha=gamma, beta=0)

    return adjusted_image

def create_folder_and_return_path(file_path):
    # ファイル名（拡張子なし）を抽出する
    filename_without_extension = os.path.splitext(os.path.basename(file_path))[0]

    # フォルダをファイル名を基に作成する
    folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename_without_extension)
    os.makedirs(folder_path, exist_ok=True)

    # フォルダパスに "\\" を追加する
    folder_path_with_backslash = os.path.join(folder_path, "")

    return folder_path_with_backslash


def resize_frame(frame, width, height):
    return cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA)


# 解像度の指定（幅x高さ）
# 240p: 426x240
# 360p: 640x360
# 480p: 854x480
# 720p: 1280x720
# 1080p: 1920x1080

# 解像度ごとの幅と高さを自動的に設定
# 解像度を指定する（240, 360, 480, 720, 1080）
resolution = 1080  # ここで解像度を指定

if resolution == 240:
    output_width = 426
    output_height = 240
elif resolution == 360:
    output_width = 640
    output_height = 360
elif resolution == 480:
    output_width = 854
    output_height = 480
elif resolution == 720:
    output_width = 1280
    output_height = 720
elif resolution == 1080:
    output_width = 1920
    output_height = 1080
else:
    print("指定した解像度はサポートされていません")
    sys.exit()

# 動画ファイル読み込み
cap = cv2.VideoCapture(video_path)

if not cap.isOpened():
    print("動画の読み込み失敗")
    sys.exit()

# フレーム数の桁数を計算
digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))
n = 0
frame_count = 0

# 動画の総フレーム数を取得
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

# tqdm 進捗バーを初期化
progress_bar = tqdm(total=total_frames, unit=" フレーム", file=sys.stdout)

while True:
    is_image, frame_img = cap.read()

    if is_image:
        # 指定の解像度にリサイズする
        resized_frame = resize_frame(frame_img, output_width, output_height)

        # 画像のコントラストを上げる
        contrast_frame = adjust_contrast(resized_frame)
        progress_bar.update(1)
        # 1フレームごとに保存
        if frame_count % 1 == 0:
            resulting_folder_path = create_folder_and_return_path(video_path)
            cv2.imwrite(resulting_folder_path + str(n).zfill(digit) + ".jpg", contrast_frame)
            n += 1
        
        frame_count += 1
    else:
        break

# ビデオキャプチャを解放
cap.release()

# tqdm 進捗バーを閉じる
progress_bar.close()
```

</details>

<details><summary>画像をAA化</summary>

```python:AA.py
from PIL import Image, ImageDraw,ImageFont
import numpy as np
import os
import sys
#文字の密度関連
char_siza=10 #デフォルト20
image_width=600 #デフォルト150

def read_init_file(init_file_path):
    values = {}
    with open(init_file_path, 'r') as file:
        lines = file.readlines()
        for line in lines:
            parts = line.strip().split(',')
            if len(parts) == 2:
                key = parts[0].strip()
                value = parts[1].strip()
                values[key] = value
    return values

# ファイルパスから各値を取得
init_file_path = 'option.txt'
init_values = read_init_file(init_file_path)

# 各値へのアクセス例
data_path = init_values.get('DataPath', '')
txt_folder = init_values.get('TxtFolder', '')
image_folder_path = init_values.get('imageFolder', '')

def make_map(str_list):
    l=[]
    font = ImageFont.truetype('msgothic.ttc', 20)
    for i in str_list:
        im = Image.new("L",(char_siza,char_siza),"white")
        draw = ImageDraw.Draw(im)
        draw.text((0,0),i,font=font)
        l.append(np.asarray(im).mean())
    l_as=np.argsort(l)
    lenl=len(l)
    l2256=np.r_[np.repeat(l_as[:-(256%lenl)],256//lenl),np.repeat(l_as[-(256%lenl):],256//lenl+1)]
    chr_map=np.array(str_list)[l2256]
    return chr_map

def output(chr_map,imarray,isOutText,out_path):
    aa=chr_map[imarray].tolist()
    if isOutText:
        with open(out_path,"w") as f:
            for i in range(len(imarray)):f.write(''.join(aa[i])+"\n")
    else:
        for i in range(len(imarray)):print(''.join(aa[i]))

def make_AA(file_path,str_list="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +-*/%'"+'"!?#&()~^|@;:.,[]{}<>_0123456789',width=image_width,isOutText=False,out_path="aa.txt",isFW=False):
    imag=Image.open(file_path).convert('L')
    if isFW:str_list=list(str_list.translate(str.maketrans({chr(0x0021 + i): chr(0xFF01 + i) for i in range(94)})))
    else:str_list=list(str_list)
    imarray=np.asarray(imag.resize((width,width*imag.height//imag.width//(2-int(isFW)))))
    output(make_map(str_list),imarray,isOutText,out_path)

def main():
    # 出力フォルダを作成
    output_folder_path = image_folder_path + '_txt'
    os.makedirs(output_folder_path, exist_ok=True)

    # 画像フォルダ内のjpgファイルを取得
    jpg_files = [f for f in os.listdir(image_folder_path) if f.endswith('.jpg')]

    total_images = len(jpg_files)
    processed_images = 0

    # 画像ごとに変換
    for jpg_file in jpg_files:
        jpg_file_path = os.path.join(image_folder_path, jpg_file)
        output_file_name = os.path.splitext(jpg_file)[0] + '_output.txt'  # 出力テキストファイル名
        output_path = os.path.join(output_folder_path, output_file_name)  # 出力テキストファイルのパス

        make_AA(jpg_file_path, str_list="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +-*/%'\"!?#&()~^|@;:.,[]{}<>_0123456789", width=image_width, isOutText=True, out_path=output_path, isFW=False)

        processed_images += 1
        progress=processed_images/total_images*100
        sys.stdout.write("\rProgress: {:.2f} %".format(progress))
        sys.stdout.flush()

if __name__ == '__main__':
    main()
```

</details>

<details><summary>フォルダ内の画像を動画化</summary>

```python:convert_movie.py
import cv2
import os
import sys
from PIL import Image, ImageDraw, ImageFont
def read_init_file(init_file_path):
    values = {}
    with open(init_file_path, 'r') as file:
        lines = file.readlines()
        for line in lines:
            parts = line.strip().split(',')
            if len(parts) == 2:
                key = parts[0].strip()
                value = parts[1].strip()
                values[key] = value
    return values

# ファイルパスから各値を取得
init_file_path = 'option.txt'
init_values = read_init_file(init_file_path)

# 各値へのアクセス例
data_path = init_values.get('DataPath', '')
text_folder_path = init_values.get('TxtFolder', '')
image_folder = init_values.get('imageFolder', '')

# テキストから画像を生成する関数
def text_to_image(text, output_image_path, image_size=(640, 480), background_color='white', text_color='black', font_size=24):
    image = Image.new('RGB', image_size, background_color)
    draw = ImageDraw.Draw(image)
    font = ImageFont.load_default()

    draw.text((10, 10), text, fill=text_color, font=font)
    image.save(output_image_path)

# 画像から動画を作成する関数
def images_to_video(image_folder, output_video_path, frame_rate=10, output_resolution=(1920, 1080)):
    images = [img for img in os.listdir(image_folder) if img.endswith(".jpg")]
    num_images = len(images)

    video = cv2.VideoWriter(output_video_path, cv2.VideoWriter_fourcc(*'mp4v'), frame_rate, output_resolution)

    for i, image in enumerate(images):
        image_path = os.path.join(image_folder, image)
        img = cv2.imread(image_path)
        resized_img = cv2.resize(img, output_resolution)  # 画像の解像度を変更
        video.write(resized_img)
        progress = (i + 1) / num_images * 100
        sys.stdout.write('\r進捗: {:.2f}%'.format(progress))
        sys.stdout.flush()

    cv2.destroyAllWindows()
    video.release()

def progress_callback(progress):
    print(f"進捗: {progress * 100:.2f}%")

# 特定フォルダ内のテキストファイルを名前順に取得
text_files = sorted([f for f in os.listdir(text_folder_path) if f.endswith(".txt")])

# 画像を保存するフォルダを作成
image_folder_path = text_folder_path+'images'
os.makedirs(image_folder_path, exist_ok=True)

# 解像度ごとの幅と高さを辞書にまとめる
resolutions = {
    240: (426, 240),
    360: (640, 360),
    480: (854, 480),
    576: (1024, 576),
    720: (1280, 720),
    1080: (1920, 1080),
    2160: (3840, 2160)
}

# 解像度の指定
resolution = 2160  # 解像度を選択 (240, 360, 480, 720, 1080)

# 解像度がサポートされているかチェック
if resolution not in resolutions:
    print("指定した解像度はサポートされていません")
    sys.exit()

# 解像度に対応する幅と高さを取得
output_width, output_height = resolutions[resolution]

# テキストファイルを画像化して保存
total_text_files = len(text_files)
for i, text_file in enumerate(text_files):
    with open(os.path.join(text_folder_path, text_file), 'r') as f:
        text = f.read()
    output_image_path = os.path.join(image_folder_path, os.path.splitext(text_file)[0] + '.jpg')
    text_to_image(text, output_image_path, image_size=(output_width, output_height))
    progress = (i + 1) / total_text_files * 100
    sys.stdout.write('\rテキストから画像への変換進捗: {:.2f}%'.format(progress))
    sys.stdout.flush()

# 画像から動画を作成
output_video_path = image_folder+'.mp4'  # 出力動画のパスに置き換えてください
print('\n')
images_to_video(image_folder_path, output_video_path, 40,output_resolution=(1920, 1080))
print('\n動画作成完了。')
```

</details>

```text:option.txt
DataPath,D:\data\sigure.mp4
TxtFolder,sigure_txt
imageFolder,sigure
```