コード例 #1
0
ファイル: event.c プロジェクト: inscriptionweb/pikrellcam
void
at_command_add(char *at_line)
	{
	AtCommand	*at;
	char		 frequency[32], at_time[64], cmd[200];
	int			 n;

	frequency[0] = '\0';
	n = sscanf(at_line, "%31s %63s \"%199[^\n\"]", frequency, at_time, cmd);
	if (frequency[0] == '#' || frequency[0] == '\n' || frequency[0] == '\0')
		return;
	if (n != 3)
		{
		log_printf_no_timestamp("Bad at command: %s\n", at_line);
		return;
		}
	at = (AtCommand *) calloc(1, sizeof(AtCommand));
	at->frequency = strdup(frequency);
	at->at_time = strdup(at_time);
	at->command = strdup(cmd);
	at_command_list = slist_append(at_command_list, at);

	log_printf_no_timestamp("at_command_add [%s] at: %s  command: [%s]\n",
				at->frequency, at->at_time, at->command);
	}
コード例 #2
0
ファイル: event.c プロジェクト: inscriptionweb/pikrellcam
void
sun_times_init(void)
	{
	struct tm	*tm = &pikrellcam.tm_local;
	int		n, year, month, mday;

	if (   sscanf(pikrellcam.latitude, "%lf", &sun.latitude) == 1
	    && sscanf(pikrellcam.longitude, "%lf", &sun.longitude) == 1
	   )
		{
		n = strlen(pikrellcam.latitude);
		if (pikrellcam.latitude[n - 1] == 'S' || pikrellcam.latitude[n - 1] == 's')
			sun.latitude = -sun.latitude;
		n = strlen(pikrellcam.longitude);
		if (pikrellcam.longitude[n - 1] == 'W' || pikrellcam.longitude[n - 1] == 'w')
			sun.longitude = -sun.longitude;

		year  = tm->tm_year + 1900;
		month = tm->tm_mon  + 1;
		mday  = tm->tm_mday + 1;

		sun.sun_valid = sun_rise_set(year, month, mday,
						sun.longitude, sun.latitude,
						&sun.d_sunrise, &sun.d_sunset);
		sun.civil_valid = civil_twilight(year, month, mday,
						sun.longitude, sun.latitude,
						&sun.d_dawn, &sun.d_dusk);
		sun.nautical_valid = nautical_twilight(year, month, mday,
						sun.longitude, sun.latitude,
						&sun.d_nautical_dawn, &sun.d_nautical_dusk);

		sun.d_sunrise = TMOD(sun.d_sunrise + tm->tm_gmtoff / 3600);
		sun.sunrise = sun.d_sunrise * 60;

		sun.d_sunset  = TMOD(sun.d_sunset  + tm->tm_gmtoff / 3600);
		sun.sunset = sun.d_sunset * 60;

		sun.d_dawn = TMOD(sun.d_dawn + tm->tm_gmtoff / 3600);
		sun.dawn = sun.d_dawn * 60;

		sun.d_dusk = TMOD(sun.d_dusk + tm->tm_gmtoff / 3600);
		sun.dusk = sun.d_dusk * 60;

		sun.d_nautical_dawn = TMOD(sun.d_nautical_dawn + tm->tm_gmtoff / 3600);
		sun.nautical_dawn = sun.d_nautical_dawn * 60;

		sun.d_nautical_dusk = TMOD(sun.d_nautical_dusk + tm->tm_gmtoff / 3600);
		sun.nautical_dusk = sun.d_nautical_dusk * 60;

		log_printf_no_timestamp("sunrise/sunset times: %s  dawn/dusk times: %s\n",
			sun.sun_valid ? "invalid" : "valid",
			sun.civil_valid ? "invalid" : "valid");
		log_printf_no_timestamp("  dawn:    %d:%02d\n", sun.dawn / 60, sun.dawn % 60);
		log_printf_no_timestamp("  sunrise: %d:%02d\n", sun.sunrise / 60, sun.sunrise % 60);
		log_printf_no_timestamp("  sunset:  %d:%02d\n", sun.sunset / 60, sun.sunset % 60);
		log_printf_no_timestamp("  dusk:    %d:%02d\n", sun.dusk / 60, sun.dusk % 60);
		}
	}
