Start_python’s diary

ふたり暮らし

アラフィフ夫婦のフリーランスプラン

Python GIFファイルを再生する(Kivy 画像更新)

f:id:Start_python:20200108213521g:plain

はじめに

今回はClockオブジェクトを利用して画像を更新する方法です。指定された間隔で繰り返し処理を行います。

 

プログラムのコード

# フル画面を解除して画面の幅と高さを設定
from kivy.config import Config
Config.set('graphics', 'fullscreen', 0)
Config.set('graphics', 'width', 320)
Config.set('graphics', 'height', 568)
Config.set('graphics', 'resizable', 1)

import os
import glob
import cv2
import numpy as np
from PIL import Image

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Label
from kivy.uix.button import Button
from kivy.uix.image import Image as Img
from kivy.graphics.texture import Texture
from kivy.properties import StringProperty, ObjectProperty
from kivy.clock import Clock

from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path

resource_add_path('c:/Windows/Fonts')
LabelBase.register(DEFAULT_FONT, 'msgothic.ttc')


class CustomLayout(BoxLayout):
    pass


class MainScreen(BoxLayout):
    image_texture = ObjectProperty(None)
    image_src = StringProperty('')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        files = glob.glob('./data/*.gif')
        for file in files:

            gif = cv2.VideoCapture(file)
            is_success, img = gif.read()

            image = np.array(img)
            image = image[:, :, [2, 1, 0]]    # BGR <-> RGB
            image = Image.fromarray(image)

            texture = Texture.create(size=image.size) 
            texture.blit_buffer(image.tobytes())
            texture.flip_vertical()

            self.ids.sv.add_widget(Img(texture = texture, size_hint_x=None, width=80))
            btn = Button(text=os.path.basename(file), on_release=self.on_command,
                    size_hint_y=None, height=80, color=(0,0,0,1), background_color=(1,1,1,0.1))
            self.ids.sv.add_widget(btn)

    def on_slider(self):
        try:
            image = Image.fromarray(self.images[int(self.ids.slider1.value)-1])

            texture = Texture.create(size=image.size) 
            texture.blit_buffer(image.tobytes())
            texture.flip_vertical()

            self.image_texture = texture
        except:
            pass

    def on_command(self,btn):
        self.ids.label1.text = btn.text
        self.ids.button8.text = '▶'

        # 読み込み
        gif = cv2.VideoCapture('./data/' + btn.text)
        self.fps = gif.get(cv2.CAP_PROP_FPS)  # fpsは1秒あたりのコマ数

        # 編集
        self.images = []
        i = 0
        while True:
            is_success, img = gif.read()
            if not is_success:
                self.max_img = i
                break

            self.images.append(img)
            i += 1

        self.images = np.array(self.images)
        self.images = self.images[:, :, :, [2, 1, 0]]    # BGR <-> RGB

        self.ids.slider1.max = self.max_img

        image = Image.fromarray(self.images[0])

        texture = Texture.create(size=image.size) 
        texture.blit_buffer(image.tobytes())
        texture.flip_vertical()

        self.image_texture = texture

        self.ids.slider1.value = self.ids.slider1.min

        self.ids.carousel.load_next()

    def on_btn1(self):
        self.ids.carousel.load_previous()
        if self.ids.button8.text == '■':
            self.ids.button8.text = '▶'
            self.event.cancel()

    def on_btn2(self):
        self.ids.carousel.load_next()

    def on_btn3(self):
        self.ids.slider1.min = self.ids.slider1.value

    def on_btn4(self):
        self.ids.slider1.max = self.ids.slider1.value

    def on_btn5(self):
        self.ids.slider1.min = 1
        self.ids.slider1.max = self.max_img

    def on_btn6(self):
        self.ids.slider1.value = 1
        self.on_slider()

    def on_btn7(self):
        self.ids.slider1.value -= 1
        self.on_slider()

    def on_btn8(self):
        if self.ids.button8.text == '▶':
            self.ids.button8.text = '■'
            self.event = Clock.schedule_interval(self.update, 1 / self.fps)
        else:
            self.ids.button8.text = '▶'
            self.event.cancel()

    def on_btn9(self):
        self.ids.slider1.value += 1
        self.on_slider()

    def update(self, dt):
        if self.ids.slider1.value >= self.ids.slider1.max:
            self.ids.slider1.value = self.ids.slider1.min
        else:
            self.ids.slider1.value += 1
            self.on_slider()

