/* vcb should be locked before calling video_record_start() */ void video_record_start(VideoCircularBuffer *vcb, int start_state) { char *s, *tag, *path, seq_buf[12]; int *seq; if (vcb->state == VCB_STATE_MANUAL_RECORD) return; if (start_state == VCB_STATE_MOTION_RECORD_START) { tag = pikrellcam.video_motion_tag; seq = &pikrellcam.video_motion_sequence; } else { tag = pikrellcam.video_manual_tag; seq = &pikrellcam.video_manual_sequence; } snprintf(seq_buf, sizeof(seq_buf), "%d", *seq); path = media_pathname(pikrellcam.video_dir, pikrellcam.video_filename, 'N', seq_buf, 'M', tag); *seq += 1; dup_string(&pikrellcam.video_pathname, path); free(path); path = pikrellcam.video_pathname; if ((s = strstr(path, ".mp4")) != NULL && *(s + 4) == '\0') { asprintf(&path, "%s.h264", pikrellcam.video_pathname); dup_string(&pikrellcam.video_h264, path); free(path); path = pikrellcam.video_h264; pikrellcam.video_mp4box = TRUE; } else pikrellcam.video_mp4box = FALSE; if ((vcb->file = fopen(path, "w")) == NULL) log_printf("Could not create video file %s. %m\n", path); else { log_printf("Video record: %s ...\n", path); vcb->state = start_state; } }
/* exec a command with the given arg. Any strftime() % replacements should | have been done before calling this so there will remain only pikrellcam | specific $X conversions. Change all '$X' to '%s' and printf in what we | want according to X. */ static int exec_command(char *command, char *arg, boolean wait, pid_t *pid) { struct tm *tm_now; CompositeVector *frame_vec = &motion_frame.final_preview_vector; char specifier, *fmt, *fmt_arg, *copy, *cmd_line, *name, buf[BUFSIZ]; int t, i, status = 0; if (!command || !*command) return -1; copy = strdup(command); cmd_line = copy; while ( (fmt = strchr(copy, '$')) != NULL && *(fmt + 1) ) { specifier = *(fmt + 1); *fmt++ = '%'; *fmt = 's'; switch (specifier) { case 'F': fmt_arg = arg ? arg : ""; break; case 'H': fmt_arg = pikrellcam.hostname; break; case 'E': fmt_arg = pikrellcam.effective_user; break; case 'I': fmt_arg = pikrellcam.install_dir; break; case 's': fmt_arg = pikrellcam.still_last; break; case 'S': fmt_arg = pikrellcam.still_dir; break; case 'v': fmt_arg = pikrellcam.video_last; break; case 'V': fmt_arg = pikrellcam.video_dir; break; case 't': fmt_arg = pikrellcam.thumb_dir; break; case 'T': snprintf(buf, sizeof(buf), "%05d", time_lapse.series); name = media_pathname(pikrellcam.video_dir, pikrellcam.timelapse_video_name, 'n', buf, '\0', NULL); snprintf(buf, sizeof(buf), "%s", name); free(name); fmt_arg = buf; dup_string(&pikrellcam.timelapse_video_pending, fmt_arg); break; case 'l': snprintf(buf, sizeof(buf), "%05d", time_lapse.series); name = strdup(pikrellcam.timelapse_format); name = substitute_var(name, 'n', buf); name = substitute_var(name, 'N', "%05d"); snprintf(buf, sizeof(buf), "%s", name); free(name); fmt_arg = buf; break; case 'L': fmt_arg = pikrellcam.timelapse_dir; break; case 'a': fmt_arg = pikrellcam.archive_dir; break; case 'm': fmt_arg = pikrellcam.media_dir; break; case 'M': fmt_arg = pikrellcam.mjpeg_filename; break; case 'P': fmt_arg = pikrellcam.command_fifo; break; case 'C': fmt_arg = pikrellcam.script_dir; break; case 'G': fmt_arg = pikrellcam.log_file; break; case 'N': snprintf(buf, sizeof(buf), "%05d", time_lapse.sequence); fmt_arg = buf; break; case 'n': snprintf(buf, sizeof(buf), "%05d", time_lapse.series); fmt_arg = buf; break; case 'i': /* width of motion area */ t = frame_vec->box_w; snprintf(buf, sizeof(buf), "%d", t); fmt_arg = buf; break; case 'J': /* height of motion area */ t = frame_vec->box_h; snprintf(buf, sizeof(buf), "%d", t); fmt_arg = buf; break; case 'K': /* center X of motion area */ t = frame_vec->x; snprintf(buf, sizeof(buf), "%d", t); fmt_arg = buf; break; case 'Y': /* center Y of motion area */ t = frame_vec->y; snprintf(buf, sizeof(buf), "%d", t); fmt_arg = buf; break; case 'D': /* current_minute dawn sunrise sunset dusk */ tm_now = localtime(&pikrellcam.t_now); snprintf(buf, sizeof(buf), "%d %d %d %d %d", tm_now->tm_hour * 60 + tm_now->tm_min, sun.dawn, sun.sunrise, sun.sunset, sun.dusk); fmt_arg = buf; break; case 'Z': fmt_arg = pikrellcam.version; break; default: fmt_arg = "?"; break; } if (!fmt_arg || !fmt_arg) log_printf(" Bad fmt_arg %p for specifier %c\n", fmt_arg, specifier); asprintf(&cmd_line, copy, fmt_arg); free(copy); copy = cmd_line; } log_printf("execl:[%s]\n", cmd_line); if ((*pid = fork()) == 0) { /* child - execute command in background */ for (i = getdtablesize(); i > 2; --i) close(i); setsid(); /* new session group - ie detach */ execl("/bin/sh", "sh", "-c", cmd_line, " &", NULL); _exit (EXIT_FAILURE); } else if (*pid < 0) { perror("Fork failed."); status = -1; } else if (wait) /* If parent needs to wait */ { if (waitpid (*pid, &status, 0) != *pid) status = -1; } free(cmd_line); return status; }
void command_process(char *command_line) { VideoCircularBuffer *vcb = &video_circular_buffer; Command *cmd; char command[64], args[128], buf[32], *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) { if ( !config_set_option(command, args, FALSE) && !mmalcam_config_parameter_set(command, args, TRUE) ) log_printf("Bad command: [%s] [%s]\n", command, args); return; } if (cmd->code != display_cmd) log_printf("command_process: %s\n", command_line); 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 save_config: config_save(pikrellcam.config_file); break; case quit: config_timelapse_save_status(); if (pikrellcam.config_modified) config_save(pikrellcam.config_file); display_quit(); exit(0); break; default: log_printf("command in table with no action!\n"); break; } }
void timelapse_capture(void) { char *path, seq_buf[12], series_buf[12]; int n, nd; MMAL_STATUS_T status; if (time_lapse.on_hold) return; /* still_capture() also uses the still_jpeg_encoder, so wait if busy. */ for (n = 0; n < 5; ++n) { if (still_jpeg_encoder.file == NULL) break; usleep(50000); } if (still_jpeg_encoder.file != NULL) { log_printf("timelapse capture failed because jpeg encoder is busy.\n"); return; } snprintf(seq_buf, sizeof(seq_buf), "%05d", time_lapse.sequence); snprintf(series_buf, sizeof(series_buf), "%05d", time_lapse.series); path = media_pathname(pikrellcam.timelapse_dir, pikrellcam.timelapse_format, 'N', seq_buf, 'n', series_buf); time_lapse.sequence += 1; if ((still_jpeg_encoder.file = fopen(path, "w")) == NULL) log_printf("Could not create timelapse file %s. %m\n", path); else { if ((status = mmal_port_parameter_set_boolean( camera.component->output[CAMERA_CAPTURE_PORT], MMAL_PARAMETER_CAPTURE, 1)) != MMAL_SUCCESS) { fclose(still_jpeg_encoder.file); still_jpeg_encoder.file = NULL; log_printf("Timelapse capture startup failed. Status %s\n", mmal_status[status]); } else { log_printf("Timelapse still: %s\n", path); dup_string(&pikrellcam.timelapse_last_save, path); /* timelapse_capture() is an event call (inside the event loop) | and we here add an event to the list. | This only modifies the last event list next pointer, and | the event loop is not there yet. */ nd = pikrellcam.notify_duration; if (nd > time_lapse.period - 3) nd = time_lapse.period / 2; if (nd > 1) nd *= EVENT_LOOP_FREQUENCY; else nd = EVENT_LOOP_FREQUENCY / 2; event_count_down_add("timelapse saved", nd, event_notify_expire, &pikrellcam.timelapse_notify); pikrellcam.timelapse_notify = TRUE; } } free(path); }