コード例 #3
0
ファイル: pikrellcam.c プロジェクト: tyborall/pikrellcam
boolean
make_fifo(char *fifo_path)
    {
    boolean		fifo_exists;

	if ((fifo_exists = isfifo(fifo_path)) == FALSE)
		{
        if (mkfifo(fifo_path, 0664) < 0)
		    log_printf_no_timestamp("Make fifo %s failed: %m\n", fifo_path);
		else
			fifo_exists = TRUE;
		}
	if (fifo_exists)
		check_modes(fifo_path, 0664);
	return fifo_exists;
	}
コード例 #4
0
ファイル: pikrellcam.c プロジェクト: tyborall/pikrellcam
static boolean
make_dir(char *dir)
	{
	boolean 		dir_exists;

	if ((dir_exists = isdir(dir)) == FALSE)
		{
		exec_wait("sudo mkdir -p $F", dir);
		if ((dir_exists = isdir(dir)) == FALSE)
			log_printf_no_timestamp("Make directory failed: %s\n", dir);
		else
			dir_exists = TRUE;
		}
	if (dir_exists)
		check_modes(dir, 0775);
	return dir_exists;
	}
コード例 #5
0
ファイル: event.c プロジェクト: inscriptionweb/pikrellcam
void
event_process(void)
	{
	Event		*event;
	AtCommand	*at;
	SList	*list,
			*prev_link = NULL,
			*next_link,
			*expired_link,
			tmp_link;
	int		minute_tick, five_minute_tick, ten_minute_tick,
			fifteen_minute_tick, thirty_minute_tick, hour_tick, day_tick;
	struct tm		*tm_now;
	static struct tm tm_prev;
	static time_t	t_prev;

	time(&pikrellcam.t_now);
	pikrellcam.second_tick = (pikrellcam.t_now == t_prev) ? FALSE : TRUE;
	t_prev = pikrellcam.t_now;

	if (pikrellcam.second_tick)
		{
		tm_prev = pikrellcam.tm_local;
		tm_now = localtime(&pikrellcam.t_now);
		minute_tick = (tm_now->tm_min != tm_prev.tm_min)  ? TRUE : FALSE;
		pikrellcam.tm_local = *tm_now;
		}
	else
		minute_tick = FALSE;

	if (pikrellcam.state_modified || minute_tick)
		{
		pikrellcam.state_modified = FALSE;
		state_file_write();
		}

	tmp_link.next = NULL;
	for (list = event_list; list; prev_link = list, list = list->next)
		{
		/* Event loop processing is done with the list unlocked until we
		|  have an expired link that must be removed from the list so that
		|  called event functions cad add events to the list.
		|  If another thread adds an event, only the last list->next
		|  will be modified and this loop will catch it or it won't.
		*/
		event = (Event *) list->data;
		if (   (event->time == 0 && event->count == 0)
			|| event->func == NULL
		   )				/* not activated */
			continue;

		if (event->time > 0 && event->time > pikrellcam.t_now)
			continue;
		else if (event->count > 0 && --event->count > 0)
			continue;

		if (pikrellcam.verbose)
			printf("Event func -> [%s] period=%d\n",
						event->name, (int) event->period);
		if (event->func)
			(*event->func)(event->data);
		if (event->period > 0)
			event->time += event->period;
		else
			{
			pthread_mutex_lock(&event_mutex);
			expired_link = list;
			if (list == event_list)
				{
				event_list = list->next;
				tmp_link.next = list->next;
				list = &tmp_link;
				}
			else
				{
				next_link = list->next;
				list = prev_link;
				list->next = next_link;
				}
			free(expired_link->data);
			free(expired_link);
			pthread_mutex_unlock(&event_mutex);
			}
		}

	if (minute_tick)
		{
		char		*p, buf[IBUF_LEN];
		int			i, n, minute_now, minute_at, minute_offset = 0;
		static int	start = TRUE;
		static int	at_notify_fd, at_notify_wd;
		struct inotify_event *event;

		if (at_notify_fd == 0)
			{
			at_notify_fd = inotify_init();
			if (at_notify_fd > 0)
				{
				fcntl(at_notify_fd, F_SETFL,
						fcntl(at_notify_fd, F_GETFL) | O_NONBLOCK);
				at_notify_wd = inotify_add_watch(at_notify_fd,
						pikrellcam.config_dir, IN_CREATE | IN_MODIFY);
				}
			}
		else if (at_notify_wd > 0)
			{
			n = read(at_notify_fd, buf, IBUF_LEN);
			if (n > 0)
				{
				for (i = 0; i < n; i += sizeof(*event) + event->len)
					{
					event = (struct inotify_event *) &buf[i];
					if (   event->len > 0
					    && !strcmp(event->name,  PIKRELLCAM_AT_COMMANDS_CONFIG)
					   )
						at_commands_config_load(pikrellcam.at_commands_config_file);
					}
				}
			}

		tm_now = &pikrellcam.tm_local;
		minute_now = tm_now->tm_hour * 60 + tm_now->tm_min;

		five_minute_tick = ((tm_now->tm_min % 5) == 0) ? TRUE : FALSE;
		ten_minute_tick = ((tm_now->tm_min % 10) == 0) ? TRUE : FALSE;
		fifteen_minute_tick = ((tm_now->tm_min % 15) == 0) ? TRUE : FALSE;
		thirty_minute_tick = ((tm_now->tm_min % 10) == 0) ? TRUE : FALSE;
		hour_tick = (tm_now->tm_hour  != tm_prev.tm_hour)  ? TRUE : FALSE;
		day_tick =  (tm_now->tm_mday  != tm_prev.tm_mday)  ? TRUE : FALSE;

		if (day_tick || !sun.initialized)
			{
			if (sun.initialized)
				{
				char	tbuf[32];

				log_printf_no_timestamp("\n========================================================\n");
				strftime(tbuf, sizeof(tbuf), "%F", localtime(&pikrellcam.t_now));
				log_printf_no_timestamp("%s ================== New Day ==================\n", tbuf);
				log_printf_no_timestamp("========================================================\n");

				strftime(tbuf, sizeof(tbuf), "%F", localtime(&pikrellcam.t_now));
				}
			sun_times_init();
			sun.initialized = TRUE;
			log_lines();
			state_file_write();
			}

		for (list = at_command_list; list; list = list->next)
			{
			at = (AtCommand *) list->data;

			if ((p = strchr(at->at_time, '+')) != NULL)
				minute_offset = atoi(p + 1);
			else if ((p = strchr(at->at_time, '-')) != NULL)
				minute_offset = -atoi(p + 1);

			if (!strcmp(at->at_time, "start"))
				minute_at = start ? minute_now : 0;
			else if (!strcmp(at->at_time, "minute"))
				minute_at = minute_now;
			else if (!strcmp(at->at_time, "5minute"))
				minute_at = five_minute_tick ? minute_now : 0;
			else if (!strcmp(at->at_time, "10minute"))
				minute_at = ten_minute_tick ? minute_now : 0;
			else if (!strcmp(at->at_time, "15minute"))
				minute_at = fifteen_minute_tick ? minute_now : 0;
			else if (!strcmp(at->at_time, "30minute"))
				minute_at = thirty_minute_tick ? minute_now : 0;
			else if (!strcmp(at->at_time, "hour"))
				minute_at = hour_tick ? minute_now : 0;
			else if (!strncmp(at->at_time, "dawn", 4))
				minute_at = sun.dawn + minute_offset;
			else if (!strncmp(at->at_time, "dusk", 4))
				minute_at = sun.dusk + minute_offset;
			else if (!strncmp(at->at_time, "sunrise", 7))
				minute_at = sun.sunrise + minute_offset;
			else if (!strncmp(at->at_time, "sunset", 6))
				minute_at = sun.sunset + minute_offset;
			else if (!strncmp(at->at_time, "nautical_dawn", 13))
				minute_at = sun.nautical_dawn + minute_offset;
			else if (!strncmp(at->at_time, "nautical_dusk", 13))
				minute_at = sun.nautical_dusk + minute_offset;
			else
				{
				minute_at = (int) strtol(at->at_time, &p, 10) * 60;
				if (*p == ':')
					minute_at += strtol(p + 1, NULL, 10);
				else
					{
					minute_at = 0;	/* error in at_time string */
					log_printf("Error in at_command: [%s] bad at_time: [%s]\n",
							at->command, at->at_time);
					}
				}
			if (minute_now != minute_at)
				continue;

			/* Have a time match so check frequency.
			*/
			if (   !strcmp(at->frequency, "daily")
			    || (   !strcmp(at->frequency, "Sat-Sun")
			        && (tm_now->tm_wday == 0 || tm_now->tm_wday == 6)
			       )
			    || (   !strcmp(at->frequency, "Mon-Fri")
			        && tm_now->tm_wday > 0 && tm_now->tm_wday < 6
			       )
			    || (   (p = strstr(weekdays, at->frequency)) != NULL
			        && (p - weekdays) / 3 == tm_now->tm_wday
			       )
			   )
				{
				if (*(at->command) == '@')
					command_process(at->command + 1);
				else
					exec_no_wait(at->command, NULL);
				}
			}
		start = FALSE;
		if (pikrellcam.config_modified)
			config_save(pikrellcam.config_file);
		}
	}