class TestApp(App):
    def build(self):
        self.title = 'テスト'
        return MainScreen()

if __name__ == '__main__':
    TestApp().run()

kvファイル(test.kv)

<MainScreen>:
    CustomLayout:
        orientation: "vertical"

        BoxLayout:
            orientation: "vertical"

            BoxLayout:
                Button:
                    size_hint_x: 0.2
                    text: "<"
                    on_release: root.on_btn1()
                Label:
                    id: label1
                    size_hint_x: 0.6
                    text: "FILE NAME"
                    color: 0, 0, 0, 1
                Button:
                    size_hint_x: 0.2
                    text: ">"
                    on_release: root.on_btn2()

            Carousel:
                id: carousel
                size_hint_y: 10
                scroll_timeout: 0
                anim_move_duration: 0.1
                ScrollView:
                    GridLayout:
                        id: sv
                        cols: 2
                        size_hint_y: None
                        orientation: "vertical"
                        height: self.minimum_height
                BoxLayout:
                    orientation: "vertical"
                    Image:
                        size_hint_y: 10
                        texture: root.image_texture
                    Slider:
                        canvas.before:
                            Color:
                                rgba: 0, 0, 0, 1
                            Rectangle:
                                pos: self.pos
                                size: self.size
                        id:slider1
                        min:1
                        value_track: True
                        value_track_color: 1,0,0,1
                        on_touch_up: root.on_slider()
                        on_touch_move: root.on_slider()
                    BoxLayout:
                        Button:
                            background_color: 0,0,0,1
                            text: "|◀"
                            on_release: root.on_btn6()
                        Button:
                            background_color: 0,0,0,1
                            text: "◀|"
                            on_release: root.on_btn7()
                        Button:
                            id: button8
                            background_color: 0,0,0,1
                            font_size: 24
                            text: u"▶"
                            on_release: root.on_btn8()
                        Button:
                            background_color: 0,0,0,1
                            text: "|▶"
                            on_release: root.on_btn9()
                        Button:
                            background_color: 0,0,0,1
                            text: ""
                            #on_release: root.on_btn10()

            BoxLayout:
                Button:
                    text: "前カット"
                    on_release: root.on_btn3()
                Button:
                    text: "後カット"
                    on_release: root.on_btn4()
                Button:
                    text: "元に戻す"
                    on_release: root.on_btn5()

<CustomLayout>:
    canvas.before:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            pos: self.pos
            size: self.size

 

解説

self.event = Clock.schedule_interval(self.update, 1 / self.fps)

「1 / self.fps秒」毎に「def update(self, dt):」を実行します。

self.event.cancel()

繰り返しを中止します。(これがないとずっと実行し続けます)

def on_slider(self): の中身は、

GIFデータの「現在のスライダーの位置」番目の画像テクスチャを作ってImageウィジェットに表示します。それを「self.on_slider()」で呼び出すことでスライダーを動かしたとき以外も利用します。
例えば、1コマ進めたり(ボタン9)、1コマ戻したり(ボタン7)、初めに戻したり(ボタン6)します。

 

 

まとめ

簡単に解説しましたが何度もつまづいて行き詰っています。
苦労した点は、
・スライダーの背景色を黒色にする
・画像をリストの状態からテクスチャに移して表示する
・再生する(リロードでは画像を更新する方法がわからなかったです)
・正月休みで今までのことを結構忘れている(これが一番つらかった)
前の部分のカットと後ろ部分のカットも付けられました。あとは256色に減色してから保存すれば完成でもいいのですが、次回からトリミングに挑戦してみます。
保存のときにファイル名を決められて再生速度も変更できるようにして、真ん中の部分もカットできるようにしたいですが、別のこともやりたくなったのでトリミングで一旦完成にしたいと思います。

 

 

保存ファイル

lesson80.py

test.kv

 

 

文責:Luke