pythonでmanimとvoicevoxを操って、ずんだもんの平面幾何解説動画を作ってみた。

import os
import shutil
import __main__
import sys

from manim import *
import numpy as np
import requests
from pathlib import Path
from pydub import AudioSegment


# レンダリング設定
config.frame_rate           = 60
#config.background_color     = DARK_GREY
config.format               = "mp4"
config.quality              = "high_quality"
config.frame_height = 10
config.frame_width = 10
width = int(1080)
height = int(1920)
config.frame_size = [width, height]




def voicevox(text, output_path, speaker=2, speedScale=1):
    url = "http://localhost:50021/audio_query"
    params = {"text": text, "speaker": speaker}
    timeout = 15
    query_synthesis = requests.post(url, params=params, timeout=timeout)
    json = query_synthesis.json()
    json['speedScale'] = speedScale
    json['prePhonemeLength'] = 0.3 #前に0.3秒の無音を追加
    json['postPhonemeLength'] = 0.3 #後に0.3秒の無音を追加

    response = requests.post(
                "http://localhost:50021/synthesis",
                params=params,
                json=json,
            )
    out = Path(output_path)
    out.write_bytes(response.content)
    print(text)
    return AudioSegment.from_file(output_path, "wav")


def get_points(dots, dots_on_line):
    #点の座標を取得
    points = []
    for char in dots_on_line:
        points.append(dots[char])
    return points

def get_internal_division_point(dots, dots_on_line,m,n):
    #2点の座標を取得
    points = get_points(dots,dots_on_line)
    #2点をm:nに内分する点を返す
    return (np.array(points[0])*n+np.array(points[1])*m)/(m+n)

def get_Equation_Line(dots,dots_on_line):
    #2点を通る直線の方程式係数を返す。
    #ax+by=c  [a b c]
    #直線の式の係数(a,b)は2点を通るベクトルと直交するから、2点を通るベクトルのx,y成分を入れ替えて片方の符号を変えたもの
    a=   dots[dots_on_line[0]][1]-dots[dots_on_line[1]][1]
    b= -(dots[dots_on_line[0]][0]-dots[dots_on_line[1]][0])
    #直線の上の2点(x,y),(x1,y1)と(a,b)は直交するから、(x-x0)*a+(y-y0)*b=0
    # ax+by=ax0+by0だから、c=ax0+by0
    c=  a*dots[dots_on_line[0]][0] + b*dots[dots_on_line[0]][1]
    return np.array([a,b,c])

def get_Intersection_lines(dots,line1,line2):
    # line1とline2の交点を求める
    left = [[line1[0], line1[1]], [line2[0], line2[1]]]     #連立方程式の左辺の行列
    right = [line1[2], line2[2]]                            #連立方程式の右辺の行列
    return np.linalg.solve(left, right)

def get_Foot_perpendicular(dots,dots_on_line , dot1):
    #点dot1から2点を通る直線に下ろした垂線の足の求める
    #2点dots_on_lineを通る直線
    line1 = get_Equation_Line(dots, dots_on_line)
    #line1の係数から、点dot1を通りline1に直交する直線を求める
    line2 = [line1[1] ,-line1[0] ,line1[1]*dots[dot1][0] - line1[0]*dots[dot1][1]]
    #line1とline2の交点を求めて返り値を戻す
    return get_Intersection_lines(dots,line1,line2)


def talk_voiv(self, speaker, text, dis_text=''):
    if dis_text != '':
        vg_text = Text(dis_text, font="Yu Gothic UI")
    else:
        vg_text = Text(text, font="Yu Gothic UI")
    self.add(vg_text.move_to([0, 5, 0]))
    sound = voicevox(text, "voicevox.wav", speaker, 1.2)
    self.add_sound("voicevox.wav")
    self.wait(sound.duration_seconds)
    self.remove(vg_text)
    #self.wait(0.3)


