@@ -26,9 +26,12 @@ const char* previews_str[] = {
2626 " vae" ,
2727};
2828
29+ std::regex format_specifier_regex (" (?:[^%]|^)(?:%%)*(%\\ d{0,3}d)" );
30+
2931struct SDCliParams {
3032 SDMode mode = IMG_GEN;
3133 std::string output_path = " output.png" ;
34+ int output_begin_idx = -1 ;
3235
3336 bool verbose = false ;
3437 bool canny_preprocess = false ;
@@ -50,7 +53,7 @@ struct SDCliParams {
5053 options.string_options = {
5154 {" -o" ,
5255 " --output" ,
53- " path to write result image to (default: ./output.png)" ,
56+ " path to write result image to. you can use printf-style %d format specifiers for image sequences (default: ./output.png) (eg. output_%03d .png)" ,
5457 &output_path},
5558 {" " ,
5659 " --preview-path" ,
@@ -63,6 +66,10 @@ struct SDCliParams {
6366 " --preview-interval" ,
6467 " interval in denoising steps between consecutive updates of the image preview file (default is 1, meaning updating at every step)" ,
6568 &preview_interval},
69+ {" " ,
70+ " --output-begin-idx" ,
71+ " starting index for output image sequence, must be non-negative (default 0 if specified %d in output path, 1 otherwise)" ,
72+ &output_begin_idx},
6673 };
6774
6875 options.bool_options = {
@@ -344,6 +351,25 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
344351 }
345352}
346353
354+ std::string format_frame_idx (std::string pattern, int frame_idx) {
355+ std::smatch match;
356+ std::string result = pattern;
357+ while (std::regex_search (result, match, format_specifier_regex)) {
358+ std::string specifier = match.str (1 );
359+ char buffer[32 ];
360+ snprintf (buffer, sizeof (buffer), specifier.c_str (), frame_idx);
361+ result.replace (match.position (1 ), match.length (1 ), buffer);
362+ }
363+
364+ // Then replace all '%%' with '%'
365+ size_t pos = 0 ;
366+ while ((pos = result.find (" %%" , pos)) != std::string::npos) {
367+ result.replace (pos, 2 , " %" );
368+ pos += 1 ;
369+ }
370+ return result;
371+ }
372+
347373int main (int argc, const char * argv[]) {
348374 if (argc > 1 && std::string (argv[1 ]) == " --version" ) {
349375 std::cout << version_string () << " \n " ;
@@ -719,25 +745,59 @@ int main(int argc, const char* argv[]) {
719745 is_jpg = false ;
720746 }
721747
722- if (cli_params.mode == VID_GEN && num_results > 1 ) {
723- std::string vid_output_path = cli_params.output_path ;
724- if (file_ext_lower == " .png" ) {
725- vid_output_path = base_path + " .avi" ;
748+ if (std::regex_search (cli_params.output_path , format_specifier_regex)) {
749+ std::string final_output_path = cli_params.output_path ;
750+ if (cli_params.output_begin_idx == -1 ) {
751+ cli_params.output_begin_idx = 0 ;
752+ }
753+ // writing image sequence, default to PNG
754+ if (!is_jpg && file_ext_lower != " .png" ) {
755+ base_path += file_ext;
756+ file_ext = " .png" ;
726757 }
727- create_mjpg_avi_from_sd_images (vid_output_path.c_str (), results, num_results, gen_params.fps );
728- LOG_INFO (" save result MJPG AVI video to '%s'\n " , vid_output_path.c_str ());
758+ final_output_path = base_path + file_ext;
759+ for (int i = 0 ; i < num_results; i++) {
760+ if (results[i].data == nullptr ) {
761+ continue ;
762+ }
763+ std::string final_image_path = format_frame_idx (final_output_path, cli_params.output_begin_idx + i);
764+ if (is_jpg) {
765+ int write_ok = stbi_write_jpg (final_image_path.c_str (), results[i].width , results[i].height , results[i].channel ,
766+ results[i].data , 90 , get_image_params (cli_params, ctx_params, gen_params, gen_params.seed + i).c_str ());
767+ LOG_INFO (" save result JPEG image %d to '%s' (%s)" , i, final_image_path.c_str (), write_ok == 0 ? " failure" : " success" );
768+ } else {
769+ int write_ok = stbi_write_png (final_image_path.c_str (), results[i].width , results[i].height , results[i].channel ,
770+ results[i].data , 0 , get_image_params (cli_params, ctx_params, gen_params, gen_params.seed + i).c_str ());
771+ LOG_INFO (" save result PNG image %d to '%s' (%s)" , i, final_image_path.c_str (), write_ok == 0 ? " failure" : " success" );
772+ }
773+ }
774+ } else if (cli_params.mode == VID_GEN && num_results > 1 ) {
775+ std::string final_output_path = cli_params.output_path ;
776+ if (file_ext_lower != " .avi" ) {
777+ if (!is_jpg && file_ext_lower != " .png" ) {
778+ base_path += file_ext;
779+ }
780+ file_ext = " .avi" ;
781+ final_output_path = base_path + file_ext;
782+ }
783+ create_mjpg_avi_from_sd_images (final_output_path.c_str (), results, num_results, gen_params.fps );
784+ LOG_INFO (" save result MJPG AVI video to '%s'\n " , final_output_path.c_str ());
729785 } else {
730786 // appending ".png" to absent or unknown extension
731787 if (!is_jpg && file_ext_lower != " .png" ) {
732788 base_path += file_ext;
733789 file_ext = " .png" ;
734790 }
791+ if (cli_params.output_begin_idx == -1 ) {
792+ cli_params.output_begin_idx = 1 ;
793+ }
735794 for (int i = 0 ; i < num_results; i++) {
736795 if (results[i].data == nullptr ) {
737796 continue ;
738797 }
739798 int write_ok;
740- std::string final_image_path = i > 0 ? base_path + " _" + std::to_string (i + 1 ) + file_ext : base_path + file_ext;
799+ std::string final_image_path;
800+ final_image_path = i > 0 ? base_path + " _" + std::to_string (cli_params.output_begin_idx + i) + file_ext : base_path + file_ext;
741801 if (is_jpg) {
742802 write_ok = stbi_write_jpg (final_image_path.c_str (), results[i].width , results[i].height , results[i].channel ,
743803 results[i].data , 90 , get_image_params (cli_params, ctx_params, gen_params, gen_params.seed + i).c_str ());
0 commit comments