コード例 #1
0
async def _check_ws() -> None:
    # schedule the next call
    io_loop = IOLoop.instance()
    io_loop.add_timeout(datetime.timedelta(seconds=10), _start_check_ws)

    if not motionctl.running():
        return

    now = datetime.datetime.now()
    for camera_id in config.get_camera_ids():
        camera_config = config.get_camera(camera_id)
        if not utils.is_local_motion_camera(camera_config):
            continue

        working_schedule = camera_config.get('@working_schedule')
        motion_detection = camera_config.get('@motion_detection')
        working_schedule_type = camera_config.get(
            '@working_schedule_type') or 'outside'

        if not working_schedule:  # working schedule disabled, motion detection left untouched
            continue

        if not motion_detection:  # motion detection explicitly disabled
            continue

        now_during = _during_working_schedule(now, working_schedule)
        must_be_enabled = ((now_during and working_schedule_type == 'during')
                           or (not now_during
                               and working_schedule_type == 'outside'))

        motion_detection_resp = await motionctl.get_motion_detection(camera_id)
        await _switch_motion_detection_status(camera_id, must_be_enabled,
                                              working_schedule_type,
                                              motion_detection_resp)
コード例 #2
0
    async def get(self, camera_id, op, filename=None, group=None):
        if camera_id is not None:
            camera_id = int(camera_id)
            if camera_id not in config.get_camera_ids():
                raise HTTPError(404, 'no such camera')

        if op == 'current':
            await self.current(camera_id)

        elif op == 'list':
            await self.list(camera_id)

        elif op == 'frame':
            await self.frame(camera_id)

        elif op == 'download':
            await self.download(camera_id, filename)

        elif op == 'preview':
            await self.preview(camera_id, filename)

        elif op == 'zipped':
            await self.zipped(camera_id, group)

        elif op == 'timelapse':
            await self.timelapse(camera_id, group)

        else:
            raise HTTPError(400, 'unknown operation')
コード例 #3
0
async def _disable_initial_motion_detection():
    from motioneye import config

    for camera_id in config.get_camera_ids():
        camera_config = config.get_camera(camera_id)
        if not utils.is_local_motion_camera(camera_config):
            continue

        if not camera_config['@motion_detection']:
            logging.debug(
                'motion detection disabled by config for camera with id %s' %
                camera_id)
            await set_motion_detection(camera_id, False)
コード例 #4
0
    async def get(self, camera_id, op, filename=None):
        if camera_id is not None:
            camera_id = int(camera_id)
            if camera_id not in config.get_camera_ids():
                raise HTTPError(404, 'no such camera')

        if op == 'list':
            await self.list(camera_id)
            return

        elif op == 'preview':
            await self.preview(camera_id, filename)
            return

        else:
            raise HTTPError(400, 'unknown operation')
コード例 #5
0
def make_media_folders():
    from motioneye import config

    config.get_main()  # just to have main config already loaded

    camera_ids = config.get_camera_ids()
    for camera_id in camera_ids:
        camera_config = config.get_camera(camera_id)
        if 'target_dir' in camera_config:
            if not os.path.exists(camera_config['target_dir']):
                try:
                    os.makedirs(camera_config['target_dir'])

                except Exception as e:
                    logging.error(
                        'failed to create root media folder "%s" for camera with id %s: %s'
                        % (camera_config['target_dir'], camera_id, e))
コード例 #6
0
    async def post(self, camera_id, op, filename=None, group=None):
        if group == '/':  # ungrouped
            group = ''

        if camera_id is not None:
            camera_id = int(camera_id)
            if camera_id not in config.get_camera_ids():
                raise HTTPError(404, 'no such camera')

        if op == 'delete':
            await self.delete(camera_id, filename)

        elif op == 'delete_all':
            await self.delete_all(camera_id, group)

        else:
            raise HTTPError(400, 'unknown operation')
