From 136867f8047f8bde0e0673e9ac72b701b8389e83 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 14 Jul 2022 13:13:03 +0800 Subject: [PATCH] bugfix --- kivycv/audio.py | 28 ++++++-- kivycv/xcamera/__init__.py | 15 +++++ kivycv/xcamera/android_api.py | 85 +++++++++++++++++++++++ kivycv/xcamera/data/icons.ttf | Bin 0 -> 4988 bytes kivycv/xcamera/data/shutter.wav | Bin 0 -> 43380 bytes kivycv/xcamera/main.py | 43 ++++++++++++ kivycv/xcamera/platform_api.py | 37 ++++++++++ kivycv/xcamera/version.py | 1 + kivycv/xcamera/xcamera.py | 116 ++++++++++++++++++++++++++++++++ 9 files changed, 319 insertions(+), 6 deletions(-) create mode 100755 kivycv/xcamera/__init__.py create mode 100755 kivycv/xcamera/android_api.py create mode 100755 kivycv/xcamera/data/icons.ttf create mode 100755 kivycv/xcamera/data/shutter.wav create mode 100755 kivycv/xcamera/main.py create mode 100755 kivycv/xcamera/platform_api.py create mode 100755 kivycv/xcamera/version.py create mode 100755 kivycv/xcamera/xcamera.py diff --git a/kivycv/audio.py b/kivycv/audio.py index d2b375e..c482488 100644 --- a/kivycv/audio.py +++ b/kivycv/audio.py @@ -50,6 +50,14 @@ class Audio(AppLogger, pyaudio.PyAudio): print(x) return dev_cnt - 1 + def write_audiofile(self, fn, audio_data, channels=2, rate=44100): + wf = wave.open(fn, 'wb') + wf.setnchannels(channels) + wf.setsampwidth(2) + wf.setframerate(rate) + wf.writeframesraw(audio_data) + wf.close() + def start_record(self, savefile=None, channels=2, @@ -73,6 +81,7 @@ class Audio(AppLogger, pyaudio.PyAudio): def stop_record(self): if self.recording: + self.recording = False self.stream.stop_stream() self.stream.close() self.wavfile.close() @@ -87,17 +96,28 @@ class Audio(AppLogger, pyaudio.PyAudio): time.sleep(0.1) self.stop_record() + def get_audio_spec(self, audiofile): + wavfile = wave.open(audiofile, 'rb') + sampwidth = wavfile.getsampwidth() + format = self.get_format_from_width(sampwidth) + framerate=wavfile.getframerate() + channels = wavfile.getnchannels() + return { + "format":format, + "sampwidth":sampwidth, + "framerate":framerate, + "channels":channels + } + def replay(self, play_file=None): idx = self.get_output_index() x = self.get_device_info_by_index(idx) y = self.get_default_input_device_info() - self.info('default_input=%s, default_output=%s', y, x) if play_file is None: play_file = self.temp_filename self.wavfile = wave.open(play_file, 'rb') format = self.get_format_from_width(self.wavfile.getsampwidth()) framerate=self.wavfile.getframerate() - self.info('format=%s, framerate=%s', format, framerate) self.stream = self.open(format=format, channels=self.wavfile.getnchannels(), rate=framerate, @@ -112,10 +132,6 @@ class Audio(AppLogger, pyaudio.PyAudio): self.stream.close() self.wavfile.close() - def __del__(self): - if self.temp_filename: - os.remove(self.temp_filename) - if __name__ == '__main__': import sys t_begin = time.time() diff --git a/kivycv/xcamera/__init__.py b/kivycv/xcamera/__init__.py new file mode 100755 index 0000000..019bbb0 --- /dev/null +++ b/kivycv/xcamera/__init__.py @@ -0,0 +1,15 @@ +""" +Exposes `XCamera` directly in `xcamera` rather than `xcamera.xcamera`. +Also note this may break `pip` since all imports within `xcamera.py` would be +required at setup time. This is because `version.py` (same directory) is used +by the `setup.py` file. +Hence we're not exposing `XCamera` if `pip` is detected. +""" +import os + +project_dir = os.path.abspath( + os.path.join(__file__, os.pardir, os.pardir, os.pardir, os.pardir)) +using_pip = os.path.basename(project_dir).startswith('pip-') +# only exposes `XCamera` if not within `pip` ongoing install +if not using_pip: + from .xcamera import XCamera # noqa diff --git a/kivycv/xcamera/android_api.py b/kivycv/xcamera/android_api.py new file mode 100755 index 0000000..30a2aef --- /dev/null +++ b/kivycv/xcamera/android_api.py @@ -0,0 +1,85 @@ +from kivy.logger import Logger + +from jnius import JavaException, PythonJavaClass, autoclass, java_method + +Camera = autoclass('android.hardware.Camera') +AndroidActivityInfo = autoclass('android.content.pm.ActivityInfo') +AndroidPythonActivity = autoclass('org.kivy.android.PythonActivity') +PORTRAIT = AndroidActivityInfo.SCREEN_ORIENTATION_PORTRAIT +LANDSCAPE = AndroidActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + + +class ShutterCallback(PythonJavaClass): + __javainterfaces__ = ('android.hardware.Camera$ShutterCallback', ) + + @java_method('()V') + def onShutter(self): + # apparently, it is enough to have an empty shutter callback to play + # the standard shutter sound. If you pass None instead of shutter_cb + # below, the standard sound doesn't play O_o + pass + + +class PictureCallback(PythonJavaClass): + __javainterfaces__ = ('android.hardware.Camera$PictureCallback', ) + + def __init__(self, filename, on_success): + super(PictureCallback, self).__init__() + self.filename = filename + self.on_success = on_success + + @java_method('([BLandroid/hardware/Camera;)V') + def onPictureTaken(self, data, camera): + s = data.tostring() + with open(self.filename, 'wb') as f: + f.write(s) + Logger.info('xcamera: picture saved to %s', self.filename) + camera.startPreview() + self.on_success(self.filename) + + +class AutoFocusCallback(PythonJavaClass): + __javainterfaces__ = ('android.hardware.Camera$AutoFocusCallback', ) + + def __init__(self, filename, on_success): + super(AutoFocusCallback, self).__init__() + self.filename = filename + self.on_success = on_success + + @java_method('(ZLandroid/hardware/Camera;)V') + def onAutoFocus(self, success, camera): + if success: + Logger.info('xcamera: autofocus succeeded, taking picture...') + shutter_cb = ShutterCallback() + picture_cb = PictureCallback(self.filename, self.on_success) + camera.takePicture(shutter_cb, None, picture_cb) + else: + Logger.info('xcamera: autofocus failed') + + +def take_picture(camera_widget, filename, on_success): + # to call the android API, we need access to the underlying + # android.hardware.Camera instance. However, there is no official way to + # retrieve it from the camera widget, so we need to dig into internal + # attributes :-( This works at least on kivy 1.9.1, but it might break any + # time soon. + camera = camera_widget._camera._android_camera + params = camera.getParameters() + params.setFocusMode("auto") + camera.setParameters(params) + cb = AutoFocusCallback(filename, on_success) + Logger.info('xcamera: starting autofocus...') + try: + camera.autoFocus(cb) + except JavaException as e: + Logger.info('Error when calling autofocus: {}'.format(e)) + + +def set_orientation(value): + previous = get_orientation() + AndroidPythonActivity.mActivity.setRequestedOrientation(value) + return previous + + +def get_orientation(): + return AndroidPythonActivity.mActivity.getRequestedOrientation() diff --git a/kivycv/xcamera/data/icons.ttf b/kivycv/xcamera/data/icons.ttf new file mode 100755 index 0000000000000000000000000000000000000000..1eae4aa175482b9a0e99b600ab4ddf3f6ba9c17c GIT binary patch literal 4988 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 literal 0 HcmV?d00001 diff --git a/kivycv/xcamera/data/shutter.wav b/kivycv/xcamera/data/shutter.wav new file mode 100755 index 0000000000000000000000000000000000000000..66d4380efbba85021fc2a19d904e934f5955cbb6 GIT binary patch literal 43380 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
rD* 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 BKeqq? literal 0 HcmV?d00001 diff --git a/kivycv/xcamera/main.py b/kivycv/xcamera/main.py new file mode 100755 index 0000000..35c094c --- /dev/null +++ b/kivycv/xcamera/main.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +from kivy.app import App +from kivy.lang import Builder + +kv = """ +#:import XCamera kivy_garden.xcamera.XCamera + +FloatLayout: + orientation: 'vertical' + + XCamera: + id: xcamera + on_picture_taken: app.picture_taken(*args) + + BoxLayout: + orientation: 'horizontal' + size_hint: 1, None + height: sp(50) + + Button: + text: 'Set landscape' + on_release: xcamera.force_landscape() + + Button: + text: 'Restore orientation' + on_release: xcamera.restore_orientation() +""" + + +class CameraApp(App): + def build(self): + return Builder.load_string(kv) + + def picture_taken(self, obj, filename): + print('Picture taken and saved to {}'.format(filename)) + + +def main(): + CameraApp().run() + + +if __name__ == '__main__': + main() diff --git a/kivycv/xcamera/platform_api.py b/kivycv/xcamera/platform_api.py new file mode 100755 index 0000000..fa79f8f --- /dev/null +++ b/kivycv/xcamera/platform_api.py @@ -0,0 +1,37 @@ +from kivy.utils import platform + + +def play_shutter(): + # bah, apparently we need to delay the import of kivy.core.audio, lese + # kivy cannot find a camera provider, at lease on linux. Maybe a + # gstreamer/pygame issue? + from kivy.core.audio import SoundLoader + sound = SoundLoader.load("data/shutter.wav") + sound.play() + + +if platform == 'android': + from .android_api import ( + LANDSCAPE, PORTRAIT, take_picture, set_orientation, get_orientation) + +else: + + # generic fallback for taking pictures. Probably not the best quality, + # they are meant mostly for testing + LANDSCAPE = 'landscape' + PORTRAIT = 'portrait' + + def take_picture(camera_widget, filename, on_success): + camera_widget.texture.save(filename, flipped=False) + play_shutter() + on_success(filename) + + def set_orientation(value): + previous = get_orientation() + print('FAKE orientation set to {}'.format(value)) + get_orientation.value = value + return previous + + def get_orientation(): + return get_orientation.value + get_orientation.value = PORTRAIT diff --git a/kivycv/xcamera/version.py b/kivycv/xcamera/version.py new file mode 100755 index 0000000..809d46f --- /dev/null +++ b/kivycv/xcamera/version.py @@ -0,0 +1 @@ +__version__ = '2020.0613' diff --git a/kivycv/xcamera/xcamera.py b/kivycv/xcamera/xcamera.py new file mode 100755 index 0000000..e350fc0 --- /dev/null +++ b/kivycv/xcamera/xcamera.py @@ -0,0 +1,116 @@ +import datetime +import os + +from kivy.clock import mainthread +from kivy.lang import Builder +from kivy.properties import ObjectProperty +from kivy.resources import resource_add_path +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.camera import Camera +from kivy.uix.label import Label +from kivy.utils import platform + +from .platform_api import LANDSCAPE, set_orientation, take_picture + +ROOT = os.path.dirname(os.path.abspath(__file__)) +resource_add_path(ROOT) + + +def darker(color, factor=0.5): + r, g, b, a = color + r *= factor + g *= factor + b *= factor + return r, g, b, a + + +def get_filename(): + return datetime.datetime.now().strftime('%Y-%m-%d %H.%M.%S.jpg') + + +def is_android(): + return platform == 'android' + + +def check_camera_permission(): + """ + Android runtime `CAMERA` permission check. + """ + if not is_android(): + return True + from android.permissions import Permission, check_permission + permission = Permission.CAMERA + return check_permission(permission) + + +def check_request_camera_permission(callback=None): + """ + Android runtime `CAMERA` permission check & request. + """ + had_permission = check_camera_permission() + if not had_permission: + from android.permissions import Permission, request_permissions + permissions = [Permission.CAMERA] + request_permissions(permissions, callback) + return had_permission + + +class XCameraIconButton(ButtonBehavior, Label): + pass + + +class XCamera(Camera): + directory = ObjectProperty(None) + _previous_orientation = None + __events__ = ('on_picture_taken', 'on_camera_ready') + + def __init__(self, **kwargs): + # Builder.load_file(os.path.join(ROOT, "xcamera.kv")) + super().__init__(**kwargs) + + def _on_index(self, *largs): + """ + Overrides `kivy.uix.camera.Camera._on_index()` to make sure + `camera.open()` is not called unless Android `CAMERA` permission is + granted, refs #5. + """ + @mainthread + def on_permissions_callback(permissions, grant_results): + """ + On camera permission callback calls parent `_on_index()` method. + """ + if all(grant_results): + self._on_index_dispatch(*largs) + if check_request_camera_permission(callback=on_permissions_callback): + self._on_index_dispatch(*largs) + + def _on_index_dispatch(self, *largs): + super()._on_index(*largs) + self.dispatch('on_camera_ready') + + def on_picture_taken(self, filename): + """ + This event is fired every time a picture has been taken. + """ + pass + + def on_camera_ready(self): + """ + Fired when the camera is ready. + """ + pass + + def shoot(self): + def on_success(filename): + self.dispatch('on_picture_taken', filename) + filename = get_filename() + if self.directory: + filename = os.path.join(self.directory, filename) + take_picture(self, filename, on_success) + + def force_landscape(self): + self._previous_orientation = set_orientation(LANDSCAPE) + + def restore_orientation(self): + if self._previous_orientation is not None: + set_orientation(self._previous_orientation)