Index: conf.c =================================================================== --- conf.c (revision 303) +++ conf.c (working copy) @@ -69,9 +69,10 @@ pre_capture: 0, post_capture: 0, switchfilter: 0, - ffmpeg_cap_new: 0, + ffmpeg_cap_new: "off", ffmpeg_cap_motion: 0, ffmpeg_bps: DEF_FFMPEG_BPS, + ffmpeg_fps: 25, ffmpeg_vbr: DEF_FFMPEG_VBR, ffmpeg_video_codec: DEF_FFMPEG_CODEC, webcam_port: 0, @@ -614,11 +615,15 @@ "# The options movie_filename and timelapse_filename are also used\n" "# by the ffmpeg feature\n" "############################################################\n\n" - "# Use ffmpeg to encode mpeg movies in realtime (default: off)", + "# Use ffmpeg to encode mpeg movies in realtime (default: off)\n" + "# Set to 'on' for a new movie file for each event, or set\n" + "# to 'daily' for a single movie file containing all events.\n" + "# Also: 'hourly' 'weekly-sunday' 'weekly-monday' 'monthly'.\n" + "# Conversion Specifiers can change the filename on the hour etc.\n", 0, - CONF_OFFSET(ffmpeg_cap_new), - copy_bool, - print_bool + CONF_OFFSET(ffmpeg_cap_new), + copy_string, + print_string }, { "ffmpeg_cap_motion", @@ -657,6 +662,17 @@ print_int }, { + "ffmpeg_fps", + "# Frame rate for the ffmpeg encoder (default: 25)\n" + "# This allows movies to be at standard rates, supported by newer ffmpeg versions,\n" + "# even if motion is only captured at (example) 2 fps.\n" + "# Set to 0 to use historical Motion method which is 2-30 fps defined by frame_limit.", + 0, + CONF_OFFSET(ffmpeg_fps), + copy_int, + print_int + }, + { "ffmpeg_variable_bitrate", "# Enables and defines variable bitrate for the ffmpeg encoder.\n" "# ffmpeg_bps is ignored if variable bitrate is enabled.\n" Index: conf.h =================================================================== --- conf.h (revision 303) +++ conf.h (working copy) @@ -52,9 +52,10 @@ int pre_capture; int post_capture; int switchfilter; - int ffmpeg_cap_new; + const char *ffmpeg_cap_new; int ffmpeg_cap_motion; int ffmpeg_bps; + int ffmpeg_fps; int ffmpeg_vbr; int ffmpeg_deinterlace; const char *ffmpeg_video_codec; Index: motion.c =================================================================== --- motion.c (revision 303) +++ motion.c (working copy) @@ -743,6 +743,36 @@ /* Set threshold value */ cnt->threshold = cnt->conf.max_changes; + /* Check & report on the setting of ffmpeg_cap_new since it has new options */ + /* And check that codec is mpeg1 and make it so if not, with a warning. */ + + if (strcmp(cnt->conf.ffmpeg_cap_new,"on")==0 && strcmp(cnt->conf.ffmpeg_cap_new,"off")==0) { + /* Not 'on' or 'off' so must be an interval */ + /* We must force to mpeg1 as all other codecs do not support */ + /* viewing of unclosed files. We cannot guarantee that Motion */ + /* will not terminate unexpectedly (crash, power fail) so only */ + /* with mpeg1 would the movie be viewable. */ + if (strcmp(cnt->conf.ffmpeg_video_codec,"mpeg1")!=0) { + strcpy(cnt->conf.ffmpeg_video_codec,"mpeg1"); + motion_log(-1, 0, "Warning: config option ffmpeg_codec must be mpeg1 if making interval or single ffmpeg file(s) of motion. Has been changed."); + } + motion_log(-1, 0, "Option ffmpeg_cap_new set to some interval. Movies will be made at that interval."); + } + + /* Debug note that new config option ffmpeg_fps has been recognised */ + if (cnt->conf.ffmpeg_fps > 0) { + motion_log(-1, 0, "New option ffmpeg_fps set to %d.",cnt->conf.ffmpeg_fps); + int avcodecbuild; + avcodecbuild=LIBAVCODEC_BUILD; + //motion_log(-1, 0, "Debug: libavcodec_build=%d",avcodecbuild); +#ifndef FFMPEG_NO_NONSTD_MPEG1 + if (0 && cnt->conf.ffmpeg_fps != 25 && cnt->conf.ffmpeg_fps != 30) { + cnt->conf.ffmpeg_fps=25; + motion_log(-1, 0, "Warning: new option ffmpeg_fps set to invalid figure for late versions of ffmpeg library, has been reset to 25."); + } +#endif + } + /* Initialize webcam server if webcam port is specified to not 0 */ if (cnt->conf.webcam_port) { if ( webcam_init(cnt) == -1 ) { @@ -1602,12 +1632,81 @@ } - /***** MOTION LOOP - TIMELAPSE FEATURE SECTION *****/ + /***** MOTION LOOP - MOTION FILE ROLLOVER SECTION *****/ #ifdef HAVE_FFMPEG + if (cnt->ffmpeg_new) { + /* If an ffmpeg movie for movement is configured, we check for ending it at a certain time. + * As with timelapse movies, we start one when we are on the first shot, and the seconds + * are zero. We must use the seconds to prevent the file from getting reset multiple + * times during the minute. + */ + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + if (cnt->currenttime_tm->tm_min == 0 && + (time_current_frame % 60 < time_last_frame % 60) && + cnt->shots == 0) { + if (strcasecmp(cnt->conf.ffmpeg_cap_new,"on") == 0) + ;/* No action. In this mode, a new file is started for every event. */ + /* Whether that file has the same name (and is overwritten each time) */ + /* or has a name differing for each event, is up to the user. */ + /* If we are daily, raise an extra motion-end event at midnight */ + /* The event handler in event.c will then close the movie file */ + else if (strcasecmp(cnt->conf.ffmpeg_cap_new, "daily") == 0) { + if (cnt->currenttime_tm->tm_hour == 0) { + cnt->ffmpeg_new_timeoutflag=TRUE; + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + } + } + + /* handle the hourly case */ + else if (strcasecmp(cnt->conf.ffmpeg_cap_new, "hourly") == 0) { + cnt->ffmpeg_new_timeoutflag=TRUE; + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + } + + /* If we are weekly-sunday, raise the motion-end event at midnight on sunday */ + else if (strcasecmp(cnt->conf.ffmpeg_cap_new, "weekly-sunday") == 0) { + if (cnt->currenttime_tm->tm_wday == 0 && cnt->currenttime_tm->tm_hour == 0) { + cnt->ffmpeg_new_timeoutflag=TRUE; + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + } + } + + /* If we are weekly-monday, raise the event at midnight on monday */ + else if (strcasecmp(cnt->conf.ffmpeg_cap_new, "weekly-monday") == 0) { + if (cnt->currenttime_tm->tm_wday == 1 && cnt->currenttime_tm->tm_hour == 0) { + cnt->ffmpeg_new_timeoutflag=TRUE; + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + } + } + + /* If we are monthly, raise the event at midnight on first day of month */ + else if (strcasecmp(cnt->conf.ffmpeg_cap_new, "monthly") == 0) { + if (cnt->currenttime_tm->tm_mday == 1 && cnt->currenttime_tm->tm_hour == 0) { + cnt->ffmpeg_new_timeoutflag=TRUE; + event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm); + } + } + + /* If invalid we report in syslog once and continue in daily mode */ + else { + motion_log(LOG_ERR, 0, "Invalid ffmpeg_cap_new argument '%s'", + cnt->conf.ffmpeg_cap_new); + motion_log(LOG_ERR, 0, "Defaulting to ffmpeg_cap_new daily (the default)."); + conf_cmdparse(&cnt, (char *)"ffmpeg_cap_new",(char *)"daily"); + } + } + } /* End if ffmpeg_new */ + +#endif /* HAVE_FFMPEG */ + + /***** MOTION LOOP - TIMELAPSE FEATURE SECTION *****/ + +#ifdef HAVE_FFMPEG + if (cnt->conf.timelapse) { /* Check to see if we should start a new timelapse file. We start one when Index: motion.h =================================================================== --- motion.h (revision 303) +++ motion.h (working copy) @@ -376,6 +376,7 @@ #endif #ifdef HAVE_FFMPEG + int ffmpeg_new_timeoutflag; /* TRUE when time to close ffmpeg_new file */ struct ffmpeg *ffmpeg_new; struct ffmpeg *ffmpeg_motion; struct ffmpeg *ffmpeg_timelapse; Index: event.c =================================================================== --- event.c (revision 303) +++ event.c (working copy) @@ -365,7 +365,7 @@ char stamp[PATH_MAX]; const char *mpegpath; - if (!cnt->conf.ffmpeg_cap_new && !cnt->conf.ffmpeg_cap_motion) + if ((strcmp(cnt->conf.ffmpeg_cap_new,"off")==0) && !cnt->conf.ffmpeg_cap_motion) return; /* conf.mpegpath would normally be defined but if someone deleted it by control interface @@ -382,7 +382,7 @@ snprintf(cnt->motionfilename, PATH_MAX - 4, "%s/%sm", cnt->conf.filepath, stamp); snprintf(cnt->newfilename, PATH_MAX - 4, "%s/%s", cnt->conf.filepath, stamp); - if (cnt->conf.ffmpeg_cap_new) { + if (strcmp(cnt->conf.ffmpeg_cap_new,"off")!=0) { if (cnt->imgs.type==VIDEO_PALETTE_GREY) { convbuf=mymalloc((width*height)/2); y=img; @@ -400,13 +400,21 @@ fps=30; if (fps<2) fps=2; - if ( (cnt->ffmpeg_new = - ffmpeg_open((char *)cnt->conf.ffmpeg_video_codec, cnt->newfilename, y, u, v, - cnt->imgs.width, cnt->imgs.height, fps, cnt->conf.ffmpeg_bps, - cnt->conf.ffmpeg_vbr)) == NULL) { - motion_log(LOG_ERR, 1, "ffopen_open error creating (new) file [%s]",cnt->newfilename); - cnt->finish=1; - return; + if (cnt->conf.ffmpeg_fps > 0) + fps=cnt->conf.ffmpeg_fps; /* New config option to override default method of defining fps */ + if (cnt->ffmpeg_new) { + //motion_log(LOG_DEBUG, 0, "event_ffmpeg_newfile: ffmpeg file handle set, last file not closed, so using it [%s]", cnt->newfilename); + } else { + if ( (cnt->ffmpeg_new = + ffmpeg_open((char *)cnt->conf.ffmpeg_video_codec, cnt->newfilename, y, u, v, + cnt->imgs.width, cnt->imgs.height, fps, cnt->conf.ffmpeg_bps, + cnt->conf.ffmpeg_vbr)) == NULL) { + motion_log(LOG_ERR, 1, "ffopen_open error creating (new) file [%s]",cnt->newfilename); + cnt->finish=1; + return; + } else { + //motion_log(LOG_DEBUG, 0, "event_ffmpeg_newfile: new ffmpeg file opened [%s]", cnt->newfilename); + } } ((struct ffmpeg *)cnt->ffmpeg_new)->udata=convbuf; event(cnt, EVENT_FILECREATE, NULL, cnt->newfilename, (void *)FTYPE_MPEG, NULL); @@ -429,10 +437,12 @@ fps=30; if (fps<2) fps=2; + if (cnt->conf.ffmpeg_fps > 0) + fps=cnt->conf.ffmpeg_fps; /* New config option to override default method of defining fps */ if ( (cnt->ffmpeg_motion = - ffmpeg_open((char *)cnt->conf.ffmpeg_video_codec, cnt->motionfilename, y, u, v, - cnt->imgs.width, cnt->imgs.height, fps, cnt->conf.ffmpeg_bps, - cnt->conf.ffmpeg_vbr)) == NULL){ + ffmpeg_open((char *)cnt->conf.ffmpeg_video_codec, cnt->motionfilename, y, u, v, + cnt->imgs.width, cnt->imgs.height, fps, cnt->conf.ffmpeg_bps, + cnt->conf.ffmpeg_vbr)) == NULL){ motion_log(LOG_ERR, 1, "ffopen_open error creating (motion) file [%s]", cnt->motionfilename); cnt->finish=1; return; @@ -535,15 +545,49 @@ char *dummy2 ATTRIBUTE_UNUSED, void *dummy3 ATTRIBUTE_UNUSED, struct tm *tm ATTRIBUTE_UNUSED) { + /* This event handler is called at the end of every motion detection. */ + /* Now, if ffmpeg_cap_new is equal to 'on' we always close the file. */ + /* But if ffmpeg_cap_new is set to hourly/daily/etc then we only */ + /* close the file if the flag ffmpeg_new_timeoutflag is TRUE, showing */ + /* that the configured time period has passed (hourly/daily/weekly). */ + /* That flag is set in the top-level Motion loop and unset here. */ + + if (cnt->ffmpeg_new) { /* If file has been opened, proceed */ + if (strcmp(cnt->conf.ffmpeg_cap_new,"on")==0) { + /* Always close the file, the historical behaviour */ + if (cnt->ffmpeg_new->udata) + free(cnt->ffmpeg_new->udata); + ffmpeg_close(cnt->ffmpeg_new); + cnt->ffmpeg_new=NULL; - if (cnt->ffmpeg_new) { - if (cnt->ffmpeg_new->udata) - free(cnt->ffmpeg_new->udata); - ffmpeg_close(cnt->ffmpeg_new); - cnt->ffmpeg_new=NULL; + event(cnt, EVENT_FILECLOSE, NULL, cnt->newfilename, (void *)FTYPE_MPEG, NULL); - event(cnt, EVENT_FILECLOSE, NULL, cnt->newfilename, (void *)FTYPE_MPEG, NULL); + return; + } + /* This code for when ffmpeg_cap_new is not 'off', and not 'on' + * -- since the on case has been dealt with above. + * This catches the many options of daily/weekly/etc in a neat way. + * This is a safety condition, since we should not enter here + * if ffmpeg_cap_new is set to 'off' since ffmpeg_new is not set. + * The condition may get used if http control is used to change + * ffmpeg_cap_new to off, when it was previously set up. + */ + if ((cnt->ffmpeg_new_timeoutflag!=TRUE) && (strcmp(cnt->conf.ffmpeg_cap_new,"off")!=0) ) + { + //motion_log(LOG_DEBUG, 0, "event_ffmpeg_closefile: an interval is configured, but not at interval yet, not closing file"); + } else { + cnt->ffmpeg_new_timeoutflag=FALSE; /* Unset the flag until next time */ + if (cnt->ffmpeg_new->udata) + free(cnt->ffmpeg_new->udata); + ffmpeg_close(cnt->ffmpeg_new); + cnt->ffmpeg_new=NULL; + + event(cnt, EVENT_FILECLOSE, NULL, cnt->newfilename, (void *)FTYPE_MPEG, NULL); + //motion_log(LOG_DEBUG, 0, "event_ffmpeg_closefile: file closed as configured time reached or configured off."); + } } + /* No such detailed functionality is provided for the motion-pixel-only films */ + /* (This handler is how the one for ffmpeg_new above used to look). */ if (cnt->ffmpeg_motion) { if (cnt->ffmpeg_motion->udata) free(cnt->ffmpeg_motion->udata); Index: ffmpeg.c =================================================================== --- ffmpeg.c (revision 303) +++ ffmpeg.c (working copy) @@ -252,8 +252,17 @@ } #ifdef FFMPEG_NO_NONSTD_MPEG1 } else if (strcmp(codec, "mpeg1") == 0) { - motion_log(LOG_ERR, 0, "*** mpeg1 support for normal videos has been disabled ***"); - return NULL; + //motion_log(LOG_ERR, 0, "*** mpeg1 support for normal videos has been disabled ***"); + //return NULL; + + /* Changes have been made in event.c to set ffmpeg fps to config parameter ffmpeg_fps */ + /* Now same behaviour as last case */ + ext = ".mpg"; + of = guess_format("mpeg1video", NULL, NULL); + if (of) { + /* But we want the trailer to be correctly written. */ + of->write_trailer = mpeg1_write_trailer; + } #endif } else if (strcmp(codec, "mpeg4") == 0) { ext = ".avi";