Androidアプリで、作成した画像をピクチャフォルダに保存したところ、処理自体はエラーなく完了したのに、ギャラリー(アルバムとかライブラリとか)で確認すると出てこない。
Androidを再起動するとギャラリーにも出てくるのだが、普通に使っていて撮った写真とかをすぐに確認できないのは困ってしまう。
調べたところ、メディアスキャンというのをしてあげないとギャラリーに反映されないらしい。
メディアスキャンの実装サンプルです。
import os
from plyer import storagepath
picturesPath = storagepath.get_pictures_dir()
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy import platform
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.uix.camera import Camera
import time
import cv2
import numpy as np
from kivy.properties import StringProperty
from kivy.uix.popup import Popup
from kivy.core.window import Window
currentActivity = None
CLS_Activity = None
CLS_File = None
CLS_Uri = None
CLS_Intent = None
CLS_Context = None
class ImageFileSave(BoxLayout):
def play(self, button):
self.cam = Camera(index=0)
self.cam.play = True
Clock.schedule_interval(self.update, 1.0 / 30)
button.disabled = True
self.ids.capture_button.disabled = False
def update(self, dt):
height, width = self.cam.texture.height, self.cam.texture.width
capture = np.frombuffer(self.cam.texture.pixels, np.uint8)
capture = capture.reshape(height, width, 4)
frame = capture[:,:,:3]
if type(frame).__module__ == "numpy":
ret = True
if platform == "android":
frame = cv2.flip(frame, 0)
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
self.image_data = frame
if ret:
buf = cv2.flip(frame, 0)
image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='rgb')
image_texture.blit_buffer(buf.tostring(), colorfmt='rgb', bufferfmt='ubyte')
self.ids.camera_view.texture = image_texture
def capture(self):
if self.image_data is not None:
timestr = time.strftime("%Y%m%d_%H%M%S")
file_path = os.path.join(picturesPath , "output_{}.png".format(timestr))
image_data = cv2.cvtColor(self.image_data, cv2.COLOR_RGB2BGR) # openCVの色の並びはBGR
cv2.imwrite(file_path, image_data)
# メディアスキャン
self.android_rescan_MediaStore(file_path)
# 完了メッセージ
self.popup = MessagePopup(title='', message='seved' , separator_height=0)
self.popup.open()
def android_rescan_MediaStore(self, file_path):
if platform == "android":
uri = CLS_Uri.fromFile(CLS_File(file_path))
mediaScanIntent = CLS_Intent(CLS_Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)
CLS_Context.sendBroadcast(mediaScanIntent)
class MessagePopup(Popup):
message = StringProperty('')
ok_text = StringProperty('OK')
__events__ = ('on_ok',)
def ok(self):
self.dispatch('on_ok')
self.dismiss()
def on_ok(self):
pass
def __init__(self, **kwargs) -> None:
super(MessagePopup, self).__init__(**kwargs)
self.size_hint = (0.8, 0.3)
self.pos_hint={'center_x':0.5, 'center_y':0.5}
self.auto_dismiss = False
class TestImageFileSaveApp(App):
def build(self):
if platform == "android":
Window.bind(on_keyboard=self.key_input)
from jnius import autoclass, cast
global currentActivity
global CLS_Activity
global CLS_File
global CLS_Uri
global CLS_Intent
global CLS_Context
PythonActivity = autoclass('org.kivy.android.PythonActivity')
currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
CLS_Activity = autoclass('android.app.Activity')
CLS_File = autoclass('java.io.File')
CLS_Uri = autoclass('android.net.Uri')
CLS_Intent = autoclass('android.content.Intent')
CLS_Context = cast('android.content.Context', currentActivity.getApplicationContext())
from android.permissions import request_permissions, Permission
request_permissions([Permission.READ_EXTERNAL_STORAGE,
Permission.CAMERA,
Permission.WRITE_EXTERNAL_STORAGE])
return ImageFileSave()
def key_input(self, window, key, scancode, codepoint, modifier):
if key == 27:
return True
else:
return False
if __name__ == '__main__':
TestImageFileSaveApp().run()
確認用のKVファイル
<ImageFileSave>:
camera_view:camera_view
BoxLayout:
orientation: 'vertical'
Image:
id: camera_view
Button:
text: 'play'
size_hint_y: 0.1
on_press: root.play(self)
Button:
id:capture_button
text: 'capture'
size_hint_y: 0.1
disabled: True
on_press: root.capture()
<MessagePopup>:
FloatLayout:
Label:
size_hint: 0.8, 0.6
pos_hint: {'x': 0.1, 'y':0.4}
text: root.message
text_size: self.size
halign: 'center'
valign: 'middle'
Button:
size_hint: 0.4, 0.35
pos_hint: {'x':0.5, 'y':0.05}
text: root.ok_text
on_release: root.ok()
サンプルソースがやたらと長くなってしまいましたが、メインとなる関数は「android_rescan_MediaStore」だけです。
使い方もシンプルで、画像を保存する処理(サンプルではimwrite)の後にファイルのパスを渡してあげるだけです。
Android API にアクセスするため Pyjnius を使っています。サンプルでは build内で分岐させていますが頭のグローバル変数宣言時に書いてもいいと思います。
from jnius import autoclass, cast
PythonActivity = autoclass('org.kivy.android.PythonActivity')
currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
Activity = autoclass('android.app.Activity')
File = autoclass('java.io.File')
Uri = autoclass('android.net.Uri')
Intent = autoclass('android.content.Intent')
Context = cast('android.content.Context', currentActivity.getApplicationContext())
※サンプルで変数名についてる”CLS_”は、ラップしたJavaクラスだと分かりやすくするために適当につけただけで深い意味はないです。
他のサンプル部分については今回の主旨とは外れますが少し補足説明します。
動作確認のために、カメラからの画像取得やメッセージ通知のためのクラスを使っています。これらについての使い方は別の記事で書いていますので気になる方はご覧ください。(宣伝)
保存先のピクチャパスの取得については、plyerを使っています。実際には、”/storage/emulated/0/Pictures”のようなパスになっていると思います。
以上です。
(参考)
https://developer.android.com/training/camera/photobasics?hl=ja#TaskGallery
コメント