Python GIFファイルを再生する(Kivy 画像更新)
はじめに
今回は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