コード例 #6
0
ファイル: pikrellcam.c プロジェクト: tyborall/pikrellcam
int
main(int argc, char *argv[])
	{
	int		fifo;
	int	 	i, n;
	char	*opt, *arg, *equal_arg, *homedir, *user;
	char	*line, *eol, buf[4096];

	pgm_name = argv[0];
	bcm_host_init();
	setlocale(LC_TIME, "");

	time(&pikrellcam.t_now);

	config_set_defaults();

	for (i = 1; i < argc; i++)
		get_arg_pass1(argv[i]);

	if (!config_load(pikrellcam.config_file))
		config_save(pikrellcam.config_file);
	if (!motion_regions_config_load(pikrellcam.motion_regions_config_file))
		motion_regions_config_save(pikrellcam.motion_regions_config_file);

	if (*pikrellcam.log_file != '/')
		{
		snprintf(buf, sizeof(buf), "%s/%s", pikrellcam.install_dir, pikrellcam.log_file);
		dup_string(&pikrellcam.log_file, buf);
		}
	if (!quit_flag)
		{
		log_printf_no_timestamp("\n========================================================\n");
		strftime(buf, sizeof(buf), "%F %T", localtime(&pikrellcam.t_now));
		log_printf_no_timestamp("%s ===== PiKrellCam %s started =====\n", buf, pikrellcam.version);
		log_printf_no_timestamp("========================================================\n");
		}

	if (!at_commands_config_load(pikrellcam.at_commands_config_file))
		at_commands_config_save(pikrellcam.at_commands_config_file);

	for (i = 1; i < argc; i++)
		{
		if (get_arg_pass1(argv[i]))
			continue;
		opt = argv[i];

		/* Just for initial install-pikrellcam.sh run to create config files.
		*/
		if (!strcmp(opt, "-quit"))
			exit(0);

		/* Accept: --opt arg   -opt arg    opt=arg    --opt=arg    -opt=arg
		*/
		for (i = 0; i < 2; ++i)
			if (*opt == '-')
				++opt;
		if ((equal_arg = strchr(opt, '=')) != NULL)
			{
			*equal_arg++ = '\0';
			arg = equal_arg;
			++i;
			}
		else
			arg = argv[i + 1];

		/* For camera parameters, do not set the camera, only replace
		|  values in the parameter table.
		*/
		if (   !config_set_option(opt, arg, TRUE)
		    && !mmalcam_config_parameter_set(opt, arg, FALSE)
		   )
			{
			log_printf_no_timestamp("Bad arg: %s\n", opt);
			exit(1);
			}
		}

	homedir = getpwuid(geteuid())->pw_dir;
	user = strrchr(homedir, '/');
	pikrellcam.effective_user = strdup(user ? user + 1 : "pi");

	if (*pikrellcam.media_dir != '/')
		{
		snprintf(buf, sizeof(buf), "%s/%s", pikrellcam.install_dir, pikrellcam.media_dir);
		dup_string(&pikrellcam.media_dir, buf);
		}

	snprintf(buf, sizeof(buf), "%s/%s", pikrellcam.install_dir, "www");
	check_modes(buf, 0775);

	asprintf(&pikrellcam.command_fifo, "%s/www/FIFO", pikrellcam.install_dir);
	asprintf(&pikrellcam.script_dir, "%s/scripts", pikrellcam.install_dir);
	asprintf(&pikrellcam.mjpeg_filename, "%s/mjpeg.jpg", pikrellcam.mjpeg_dir);

	log_printf_no_timestamp("using FIFO: %s\n", pikrellcam.command_fifo);
	log_printf_no_timestamp("using mjpeg: %s\n", pikrellcam.mjpeg_filename);


	/* Subdirs must match www/config.php and the init script is supposed
	|  to take care of that.
	*/
	asprintf(&pikrellcam.video_dir, "%s/%s", pikrellcam.media_dir, PIKRELLCAM_VIDEO_SUBDIR);
	asprintf(&pikrellcam.thumb_dir, "%s/%s", pikrellcam.media_dir, PIKRELLCAM_THUMBS_SUBDIR);
	asprintf(&pikrellcam.still_dir, "%s/%s", pikrellcam.media_dir, PIKRELLCAM_STILL_SUBDIR);
	asprintf(&pikrellcam.timelapse_dir, "%s/%s", pikrellcam.media_dir, PIKRELLCAM_TIMELAPSE_SUBDIR);

	if (!make_dir(pikrellcam.media_dir))
		exit(1);

	snprintf(buf, sizeof(buf), "%s/scripts-dist/_init $I $m $M $P $G",
								pikrellcam.install_dir);
	exec_wait(buf, NULL);

	/* User may have enabled a mount disk on media_dir
	*/
	exec_wait(pikrellcam.on_startup_cmd, NULL);
	check_modes(pikrellcam.media_dir, 0775);
	check_modes(pikrellcam.log_file, 0664);

	if (   !make_dir(pikrellcam.mjpeg_dir)
	    || !make_dir(pikrellcam.video_dir)
	    || !make_dir(pikrellcam.thumb_dir)
	    || !make_dir(pikrellcam.still_dir)
	    || !make_dir(pikrellcam.timelapse_dir)
	    || !make_fifo(pikrellcam.command_fifo)
	   )
		exit(1);

	if ((fifo = open(pikrellcam.command_fifo, O_RDONLY | O_NONBLOCK)) < 0)
		{
		log_printf("Failed to open FIFO: %s.  %m\n", pikrellcam.command_fifo);
		exit(1);
		}

	fcntl(fifo, F_SETFL, 0);
	read(fifo, buf, sizeof(buf));
	
	camera_start();
	config_timelapse_load_status();

	signal(SIGINT, signal_quit);
	signal(SIGTERM, signal_quit);
	signal(SIGCHLD, event_child_signal);

	while (1)
		{
		usleep(1000000 / EVENT_LOOP_FREQUENCY);
		event_process();

		/* Process lines in the FIFO.  Single lines via an echo "xxx" > FIFO
		|  or from a web page may not have a terminating \n.
		|  Local scripts may dump multiple \n terminated lines into the FIFO.
		*/
		if ((n = read(fifo, buf, sizeof(buf) - 2)) > 0)
			{
			if (buf[n - 1] != '\n')
				buf[n++] = '\n';	/* ensures all lines in buf end in \n */
			buf[n] = '\0';
			line = buf;
			eol = strchr(line, '\n');

			while (eol > line)
				{
				*eol++ = '\0';
				command_process(line);
				while (*eol == '\n')
					++eol;
				line = eol;
				eol = strchr(line, '\n');
				}
			}
		}
	return 0;
	}
