bugfix
This commit is contained in:
parent
a922670413
commit
136867f804
@ -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()
|
||||
|
15
kivycv/xcamera/__init__.py
Executable file
15
kivycv/xcamera/__init__.py
Executable file
@ -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
|
85
kivycv/xcamera/android_api.py
Executable file
85
kivycv/xcamera/android_api.py
Executable file
@ -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()
|
BIN
kivycv/xcamera/data/icons.ttf
Executable file
BIN
kivycv/xcamera/data/icons.ttf
Executable file
Binary file not shown.
BIN
kivycv/xcamera/data/shutter.wav
Executable file
BIN
kivycv/xcamera/data/shutter.wav
Executable file
Binary file not shown.
43
kivycv/xcamera/main.py
Executable file
43
kivycv/xcamera/main.py
Executable file
@ -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()
|
37
kivycv/xcamera/platform_api.py
Executable file
37
kivycv/xcamera/platform_api.py
Executable file
@ -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
|
1
kivycv/xcamera/version.py
Executable file
1
kivycv/xcamera/version.py
Executable file
@ -0,0 +1 @@
|
||||
__version__ = '2020.0613'
|
116
kivycv/xcamera/xcamera.py
Executable file
116
kivycv/xcamera/xcamera.py
Executable file
@ -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)
|
Loading…
Reference in New Issue
Block a user