@@ -1327,28 +1327,54 @@ namespace pythonic
13271327 std::cout << " \n\033 [90mUsing CPU encoder (GPU disabled by user)\033 [0m\n " ;
13281328 }
13291329
1330- // Build encoder-specific options
1331- std::string encoder_opts;
1332- if (encoder == " h264_nvenc" )
1330+ // Build encoder-specific options (returns pair: encoder_opts, is_hw_encoder)
1331+ auto build_encoder_opts = [](const std::string &enc) -> std::pair<std::string, bool >
13331332 {
1334- encoder_opts = " -c:v h264_nvenc -preset fast -rc vbr -cq 23" ;
1335- }
1336- else if (encoder == " h264_qsv" )
1337- {
1338- encoder_opts = " -c:v h264_qsv -preset faster -global_quality 23" ;
1339- }
1340- else if (encoder == " h264_vaapi" )
1341- {
1342- encoder_opts = " -vaapi_device /dev/dri/renderD128 -c:v h264_vaapi -qp 23" ;
1343- }
1344- else if (encoder == " h264_videotoolbox" )
1345- {
1346- encoder_opts = " -c:v h264_videotoolbox -q:v 65" ;
1347- }
1348- else
1333+ if (enc == " h264_nvenc" )
1334+ return {" -c:v h264_nvenc -preset fast -rc vbr -cq 23" , true };
1335+ if (enc == " h264_qsv" )
1336+ return {" -c:v h264_qsv -preset faster -global_quality 23" , true };
1337+ if (enc == " h264_vaapi" )
1338+ return {" -vaapi_device /dev/dri/renderD128 -c:v h264_vaapi -qp 23" , true };
1339+ if (enc == " h264_videotoolbox" )
1340+ return {" -c:v h264_videotoolbox -q:v 65" , true };
1341+ return {" -c:v libx264 -preset faster -crf 23" , false };
1342+ };
1343+
1344+ auto [encoder_opts, is_hw_encoder] = build_encoder_opts (encoder);
1345+
1346+ // Video filter to ensure dimensions are divisible by 2 (required for yuv420p)
1347+ // Using single quotes for shell compatibility
1348+ std::string scale_filter = " -vf 'scale=trunc(iw/2)*2:trunc(ih/2)*2'" ;
1349+
1350+ // Lambda to build and execute FFmpeg command with fallback
1351+ auto run_encode = [&](const std::string &audio_path) -> int
13491352 {
1350- encoder_opts = " -c:v libx264 -preset faster -crf 23" ;
1351- }
1353+ // Use glob pattern for input to handle non-sequential frame numbers
1354+ // (some frames may fail to render, causing gaps in the sequence)
1355+ std::string base_input = " -framerate " + fps_str + " -pattern_type glob -i '" + temp_dir + " /ascii_*.png'" ;
1356+ std::string audio_input = audio_path.empty () ? " " : " -i '" + audio_path + " '" ;
1357+ std::string audio_opts = audio_path.empty () ? " " : " -c:a aac -shortest" ;
1358+ std::string pix_fmt = " -pix_fmt yuv420p" ;
1359+
1360+ // Try with hardware encoder first
1361+ std::string video_cmd = " ffmpeg -y " + base_input + audio_input + " " +
1362+ scale_filter + " " + encoder_opts + audio_opts + pix_fmt +
1363+ " '" + output_path + " ' 2>/dev/null" ;
1364+ int res = std::system (video_cmd.c_str ());
1365+
1366+ // If hardware encoder fails, fallback to CPU encoder
1367+ if (res != 0 && is_hw_encoder)
1368+ {
1369+ std::cout << " \n\033 [33mHardware encoder failed, falling back to CPU (libx264)\033 [0m\n " ;
1370+ std::string cpu_opts = " -c:v libx264 -preset faster -crf 23" ;
1371+ video_cmd = " ffmpeg -y " + base_input + audio_input + " " +
1372+ scale_filter + " " + cpu_opts + audio_opts + pix_fmt +
1373+ " '" + output_path + " ' 2>/dev/null" ;
1374+ res = std::system (video_cmd.c_str ());
1375+ }
1376+ return res;
1377+ };
13521378
13531379 // Combine ASCII frames into video
13541380 if (audio == Audio::on)
@@ -1360,26 +1386,18 @@ namespace pythonic
13601386
13611387 if (audio_result == 0 )
13621388 {
1363- // Create video with audio using detected encoder
1364- std::string video_cmd = " ffmpeg -y -framerate " + fps_str + " -i \" " + temp_dir + " /ascii_%05d.png\" "
1365- " -i \" " +
1366- audio_path + " \" " + encoder_opts + " -c:a aac -pix_fmt yuv420p "
1367- " -shortest \" " +
1368- output_path + " \" 2>/dev/null" ;
1369- result = std::system (video_cmd.c_str ());
1389+ result = run_encode (audio_path);
13701390 }
13711391 else
13721392 {
13731393 // No audio in source or extraction failed, create video without audio
1374- std::string video_cmd = " ffmpeg -y -framerate " + fps_str + " -i \" " + temp_dir + " /ascii_%05d.png\" " + encoder_opts + " -pix_fmt yuv420p \" " + output_path + " \" 2>/dev/null" ;
1375- result = std::system (video_cmd.c_str ());
1394+ result = run_encode (" " );
13761395 }
13771396 }
13781397 else
13791398 {
1380- // Create video without audio using detected encoder
1381- std::string video_cmd = " ffmpeg -y -framerate " + fps_str + " -i \" " + temp_dir + " /ascii_%05d.png\" " + encoder_opts + " -pix_fmt yuv420p \" " + output_path + " \" 2>/dev/null" ;
1382- result = std::system (video_cmd.c_str ());
1399+ // Create video without audio
1400+ result = run_encode (" " );
13831401 }
13841402
13851403 // Cleanup
0 commit comments