コード例 #7
0
ファイル: pikrellcam.c プロジェクト: tyborall/pikrellcam
void
command_process(char *command_line)
	{
	VideoCircularBuffer	*vcb = &video_circular_buffer;
	Command	*cmd;
	char	command[64], args[128], buf[128], *path;
	int		i, n;

	if (!command_line || *command_line == '\0')
		return;

	n = sscanf(command_line, "%63s %[^\n]", command, args);
	if (n < 1 || command[0] == '#')
		return;
	for (cmd = NULL, i = 0; i < COMMAND_SIZE; cmd = NULL, ++i)
		{
		cmd = &commands[i];
		if (!strcmp(command, cmd->name))
			{
			if (cmd->n_args != n - 1)
				{
				log_printf("Wrong number of args for command: %s\n", command);
				return;
				}
			break;
			}
		}
	if (!cmd || (cmd->code != display_cmd && cmd->code != inform))
		log_printf("command_process: %s\n", command_line);
	if (!cmd)
		{
		if (   !config_set_option(command, args, FALSE)
		    && !mmalcam_config_parameter_set(command, args, TRUE)
	       )
			log_printf("Bad command: [%s] [%s]\n", command, args);
		else
			pikrellcam.config_modified = TRUE;
		return;
		}

	if (cmd->code < display_cmd && !display_is_default())
		{
		display_set_default();
		return;
		}

	switch (cmd->code)
		{
		case record:
			pthread_mutex_lock(&vcb->mutex);
			if (config_boolean_value(args) == TRUE)
				{
				if (vcb->pause)
					vcb->pause = FALSE;
				else
					{
					if (vcb->state == VCB_STATE_MOTION_RECORD)
						video_record_stop(vcb);
					video_record_start(vcb, VCB_STATE_MANUAL_RECORD_START);
					}
				}
			else
				video_record_stop(vcb);
			pthread_mutex_unlock(&vcb->mutex);
			break;

		case record_pause:
			/* Can pause manual record only.  Because of event_gap/capture
			|  times, I'm not even sure what it would mean to pause a
			|  motion record.
			*/
			pthread_mutex_lock(&vcb->mutex);
			if (vcb->state == VCB_STATE_MANUAL_RECORD)
				vcb->pause = vcb->pause ? FALSE : TRUE;
			else
				vcb->pause = FALSE;
			pthread_mutex_unlock(&vcb->mutex);
			break;

		case still:
			snprintf(buf, sizeof(buf), "%d", pikrellcam.still_sequence);
			path = media_pathname(pikrellcam.still_dir, pikrellcam.still_filename,
							'N',  buf,
							'\0', NULL);
			pikrellcam.still_sequence += 1;
			still_capture(path);
			free(path);
			break;

		case tl_start:
			if ((n = atoi(args)) < 1)
				n = 0;
			time_lapse.activated = TRUE;
			time_lapse.on_hold = FALSE;
			if (!time_lapse.event && n > 0)
				{
				time_lapse.sequence = 0;
				++time_lapse.series;
				time_lapse.event = event_add("timelapse",
							pikrellcam.t_now, n, timelapse_capture, NULL);
				}
			else if (n > 0)		/* Change the period */
				{
				time_lapse.event->time += (n - time_lapse.period);
				time_lapse.event->period = n;
				}
			if (n > 0)
				time_lapse.period = n;	/* n == 0 just sets on_hold FALSE */
			config_timelapse_save_status();	
			break;

		case tl_hold:
				config_set_boolean(&time_lapse.on_hold, args);
				config_timelapse_save_status();
			break;

		case tl_end:
			if (time_lapse.activated)
				{
				event_remove(time_lapse.event);
				time_lapse.event = NULL;
				time_lapse.activated = FALSE;
				time_lapse.on_hold = FALSE;
				config_timelapse_save_status();
				exec_no_wait(pikrellcam.on_timelapse_end_cmd, NULL);
				}
			break;

		case tl_inform_convert:
			if (!strcmp(args, "done"))
				{
				event_remove(time_lapse.inform_event);
				dup_string(&time_lapse.convert_name, "");
				time_lapse.convert_size = 0;
				}
			else
				{
				dup_string(&time_lapse.convert_name, args);
				time_lapse.inform_event = event_add("tl_inform_convert",
						pikrellcam.t_now, 5, timelapse_inform_convert, NULL);
				}
			break;

		case tl_show_status:
			config_set_boolean(&time_lapse.show_status, args);
			break;

		case display_cmd:
			display_command(args);
			break;

		case motion_cmd:
			motion_command(args);
			break;

		case motion_enable:
			n = motion_frame.motion_enable;
			config_set_boolean(&motion_frame.motion_enable, args);

			if (n && !motion_frame.motion_enable)
				{
				pthread_mutex_lock(&vcb->mutex);
				if (vcb->state == VCB_STATE_MOTION_RECORD)
					video_record_stop(vcb);
				pthread_mutex_unlock(&vcb->mutex);
				}
			break;

		case video_fps:
			if ((n = atoi(args)) < 1)
				n = 1;
			if (n > 49)
				n = 24;
			camera_adjust_temp.video_fps = n;
			pikrellcam.camera_adjust.video_fps = n;
			camera_restart();
			pikrellcam.config_modified = TRUE;
			break;

		case video_mp4box_fps:
			n = atoi(args);
			camera_adjust_temp.video_mp4box_fps = n;
			pikrellcam.camera_adjust.video_mp4box_fps = n;
			pikrellcam.config_modified = TRUE;
			break;

		case inform:
			display_inform(args);
			break;

		case save_config:
			config_save(pikrellcam.config_file);
			break;

		case delete_log:
			unlink(pikrellcam.log_file);
			break;

		case upgrade:
			snprintf(buf, sizeof(buf), "%s/scripts-dist/_upgrade $I $P $G $Z",
						pikrellcam.install_dir);
			exec_no_wait(buf, NULL);
			break;

		case quit:
			config_timelapse_save_status();
			if (pikrellcam.config_modified)
				config_save(pikrellcam.config_file);
			display_quit();
			exit(0);
			break;

		default:
			log_printf_no_timestamp("command in table with no action!\n");
			break;
		}
	}