class Hello(Scene):
    def setup_config(self):  # アニメーションの設定値をインスタンス属性としてセットアップするメソッドです。
        """Set up configuration values as instance attributes."""  # メソッドの説明(docstring):設定値をインスタンス属性としてセットアップします。
        self.screen_height = config.frame_height  # Manimの設定から画面の高さを取得し、インスタンス属性に格納します。
        self.screen_width = config.frame_width  # Manimの設定から画面の幅を取得し、インスタンス属性に格納します。

    def construct(self):
        self.setup_config()  # 設定値をロードします。

        f_voice=True
        vg_scene0 = VGroup()

        # 9x9の図形なのでxyが-1から9までの座標軸を作る。
        ax_graph= Axes(x_range=[-1,9,1],y_range=[-1,9,1],x_length=10,y_length=10)
        #vg_scene0.add(*[ax_graph])

        dots = {
            'A': np.array([0, 6, 0]),
            'B': np.array([0, 0, 0]),
            'C': np.array([8, 0, 0]),
            'D': np.array([3, 0, 0]),
        }


        tri1 = Polygon(*get_points(dots, "ABD"),stroke_color=WHITE)
        tri2 = Polygon(*get_points(dots, "ADC"), stroke_color=WHITE)
        vg_scene0.add(*[tri2],*[tri1])

        line1 = Line(dots["B"], dots["A"])
        line2 = Line(dots["B"], dots["D"])
        vg_scene0.add(*[RightAngle(line1, line2)])  # 直角マーク
        line3 = Line(dots["D"], dots["C"],stroke_color='#FF0000',stroke_width=10)
        line4 = Line(dots["A"], dots["D"])
        line1 = Line(dots["A"], dots["B"])
        line5 = Line(dots["A"], dots["C"])
        vg_scene0.add(*[Angle(line1, line4, radius=1 ,dot=True, dot_color=GREEN,dot_distance=1.2)])  # 角度マーク
        vg_scene0.add(*[Angle(line4, line5, radius=1, dot=True, dot_color=GREEN,dot_distance=1.2)])  # 角度マーク

        # BからADに対して垂線を引き、ACとの交点をFとし、黒色で表示させる(表示させない)
        foot = get_Foot_perpendicular(dots, "AD", "B")
        dots["E"] = np.array([foot[0], foot[1], 0])
        foot = get_Intersection_lines(dots, get_Equation_Line(dots, "BE"), get_Equation_Line(dots, "AC"))
        dots["F"] = np.array([foot[0], foot[1], 0])
        line6 = Line(dots["B"], dots["F"],stroke_color=BLACK)
        vg_scene0.add(line4, line3, line6)

        objdots = VGroup(*[Dot(point) for point in dots.values()])

        #点の名前や長さを表示
        vg_scene0.add(*[Text('A',slant=ITALIC).next_to(objdots[0],[-1, 1, 0])])
        vg_scene0.add(*[Text('B',slant=ITALIC).next_to(objdots[1], [-1, -1, 0])])
        vg_scene0.add(*[Text('C',slant=ITALIC).next_to(objdots[2], [1, -1, 0])])
        vg_scene0.add(*[Text('D',slant=ITALIC).next_to(objdots[3], [0, -1, 0])])
        vg_scene0.add(*[Text('6').next_to(line1, [-1, 0, 0])])
        vg_scene0.add(*[Text('3').next_to(line4, [0, -1, 0])])
        ans_txt=Text('?',color='#FF0000')
        vg_scene0.add(*[ans_txt.next_to(line3, [0, -1, 0])])


        self.add(vg_scene0.shift(ax_graph.c2p([0, 0])))

        if f_voice:
            talk_voiv(self, 2,'あの三角形を知っていれば\n瞬殺の問題')

        suc=tri1.copy().set_fill(opacity=0.3)
        self.add(suc)

        if f_voice:
            talk_voiv(self, 1,'三角形ABDは底辺が3で高さが6')
        if f_voice:
            talk_voiv(self, 1,'つまりは高さが底辺の2倍の\n直角三角形')

        self.remove(suc)

        if f_voice:
            talk_voiv(self, 1,'そして、角Aはその直角三角形のちょうかくで二等分されている','∠Aはその直角三角形の\n頂角で二等分')

        if f_voice:
            talk_voiv(self, 1,'こういうときは')

        line6.set_stroke(color=WHITE)
        if f_voice:
            talk_voiv(self, 1,'ビーからADに対して垂直な\n補助線を引くのが定石','BからADに対して垂直な\n補助線を引くのが定石')
        if f_voice:
            talk_voiv(self, 1,'でも今回はそんなことはしないのだ')

        line6.set_stroke(color=BLACK)

        if f_voice:
            talk_voiv(self, 1,'角Aが、高さが底辺の2倍の\n直角三角形の2倍のときは','∠Aが、高さが底辺の2倍の\n直角三角形の2倍のときは')

        if f_voice:
            talk_voiv(self, 1,'三角形ABCはさんよんごの三角形だから','三角形ABCは3・4・5の三角形だから')

        if f_voice:
            talk_voiv(self, 1,'ABが6なら、BCは8')

        if f_voice:
            talk_voiv(self, 1,'よってCDは8引く3で5なのだ。','よってCDは8-3で5なのだ。')


        self.play(Transform(ans_txt, Text('5',color='#FF0000').move_to(ans_txt)))
        self.wait(5)
        return


#スクリプト配下のmedia\\videos\\以下を消す
target_dir = "media\\videos\\"
print(target_dir)
if os.path.isdir(target_dir):
    shutil.rmtree(target_dir)
    os.mkdir(target_dir)



#pycharmで実行させるときはこれがないとファイルが作られない
scene = Hello()
scene.render()
カテゴリー: Manim

0件のコメント

コメントを残す

アバタープレースホルダー

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください