├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── include ├── Api.h ├── Base64.h ├── CameraController.h ├── Command.h ├── Helper.h ├── Server.h └── Settings.h ├── resources ├── error_messages.xml └── settings.xml ├── src ├── Api.cpp ├── Base64.cpp ├── CameraController.cpp ├── Command.cpp ├── Helper.cpp ├── Server.cpp ├── Settings.cpp └── main.cpp └── webif ├── css ├── bootstrap-theme.css ├── bootstrap-theme.min.css ├── bootstrap.css ├── bootstrap.min.css ├── slider.css └── styles.css ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── img ├── debut_dark.png ├── loader.gif ├── pic.jpg └── sos.png ├── index.html ├── js └── app.js └── lib ├── backbone-min.js ├── bootstrap-slider.js ├── bootstrap.min.js ├── caman.full.min.js ├── jquery.easing.1.3.js ├── jquery.min.js └── underscore-min.js /.gitignore: -------------------------------------------------------------------------------- 1 | ######### 2 | # Compiled source # 3 | ################### 4 | *.com 5 | *.class 6 | *.dll 7 | *.exe 8 | *.o 9 | *.so 10 | *.bc 11 | 12 | # Logs and databases # 13 | ###################### 14 | *.log 15 | *.sql 16 | *.sqlite 17 | 18 | # OS generated files # 19 | ###################### 20 | .DS_Store 21 | .DS_Store? 22 | ._* 23 | .Spotlight-V100 24 | .Trashes 25 | ehthumbs.db 26 | Thumbs.db 27 | 28 | #.ignore .idea folder 29 | .idea/ 30 | ======= 31 | # Mac Stuff 32 | .DS_STORE 33 | 34 | 35 | image.jpg 36 | bin 37 | 38 | .cproject 39 | .project 40 | .settings/ 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: trusty 3 | sudo: false 4 | compiler: 5 | - gcc 6 | addons: 7 | apt: 8 | packages: 9 | - libboost-dev 10 | - libboost-system-dev 11 | - libmicrohttpd-dev 12 | - libgphoto2-dev 13 | - libexiv2-dev 14 | script: 15 | - make VERBOSE=1 16 | 17 | before_deploy: "cd bin && tar -zcvf ../CameraControllerApi-$TRAVIS_TAG.tar.gz . && cd .." 18 | deploy: 19 | provider: releases 20 | api_key: 21 | secure: faqXDjv3SX3BYg36+a4xvFZ5L26sIz1u7edjtj1KgqIqUPrBXfj1FAkEjpEAlvLlS0OL2OaJghXua1NuZlzL7+dl2eRtD32oZWyejUS+jy2yFwRJ3oSXGL5T20MM5xFUpDXRm6VuoIFVfuzieJqzz7gVPMr/uT1JnCqbB3GEtAc= 22 | file: "CameraControllerApi-$TRAVIS_TAG.tar.gz" 23 | skip_cleanup: true 24 | on: 25 | repo: scheckmedia/CameraControllerApi 26 | tags: true 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=g++ -g 2 | CXXFLAGS=-c -Wall -I include -std=c++0x 3 | LDFLAGS= -lboost_system -lgphoto2 -lmicrohttpd -lpthread -lexiv2 4 | CPP_FILES := $(wildcard src/*.cpp) 5 | OBJ_FILES := $(addprefix obj/,$(notdir $(CPP_FILES:.cpp=.o))) 6 | EXECUTABLE=CameraControllerApi 7 | RESOURCES= resources/error_messages.xml resources/settings.xml webif 8 | 9 | all: dirs $(CPP_FILES) bin/$(EXECUTABLE) 10 | 11 | bin/$(EXECUTABLE): $(OBJ_FILES) 12 | $(CC) $(LDFLAGS) -o $@ $^ $(LDFLAGS) 13 | 14 | obj/%.o: src/%.cpp 15 | $(CC) $(CXXFLAGS) -c -o $@ $< 16 | 17 | dirs: 18 | @mkdir -p bin obj 19 | @cp -R $(RESOURCES) bin 20 | 21 | 22 | .PHONY: clean 23 | clean: 24 | $(RM) -rf $(EXECUTABLE) obj bin 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CameraControllerApi 2 | =================== 3 | The CameraControlerApi is an attempt to control a DSLR via REST functionality. At the moment is it possible 4 | to change the camera settings (ISO, aperture, time), take pictures and stream the live view of the camera (only tested with a Nikon D90). 5 | 6 | ###Web-Interface### 7 | 8 | `http://device_ip:port/webif/` 9 | 10 | 11 | ###Demonstration### 12 | 13 | [![a demonstration for the CameraControlerApi](http://img.youtube.com/vi/tkMP7_gnoiU/3.jpg)](http://www.youtube.com/watch?v=tkMP7_gnoiU) 14 | 15 | 16 | How to use 17 | ----------- 18 | You will get all valid data for a command from the "list" action. 19 | 20 | ###Settings### 21 | 22 | **List the configuration with validate values** 23 | 24 | `http://device_ip:port/settings?action=list` 25 | 26 | 27 | 28 | **ISO** 29 | 30 | `http://device_ip:port/settings?action=iso&value=200` 31 | 32 | 33 | 34 | **Aperture** 35 | 36 | `http://device_ip:port/settings?action=aperture&value=f/22` 37 | 38 | 39 | 40 | **Shutter Speed** 41 | 42 | `http://device_ip:port/settings?action=speed&value=1/1000` 43 | 44 | 45 | 46 | **Whitebalance** 47 | 48 | `http://device_ip:port/settings?action=whitebalance&value=Cloudy` 49 | 50 | 51 | 52 | ###Capture### 53 | 54 | **take a picture** 55 | 56 | `http://device_ip:port/capture?action=shot` 57 | 58 | 59 | 60 | **autofocus** 61 | 62 | `http://device_ip:port/capture?action=autofocus` 63 | 64 | 65 | ###File system### 66 | 67 | **list of the available images on camera** 68 | 69 | `http://device_ip:port/fs?action=list` 70 | 71 | 72 | 73 | **get an image** 74 | 75 | `http://device_ip:port/fs?action=get&value=filename.jpg&path=/path/to/file` 76 | 77 | 78 | 79 | 80 | Each method will response with a JSON file. If you want a XML response you have to put the command "&type=xml" on the end of the upper commands 81 | 82 | 83 | ####Live View 84 | live view will be generated as mjpeg-stream. you can easy implement this stream in html inside an image tag e.g: 85 | ``` 86 | 87 | 88 | 89 | 90 | Live View 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ``` 99 | 100 | 101 | 102 | ##Dependencies## 103 | ```apt-get install libboost-dev libboost-system-dev libmicrohttpd-dev libgphoto2-dev libexiv2-dev``` 104 | 105 | + libgphoto 106 | + libboost 107 | + libboost-system 108 | + libmicrohttpd 109 | + libexiv2 110 | -------------------------------------------------------------------------------- /include/Api.h: -------------------------------------------------------------------------------- 1 | // 2 | // Api.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__Api__ 10 | #define __CameraControllerApi__Api__ 11 | 12 | #include "CameraController.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define CCA_ERROR_MESSAGE_FILE "error_messages.xml" 23 | 24 | namespace CameraControllerApi { 25 | 26 | using std::map; 27 | using std::string; 28 | using boost::property_tree::ptree; 29 | using boost::property_tree::basic_ptree; 30 | 31 | typedef enum { 32 | CCA_API_RESPONSE_SUCCESS = 1, 33 | CCA_API_RESPONSE_INVALID = -1, 34 | CCA_API_RESPONSE_CAMERA_NOT_FOUND = -2, 35 | CCA_API_RESPONSE_CAMERA_BUSY = -3, 36 | } CCA_API_RESPONSE; 37 | 38 | typedef enum { 39 | CCA_OUTPUT_TYPE_XML, 40 | CCA_OUTPUT_TYPE_JSON 41 | } CCA_API_OUTPUT_TYPE; 42 | 43 | /** 44 | @class Api 45 | @\brief responsible for the interaction between a command and the camera 46 | */ 47 | class Api { 48 | private: 49 | 50 | /** 51 | @brief CameraControler Instance 52 | */ 53 | CameraController *_cc; 54 | 55 | /** 56 | @brief creates a "camera not found" method 57 | @param[in] resp 58 | @param[in] CCA_API_OUTPUT_TYPE 59 | @param[out] output 60 | */ 61 | bool _buildCameraNotFound(CCA_API_RESPONSE resp, CCA_API_OUTPUT_TYPE type, string &output); 62 | 63 | /** 64 | @brief sets a value in camera config 65 | @param[in] key - config value key 66 | @param[in] value - value for key 67 | @param[in] CCA_API_OUTPUT_TYPE 68 | @param[out] output 69 | */ 70 | bool _set_settings_value(string key, string value, CCA_API_OUTPUT_TYPE type, string &output); 71 | bool _check(CCA_API_OUTPUT_TYPE type, string &output); 72 | public: 73 | Api(CameraController *cc); 74 | static void buildResponse(ptree data, CCA_API_OUTPUT_TYPE type, CCA_API_RESPONSE resp, string &output); 75 | static void errorMessage(CCA_API_RESPONSE errnr, string &message); 76 | bool status(CCA_API_OUTPUT_TYPE type, string &output); 77 | bool init(CCA_API_OUTPUT_TYPE type, string &output); 78 | bool list_settings(CCA_API_OUTPUT_TYPE type, string &output); 79 | bool list_files(CCA_API_OUTPUT_TYPE type, string &output); 80 | bool get_settings_by_key(string key, CCA_API_OUTPUT_TYPE type, string &output); 81 | bool get_file(string file, string path, CCA_API_OUTPUT_TYPE type, string &output); 82 | bool set_focus_point(string focus_point, CCA_API_OUTPUT_TYPE type, string &output); 83 | bool set_aperture(string aperture, CCA_API_OUTPUT_TYPE type, string &output); 84 | bool set_speed(string speed, CCA_API_OUTPUT_TYPE type, string &output); 85 | bool set_iso(string iso, CCA_API_OUTPUT_TYPE type, string &output); 86 | bool set_whitebalance(string wb, CCA_API_OUTPUT_TYPE type, string &output); 87 | bool shot(CCA_API_OUTPUT_TYPE type, string &output); 88 | bool autofocus(CCA_API_OUTPUT_TYPE type, string &output); 89 | bool bulb(CCA_API_OUTPUT_TYPE type, string &output); 90 | bool timelapse(int interval, time_t start, time_t end, CCA_API_OUTPUT_TYPE type, string &output); 91 | bool burst(int number_of_images, CCA_API_OUTPUT_TYPE type, string &output); 92 | }; 93 | } 94 | 95 | #endif /* defined(__CameraControllerApi__Api__) */ 96 | -------------------------------------------------------------------------------- /include/Base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adam Rudd. 3 | * See LICENSE for more information 4 | */ 5 | #ifndef _BASE64_H 6 | #define _BASE64_H 7 | 8 | /* b64_alphabet: 9 | * Description: Base64 alphabet table, a mapping between integers 10 | * and base64 digits 11 | * Notes: This is an extern here but is defined in Base64.c 12 | */ 13 | extern const char b64_alphabet[]; 14 | 15 | /* base64_encode: 16 | * Description: 17 | * Encode a string of characters as base64 18 | * Parameters: 19 | * output: the output buffer for the encoding, stores the encoded string 20 | * input: the input buffer for the encoding, stores the binary to be encoded 21 | * inputLen: the length of the input buffer, in bytes 22 | * Return value: 23 | * Returns the length of the encoded string 24 | * Requirements: 25 | * 1. output must not be null or empty 26 | * 2. input must not be null 27 | * 3. inputLen must be greater than or equal to 0 28 | */ 29 | int base64_encode(char *output, const char *input, int inputLen); 30 | 31 | /* base64_decode: 32 | * Description: 33 | * Decode a base64 encoded string into bytes 34 | * Parameters: 35 | * output: the output buffer for the decoding, 36 | * stores the decoded binary 37 | * input: the input buffer for the decoding, 38 | * stores the base64 string to be decoded 39 | * inputLen: the length of the input buffer, in bytes 40 | * Return value: 41 | * Returns the length of the decoded string 42 | * Requirements: 43 | * 1. output must not be null or empty 44 | * 2. input must not be null 45 | * 3. inputLen must be greater than or equal to 0 46 | */ 47 | int base64_decode(char *output, char *input, int inputLen); 48 | 49 | /* base64_enc_len: 50 | * Description: 51 | * Returns the length of a base64 encoded string whose decoded 52 | * form is inputLen bytes long 53 | * Parameters: 54 | * inputLen: the length of the decoded string 55 | * Return value: 56 | * The length of a base64 encoded string whose decoded form 57 | * is inputLen bytes long 58 | * Requirements: 59 | * None 60 | */ 61 | int base64_enc_len(int inputLen); 62 | 63 | /* base64_dec_len: 64 | * Description: 65 | * Returns the length of the decoded form of a 66 | * base64 encoded string 67 | * Parameters: 68 | * input: the base64 encoded string to be measured 69 | * inputLen: the length of the base64 encoded string 70 | * Return value: 71 | * Returns the length of the decoded form of a 72 | * base64 encoded string 73 | * Requirements: 74 | * 1. input must not be null 75 | * 2. input must be greater than or equal to zero 76 | */ 77 | int base64_dec_len(char *input, int inputLen); 78 | 79 | #endif // _BASE64_H 80 | -------------------------------------------------------------------------------- /include/CameraController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CameraController.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 10.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__CameraController__ 10 | #define __CameraControllerApi__CameraController__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | using std::string; 21 | using boost::property_tree::ptree; 22 | 23 | namespace CameraControllerApi { 24 | 25 | class CameraController { 26 | 27 | static GPContextErrorFunc _error_callback(GPContext *context, const char *text, void *data); 28 | static GPContextMessageFunc _message_callback(GPContext *context, const char *text, void *data); 29 | 30 | public: 31 | bool camera_found(); 32 | bool is_initialized(); 33 | 34 | static CameraController* getInstance(); 35 | static void release(); 36 | 37 | void init(); 38 | bool is_busy(); 39 | void is_bussy(bool busy); 40 | int capture(const char *filename, string &data); 41 | int preview(const char **file_data); 42 | int bulb(const char *filename, string &data); 43 | int get_settings(ptree &sett); 44 | int get_settings_value(const char *key, string &val); 45 | int get_settings_choices(const char *key, std::vector &choices); 46 | int set_settings_value(const char *key, const char *val); 47 | int get_files(ptree &tree); 48 | int get_file(const char *filename, const char *filepath, string &base64out); 49 | 50 | private: 51 | static CameraController *_instance; 52 | Camera *_camera; 53 | GPContext *_ctx; 54 | bool _is_busy; 55 | bool _liveview_running; 56 | bool _camera_found; 57 | bool _is_initialized; 58 | bool _save_images; 59 | 60 | CameraController(); 61 | ~CameraController(); 62 | 63 | void _init_camera(); 64 | int _wait_and_handle_event (useconds_t waittime, CameraEventType *type, int download); 65 | int _get_files(ptree &tree, const char *folder); 66 | void _build_settings_tree(CameraWidget *w); 67 | void _read_widget(CameraWidget *w, ptree &tree, string node); 68 | void _get_item_value(CameraWidget *w, ptree &tree); 69 | void _set_capturetarget(int index); 70 | int _file_to_base64(CameraFile *file, string &output); 71 | int _file_to_base64(const char *data, unsigned int data_size, string &output); 72 | }; 73 | } 74 | 75 | #endif /* defined(__CameraControllerApi__CameraController__) */ 76 | -------------------------------------------------------------------------------- /include/Command.h: -------------------------------------------------------------------------------- 1 | // 2 | // Command.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__Command__ 10 | #define __CameraControllerApi__Command__ 11 | 12 | #include "Api.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define CCA_CMD_INVALID -1; 20 | #define CCA_CMD_SUCCESS 1; 21 | 22 | using std::map; 23 | using std::string; 24 | using std::set; 25 | using std::vector; 26 | 27 | namespace CameraControllerApi { 28 | class Command { 29 | public: 30 | Command(Api *api); 31 | int execute(const string& url, const map& argvals, string& response); 32 | static string find_url_param(const std::map&map, const char *keyword); 33 | private: 34 | Api *_api; 35 | map > _valid_commands; 36 | bool _executeAPI(const string &url, string action, const map &urlparams, CCA_API_OUTPUT_TYPE type, string &response); 37 | bool _validate(const void *data); 38 | }; 39 | } 40 | 41 | #endif /* defined(__CameraControllerApi__Command__) */ 42 | -------------------------------------------------------------------------------- /include/Helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 16.10.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__Helper__ 10 | #define __CameraControllerApi__Helper__ 11 | 12 | #include 13 | #include 14 | 15 | using std::string; 16 | 17 | namespace CameraControllerApi { 18 | class Helper { 19 | public: 20 | typedef enum { 21 | THUMBNAIL, 22 | FULL 23 | } ImageType; 24 | //static void resize_image_to_base64(int w, int h, const char *image_data, unsigned long data_size, string &base); 25 | //static void resize_image_to_base64(int square, const char *image_data, unsigned long data_size, string &base); 26 | static bool get_thumbnail_from_exif(const char *image_data, unsigned long data_size, string &base, ImageType type = THUMBNAIL); 27 | static bool get_image_from_exif(const char *image_data, unsigned long data_size, string &base); 28 | static void to_base64(std::string *data, string &base); 29 | 30 | private: 31 | //static void _resize(int w, int h, const char *image_data, unsigned long data_size, string &base); 32 | }; 33 | } 34 | 35 | #endif /* defined(__CameraControllerApi__Helper__) */ 36 | -------------------------------------------------------------------------------- /include/Server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Server.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__Server__ 10 | #define __CameraControllerApi__Server__ 11 | 12 | #include 13 | #include "microhttpd.h" 14 | #include "Api.h" 15 | #include "CameraController.h" 16 | #include "Command.h" 17 | #include 18 | 19 | 20 | namespace CameraControllerApi { 21 | class Server{ 22 | static int get_url_args(void *cls, MHD_ValueKind kind, const char *key , const char* value); 23 | static int send_bad_response( struct MHD_Connection *connection); 24 | static int send_auth_fail( struct MHD_Connection *connection); 25 | 26 | public: 27 | static Server* getInstance(); 28 | void run(int port); 29 | Api *api; 30 | CameraController *cc; 31 | Command *cmd; 32 | 33 | static void* initial(void*); 34 | 35 | void terminate(int sig); 36 | void http(); 37 | void http_mjpeg(); 38 | 39 | 40 | private: 41 | Server(); 42 | static Server *_instance; 43 | static int url_handler (void *cls, 44 | struct MHD_Connection *connection, 45 | const char *url, 46 | const char *method, 47 | const char *version, 48 | const char *upload_data, size_t *upload_data_size, void **ptr); 49 | static MHD_Response* handle_webif(void *cls, 50 | struct MHD_Connection *connection, 51 | const char *url); 52 | static MHD_Response* handle_api(void *cls, 53 | const char *url, 54 | map url_args, 55 | string respdata, 56 | void **ptr); 57 | static ssize_t handle_mjpeg(void *cls, uint64_t pos, char *buf, size_t max); 58 | static void free_mjpeg(void *cls); 59 | 60 | 61 | int _port; 62 | int _shoulNotExit; 63 | bool _webif; 64 | bool _auth; 65 | std::stringstream *_live_stream; 66 | 67 | }; 68 | } 69 | #endif /* defined(__CameraControllerApi__Server__) */ 70 | -------------------------------------------------------------------------------- /include/Settings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.h 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 28.09.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #ifndef __CameraControllerApi__Settings__ 10 | #define __CameraControllerApi__Settings__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define CCA_SETTINGS_FILE "settings.xml" 18 | 19 | namespace CameraControllerApi { 20 | using std::string; 21 | using boost::property_tree::ptree; 22 | using boost::property_tree::basic_ptree; 23 | 24 | class Settings { 25 | static Settings *_instance; 26 | public: 27 | static Settings* getInstance(); 28 | static string get_value(string key); 29 | static void release(); 30 | bool get_value(string key, string &res); 31 | void base_path(string path); 32 | string get_base_path(); 33 | 34 | private: 35 | Settings(); 36 | ~Settings(){}; 37 | void _init(); 38 | bool _initialized; 39 | string _base_path; 40 | ptree _pt; 41 | 42 | }; 43 | } 44 | 45 | #endif /* defined(__CameraControllerApi__Settings__) */ 46 | -------------------------------------------------------------------------------- /resources/error_messages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | No error message found 5 | Invalid Command 6 | Camera not found 7 | Not enough url parameter 8 | Camera is busy 9 | 10 | -------------------------------------------------------------------------------- /resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | true 8 | 9 | true 10 | 11 | 12 | 13 | false 14 | example 15 | example 16 | 17 | 8888 18 | 19 | -------------------------------------------------------------------------------- /src/Api.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Api.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include "Api.h" 10 | #include "Settings.h" 11 | #include 12 | 13 | using namespace CameraControllerApi; 14 | 15 | Api::Api(CameraController *cc){ 16 | this->_cc = cc; 17 | } 18 | 19 | bool Api::status(CCA_API_OUTPUT_TYPE type, string &output){ 20 | if(this->_check(type, output)){ 21 | ptree settings; 22 | Api::buildResponse(settings, type, CCA_API_RESPONSE_SUCCESS, output); 23 | 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | bool Api::init(CCA_API_OUTPUT_TYPE type, string &output){ 31 | this->_cc->init(); 32 | if(this->_check(type, output)){ 33 | ptree settings; 34 | Api::buildResponse(settings, type, CCA_API_RESPONSE_SUCCESS, output); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | bool Api::list_settings(CCA_API_OUTPUT_TYPE type, string &output){ 41 | if(this->_check(type, output)){ 42 | ptree settings; 43 | this->_cc->get_settings(settings); 44 | Api::buildResponse(settings, type, CCA_API_RESPONSE_SUCCESS, output); 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | bool Api::get_settings_by_key(string key, CCA_API_OUTPUT_TYPE type, string &output){ 52 | if(this->_check(type, output)){ 53 | ptree settings; 54 | string value; 55 | int ret; 56 | ret = this->_cc->get_settings_value(key.c_str(), value); 57 | 58 | if(ret < GP_OK) 59 | return ret; 60 | 61 | std::vector choices; 62 | ret = this->_cc->get_settings_choices(key.c_str(), choices); 63 | 64 | if(ret < GP_OK) 65 | return ret; 66 | 67 | settings.put("value", value); 68 | 69 | ptree choices_items; 70 | for(int i = 0; i < choices.size(); i++){ 71 | ptree choice_value; 72 | string choice = choices.at(i); 73 | 74 | choice_value.put_value(choice); 75 | choices_items.push_back(std::make_pair("", choice_value)); 76 | } 77 | settings.put_child("choices", choices_items); 78 | Api::buildResponse(settings, type, CCA_API_RESPONSE_SUCCESS, output); 79 | return true; 80 | } 81 | 82 | return false; 83 | } 84 | 85 | bool Api::list_files(CCA_API_OUTPUT_TYPE type, string &output){ 86 | if(this->_check(type, output)){ 87 | ptree settings; 88 | int ret = this->_cc->get_files(settings); 89 | 90 | if(ret) 91 | Api::buildResponse(settings, type, CCA_API_RESPONSE_SUCCESS, output); 92 | else 93 | Api::buildResponse(settings, type, CCA_API_RESPONSE_INVALID, output); 94 | 95 | return true; 96 | } 97 | 98 | return false; 99 | } 100 | 101 | bool Api::get_file(string file, string path, CCA_API_OUTPUT_TYPE type, string &output){ 102 | if(this->_check(type, output)){ 103 | ptree tree; 104 | string image; 105 | int ret = this->_cc->get_file(file.c_str(), path.c_str(), image); 106 | 107 | if(ret){ 108 | tree.put("image", image); 109 | Api::buildResponse(tree, type, CCA_API_RESPONSE_SUCCESS, output); 110 | 111 | } else { 112 | Api::buildResponse(tree, type, CCA_API_RESPONSE_INVALID, output); 113 | } 114 | 115 | return ret; 116 | } 117 | return false; 118 | } 119 | 120 | bool Api::set_focus_point(string focus_point, CCA_API_OUTPUT_TYPE type, string &output){ 121 | if(this->_check(type, output)){ 122 | return this->_set_settings_value("d108", focus_point, type, output); 123 | } 124 | return false; 125 | } 126 | 127 | bool Api::set_aperture(string aperture, CCA_API_OUTPUT_TYPE type, string &output){ 128 | if(this->_check(type, output)){ 129 | return this->_set_settings_value("f-number", aperture, type, output); 130 | } 131 | return false; 132 | } 133 | 134 | bool Api::set_speed(string speed, CCA_API_OUTPUT_TYPE type, string &output){ 135 | if(this->_check(type, output)){ 136 | return this->_set_settings_value("shutterspeed2", speed, type, output); 137 | } 138 | return false; 139 | } 140 | 141 | bool Api::set_iso(string iso, CCA_API_OUTPUT_TYPE type, string &output){ 142 | if(this->_check(type, output)){ 143 | return this->_set_settings_value("iso", iso, type, output); 144 | } 145 | return false; 146 | } 147 | 148 | bool Api::set_whitebalance(string wb, CCA_API_OUTPUT_TYPE type, string &output){ 149 | if(this->_check(type, output)){ 150 | return this->_set_settings_value("whitebalance", wb, type, output); 151 | } 152 | return false; 153 | } 154 | 155 | bool Api::shot(CCA_API_OUTPUT_TYPE type, string &output){ 156 | if(this->_check(type, output)){ 157 | ptree tree; 158 | string image; 159 | int ret = this->_cc->capture("image.jpg", image); 160 | if(ret){ 161 | tree.put("image", image); 162 | Api::buildResponse(tree, type, CCA_API_RESPONSE_SUCCESS, output); 163 | 164 | } else { 165 | Api::buildResponse(tree, type, CCA_API_RESPONSE_INVALID, output); 166 | } 167 | 168 | return true; 169 | } 170 | return false; 171 | } 172 | 173 | bool Api::timelapse(int interval, time_t start, time_t end, CCA_API_OUTPUT_TYPE type, string &output){ 174 | if(this->_check(type, output)){ 175 | ptree tree; 176 | time_t now; 177 | time(&now); 178 | 179 | int ret; 180 | float shutterspeed; 181 | 182 | string val; 183 | ret = this->_cc->get_settings_value("shutterspeed", val); 184 | 185 | if(ret < GP_OK) 186 | return false; 187 | 188 | time_t diff = end - start; 189 | shutterspeed = std::stof(val); 190 | 191 | //buffer for exposure and image process time 192 | if (interval >= shutterspeed * 2 && (start + 1000) > now && diff > 0) { 193 | //delay 194 | this->_cc->is_bussy(true); 195 | while(start >= now){ 196 | time(&now); 197 | usleep(100); 198 | } 199 | 200 | time_t timer = 0; 201 | time_t idiff = 0; 202 | time_t active = 0; 203 | while(this->_cc->is_busy() && diff > 0){ 204 | 205 | if(idiff <= 0){ 206 | printf("shot at : %ld\n", diff); 207 | ret = this->shot(type, output); 208 | if(static_cast(ret) < GP_OK) 209 | break; 210 | 211 | time(&timer); 212 | timer += interval; 213 | } 214 | 215 | usleep(100); 216 | time(&active); 217 | diff = end - active; 218 | idiff = timer - active; 219 | } 220 | this->_cc->is_bussy(false); 221 | Api::buildResponse(tree, type, CCA_API_RESPONSE_SUCCESS, output); 222 | return true; 223 | } else { 224 | this->_cc->is_bussy(false); 225 | Api::buildResponse(tree, type, CCA_API_RESPONSE_INVALID, output); 226 | return false; 227 | } 228 | } 229 | return false; 230 | } 231 | 232 | bool Api::bulb(CCA_API_OUTPUT_TYPE type, string &output){ 233 | if(this->_check(type, output)){ 234 | return this->_cc->bulb("bulb.jpg", output); 235 | } 236 | return false; 237 | } 238 | 239 | bool Api::burst(int number_of_images, CCA_API_OUTPUT_TYPE type, string &output){ 240 | if(this->_check(type, output)){ 241 | int ret = 0; 242 | ptree tree, images; 243 | string base64image; 244 | char filename[256]; 245 | 246 | for(int i = 0; i < number_of_images; i++){ 247 | 248 | snprintf(filename, 256, "shot-%04d.jpg", i); 249 | if(ret){ 250 | ptree image; 251 | image.put_value(base64image); 252 | images.push_back(std::make_pair("", image)); 253 | } else { 254 | return false; 255 | } 256 | } 257 | tree.put_child("preview_images", images); 258 | Api::buildResponse(tree, type, CCA_API_RESPONSE_SUCCESS, output); 259 | return true; 260 | } 261 | return false; 262 | } 263 | 264 | bool Api::autofocus(CCA_API_OUTPUT_TYPE type, string &output){ 265 | if(this->_check(type, output)){ 266 | string value = "1"; 267 | /* 268 | not needed 269 | this->_cc->get_settings_value("autofocusdrive", (void *)&value); 270 | if(value.empty()){ 271 | int val = atoi(value.c_str()); 272 | val++; 273 | value = boost::lexical_cast(val); 274 | }*/ 275 | this->_set_settings_value("autofocusdrive", value, type, output); 276 | return true; 277 | } 278 | return false; 279 | } 280 | 281 | bool Api::_buildCameraNotFound(CCA_API_RESPONSE resp, CCA_API_OUTPUT_TYPE type, string &output){ 282 | ptree n; 283 | Api::buildResponse(n, type, resp, output); 284 | return false; 285 | } 286 | 287 | void Api::buildResponse(ptree data, CCA_API_OUTPUT_TYPE type, CCA_API_RESPONSE resp, string &output){ 288 | try{ 289 | boost::property_tree::ptree root; 290 | std::stringstream ss; 291 | if(resp != CCA_API_RESPONSE_SUCCESS){ 292 | root.put("cca_response.state", "fail"); 293 | string message; 294 | Api::errorMessage(resp, message); 295 | root.put("cca_response.message", message); 296 | } else { 297 | root.put("cca_response.state", "success"); 298 | string message; 299 | if(data.empty() == false) 300 | root.add_child("cca_response.data", data); 301 | } 302 | 303 | if(type == CCA_OUTPUT_TYPE_JSON){ 304 | boost::property_tree::write_json(ss, root); 305 | } else if(type == CCA_OUTPUT_TYPE_XML){ 306 | boost::property_tree::write_xml(ss, root); 307 | } 308 | 309 | output = ss.str(); 310 | } catch(std::exception &e){ 311 | std::cout<<"Error: " << e.what(); 312 | } 313 | } 314 | 315 | bool Api::_set_settings_value(string key, string value, CCA_API_OUTPUT_TYPE type, string &output){ 316 | ptree tree; 317 | int ret = this->_cc->set_settings_value(key.c_str(), value.c_str()); 318 | if(ret) 319 | Api::buildResponse(tree, type, CCA_API_RESPONSE_SUCCESS, output); 320 | else 321 | Api::buildResponse(tree, type, CCA_API_RESPONSE_INVALID, output); 322 | 323 | return ret; 324 | } 325 | 326 | bool Api::_check(CCA_API_OUTPUT_TYPE type, string &output){ 327 | if(this->_cc->camera_found() == false) 328 | return this->_buildCameraNotFound(CCA_API_RESPONSE_CAMERA_NOT_FOUND, type, output); 329 | else if(this->_cc->is_busy()){ 330 | ptree n; 331 | Api::buildResponse(n, type, CCA_API_RESPONSE_CAMERA_BUSY, output); 332 | return false; 333 | } 334 | return true; 335 | } 336 | 337 | void Api::errorMessage(CCA_API_RESPONSE errnr, string &message){ 338 | try { 339 | boost::property_tree::ptree pt; 340 | Settings *s = Settings::getInstance(); 341 | boost::property_tree::read_xml( s->get_base_path() + "/" + CCA_ERROR_MESSAGE_FILE, pt); 342 | std::ostringstream errorstr; 343 | errorstr << errnr; 344 | 345 | string error_id = errorstr.str(); 346 | 347 | BOOST_FOREACH(boost::property_tree::ptree::value_type &v, pt.get_child("CCA.errors")){ 348 | std::string id = v.second.get_child(".id").data(); 349 | if(id == error_id){ 350 | message = v.second.data(); 351 | return; 352 | } 353 | } 354 | message = pt.get("CCA.errors", 0); 355 | } catch (std::exception const &e) { 356 | std::cout<<"Error: " << e.what(); 357 | } 358 | } -------------------------------------------------------------------------------- /src/Base64.cpp: -------------------------------------------------------------------------------- 1 | #include "Base64.h" 2 | 3 | const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 4 | "abcdefghijklmnopqrstuvwxyz" 5 | "0123456789+/"; 6 | 7 | /* 'Private' declarations */ 8 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3); 9 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4); 10 | inline unsigned char b64_lookup(char c); 11 | 12 | int base64_encode(char *output, const char *input, int inputLen) { 13 | int i = 0, j = 0; 14 | int encLen = 0; 15 | unsigned char a3[3]; 16 | unsigned char a4[4]; 17 | 18 | while(inputLen--) { 19 | a3[i++] = *(input++); 20 | if(i == 3) { 21 | a3_to_a4(a4, a3); 22 | 23 | for(i = 0; i < 4; i++) { 24 | output[encLen++] = b64_alphabet[a4[i]]; 25 | } 26 | 27 | i = 0; 28 | } 29 | } 30 | 31 | if(i) { 32 | for(j = i; j < 3; j++) { 33 | a3[j] = '\0'; 34 | } 35 | 36 | a3_to_a4(a4, a3); 37 | 38 | for(j = 0; j < i + 1; j++) { 39 | output[encLen++] = b64_alphabet[a4[j]]; 40 | } 41 | 42 | while((i++ < 3)) { 43 | output[encLen++] = '='; 44 | } 45 | } 46 | output[encLen] = '\0'; 47 | return encLen; 48 | } 49 | 50 | int base64_decode(char * output, char * input, int inputLen) { 51 | int i = 0, j = 0; 52 | int decLen = 0; 53 | unsigned char a3[3]; 54 | unsigned char a4[4]; 55 | 56 | 57 | while (inputLen--) { 58 | if(*input == '=') { 59 | break; 60 | } 61 | 62 | a4[i++] = *(input++); 63 | if (i == 4) { 64 | for (i = 0; i <4; i++) { 65 | a4[i] = b64_lookup(a4[i]); 66 | } 67 | 68 | a4_to_a3(a3,a4); 69 | 70 | for (i = 0; i < 3; i++) { 71 | output[decLen++] = a3[i]; 72 | } 73 | i = 0; 74 | } 75 | } 76 | 77 | if (i) { 78 | for (j = i; j < 4; j++) { 79 | a4[j] = '\0'; 80 | } 81 | 82 | for (j = 0; j <4; j++) { 83 | a4[j] = b64_lookup(a4[j]); 84 | } 85 | 86 | a4_to_a3(a3,a4); 87 | 88 | for (j = 0; j < i - 1; j++) { 89 | output[decLen++] = a3[j]; 90 | } 91 | } 92 | output[decLen] = '\0'; 93 | return decLen; 94 | } 95 | 96 | int base64_enc_len(int plainLen) { 97 | int n = plainLen; 98 | return (n + 2 - ((n + 2) % 3)) / 3 * 4; 99 | } 100 | 101 | int base64_dec_len(char * input, int inputLen) { 102 | int i = 0; 103 | int numEq = 0; 104 | for(i = inputLen - 1; input[i] == '='; i--) { 105 | numEq++; 106 | } 107 | 108 | return ((6 * inputLen) / 8) - numEq; 109 | } 110 | 111 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { 112 | a4[0] = (a3[0] & 0xfc) >> 2; 113 | a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); 114 | a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); 115 | a4[3] = (a3[2] & 0x3f); 116 | } 117 | 118 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { 119 | a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); 120 | a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); 121 | a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; 122 | } 123 | 124 | inline unsigned char b64_lookup(char c) { 125 | if(c >='A' && c <='Z') return c - 'A'; 126 | if(c >='a' && c <='z') return c - 71; 127 | if(c >='0' && c <='9') return c + 4; 128 | if(c == '+') return 62; 129 | if(c == '/') return 63; 130 | return -1; 131 | } 132 | -------------------------------------------------------------------------------- /src/CameraController.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // CameraController.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 10.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include "CameraController.h" 10 | #include "Settings.h" 11 | #include "Helper.h" 12 | #include "Base64.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | 25 | using namespace CameraControllerApi; 26 | using namespace boost::system; 27 | using namespace boost::asio; 28 | using namespace std; 29 | 30 | using boost::property_tree::ptree; 31 | 32 | 33 | 34 | CameraController* CameraController::_instance = NULL; 35 | 36 | CameraController* CameraController::getInstance(){ 37 | if(_instance == NULL) 38 | _instance = new CameraController(); 39 | 40 | 41 | return _instance; 42 | } 43 | 44 | void CameraController::release(){ 45 | if(_instance != NULL){ 46 | gp_camera_exit(_instance->_camera, _instance->_ctx); 47 | delete _instance; 48 | } 49 | 50 | _instance = NULL; 51 | 52 | } 53 | 54 | 55 | CameraController::CameraController() : 56 | _camera_found(false), _is_busy(false), _is_initialized(false), 57 | _liveview_running(false), _camera(NULL), _ctx(NULL), _save_images(true) 58 | { 59 | this->init(); 60 | } 61 | 62 | void CameraController::init(){ 63 | #ifdef __APPLE__ 64 | system("killall -9 PTPCamera"); 65 | #endif 66 | 67 | if(!this->_camera_found){ 68 | this->_init_camera(); 69 | Settings *s = Settings::getInstance(); 70 | string val = ""; 71 | s->get_value("general.save_images", val); 72 | 73 | if(val.compare("TRUE") == 0 || val.compare("true") == 0) 74 | this->_save_images = true; 75 | else 76 | this->_save_images = false; 77 | 78 | 79 | //if(this->_save_images && this->_camera_found) 80 | // this->_set_capturetarget(1); 81 | } 82 | } 83 | 84 | void CameraController::_init_camera(){ 85 | gp_camera_new(&this->_camera); 86 | this->_ctx = gp_context_new(); 87 | gp_context_set_error_func(this->_ctx, (GPContextErrorFunc)CameraController::_error_callback, NULL); 88 | gp_context_set_message_func(this->_ctx, (GPContextMessageFunc)CameraController::_message_callback, NULL); 89 | 90 | int ret = gp_camera_init(this->_camera, this->_ctx); 91 | if(ret < GP_OK){ 92 | gp_camera_free(this->_camera); 93 | } else { 94 | this->_camera_found = true; 95 | } 96 | this->_is_initialized = true; 97 | } 98 | 99 | CameraController::~CameraController(){ 100 | gp_camera_exit(this->_camera, this->_ctx); 101 | gp_context_unref(this->_ctx); 102 | } 103 | 104 | 105 | bool CameraController::is_busy(){ 106 | return this->_is_busy; 107 | } 108 | 109 | void CameraController::is_bussy(bool busy){ 110 | this->_is_busy = busy; 111 | } 112 | 113 | bool CameraController::camera_found(){ 114 | return this->_camera_found; 115 | } 116 | 117 | bool CameraController::is_initialized(){ 118 | return this->_is_initialized; 119 | } 120 | 121 | int CameraController::capture(const char *filename, string &data){ 122 | this->_is_busy = true; 123 | int ret, fd ; 124 | CameraFile *file; 125 | CameraFilePath path; 126 | 127 | strcpy(path.folder, "/"); 128 | strcpy(path.name, filename); 129 | 130 | ret = gp_camera_capture(this->_camera, GP_CAPTURE_IMAGE, &path, this->_ctx); 131 | if (ret != GP_OK) 132 | return ret; 133 | 134 | 135 | if(this->_save_images == false){ 136 | ret = gp_file_new(&file); 137 | } else { 138 | fd = open(filename, O_CREAT | O_RDWR, 0644); 139 | ret = gp_file_new_from_fd(&file, fd); 140 | } 141 | 142 | if (ret != GP_OK){ 143 | this->_is_busy = false; 144 | return ret; 145 | } 146 | 147 | ret = gp_camera_file_get(this->_camera, path.folder, path.name, GP_FILE_TYPE_NORMAL, file, this->_ctx); 148 | 149 | if (ret != GP_OK){ 150 | this->_is_busy = false; 151 | return ret; 152 | } 153 | 154 | this->_file_to_base64(file, data); 155 | 156 | if(this->_save_images == false){ 157 | ret = gp_camera_file_delete(this->_camera, path.folder, path. name, this->_ctx); 158 | 159 | if (ret != GP_OK){ 160 | this->_is_busy = false; 161 | return ret; 162 | } 163 | } 164 | 165 | 166 | int waittime = 10; 167 | CameraEventType type; 168 | void *eventdata; 169 | 170 | while(1) { 171 | 172 | gp_camera_wait_for_event(this->_camera, waittime, &type, &eventdata, this->_ctx); 173 | 174 | if(type == GP_EVENT_TIMEOUT) { 175 | break; 176 | } 177 | else if (type == GP_EVENT_CAPTURE_COMPLETE || type == GP_EVENT_FILE_ADDED) { 178 | waittime = 10; 179 | } 180 | else if (type != GP_EVENT_UNKNOWN) { 181 | printf("Unexpected event received from camera: %d\n", (int)type); 182 | } 183 | } 184 | 185 | gp_file_free(file); 186 | 187 | this->_is_busy = false; 188 | return true; 189 | } 190 | 191 | int CameraController::preview(const char **file_data){ 192 | if(this->_camera_found == false) 193 | return GP_ERROR; 194 | 195 | this->_is_busy = true; 196 | int ret; 197 | CameraFile *file; 198 | ret = gp_file_new(&file); 199 | if (ret != GP_OK){ 200 | this->_is_busy = false; 201 | return ret; 202 | } 203 | 204 | ret = gp_camera_capture_preview(this->_camera, file, this->_ctx); 205 | 206 | if(ret != GP_OK){ 207 | this->_is_busy = false; 208 | return ret; 209 | } 210 | 211 | unsigned long int file_size = 0; 212 | const char *data; 213 | ret = gp_file_get_data_and_size(file, &data, &file_size); 214 | 215 | if(ret != GP_OK){ 216 | this->_is_busy = false; 217 | return ret; 218 | } 219 | this->_is_busy = false; 220 | *file_data = new char[file_size]; 221 | memcpy((void *)*file_data, data, file_size); 222 | gp_file_unref(file); 223 | 224 | return static_cast(file_size); 225 | } 226 | 227 | int CameraController::bulb(const char *filename, string &data){ 228 | /* 229 | This doesn't work with a nikon and i've no canon available 230 | 231 | int ret; 232 | CameraEventType evtype; 233 | std::vector choices; 234 | 235 | ret = this->get_settings_choices("shutterspeed2", choices); 236 | size_t count = choices.size(); 237 | if(ret && count > 0){ 238 | string choice = choices.at(count - 1); 239 | this->set_settings_value("shutterspeed2", choice.c_str()); 240 | } 241 | 242 | ret = this->set_settings_value("d100", "-1"); 243 | 244 | CameraFile *file; 245 | CameraFilePath path; 246 | 247 | strcpy(path.folder, "/"); 248 | strcpy(path.name, filename); 249 | 250 | ret = gp_camera_capture(this->_camera, GP_CAPTURE_IMAGE, &path, this->_ctx); 251 | if (ret != GP_OK) 252 | return ret; 253 | 254 | time_t lastsec = time(NULL)-3; 255 | struct timeval tval; 256 | while(1){ 257 | ret = this->_wait_and_handle_event(5, &evtype, 1); 258 | } 259 | 260 | if(ret != GP_OK) 261 | return ret; 262 | */ 263 | return true; 264 | } 265 | 266 | int CameraController::get_file(const char *filename, const char *filepath, string &base64out){ 267 | this->_is_busy = true; 268 | int ret; 269 | CameraFile *file; 270 | 271 | ret = gp_file_new(&file); 272 | if(ret < GP_OK){ 273 | this->_is_busy = false; 274 | return ret; 275 | } 276 | 277 | ret = gp_camera_file_get(this->_camera, filepath, filename, GP_FILE_TYPE_NORMAL, file, this->_ctx); 278 | if(ret < GP_OK){ 279 | this->_is_busy = false; 280 | return ret; 281 | } 282 | 283 | this->_file_to_base64(file, base64out); 284 | this->_is_busy = false; 285 | return true; 286 | } 287 | 288 | 289 | int CameraController::get_files(ptree &tree){ 290 | this->_is_busy = true; 291 | int ret = this->_get_files(tree, "/"); 292 | this->_is_busy = false; 293 | return ret; 294 | } 295 | 296 | int CameraController::_get_files(ptree &tree, const char *path){ 297 | int ret; 298 | 299 | const char *name; 300 | CameraList *folder, *files; 301 | 302 | ret = gp_list_new(&folder); 303 | if(ret < GP_OK) 304 | return ret; 305 | 306 | ret = gp_camera_folder_list_folders(this->_camera, path, folder, this->_ctx); 307 | if(ret < GP_OK) 308 | return ret; 309 | 310 | int count_folders = gp_list_count(folder); 311 | 312 | if(count_folders){ 313 | for(int i = 0; i < count_folders; i++){ 314 | gp_list_get_name(folder, i, &name); 315 | 316 | string abspath(path); 317 | abspath.append(name); 318 | abspath.append("/"); 319 | 320 | this->_get_files(tree, abspath.c_str()); 321 | } 322 | } else { 323 | bool show_thumb = Settings::get_value("general.thumbnail").compare("true") == 0; 324 | 325 | ret = gp_list_new(&files); 326 | if(ret < GP_OK) 327 | return ret; 328 | 329 | ret = gp_camera_folder_list_files(this->_camera, path, files, this->_ctx); 330 | if(ret < GP_OK) 331 | return ret; 332 | 333 | int count_files = gp_list_count(files); 334 | 335 | ptree current_folder, filelist; 336 | current_folder.put("absolute_path", path); 337 | 338 | 339 | unsigned long int file_size = 0; 340 | const char *file_data = NULL; 341 | 342 | for(int j = 0; j < count_files; j++){ 343 | gp_list_get_name(files, j, &name); 344 | 345 | ptree valuechild; 346 | valuechild.put("name", name); 347 | 348 | std::string ext = std::string(name); 349 | boost::algorithm::to_lower(ext); 350 | ext = ext.substr(ext.find_last_of(".") + 1); 351 | if(show_thumb && ext.compare("avi") != 0){ 352 | CameraFilePath cPath; 353 | strcpy(cPath.folder, path); 354 | strcpy(cPath.name, name); 355 | 356 | printf("start read file %s", name); 357 | 358 | CameraFile *file; 359 | ret = gp_file_new(&file); 360 | 361 | if (ret != GP_OK) 362 | continue; 363 | 364 | ret = gp_camera_file_get(this->_camera, cPath.folder, cPath.name, GP_FILE_TYPE_NORMAL, file, this->_ctx); 365 | 366 | if (ret != GP_OK) 367 | continue; 368 | 369 | ret = gp_file_get_data_and_size (file, &file_data, &file_size); 370 | if (ret != GP_OK) 371 | continue; 372 | 373 | string thumb = ""; 374 | if (Helper::get_thumbnail_from_exif(file_data, file_size, thumb) == false) { 375 | //if we have no thumbnail data, we sending the whole image 376 | std::string img(file_data, file_size); 377 | Helper::to_base64(&img, thumb); 378 | } 379 | valuechild.put("thumbnail", thumb); 380 | 381 | gp_file_free(file); 382 | file_size = 0; 383 | file_data = NULL; 384 | } 385 | 386 | filelist.push_back(std::make_pair("", valuechild)); 387 | } 388 | current_folder.put_child("files", filelist); 389 | tree.put_child("folder", current_folder); 390 | gp_list_free(files); 391 | 392 | } 393 | gp_list_free(folder); 394 | 395 | return true; 396 | } 397 | 398 | int CameraController::get_settings(ptree &sett){ 399 | this->_is_busy = true; 400 | 401 | CameraWidget *w, *children; 402 | int ret; 403 | ret = gp_camera_get_config(this->_camera, &w, this->_ctx); 404 | if(ret < GP_OK){ 405 | this->_is_busy = false; 406 | return false; 407 | } 408 | 409 | ret = gp_widget_get_child_by_name(w, "main", &children); 410 | if(ret < GP_OK){ 411 | this->_is_busy = false; 412 | return false; 413 | } 414 | 415 | this->_read_widget(children, sett, "settings"); 416 | this->_is_busy = false; 417 | return true; 418 | } 419 | 420 | int CameraController::get_settings_choices(const char *key, vector &choices){ 421 | this->_is_busy = true; 422 | CameraWidget *w, *child; 423 | int ret; 424 | 425 | ret = gp_camera_get_config(this->_camera, &w, this->_ctx); 426 | if(ret < GP_OK){ 427 | this->_is_busy = false; 428 | return ret; 429 | } 430 | 431 | ret = gp_widget_get_child_by_name(w, key, &child); 432 | if(ret < GP_OK){ 433 | this->_is_busy = false; 434 | return ret; 435 | } 436 | 437 | int count = gp_widget_count_choices(child); 438 | for(int i = 0; i < count; i++){ 439 | const char *choice; 440 | ret = gp_widget_get_choice(child, i, &choice); 441 | 442 | if(ret < GP_OK){ 443 | this->_is_busy = false; 444 | return ret; 445 | } 446 | 447 | choices.push_back(string(choice)); 448 | } 449 | this->_is_busy = false; 450 | return true; 451 | } 452 | 453 | int CameraController::get_settings_value(const char *key, string &val){ 454 | this->_is_busy = true; 455 | CameraWidget *w, *child; 456 | int ret; 457 | 458 | ret = gp_camera_get_config(this->_camera, &w, this->_ctx); 459 | if(ret < GP_OK){ 460 | this->_is_busy = false; 461 | return ret; 462 | } 463 | 464 | ret = gp_widget_get_child_by_name(w, key, &child); 465 | if(ret < GP_OK){ 466 | this->_is_busy = false; 467 | return ret; 468 | } 469 | 470 | void *item_value; 471 | ret = gp_widget_get_value(child, &item_value); 472 | if(ret < GP_OK){ 473 | this->_is_busy = false; 474 | return ret; 475 | } 476 | 477 | unsigned char *value = static_cast(item_value); 478 | val.append((char *)value); 479 | 480 | this->_is_busy = false; 481 | return true; 482 | } 483 | 484 | int CameraController::set_settings_value(const char *key, const char *val){ 485 | this->_is_busy = true; 486 | CameraWidget *w, *child; 487 | int ret = gp_camera_get_config(this->_camera, &w, this->_ctx); 488 | if(ret < GP_OK){ 489 | this->_is_busy = false; 490 | return false; 491 | } 492 | 493 | ret = gp_widget_get_child_by_name(w, key, &child); 494 | if(ret < GP_OK){ 495 | this->_is_busy = false; 496 | return false; 497 | } 498 | 499 | ret = gp_widget_set_value(child, val); 500 | if(ret < GP_OK){ 501 | this->_is_busy = false; 502 | return false; 503 | } 504 | 505 | 506 | ret = gp_camera_set_config(this->_camera, w, this->_ctx); 507 | if(ret < GP_OK){ 508 | this->_is_busy = false; 509 | return false; 510 | } 511 | gp_widget_free(w); 512 | 513 | this->_is_busy = false; 514 | return (ret == GP_OK); 515 | } 516 | 517 | 518 | /** 519 | borrowed gphoto2 520 | http://sourceforge.net/p/gphoto/code/HEAD/tree/trunk/gphoto2/gphoto2/main.c#l834 521 | */ 522 | int CameraController::_wait_and_handle_event (useconds_t waittime, CameraEventType *type, int download) { 523 | int result; 524 | CameraEventType evtype; 525 | void *data; 526 | CameraFilePath *path; 527 | 528 | if (!type) type = &evtype; 529 | evtype = GP_EVENT_UNKNOWN; 530 | data = NULL; 531 | result = gp_camera_wait_for_event(this->_camera, waittime, type, &data, this->_ctx); 532 | if (result == GP_ERROR_NOT_SUPPORTED) { 533 | *type = GP_EVENT_TIMEOUT; 534 | usleep(waittime*1000); 535 | return GP_OK; 536 | } 537 | if (result != GP_OK) 538 | return result; 539 | path = (CameraFilePath*)data; 540 | switch (*type) { 541 | case GP_EVENT_TIMEOUT: 542 | break; 543 | case GP_EVENT_CAPTURE_COMPLETE: 544 | break; 545 | case GP_EVENT_FOLDER_ADDED: 546 | free (data); 547 | break; 548 | case GP_EVENT_FILE_ADDED: 549 | //result = save_captured_file (path, download); 550 | free (data); 551 | /* result will fall through to final return */ 552 | break; 553 | case GP_EVENT_UNKNOWN: 554 | free (data); 555 | break; 556 | default: 557 | break; 558 | } 559 | return result; 560 | } 561 | 562 | void CameraController::_set_capturetarget(int index){ 563 | vector choices; 564 | 565 | int ret; 566 | ret = this->get_settings_choices("capturetarget", choices); 567 | 568 | if(ret && index < choices.size()){ 569 | string choice = choices.at(index); 570 | this->set_settings_value("capturetarget", choice.c_str()); 571 | } 572 | 573 | } 574 | 575 | 576 | void CameraController::_read_widget(CameraWidget *w, ptree &tree, string node){ 577 | const char *name; 578 | ptree subtree; 579 | gp_widget_get_name(w, &name); 580 | string nodename = node + "." + name; 581 | 582 | int items = gp_widget_count_children(w); 583 | if(items > 0){ 584 | for(int i = 0; i < items; i++){ 585 | CameraWidget *item_widget; 586 | gp_widget_get_child(w, i, &item_widget); 587 | 588 | this->_read_widget(item_widget, subtree, nodename); 589 | } 590 | } else { 591 | this->_get_item_value(w, subtree); 592 | } 593 | 594 | tree.put_child(name, subtree); 595 | } 596 | 597 | 598 | void CameraController::_get_item_value(CameraWidget *w, ptree &tree){ 599 | CameraWidgetType type; 600 | int id; 601 | const char *item_label, *item_name; 602 | ptree item_choices; 603 | void* item_value; 604 | gp_widget_get_id(w, &id); 605 | gp_widget_get_label(w, &item_label); 606 | gp_widget_get_name(w, &item_name); 607 | gp_widget_get_value(w, &item_value); 608 | gp_widget_get_type(w, &type); 609 | 610 | if(item_value == NULL) 611 | return; 612 | 613 | int number_of_choices = gp_widget_count_choices(w); 614 | if(number_of_choices > 0){ 615 | ptree choice_value; 616 | for(int i = 0; i < number_of_choices; i++){ 617 | const char *choice; 618 | gp_widget_get_choice(w, i, &choice); 619 | choice_value.put_value(choice); 620 | item_choices.push_back(std::make_pair("", choice_value)); 621 | } 622 | } 623 | 624 | 625 | 626 | tree.put("id", id); 627 | tree.put("name", item_name); 628 | tree.put("label", item_label); 629 | tree.put_child("choices", item_choices); 630 | 631 | switch (type) { 632 | case GP_WIDGET_TEXT: 633 | case GP_WIDGET_RADIO: 634 | case GP_WIDGET_MENU: { 635 | unsigned char *val = static_cast(item_value); 636 | tree.put("value", val); 637 | break; 638 | } 639 | case GP_WIDGET_TOGGLE:{ 640 | int val = *((int*)&item_value); 641 | tree.put("value", val); 642 | break; 643 | } 644 | 645 | case GP_WIDGET_RANGE: { 646 | float val = *((float*)&item_value); 647 | tree.put("value", val); 648 | break; 649 | } 650 | 651 | default: 652 | break; 653 | } 654 | } 655 | 656 | int CameraController::_file_to_base64(CameraFile *file, string &output){ 657 | int ret; 658 | unsigned long int file_size = 0; 659 | const char *file_data = NULL; 660 | 661 | ret = gp_file_get_data_and_size (file, &file_data, &file_size); 662 | if (ret != GP_OK) 663 | return ret; 664 | 665 | const char *m; 666 | ret = gp_file_get_mime_type(file, &m); 667 | 668 | std::string mime(m); 669 | cout << "mime: " << mime << std::endl; 670 | if(mime.compare("image/jpeg") == 0) { 671 | std::string img(file_data, file_size); 672 | Helper::to_base64(&img, output); 673 | } else { 674 | Helper::get_image_from_exif(file_data, file_size, output); 675 | } 676 | return true; 677 | } 678 | 679 | 680 | GPContextErrorFunc CameraController::_error_callback(GPContext *context, const char *text, void *data){ 681 | 682 | return 0; 683 | } 684 | 685 | GPContextMessageFunc CameraController::_message_callback(GPContext *context, const char *text, void *data){ 686 | 687 | return 0; 688 | } 689 | -------------------------------------------------------------------------------- /src/Command.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Command.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include "Command.h" 10 | #include "Api.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | using namespace CameraControllerApi; 18 | 19 | struct validate_data { 20 | string api; 21 | string action; 22 | }; 23 | 24 | Command::Command(Api *api){ 25 | this->_api = api; 26 | set params; 27 | string param_camera[] = {"status", "initialize"}; 28 | string param_settings[] = {"list", "aperture", "speed", "iso", "whitebalance","focus_point","focus_mode", "by_key"}; 29 | string param_execute[] = {"shot", "bulb", "timelapse","autofocus", "manualfocus"}; 30 | string param_files[] = {"list", "get", "delete"}; 31 | _valid_commands["/camera"] = set(param_camera, param_camera + 2); 32 | _valid_commands["/settings"] = set(param_settings, param_settings + 8); 33 | _valid_commands["/capture"] = set(param_execute, param_execute + 5); 34 | _valid_commands["/fs"] = set(param_files, param_files + 3); 35 | } 36 | 37 | int Command::execute(const string &url, const map &argvals, string &response){ 38 | string param; 39 | CCA_API_OUTPUT_TYPE type = CCA_OUTPUT_TYPE_JSON; 40 | validate_data vdata; 41 | vdata.api = url; 42 | map::const_iterator iterator = argvals.find("action"); 43 | 44 | if(iterator != argvals.end()){ 45 | param = iterator->second; 46 | boost::trim(param); 47 | } 48 | 49 | iterator = argvals.find("type"); 50 | if(iterator != argvals.end()){ 51 | const string out_type = iterator->second; 52 | if(strcasecmp(out_type.c_str(), "xml") == 0) 53 | type = CCA_OUTPUT_TYPE_XML; 54 | } 55 | 56 | vdata.action = param; 57 | 58 | if ( !this->_validate(&vdata) || param.empty()) { 59 | ptree p; 60 | CCA_API_RESPONSE ret = CCA_API_RESPONSE_INVALID; 61 | Api::buildResponse(p, type, ret, response); 62 | return ret; 63 | } 64 | 65 | return this->_executeAPI(url, param, argvals, type, response); 66 | } 67 | 68 | bool Command::_executeAPI(const string &url, string action, const map &urlparams, CCA_API_OUTPUT_TYPE type, string &response){ 69 | bool ret = CCA_CMD_SUCCESS; 70 | 71 | string value = Command::find_url_param(urlparams, "value"); 72 | 73 | if(url == "/camera"){ 74 | if(action.compare("status") == 0){ 75 | ret = this->_api->status(type, response); 76 | } else if(action.compare("initialize") == 0){ 77 | ret = this->_api->init(type, response); 78 | } 79 | } else if(url == "/settings"){ 80 | if(action.compare("list") == 0){ 81 | ret = this->_api->list_settings(type, response); 82 | } else if(action.compare("focus_point") == 0){ 83 | ret = this->_api->set_focus_point(value, type, response); 84 | } else if(action.compare("aperture") == 0){ 85 | ret = this->_api->set_aperture(value, type, response); 86 | } else if(action.compare("speed") == 0){ 87 | ret = this->_api->set_speed(value, type, response); 88 | } else if(action.compare("iso") == 0){ 89 | ret = this->_api->set_iso(value, type, response); 90 | } else if(action.compare("whitebalance") == 0){ 91 | ret = this->_api->set_whitebalance(value, type, response); 92 | } else if(action.compare("key") == 0){ 93 | ret = this->_api->get_settings_by_key(value, type, response); 94 | } 95 | } else if(url == "/capture"){ 96 | if(action.compare("shot") == 0){ 97 | ret = this->_api->shot(type, response); 98 | } else if(action.compare("autofocus") == 0){ 99 | ret = this->_api->autofocus(type, response); 100 | } else if(action.compare("bulb") == 0){ 101 | ret = this->_api->bulb(type, response); 102 | } else if(action.compare("timelapse") == 0){ 103 | string interval = Command::find_url_param(urlparams, "interval"); 104 | string start = Command::find_url_param(urlparams, "start"); 105 | string end = Command::find_url_param(urlparams, "end"); 106 | 107 | if(interval.empty()||end.empty()) 108 | return false; 109 | 110 | int seconds = std::stoi(interval); 111 | time_t start_time, end_time; 112 | 113 | if(start.empty()) time(&start_time); 114 | else start_time = std::stoi(start); 115 | 116 | end_time = std::stoi(end); 117 | ret = this->_api->timelapse(seconds, start_time, end_time, type, response); 118 | 119 | } 120 | } else if(url == "/fs"){ 121 | if(action.compare("list") == 0){ 122 | ret = this->_api->list_files(type, response); 123 | } else if(action.compare("get") == 0){ 124 | string path = Command::find_url_param(urlparams, "path"); 125 | if(!path.empty()) 126 | ret = this->_api->get_file(value, path, type, response); 127 | else 128 | ret = false; 129 | } 130 | } 131 | return ret; 132 | } 133 | 134 | string Command::find_url_param(const map¶ms, const char *keyword){ 135 | std::map::const_iterator it = params.find(keyword); 136 | if(it != params.end()){ 137 | string value; 138 | value = it->second; 139 | boost::trim(value); 140 | return value; 141 | } 142 | 143 | return ""; 144 | } 145 | 146 | bool Command::_validate(const void *data){ 147 | const validate_data *vdata = static_cast(data); 148 | map >::iterator it = this->_valid_commands.find(vdata->api); 149 | 150 | if(it == this->_valid_commands.end()){ 151 | return false; 152 | } 153 | 154 | return true; 155 | } 156 | -------------------------------------------------------------------------------- /src/Helper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 16.10.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include "Helper.h" 10 | #include "Base64.h" 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace CameraControllerApi; 16 | 17 | bool Helper::get_image_from_exif(const char *image_data, unsigned long data_size, string &base) { 18 | return Helper::get_thumbnail_from_exif(image_data, data_size, base, FULL); 19 | } 20 | 21 | bool Helper::get_thumbnail_from_exif(const char *image_data, unsigned long data_size, string &base, ImageType type) { 22 | try { 23 | Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const Exiv2::byte*)image_data, data_size); 24 | assert(image.get() != 0); 25 | image->readMetadata(); 26 | 27 | Exiv2::ExifData &exifData = image->exifData(); 28 | if (exifData.empty()) { 29 | std::cerr << "exif data are empty" << std::endl; 30 | return false; 31 | } 32 | 33 | Exiv2::PreviewManager loader(*image); 34 | Exiv2::PreviewPropertiesList list = loader.getPreviewProperties(); 35 | 36 | Exiv2::PreviewProperties thumb; 37 | unsigned long start = type == THUMBNAIL ? 0 : list.size() - 1; 38 | unsigned long end = type == THUMBNAIL ? list.size() - 1 : 0; 39 | 40 | for(unsigned long i = start; i != end; type == THUMBNAIL ? ++i : --i) 41 | { 42 | thumb = list.at(i); 43 | if(thumb.extension_.compare(".jpg") == 0) { 44 | std::cerr << "Preview Item: " << thumb.width_ << "x" << thumb.height_ << " ext:" << thumb.extension_ << std::endl; 45 | break; 46 | } 47 | } 48 | 49 | Exiv2::PreviewImage preview = loader.getPreviewImage(thumb); 50 | if(preview.size() <= 0){ 51 | std::cerr << "exif thumbnail is empty" << std::endl; 52 | return false; 53 | } 54 | 55 | Exiv2::DataBuf buf = preview.copy(); 56 | std::string *data = new std::string(reinterpret_cast(buf.pData_), buf.size_); 57 | Helper::to_base64(data, base); 58 | 59 | delete data; 60 | buf.release(); 61 | 62 | return true; 63 | } catch (Exiv2::Error& e) { 64 | std::cerr << "error in image: " << e.what() << " " << std::endl; 65 | return false; 66 | } 67 | } 68 | 69 | void Helper::to_base64(std::string *data, string &base) { 70 | char *buffer = new char[data->size() * sizeof(char*)]; 71 | if(buffer == NULL) return; 72 | 73 | base64_encode(buffer, data->c_str(), static_cast(data->size())); 74 | base.append(buffer); 75 | 76 | delete[] buffer; 77 | } -------------------------------------------------------------------------------- /src/Server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Server.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include 10 | #include "Settings.h" 11 | #include "Server.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "Command.h" 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | using std::map; 24 | using std::regex; 25 | using std::string; 26 | using namespace CameraControllerApi; 27 | 28 | #define PAGE "Error" 29 | 30 | Server* Server::_instance = NULL; 31 | 32 | Server::Server() 33 | { 34 | this->_live_stream = new std::stringstream(std::ios::app | std::ios::in | std::ios::out); 35 | } 36 | 37 | Server* Server::getInstance(){ 38 | if(_instance == NULL){ 39 | _instance = new Server(); 40 | } 41 | 42 | return _instance; 43 | } 44 | 45 | void Server::run(int port) { 46 | this->_port = port; 47 | this->_shoulNotExit = 1; 48 | 49 | string auth = Settings::get_value("server.auth"); 50 | string webif = Settings::get_value("general.webif"); 51 | 52 | 53 | if(auth.compare("true") == 0) 54 | this->_auth = true; 55 | else 56 | this->_auth = false; 57 | 58 | if(webif.compare("true") == 0) 59 | this->_webif = true; 60 | else 61 | this->_webif = false; 62 | 63 | pthread_t tServer; 64 | if (0 != pthread_create(&tServer, NULL, Server::initial, this)) { 65 | exit(0); 66 | } 67 | 68 | pthread_join(tServer, NULL); 69 | } 70 | 71 | void *Server::initial(void *context){ 72 | Server *s = (Server *)context; 73 | s->cc = CameraController::getInstance(); 74 | 75 | if(s->cc->is_initialized()){ 76 | s->api = new Api(s->cc); 77 | s->cmd = new Command(s->api); 78 | s->http(); 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | void Server::terminate(int sig){ 85 | this->_shoulNotExit = 0; 86 | CameraController *cc = CameraController::getInstance(); 87 | cc->release(); 88 | } 89 | 90 | int Server::send_bad_response( struct MHD_Connection *connection) 91 | { 92 | static char *bad_response = (char *)PAGE; 93 | int bad_response_len = static_cast< int >(strlen(bad_response)); 94 | int ret; 95 | struct MHD_Response *response; 96 | 97 | response = MHD_create_response_from_buffer ( bad_response_len, 98 | bad_response,MHD_RESPMEM_PERSISTENT); 99 | if (response == 0){ 100 | return MHD_NO; 101 | } 102 | ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 103 | MHD_destroy_response (response); 104 | return ret; 105 | } 106 | 107 | int Server::send_auth_fail( struct MHD_Connection *connection) 108 | { 109 | static char *bad_response = (char *)PAGE; 110 | int bad_response_len = static_cast< int >(strlen(bad_response)); 111 | int ret; 112 | struct MHD_Response *response; 113 | 114 | response = MHD_create_response_from_buffer ( bad_response_len, 115 | bad_response,MHD_RESPMEM_PERSISTENT); 116 | if (response == 0){ 117 | return MHD_NO; 118 | } 119 | ret = MHD_queue_basic_auth_fail_response(connection, "CameraControlerApi", response); 120 | MHD_destroy_response (response); 121 | return ret; 122 | } 123 | 124 | 125 | int Server::get_url_args(void *cls, MHD_ValueKind kind, const char *key , const char* value){ 126 | map *args = static_cast*>(cls); 127 | if(args->find(key) == args->end()){ 128 | if(!value){ 129 | (*args)[key] = ""; 130 | } else { 131 | (*args)[key] = value; 132 | } 133 | } 134 | 135 | return MHD_YES; 136 | } 137 | 138 | int Server::url_handler (void *cls, 139 | struct MHD_Connection *connection, 140 | const char *url, 141 | const char *method, 142 | const char *version, 143 | const char *upload_data, size_t *upload_data_size, void **ptr){ 144 | printf("connection received %s", method); 145 | int ret; 146 | map url_args; 147 | string respdata; 148 | 149 | static int aptr; 150 | std::string urlString(url); 151 | 152 | 153 | 154 | struct MHD_Response *response; 155 | 156 | if (0 != strcmp(method, "GET")) { 157 | return MHD_NO; 158 | } 159 | 160 | if(&aptr != *ptr){ 161 | *ptr = &aptr; 162 | return MHD_YES; 163 | } 164 | 165 | Server s = *(Server *)cls; 166 | 167 | if(s._auth){ 168 | Settings *settings = Settings::getInstance(); 169 | string username, password; 170 | settings->get_value("server.username", username); 171 | settings->get_value("server.password", password); 172 | 173 | char *user, *pass; 174 | pass = NULL; 175 | user = MHD_basic_auth_get_username_password(connection, &pass); 176 | bool auth_fail = ((user == NULL) || (0 != username.compare(user)) || (0 != password.compare(pass))); 177 | if(auth_fail) 178 | return Server::send_auth_fail(connection); 179 | 180 | } 181 | 182 | if(MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, Server::get_url_args, &url_args) < 0){ 183 | return Server::send_bad_response(connection); 184 | } 185 | 186 | if( urlString.find("webif") != std::string::npos && s._webif){ 187 | response = handle_webif(cls, connection, url); 188 | } else if(urlString.find("liveview") != std::string::npos) { 189 | response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 190 | 512 * 1024, 191 | Server::handle_mjpeg, cls, Server::free_mjpeg); 192 | MHD_add_response_header(response, "Content-Type", "multipart/x-mixed-replace;boundary=CCA_LV_FRAME"); 193 | } else if(urlString.find("preview") != std::string::npos) { 194 | const char *data; 195 | 196 | int size = s.cc->preview(&data); 197 | response = MHD_create_response_from_buffer(size, (void *)data, MHD_RESPMEM_MUST_COPY); 198 | MHD_add_response_header(response, "Content-Type", "image/jpeg"); 199 | 200 | } else { 201 | response = handle_api(cls, url, url_args, respdata, ptr); 202 | } 203 | 204 | 205 | ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 206 | MHD_destroy_response(response); 207 | return ret; 208 | } 209 | 210 | MHD_Response* Server::handle_webif(void *cls, 211 | struct MHD_Connection *connection, 212 | const char *url) { 213 | std::string path(url); 214 | struct stat buff; 215 | int fd; 216 | MHD_Response *response; 217 | 218 | boost::replace_all(path, "webif", ""); 219 | boost::replace_all(path, "webif/", ""); 220 | if(path.compare("/") == 0) path = "/index.html"; 221 | path.insert(0, "./webif"); 222 | 223 | if ( (-1 == (fd = open (path.c_str(), O_RDONLY))) || (0 != fstat (fd, &buff))){ 224 | if (fd != -1) close (fd); 225 | Server::send_bad_response(connection); 226 | return NULL; 227 | } 228 | 229 | std::string ext = path.substr(path.find_last_of(".") + 1); 230 | 231 | std::string mime; 232 | if(ext.compare("js") == 0) 233 | mime = "text/javascript"; 234 | else if(ext.compare("css") == 0) 235 | mime = "text/css"; 236 | else if(ext.compare("jpg") == 0 || ext.compare(".jpeg") == 0 || ext.compare(".jpe") == 0) 237 | mime = "image/jpeg"; 238 | else if(ext.compare("gif") == 0) 239 | mime = "image/gif"; 240 | else if(ext.compare("png") == 0) 241 | mime = "image/png"; 242 | else 243 | mime = "text/html"; 244 | 245 | response = MHD_create_response_from_fd (buff.st_size, fd); 246 | MHD_add_response_header (response, "Content-Type", mime.c_str()); 247 | 248 | return response; 249 | } 250 | 251 | ssize_t Server::handle_mjpeg(void *cls, uint64_t pos, char *buf, size_t max){ 252 | Server *s = static_cast(cls); 253 | s->_live_stream->seekg(pos); 254 | 255 | std::streamsize available = s->_live_stream->rdbuf()->in_avail(); 256 | 257 | if(available < max) { 258 | const char *data; 259 | int size = s->cc->preview(&data); 260 | if(size < GP_OK) 261 | return MHD_CONTENT_READER_END_OF_STREAM; 262 | 263 | *s->_live_stream << "--CCA_LV_FRAME\r\nContent-Type: image/jpeg\r\n\r\n"; 264 | *s->_live_stream << std::string(data, size); 265 | *s->_live_stream << "\r\n"; 266 | delete data; 267 | } 268 | 269 | 270 | s->_live_stream->read(buf, max); 271 | return max; 272 | } 273 | 274 | void Server::free_mjpeg(void *cls) { 275 | Server *s = static_cast(cls); 276 | s->_live_stream->str(""); 277 | s->_live_stream->clear(); 278 | } 279 | 280 | MHD_Response* Server::handle_api(void *cls, 281 | const char *url, 282 | map url_args, 283 | string respdata, 284 | void **ptr){ 285 | 286 | Server s = *(Server *)cls; 287 | map::iterator it; 288 | const char *typexml = "xml"; 289 | const char *typejson = "json"; 290 | const char *type = typejson; 291 | char *me; 292 | MHD_Response *response; 293 | 294 | s.cmd->execute(url, url_args, respdata); 295 | 296 | *ptr = 0; 297 | me = (char *)malloc(respdata.size() + 1); 298 | if(me == 0) 299 | return MHD_NO; 300 | 301 | strncpy(me, respdata.c_str(), respdata.size() + 1); 302 | 303 | response = MHD_create_response_from_buffer(strlen(me), (void *)me, MHD_RESPMEM_MUST_COPY); 304 | 305 | if(response == 0){ 306 | free(me); 307 | return MHD_NO; 308 | } 309 | 310 | it = url_args.find("type"); 311 | if (it != url_args.end() && strcasecmp(it->second.c_str(), "xml")) { 312 | type = typexml; 313 | } 314 | 315 | if(type == typejson){ 316 | MHD_add_response_header(response, "Content-Type", "application/json"); 317 | //MHD_add_response_header(response, "Content-Disposition", "attachment;filename=\"cca.json\""); 318 | } else { 319 | MHD_add_response_header(response, "Content-Type", "application/xml"); 320 | //MHD_add_response_header(response, "Content-Disposition", "attachment;filename=\"cca.xml\""); 321 | } 322 | 323 | MHD_add_response_header (response, "Access-Control-Allow-Origin", "*"); 324 | return response; 325 | } 326 | 327 | void Server::http(){ 328 | struct MHD_Daemon *d; 329 | d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, this->_port, 330 | 0, 0, Server::url_handler, (void*)this ,MHD_OPTION_END); 331 | if(d == NULL){ 332 | return; 333 | } 334 | 335 | while (this->_shoulNotExit) { 336 | sleep(1); 337 | } 338 | 339 | 340 | MHD_stop_daemon(d); 341 | return; 342 | } 343 | 344 | -------------------------------------------------------------------------------- /src/Settings.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 28.09.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include "Settings.h" 10 | 11 | using namespace CameraControllerApi; 12 | 13 | 14 | Settings* Settings::_instance = NULL; 15 | 16 | Settings* Settings::getInstance(){ 17 | if(_instance == NULL){ 18 | _instance = new Settings(); 19 | } 20 | 21 | return _instance; 22 | } 23 | 24 | void Settings::release(){ 25 | if(_instance != NULL) 26 | delete _instance; 27 | 28 | _instance = NULL; 29 | } 30 | 31 | Settings::Settings() { } 32 | 33 | void Settings::_init() { 34 | if(this->_base_path.empty()) 35 | throw std::runtime_error("no base path available"); 36 | 37 | if(this->_initialized == false) { 38 | boost::property_tree::read_xml(this->_base_path + "/" + CCA_SETTINGS_FILE, _pt); 39 | this->_initialized = true; 40 | } 41 | } 42 | 43 | bool Settings::get_value(string key, string &res){ 44 | if(this->_initialized == false) { 45 | this->_init(); 46 | } 47 | 48 | try { 49 | res = _pt.get("CCA_SETTINGS."+key); 50 | return true; 51 | } catch (std::exception const &e) { 52 | std::cout<<"Error: " << e.what(); 53 | } 54 | return false; 55 | } 56 | 57 | string Settings::get_value(string key){ 58 | string res = ""; 59 | Settings *s = Settings::getInstance(); 60 | s->get_value(key, res); 61 | return res; 62 | } 63 | 64 | void Settings::base_path(string path) { 65 | this->_base_path = path; 66 | } 67 | 68 | string Settings::get_base_path() { 69 | return this->_base_path; 70 | } 71 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // CameraControllerApi 4 | // 5 | // Created by Tobias Scheck on 09.08.13. 6 | // Copyright (c) 2013 scheck-media. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include "Settings.h" 13 | #include "Server.h" 14 | #include 15 | using namespace CameraControllerApi; 16 | Server *srv; 17 | 18 | void sighandler(int sig){ 19 | if(srv != NULL) 20 | srv->terminate(sig); 21 | } 22 | 23 | int main(int argc, const char * argv[]) 24 | { 25 | try { 26 | string port; 27 | Settings *cfg = Settings::getInstance(); 28 | std::string sub(argv[0]); 29 | cfg->base_path(sub.substr(0, sub.find_last_of("/"))); 30 | bool ret = cfg->get_value("server.port", port); 31 | 32 | std::cout << "CameraControllerApi listening port " << port << std::endl; 33 | 34 | if(ret){ 35 | int http_port = atoi(port.c_str()); 36 | srv = Server::getInstance(); 37 | srv->run(http_port); 38 | signal(SIGTERM, sighandler); 39 | } 40 | 41 | cfg->release(); 42 | } catch (std::exception const &e) { 43 | std::cout<<"Error: " << e.what(); 44 | } 45 | 46 | 47 | return 0; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /webif/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | .btn-default, 2 | .btn-primary, 3 | .btn-success, 4 | .btn-info, 5 | .btn-warning, 6 | .btn-danger { 7 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 8 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 9 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 10 | } 11 | 12 | .btn-default:active, 13 | .btn-primary:active, 14 | .btn-success:active, 15 | .btn-info:active, 16 | .btn-warning:active, 17 | .btn-danger:active, 18 | .btn-default.active, 19 | .btn-primary.active, 20 | .btn-success.active, 21 | .btn-info.active, 22 | .btn-warning.active, 23 | .btn-danger.active { 24 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 25 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 26 | } 27 | 28 | .btn:active, 29 | .btn.active { 30 | background-image: none; 31 | } 32 | 33 | .btn-default { 34 | text-shadow: 0 1px 0 #fff; 35 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); 36 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); 37 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); 38 | background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); 39 | background-repeat: repeat-x; 40 | border-color: #e0e0e0; 41 | border-color: #ccc; 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 43 | } 44 | 45 | .btn-default:active, 46 | .btn-default.active { 47 | background-color: #e6e6e6; 48 | border-color: #e0e0e0; 49 | } 50 | 51 | .btn-primary { 52 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 53 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 54 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 55 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 56 | background-repeat: repeat-x; 57 | border-color: #2d6ca2; 58 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 59 | } 60 | 61 | .btn-primary:active, 62 | .btn-primary.active { 63 | background-color: #3071a9; 64 | border-color: #2d6ca2; 65 | } 66 | 67 | .btn-success { 68 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 69 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 70 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 71 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 72 | background-repeat: repeat-x; 73 | border-color: #419641; 74 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 75 | } 76 | 77 | .btn-success:active, 78 | .btn-success.active { 79 | background-color: #449d44; 80 | border-color: #419641; 81 | } 82 | 83 | .btn-warning { 84 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 85 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 86 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 87 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 88 | background-repeat: repeat-x; 89 | border-color: #eb9316; 90 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 91 | } 92 | 93 | .btn-warning:active, 94 | .btn-warning.active { 95 | background-color: #ec971f; 96 | border-color: #eb9316; 97 | } 98 | 99 | .btn-danger { 100 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 101 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 102 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 103 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 104 | background-repeat: repeat-x; 105 | border-color: #c12e2a; 106 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 107 | } 108 | 109 | .btn-danger:active, 110 | .btn-danger.active { 111 | background-color: #c9302c; 112 | border-color: #c12e2a; 113 | } 114 | 115 | .btn-info { 116 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 117 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 118 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 119 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 120 | background-repeat: repeat-x; 121 | border-color: #2aabd2; 122 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 123 | } 124 | 125 | .btn-info:active, 126 | .btn-info.active { 127 | background-color: #31b0d5; 128 | border-color: #2aabd2; 129 | } 130 | 131 | .thumbnail, 132 | .img-thumbnail { 133 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 134 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 135 | } 136 | 137 | .dropdown-menu > li > a:hover, 138 | .dropdown-menu > li > a:focus, 139 | .dropdown-menu > .active > a, 140 | .dropdown-menu > .active > a:hover, 141 | .dropdown-menu > .active > a:focus { 142 | background-color: #357ebd; 143 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 144 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 145 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 146 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 147 | background-repeat: repeat-x; 148 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 149 | } 150 | 151 | .navbar { 152 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 153 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); 154 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 155 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 156 | background-repeat: repeat-x; 157 | border-radius: 4px; 158 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 159 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 160 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 161 | } 162 | 163 | .navbar .navbar-nav > .active > a { 164 | background-color: #f8f8f8; 165 | } 166 | 167 | .navbar-brand, 168 | .navbar-nav > li > a { 169 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 170 | } 171 | 172 | .navbar-inverse { 173 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 174 | background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); 175 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 176 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 177 | background-repeat: repeat-x; 178 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 179 | } 180 | 181 | .navbar-inverse .navbar-nav > .active > a { 182 | background-color: #222222; 183 | } 184 | 185 | .navbar-inverse .navbar-brand, 186 | .navbar-inverse .navbar-nav > li > a { 187 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 188 | } 189 | 190 | .navbar-static-top, 191 | .navbar-fixed-top, 192 | .navbar-fixed-bottom { 193 | border-radius: 0; 194 | } 195 | 196 | .alert { 197 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 198 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 199 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 200 | } 201 | 202 | .alert-success { 203 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 204 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); 205 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 206 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 207 | background-repeat: repeat-x; 208 | border-color: #b2dba1; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 210 | } 211 | 212 | .alert-info { 213 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 214 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); 215 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 216 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 217 | background-repeat: repeat-x; 218 | border-color: #9acfea; 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 220 | } 221 | 222 | .alert-warning { 223 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 224 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); 225 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 226 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 227 | background-repeat: repeat-x; 228 | border-color: #f5e79e; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 230 | } 231 | 232 | .alert-danger { 233 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 234 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); 235 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 236 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 237 | background-repeat: repeat-x; 238 | border-color: #dca7a7; 239 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 240 | } 241 | 242 | .progress { 243 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 244 | background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); 245 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 246 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 247 | background-repeat: repeat-x; 248 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 249 | } 250 | 251 | .progress-bar { 252 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 253 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 254 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | background-repeat: repeat-x; 257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 258 | } 259 | 260 | .progress-bar-success { 261 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 262 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 263 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 264 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 265 | background-repeat: repeat-x; 266 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 267 | } 268 | 269 | .progress-bar-info { 270 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 271 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 272 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 273 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 274 | background-repeat: repeat-x; 275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 276 | } 277 | 278 | .progress-bar-warning { 279 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 280 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 281 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 282 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 283 | background-repeat: repeat-x; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 285 | } 286 | 287 | .progress-bar-danger { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 289 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 290 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 291 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 292 | background-repeat: repeat-x; 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 294 | } 295 | 296 | .list-group { 297 | border-radius: 4px; 298 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 299 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 300 | } 301 | 302 | .list-group-item.active, 303 | .list-group-item.active:hover, 304 | .list-group-item.active:focus { 305 | text-shadow: 0 -1px 0 #3071a9; 306 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 307 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); 308 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 309 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 310 | background-repeat: repeat-x; 311 | border-color: #3278b3; 312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 313 | } 314 | 315 | .panel { 316 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 317 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 318 | } 319 | 320 | .panel-default > .panel-heading { 321 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 322 | background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); 323 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 324 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 325 | background-repeat: repeat-x; 326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 327 | } 328 | 329 | .panel-primary > .panel-heading { 330 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 331 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 332 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 333 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 334 | background-repeat: repeat-x; 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 336 | } 337 | 338 | .panel-success > .panel-heading { 339 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 340 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); 341 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 342 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 343 | background-repeat: repeat-x; 344 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 345 | } 346 | 347 | .panel-info > .panel-heading { 348 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 349 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); 350 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 351 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 352 | background-repeat: repeat-x; 353 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 354 | } 355 | 356 | .panel-warning > .panel-heading { 357 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 358 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); 359 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 360 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 361 | background-repeat: repeat-x; 362 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 363 | } 364 | 365 | .panel-danger > .panel-heading { 366 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 367 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); 368 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 369 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 370 | background-repeat: repeat-x; 371 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 372 | } 373 | 374 | .well { 375 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 376 | background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); 377 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 378 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 379 | background-repeat: repeat-x; 380 | border-color: #dcdcdc; 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 382 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 383 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 384 | } -------------------------------------------------------------------------------- /webif/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /webif/css/slider.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Slider for Bootstrap 3 | * 4 | * Copyright 2012 Stefan Petre 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | */ 9 | .slider { 10 | display: inline-block; 11 | vertical-align: middle; 12 | position: relative; 13 | } 14 | .slider.slider-horizontal { 15 | width: 210px; 16 | height: 20px; 17 | } 18 | .slider.slider-horizontal .slider-track { 19 | height: 10px; 20 | width: 100%; 21 | margin-top: -5px; 22 | top: 50%; 23 | left: 0; 24 | } 25 | .slider.slider-horizontal .slider-selection { 26 | height: 100%; 27 | top: 0; 28 | bottom: 0; 29 | } 30 | .slider.slider-horizontal .slider-handle { 31 | margin-left: -10px; 32 | margin-top: -5px; 33 | } 34 | .slider.slider-horizontal .slider-handle.triangle { 35 | border-width: 0 10px 10px 10px; 36 | width: 0; 37 | height: 0; 38 | border-bottom-color: #0480be; 39 | margin-top: 0; 40 | } 41 | .slider.slider-vertical { 42 | height: 210px; 43 | width: 20px; 44 | } 45 | .slider.slider-vertical .slider-track { 46 | width: 10px; 47 | height: 100%; 48 | margin-left: -5px; 49 | left: 50%; 50 | top: 0; 51 | } 52 | .slider.slider-vertical .slider-selection { 53 | width: 100%; 54 | left: 0; 55 | top: 0; 56 | bottom: 0; 57 | } 58 | .slider.slider-vertical .slider-handle { 59 | margin-left: -5px; 60 | margin-top: -10px; 61 | } 62 | .slider.slider-vertical .slider-handle.triangle { 63 | border-width: 10px 0 10px 10px; 64 | width: 1px; 65 | height: 1px; 66 | border-left-color: #0480be; 67 | margin-left: 0; 68 | } 69 | .slider input { 70 | display: none; 71 | } 72 | .slider .tooltip-inner { 73 | white-space: nowrap; 74 | } 75 | .slider-track { 76 | position: absolute; 77 | cursor: pointer; 78 | background-color: #f7f7f7; 79 | background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); 80 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); 81 | background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); 82 | background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); 83 | background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); 84 | background-repeat: repeat-x; 85 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); 86 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 87 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 88 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 89 | -webkit-border-radius: 4px; 90 | -moz-border-radius: 4px; 91 | border-radius: 4px; 92 | } 93 | .slider-selection { 94 | position: absolute; 95 | background-color: #f7f7f7; 96 | background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5); 97 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5)); 98 | background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5); 99 | background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5); 100 | background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5); 101 | background-repeat: repeat-x; 102 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); 103 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 104 | -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 105 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 106 | -webkit-box-sizing: border-box; 107 | -moz-box-sizing: border-box; 108 | box-sizing: border-box; 109 | -webkit-border-radius: 4px; 110 | -moz-border-radius: 4px; 111 | border-radius: 4px; 112 | } 113 | .slider-handle { 114 | position: absolute; 115 | width: 20px; 116 | height: 20px; 117 | background-color: #0e90d2; 118 | background-image: -moz-linear-gradient(top, #149bdf, #0480be); 119 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); 120 | background-image: -webkit-linear-gradient(top, #149bdf, #0480be); 121 | background-image: -o-linear-gradient(top, #149bdf, #0480be); 122 | background-image: linear-gradient(to bottom, #149bdf, #0480be); 123 | background-repeat: repeat-x; 124 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); 125 | -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 126 | -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 127 | box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 128 | opacity: 0.8; 129 | border: 0px solid transparent; 130 | } 131 | .slider-handle.round { 132 | -webkit-border-radius: 20px; 133 | -moz-border-radius: 20px; 134 | border-radius: 20px; 135 | } 136 | .slider-handle.triangle { 137 | background: transparent none; 138 | } -------------------------------------------------------------------------------- /webif/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url(../img/sos.png); 3 | padding-top: 10px; 4 | } 5 | 6 | select { 7 | -webkit-appearance: none; 8 | appearance: none !important; 9 | } 10 | 11 | .navbar { 12 | margin-bottom: 0px; 13 | } 14 | .container { 15 | position: relative; 16 | padding: 5px; 17 | } 18 | 19 | 20 | #content h1 { 21 | padding: 10px; 22 | display: block; 23 | background: #fff; 24 | opacity: 0.4; 25 | font-size: 4em; 26 | font-weight: bold; 27 | } 28 | 29 | #loader { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | background: rgba(0,0,0,0.9) url(../img/loader.gif) center center no-repeat; 34 | width: 100%; 35 | height: 100%; 36 | z-index: 9999; 37 | } 38 | 39 | #status { 40 | position: absolute; 41 | z-index: 9999; 42 | top: 60px; 43 | left: 50%; 44 | opacity: 0.9; 45 | margin-left: -25%; 46 | margin-top: 10px; 47 | width: 50%; 48 | text-align: center; 49 | } 50 | 51 | .btn.btn-show-controlls { 52 | position: absolute; 53 | display: none; 54 | border: 3px dotted rgba(0,0,0,0.6); 55 | background: none; 56 | z-index: 10; 57 | top: -50px; 58 | font-size: 50px; 59 | border-radius: 50px; 60 | padding-top: 40px; 61 | width: 100px; 62 | height: 100px; 63 | opacity: 0.5; 64 | text-align: center; 65 | left: 50%; 66 | margin-left: -50px; 67 | } 68 | .btn.btn-ultra { 69 | border: 3px dotted rgba(0,0,0,0.6); 70 | margin-top: 10%; 71 | padding: 18px 27px; 72 | font-size: 100px; 73 | line-height: 1.33; 74 | border-radius: 6px; 75 | opacity: 0.8; 76 | margin-right: 20px; 77 | } 78 | 79 | #content { 80 | position: relative; 81 | overflow: hidden; 82 | background: #fff; 83 | padding: 5px; 84 | min-height: 750px; 85 | } 86 | 87 | #settings-panel { 88 | position: absolute; 89 | display: none; 90 | bottom: -21px; 91 | width: 100%; 92 | left: 0%; 93 | } 94 | 95 | #content.gallery { 96 | background: url(../img/debut_dark.png); 97 | } 98 | 99 | #images img, #images .img-thumbnail { 100 | cursor: pointer; 101 | margin: 20px; 102 | opacity: 0.3; 103 | width: 160px; 104 | height: 120px; 105 | min-height: 80px; 106 | 107 | } 108 | 109 | 110 | #images img:hover { 111 | opacity: 1; 112 | } 113 | 114 | .modal-dialog { 115 | width: 95%; 116 | height: 95%; 117 | } 118 | 119 | .modal-content { 120 | background: url(../img/debut_dark.png); 121 | } 122 | 123 | #camanimage { 124 | max-width: 90%; 125 | max-height: 90% 126 | } 127 | 128 | .modal-footer { 129 | text-align: center; 130 | } 131 | 132 | .image-container { 133 | width: 100%; 134 | text-align:center; 135 | } 136 | .sidebar { 137 | float: right; 138 | width: 20%; 139 | } 140 | 141 | .sidebar .slider { 142 | width: 180px; 143 | } 144 | 145 | 146 | #result_image { 147 | position: absolute; 148 | z-index: 0; 149 | top: 0px; 150 | left: -5px; 151 | max-height: 100%; 152 | width: 101%; 153 | } -------------------------------------------------------------------------------- /webif/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /webif/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /webif/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /webif/img/debut_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/img/debut_dark.png -------------------------------------------------------------------------------- /webif/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/img/loader.gif -------------------------------------------------------------------------------- /webif/img/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/img/pic.jpg -------------------------------------------------------------------------------- /webif/img/sos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scheckmedia/CameraControllerApi/36d132560d1faf1f550e2a13d370334b1bb6e618/webif/img/sos.png -------------------------------------------------------------------------------- /webif/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CameraControllerApi - Webif 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | 32 | 43 | 44 | 95 | 96 | 102 | 103 | 125 | 126 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /webif/js/app.js: -------------------------------------------------------------------------------- 1 | window.Camera = Backbone.Model.extend({ 2 | 3 | rootUrl : window.location.protocol + '//' + window.location.hostname + (location.port && ":" + location.port) + "/", 4 | settings : null, 5 | images : null, 6 | cameraFound : false, 7 | 8 | initialize:function () { 9 | this.connect(); 10 | }, 11 | 12 | getFileList : function(callback){ 13 | if(this.images == null){ 14 | Backbone.Notifications.trigger("loader:show"); 15 | var $this = this; 16 | this.sendRequest('fs?action=list', function(result){ 17 | Backbone.Notifications.trigger("loader:hide"); 18 | if($this.isSuccess(result)){ 19 | if(callback) 20 | callback(result.cca_response.data.folder); 21 | 22 | this.images = result; 23 | } 24 | Backbone.Notifications.trigger("alert", 1, "status: " + result.cca_response.state, 3000); 25 | }); 26 | } else { 27 | if(callback) 28 | callback(this.images); 29 | } 30 | }, 31 | 32 | reloadFileList : function(){ 33 | this.images = null; 34 | getFileList(); 35 | }, 36 | 37 | getImage : function(image, path, callback){ 38 | Backbone.Notifications.trigger("loader:show"); 39 | var $this = this; 40 | this.sendRequest('fs?action=get&value='+image+'&path='+path, function(result){ 41 | Backbone.Notifications.trigger("loader:hide"); 42 | if($this.isSuccess(result)){ 43 | if(callback) 44 | callback(result.cca_response.data); 45 | 46 | this.images = result; 47 | } 48 | Backbone.Notifications.trigger("alert", 1, "status: " + result.cca_response.state, 3000); 49 | }); 50 | 51 | }, 52 | 53 | shot : function(){ 54 | Backbone.Notifications.trigger("loader:show"); 55 | var $this = this; 56 | this.sendRequest('capture?action=shot', function(result){ 57 | Backbone.Notifications.trigger("loader:hide"); 58 | if($this.isSuccess(result)){ 59 | $('#result_image').attr("src", "data:image/gif;base64," + result.cca_response.data.image); 60 | } 61 | Backbone.Notifications.trigger("alert", 1, "status: " + result.cca_response.state, 3000); 62 | }); 63 | }, 64 | 65 | connect : function() { 66 | Backbone.Notifications.trigger("loader:show"); 67 | var $this = this; 68 | this.sendRequest('camera?action=status', function(result){ 69 | if($this.isSuccess(result)) { 70 | $this.cameraFound = true; 71 | $this.sendRequest('settings?action=list', function(result){ 72 | if($this.isSuccess(result)){ 73 | $this.settings = result.cca_response.data.main; 74 | Backbone.Notifications.trigger("alert", 1, "connection " + result.cca_response.state, 3000); 75 | } else { 76 | Backbone.Notifications.trigger("alert", 4, "connection " + result.cca_response.state, 3000); 77 | } 78 | Backbone.Notifications.trigger("loader:hide"); 79 | }); 80 | } 81 | }); 82 | }, 83 | 84 | setKeyValue : function(key, value){ 85 | Backbone.Notifications.trigger("loader:show"); 86 | var $this = this; 87 | this.sendRequest('settings?action='+key+'&value='+value, function(result){ 88 | if($this.isSuccess(result)){ 89 | Backbone.Notifications.trigger("alert", 1, "connection " + result.cca_response.state, 3000); 90 | } else { 91 | Backbone.Notifications.trigger("alert", 4, "connection " + result.cca_response.state, 3000); 92 | } 93 | Backbone.Notifications.trigger("loader:hide"); 94 | }); 95 | }, 96 | 97 | sendRequest : function(params, callback){ 98 | $.ajax({ 99 | url: this.rootUrl + params, 100 | //url: "http://localhost:8888/" + params, 101 | dataType : "json", 102 | success:function(result){ 103 | callback(result); 104 | }, 105 | error : function(xhr, status, error){ 106 | Backbone.Notifications.trigger("loader:hide"); 107 | Backbone.Notifications.trigger("alert", 4, "status " + status, 3000); 108 | } 109 | }); 110 | }, 111 | 112 | isSuccess : function(result){ 113 | if(result.cca_response.state == "success")return true; 114 | else false; 115 | } 116 | }); 117 | 118 | window.Navigation = Backbone.View.extend({ 119 | el : '#navi', 120 | 121 | initialize : function(){ 122 | this.render(); 123 | }, 124 | 125 | render : function(){ 126 | var template = _.template( $("#navigation_template").html(), {} ); 127 | this.$el.html( template ); 128 | }, 129 | 130 | events : { 131 | 'click a' : 'navigate', 132 | }, 133 | 134 | navigate: function(el) { 135 | $(this.el).find('.active').removeClass('active'); 136 | Backbone.history.navigate($(el.target).attr('href'), { trigger: true }); 137 | $(el.target).parent().addClass('active'); 138 | return false; 139 | } 140 | }); 141 | 142 | window.Loader = Backbone.View.extend({ 143 | el : '#loader', 144 | 145 | initialize : function(){ 146 | Backbone.Notifications.on("loader:hide", this.hide, this); 147 | Backbone.Notifications.on("loader:show", this.show, this); 148 | }, 149 | 150 | hide : function(){ 151 | $(this.el).fadeOut(); 152 | }, 153 | 154 | show : function(){ 155 | $(this.el).fadeIn(); 156 | } 157 | 158 | }); 159 | 160 | window.StatusBox = Backbone.View.extend({ 161 | el : '#status', 162 | 163 | initialize : function(){ 164 | Backbone.Notifications.on("alert", this.setAlert, this); 165 | }, 166 | 167 | setAlert: function(status, text, time) { 168 | var alertClass = ""; 169 | switch(status){ 170 | case 1: 171 | alertClass = "alert-success"; 172 | break; 173 | 174 | case 2: 175 | alertClass = "alert-info"; 176 | break; 177 | 178 | case 3: 179 | alertClass = "alert-warning"; 180 | break; 181 | 182 | case 4: 183 | alertClass = "alert-danger"; 184 | break; 185 | 186 | default: 187 | alertClass = ""; 188 | } 189 | 190 | $(this.el).stop().fadeIn().removeAttr("class").addClass("alert " + alertClass).html(text).delay(time).fadeOut(); 191 | } 192 | }); 193 | 194 | window.EditBox = Backbone.View.extend({ 195 | el : '#dialog', 196 | 197 | initialize : function(){ 198 | 199 | }, 200 | 201 | events : { 202 | 'click .exit' : 'close', 203 | 'click .save' : 'save', 204 | 'slide .slider' : 'slide', 205 | 'slideStop .slider' : 'slideStop' 206 | }, 207 | 208 | open : function(base64){ 209 | var template = _.template( $("#editbox_template").html(), {} ); 210 | this.$el.html( template ); 211 | 212 | $('#editbox').modal('show'); 213 | this.$el.find('.image-container').html(''); 214 | $('.slider').slider() 215 | }, 216 | 217 | save : function(){ 218 | console.log("save"); 219 | }, 220 | 221 | close : function(){ 222 | $('#editbox').modal('hide'); 223 | }, 224 | slideStop : function(){ 225 | } 226 | }); 227 | 228 | window.ControlView = Backbone.View.extend({ 229 | isConfigured : false, 230 | model : Camera, 231 | 232 | initialize : function(){ 233 | this.render(); 234 | }, 235 | 236 | render : function(){ 237 | var template = _.template( $("#control_template").html(), {} ); 238 | this.$el.html( template ); 239 | $('btn').tooltip(); 240 | }, 241 | 242 | events : { 243 | 'click button#shot' : 'shot', 244 | 'click button#show' : 'fullscreen', 245 | 'click button#settings' : 'settings', 246 | 'click button#show-control' : 'fullscreen', 247 | 'change #aperture' : 'change', 248 | 'change #shutterspeed' : 'change', 249 | 'change #iso' : 'change', 250 | 'change #whitebalance' : 'change' 251 | }, 252 | 253 | shot : function(){ 254 | app.camera.shot(); 255 | }, 256 | 257 | fullscreen : function(){ 258 | $('#controls').slideToggle(function(){ 259 | $('#show-control').slideToggle('slow', 'easeOutBounce'); 260 | $('#settings-panel').slideUp('slow', 'easeOutBounce'); 261 | }); 262 | }, 263 | 264 | settings : function(){ 265 | if(this.isConfigured == false){ 266 | this.configure(); 267 | } 268 | $('#settings-panel').slideToggle('slow', 'easeOutBounce'); 269 | }, 270 | 271 | change : function(e){ 272 | var el = $(e.target); 273 | app.camera.setKeyValue(el.attr("name"), el.val()); 274 | }, 275 | 276 | configure : function(){ 277 | if(!app.camera.cameraFound) 278 | return; 279 | 280 | var oIso = app.camera.settings.imgsettings.iso; 281 | var el = $('#iso'); 282 | this.setOptionForElement(el, oIso.choices, oIso.value); 283 | 284 | var oAperture = app.camera.settings.capturesettings["f-number"]; 285 | var el = $('#aperture'); 286 | this.setOptionForElement(el, oAperture.choices, oAperture.value); 287 | 288 | var oShutterspeed = app.camera.settings.capturesettings.shutterspeed2; 289 | var el = $('#shutterspeed'); 290 | this.setOptionForElement(el, oShutterspeed.choices, oShutterspeed.value); 291 | 292 | var oWhiteBalance = app.camera.settings.imgsettings.whitebalance; 293 | var el = $('#whitebalance'); 294 | this.setOptionForElement(el, oWhiteBalance.choices, oWhiteBalance.value); 295 | 296 | this.isConfigured = true; 297 | }, 298 | 299 | setOptionForElement : function(el, values, selected){ 300 | $.each(values, function(index, value){ 301 | var option = $('').val(value).html(value); 302 | if(selected == value) 303 | option.attr("selected","selected"); 304 | 305 | el.append(option); 306 | }); 307 | } 308 | }); 309 | 310 | window.GalleryView = Backbone.View.extend({ 311 | initialize : function(){ 312 | this.render(); 313 | this.loadItems(); 314 | }, 315 | 316 | render : function(){ 317 | var template = _.template( $("#gallery_template").html(), {} ); 318 | this.$el.html( template ); 319 | }, 320 | 321 | loadItems : function(){ 322 | app.camera.getFileList(function(response){ 323 | var container = $('#images'); 324 | $.each(response.files, function(index, value){ 325 | var img; 326 | if(value.thumbnail){ 327 | img = ''+value.name+''; 328 | } else { 329 | img = $('').html(value.name).addClass("img-thumbnail"); 330 | img.attr({ 331 | 'data-path' : response.absolute_path, 332 | 'data-name' : value.name 333 | }); 334 | } 335 | 336 | container.append(img); 337 | }); 338 | }); 339 | }, 340 | 341 | events : { 342 | 'click .img-thumbnail' : 'openImage', 343 | }, 344 | 345 | openImage : function(e){ 346 | var path = $(e.target).attr('data-path'); 347 | var image = $(e.target).attr('data-name'); 348 | 349 | app.camera.getImage($.trim(image),$.trim(path),function(result){ 350 | var editbox = new EditBox(); 351 | editbox.open(result.image); 352 | }); 353 | }, 354 | }); 355 | 356 | window.InfoView = Backbone.View.extend({ 357 | initialize : function(){ 358 | this.render(); 359 | }, 360 | 361 | render : function(){ 362 | var template = _.template( $("#info_template").html(), {} ); 363 | this.$el.html( template ); 364 | }, 365 | }); 366 | 367 | 368 | window.CameraControllerApi = Backbone.Router.extend({ 369 | routes : { 370 | "" : "control", 371 | "control" : "control", 372 | "gallery" : "gallery", 373 | "info" : "info" 374 | }, 375 | 376 | initialize : function(){ 377 | this.camera = new Camera(); 378 | this.statusBox = new StatusBox(); 379 | this.loader = new Loader(); 380 | }, 381 | 382 | control : function(){ 383 | if(!this.vControl){ 384 | this.vControl = new ControlView(); 385 | } else { 386 | this.vControl.delegateEvents(); 387 | } 388 | $('#content').html(this.vControl.el).removeAttr("class").addClass("control"); 389 | }, 390 | 391 | gallery : function(){ 392 | if(!this.vGallery){ 393 | this.vGallery = new GalleryView(); 394 | this.vGallery.render(); 395 | } else { 396 | this.vGallery.delegateEvents(); 397 | } 398 | 399 | $('#content').html(this.vGallery.el).removeAttr("class").addClass("gallery"); 400 | }, 401 | 402 | info : function(){ 403 | if(!this.vInfo){ 404 | this.vInfo = new InfoView(); 405 | this.vInfo.render(); 406 | } else { 407 | this.vInfo.delegateEvents(); 408 | } 409 | 410 | $('#content').html(this.vInfo.el).removeAttr("class").addClass("gallery"); 411 | } 412 | 413 | }); 414 | 415 | window.utils = { 416 | 417 | // Asynchronously load templates located in separate .html files 418 | loadTemplate: function(views, callback) { 419 | 420 | var deferreds = []; 421 | 422 | $.each(views, function(index, view) { 423 | if (window[view]) { 424 | deferreds.push($.get('tpl/' + view + '.html', function(data) { 425 | window[view].prototype.template = _.template(data); 426 | })); 427 | } else { 428 | alert(view + " not found"); 429 | } 430 | }); 431 | 432 | $.when.apply(null, deferreds).done(callback); 433 | }, 434 | }; 435 | 436 | $(function() { 437 | Backbone.Notifications = {}; 438 | _.extend(Backbone.Notifications, Backbone.Events); 439 | 440 | nav = new Navigation(); 441 | app = new CameraControllerApi(); 442 | Backbone.history.start(); 443 | }); 444 | -------------------------------------------------------------------------------- /webif/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.1.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=T[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&E){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var E=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var k=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var $=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(k.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace(S,"(?:$1)?").replace($,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/[?#].*$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=P.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(O,"/");if(r&&this._wantsHashChange){this.iframe=a.$('