コード例 #7
0
    async def post(self, camera_id, action):
        camera_id = int(camera_id)
        if camera_id not in config.get_camera_ids():
            raise HTTPError(404, 'no such camera')

        local_config = config.get_camera(camera_id)
        if utils.is_remote_camera(local_config):
            resp = await remote.exec_action(local_config, action)
            if resp.error:
                msg = 'Failed to execute action on remote camera at %(url)s: %(msg)s.' % {
                    'url': remote.pretty_camera_url(local_config),
                    'msg': resp.error
                }

                return self.finish_json({'error': msg})

            return self.finish_json()

        if action == 'snapshot':
            logging.debug('executing snapshot action for camera with id %s' %
                          camera_id)
            await self.snapshot(camera_id)
            return

        elif action == 'record_start':
            logging.debug(
                'executing record_start action for camera with id %s' %
                camera_id)
            return self.record_start(camera_id)

        elif action == 'record_stop':
            logging.debug(
                'executing record_stop action for camera with id %s' %
                camera_id)
            return self.record_stop(camera_id)

        action_commands = config.get_action_commands(local_config)
        command = action_commands.get(action)
        if not command:
            raise HTTPError(400, 'unknown action')

        logging.debug('executing %s action for camera with id %s: "%s"' %
                      (action, camera_id, command))
        self.run_command_bg(command)
コード例 #8
0
def cleanup_media(media_type: str) -> None:
    logging.debug('cleaning up %(media_type)ss...' % {'media_type': media_type})

    if media_type == 'picture':
        exts = _PICTURE_EXTS

    else:  # media_type == 'movie'
        exts = _MOVIE_EXTS + ['.thumb']

    for camera_id in config.get_camera_ids():
        camera_config = config.get_camera(camera_id)
        if not utils.is_local_motion_camera(camera_config):
            continue

        preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
        if preserve_media == 0:
            continue  # preserve forever

        still_images_enabled = bool(camera_config['picture_filename']) or bool(camera_config['snapshot_filename'])
        movies_enabled = bool(camera_config['movie_output'])

        if media_type == 'picture' and not still_images_enabled:
            continue  # only cleanup pictures for cameras with still images enabled

        elif media_type == 'movie' and not movies_enabled:
            continue  # only cleanup movies for cameras with movies enabled

        preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_media)

        target_dir = camera_config.get('target_dir')
        cloud_enabled = camera_config.get('@upload_enabled')
        clean_cloud_enabled = camera_config.get('@clean_cloud_enabled')
        cloud_dir = camera_config.get('@upload_location')
        service_name = camera_config.get('@upload_service')
        clean_cloud_info = None
        if cloud_enabled and clean_cloud_enabled and camera_id and service_name and cloud_dir:
            clean_cloud_info = {'camera_id': camera_id, 'service_name': service_name, 'cloud_dir': cloud_dir}
        if os.path.exists(target_dir):
            # create a sentinel file to make sure the target dir is never removed
            open(os.path.join(target_dir, '.keep'), 'w').close()

        logging.debug('calling _remove_older_files: %s %s %s' % (cloud_enabled, clean_cloud_enabled, clean_cloud_info))
        _remove_older_files(target_dir, preserve_moment, clean_cloud_info, exts=exts)
コード例 #9
0
    async def get_config(self, camera_id):
        if camera_id:
            logging.debug('getting config for camera %(id)s' %
                          {'id': camera_id})

            if camera_id not in config.get_camera_ids():
                raise HTTPError(404, 'no such camera')

            local_config = config.get_camera(camera_id)
            if utils.is_local_motion_camera(local_config):
                ui_config = config.motion_camera_dict_to_ui(local_config)

                return self.finish_json(ui_config)

            elif utils.is_remote_camera(local_config):
                resp = await remote.get_config(local_config)
                if resp.error:
                    msg = 'Failed to get remote camera configuration for %(url)s: %(msg)s.' % {
                        'url': remote.pretty_camera_url(local_config),
                        'msg': resp.error
                    }
                    return self.finish_json_with_error(msg)

                for key, value in list(local_config.items()):
                    resp.remote_ui_config[key.replace('@', '')] = value

                # replace the real device url with the remote camera path
                resp.remote_ui_config['device_url'] = remote.pretty_camera_url(
                    local_config)
                return self.finish_json(resp.remote_ui_config)

            else:  # assuming simple mjpeg camera
                ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)

                return self.finish_json(ui_config)

        else:
            logging.debug('getting main config')

            ui_config = config.main_dict_to_ui(config.get_main())
            return self.finish_json(ui_config)
コード例 #10
0
    async def get(self, camera_id, filename=None, include_body=True):
        logging.debug('downloading movie %(filename)s of camera %(id)s' % {
            'filename': filename,
            'id': camera_id
        })

        self.pretty_filename = os.path.basename(filename)

        if camera_id is not None:
            camera_id = int(camera_id)
            if camera_id not in config.get_camera_ids():
                raise HTTPError(404, 'no such camera')

        camera_config = config.get_camera(camera_id)

        if utils.is_local_motion_camera(camera_config):
            filename = mediafiles.get_media_path(camera_config, filename,
                                                 'movie')
            self.pretty_filename = camera_config[
                'camera_name'] + '_' + self.pretty_filename
            await StaticFileHandler.get(self,
                                        filename,
                                        include_body=include_body)
            return

        elif utils.is_remote_camera(camera_config):
            # we will cache the movie since it takes a while to fetch from the remote camera
            # and we may be going to play it back in the browser, which will fetch the video in chunks
            tmpfile = self.tmpdir + '/' + self.pretty_filename
            if os.path.isfile(tmpfile):
                # have a cached copy, update the timestamp so it's not flushed
                import time
                mtime = os.stat(tmpfile).st_mtime
                os.utime(tmpfile, (time.time(), mtime))
                await StaticFileHandler.get(self,
                                            tmpfile,
                                            include_body=include_body)
                return

            resp = await remote.get_media_content(camera_config,
                                                  filename,
                                                  media_type='movie')
            if resp.error:
                return self.finish_json({
                    'error':
                    'Failed to download movie from %(url)s: %(msg)s.' % {
                        'url': remote.pretty_camera_url(camera_config),
                        'msg': resp.error
                    }
                })

            # check if the file has been created by another request while we were fetching the movie
            if not os.path.isfile(tmpfile):
                tmp = open(tmpfile, 'wb')
                tmp.write(resp.result)
                tmp.close()

            await StaticFileHandler.get(self,
                                        tmpfile,
                                        include_body=include_body)
            return

        else:  # assuming simple mjpeg camera
            raise HTTPError(400, 'unknown operation')
コード例 #11
0
    async def list(self):
        logging.debug('listing cameras')

        proto = self.get_argument('proto')
        if proto == 'motioneye':  # remote listing
            return self._handle_list_cameras_response(
                await remote.list_cameras(self.get_all_arguments()))

        elif proto == 'netcam':
            scheme = self.get_argument('scheme', 'http')

            if scheme in ['http', 'https', 'mjpeg']:
                resp = await test_mjpeg_url(self.get_all_arguments(),
                                            auth_modes=['basic'],
                                            allow_jpeg=True)
                return self._handle_list_cameras_response(resp)

            elif scheme == 'rtsp':
                resp = await test_rtsp_url(self.get_all_arguments())
                return self._handle_list_cameras_response(resp)

            elif scheme == 'rtmp':
                resp = test_rtmp_url(self.get_all_arguments())
                return self._handle_list_cameras_response(resp)

            else:
                return self.finish_json_with_error(
                    f'protocol {scheme} not supported')

        elif proto == 'mjpeg':
            resp = await test_mjpeg_url(self.get_all_arguments(),
                                        auth_modes=['basic', 'digest'],
                                        allow_jpeg=False)
            return self._handle_list_cameras_response(resp)

        elif proto == 'v4l2':
            configured_devices = set()
            for camera_id in config.get_camera_ids():
                data = config.get_camera(camera_id)
                if utils.is_v4l2_camera(data):
                    configured_devices.add(data['videodevice'])

            cameras = [{
                'id': d[1],
                'name': d[2]
            } for d in v4l2ctl.list_devices()
                       if (d[0] not in configured_devices) and (
                           d[1] not in configured_devices)]

            return self.finish_json({'cameras': cameras})

        elif proto == 'mmal':
            configured_devices = set()
            for camera_id in config.get_camera_ids():
                data = config.get_camera(camera_id)
                if utils.is_mmal_camera(data):
                    configured_devices.add(data['mmalcam_name'])

            cameras = [{
                'id': d[0],
                'name': d[1]
            } for d in mmalctl.list_devices()
                       if (d[0] not in configured_devices)]

            return self.finish_json({'cameras': cameras})

        else:  # assuming local motionEye camera listing
            cameras = []
            camera_ids = config.get_camera_ids()
            if not config.get_main().get('@enabled'):
                camera_ids = []

            length = [len(camera_ids)]

            for camera_id in camera_ids:
                local_config = config.get_camera(camera_id)
                if local_config is None:
                    continue

                if utils.is_local_motion_camera(local_config):
                    ui_config = config.motion_camera_dict_to_ui(local_config)
                    cameras.append(ui_config)
                    finished = self.check_finished(cameras, length)
                    if finished:
                        return

                elif utils.is_remote_camera(local_config):
                    if local_config.get('@enabled') or self.get_argument(
                            'force', None) == 'true':
                        resp = await remote.get_config(local_config)
                        return self._handle_get_config_response(
                            camera_id, local_config, resp, cameras, length)

                    else:  # don't try to reach the remote of the camera is disabled
                        return self._handle_get_config_response(
                            camera_id, local_config,
                            utils.GetConfigResponse(None, error=True), cameras,
                            length)

                else:  # assuming simple mjpeg camera
                    ui_config = config.simple_mjpeg_camera_dict_to_ui(
                        local_config)
                    cameras.append(ui_config)
                    return self.check_finished(cameras, length)

            if length[0] == 0:
                return self.finish_json({'cameras': []})
コード例 #12
0
        def set_main_config(ui_config):
            logging.debug('setting main config...')

            old_main_config = config.get_main()
            old_admin_username = old_main_config.get('@admin_username')
            old_normal_username = old_main_config.get('@normal_username')

            main_config = config.main_ui_to_dict(ui_config)
            main_config.setdefault('camera', old_main_config.get('camera', []))

            admin_username = main_config.get('@admin_username')
            admin_password = main_config.get('@admin_password')

            normal_username = main_config.get('@normal_username')
            normal_password = main_config.get('@normal_password')

            additional_configs = config.get_additional_structure(
                camera=False)[1]
            reboot_config_names = [('@_' + c['name'])
                                   for c in list(additional_configs.values())
                                   if c.get('reboot')]
            reboot = bool([
                k for k in reboot_config_names
                if old_main_config.get(k) != main_config.get(k)
            ])

            config.set_main(main_config)

            reload = False
            restart = False

            if admin_username != old_admin_username or admin_password is not None:
                logging.debug('admin credentials changed, reload needed')

                reload = True

            if normal_username != old_normal_username or normal_password is not None:
                logging.debug(
                    'surveillance credentials changed, all camera configs must be updated'
                )

                # reconfigure all local cameras to update the stream authentication options
                for camera_id in config.get_camera_ids():
                    local_config = config.get_camera(camera_id)
                    if not utils.is_local_motion_camera(local_config):
                        continue

                    ui_config = config.motion_camera_dict_to_ui(local_config)
                    local_config = config.motion_camera_ui_to_dict(
                        ui_config, local_config)

                    config.set_camera(camera_id, local_config)

                    restart = True

            if reboot and settings.ENABLE_REBOOT:
                logging.debug('system settings changed, reboot needed')

            else:
                reboot = False

            return {'reload': reload, 'reboot': reboot, 'restart': restart}
コード例 #13
0
    async def set_config(self, camera_id):
        try:
            ui_config = json.loads(self.request.body)

        except Exception as e:
            logging.error('could not decode json: %(msg)s' % {'msg': str(e)})

            raise

        camera_ids = config.get_camera_ids()

        async def set_camera_config(camera_id, ui_config, on_finish):
            logging.debug('setting config for camera %(id)s...' %
                          {'id': camera_id})

            if camera_id not in camera_ids:
                raise HTTPError(404, 'no such camera')

            local_config = config.get_camera(camera_id)
            if utils.is_local_motion_camera(local_config):
                local_config = config.motion_camera_ui_to_dict(
                    ui_config, local_config)

                config.set_camera(camera_id, local_config)

                on_finish(None, True)  # (no error, motion needs restart)

            elif utils.is_remote_camera(local_config):
                # update the camera locally
                local_config['@enabled'] = ui_config['enabled']
                config.set_camera(camera_id, local_config)

                if 'name' in ui_config:

                    def on_finish_wrapper(e=None):
                        return on_finish(e, False)

                    ui_config[
                        'enabled'] = True  # never disable the camera remotely
                    result = await remote.set_config(local_config, ui_config)
                    return on_finish(result, False)

                else:
                    # when the ui config supplied has only the enabled state
                    # and no useful fields (such as "name"),
                    # the camera was probably disabled due to errors
                    on_finish(None, False)

            else:  # assuming simple mjpeg camera
                local_config = config.simple_mjpeg_camera_ui_to_dict(
                    ui_config, local_config)

                config.set_camera(camera_id, local_config)

                on_finish(None,
                          False)  # (no error, motion doesn't need restart)

        def set_main_config(ui_config):
            logging.debug('setting main config...')

            old_main_config = config.get_main()
            old_admin_username = old_main_config.get('@admin_username')
            old_normal_username = old_main_config.get('@normal_username')

            main_config = config.main_ui_to_dict(ui_config)
            main_config.setdefault('camera', old_main_config.get('camera', []))

            admin_username = main_config.get('@admin_username')
            admin_password = main_config.get('@admin_password')

            normal_username = main_config.get('@normal_username')
            normal_password = main_config.get('@normal_password')

            additional_configs = config.get_additional_structure(
                camera=False)[1]
            reboot_config_names = [('@_' + c['name'])
                                   for c in list(additional_configs.values())
                                   if c.get('reboot')]
            reboot = bool([
                k for k in reboot_config_names
                if old_main_config.get(k) != main_config.get(k)
            ])

            config.set_main(main_config)

            reload = False
            restart = False

            if admin_username != old_admin_username or admin_password is not None:
                logging.debug('admin credentials changed, reload needed')

                reload = True

            if normal_username != old_normal_username or normal_password is not None:
                logging.debug(
                    'surveillance credentials changed, all camera configs must be updated'
                )

                # reconfigure all local cameras to update the stream authentication options
                for camera_id in config.get_camera_ids():
                    local_config = config.get_camera(camera_id)
                    if not utils.is_local_motion_camera(local_config):
                        continue

                    ui_config = config.motion_camera_dict_to_ui(local_config)
                    local_config = config.motion_camera_ui_to_dict(
                        ui_config, local_config)

                    config.set_camera(camera_id, local_config)

                    restart = True

            if reboot and settings.ENABLE_REBOOT:
                logging.debug('system settings changed, reboot needed')

            else:
                reboot = False

            return {'reload': reload, 'reboot': reboot, 'restart': restart}

        reload = False  # indicates that browser should reload the page
        reboot = [False]  # indicates that the server will reboot immediately
        restart = [
            False
        ]  # indicates that the local motion instance was modified and needs to be restarted
        error = [None]

        def finish():
            if reboot[0]:
                if settings.ENABLE_REBOOT:

                    def call_reboot():
                        PowerControl.reboot()

                    io_loop = IOLoop.instance()
                    io_loop.add_timeout(datetime.timedelta(seconds=2),
                                        call_reboot)
                    return self.finish({
                        'reload': False,
                        'reboot': True,
                        'error': None
                    })

                else:
                    reboot[0] = False

            if restart[0]:
                logging.debug('motion needs to be restarted')

                motionctl.stop()

                if settings.SMB_SHARES:
                    logging.debug('updating SMB mounts')
                    stop, start = smbctl.update_mounts()  # @UnusedVariable

                    if start:
                        motionctl.start()

                else:
                    motionctl.start()

            self.finish({
                'reload': reload,
                'reboot': reboot[0],
                'error': error[0]
            })

        if camera_id is not None:
            if camera_id == 0:  # multiple camera configs
                if len(ui_config) > 1:
                    logging.debug('setting multiple configs')

                elif len(ui_config) == 0:
                    logging.warning('no configuration to set')

                    return self.finish()

                so_far = [0]

                def check_finished(e, r):
                    restart[0] = restart[0] or r
                    error[0] = error[0] or e
                    so_far[0] += 1

                    if so_far[0] >= len(ui_config):  # finished
                        finish()

                # make sure main config is handled first
                items = list(ui_config.items())
                items.sort(key=lambda key_cfg: key_cfg[0] != 'main')

                for key, cfg in items:
                    if key == 'main':
                        result = set_main_config(cfg)
                        reload = result['reload'] or reload
                        reboot[0] = result['reboot'] or reboot[0]
                        restart[0] = result['restart'] or restart[0]
                        check_finished(None, False)

                    else:
                        await set_camera_config(int(key), cfg, check_finished)

            else:  # single camera config

                def on_finish(e, r):
                    error[0] = e
                    restart[0] = r
                    finish()

                await set_camera_config(camera_id, ui_config, on_finish)

        else:  # main config
            result = set_main_config(ui_config)
            reload = result['reload']
            reboot[0] = result['reboot']
            restart[0] = result['restart']