Skip to content

Commit 78bc5cf

Browse files
committed
fixed the bw rendering
1 parent 37a6b92 commit 78bc5cf

4 files changed

Lines changed: 238 additions & 28 deletions

File tree

include/pythonic/pythonicExport.hpp

Lines changed: 199 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,160 @@ namespace pythonic
9494
return false;
9595
}
9696

97+
/**
98+
* @brief Parse ANSI 256-color escape sequence
99+
* @param ansi ANSI escape sequence like "\033[38;5;XXXm" or combined "\033[38;5;FG;48;5;BGm"
100+
* @param r Output red component
101+
* @param g Output green component
102+
* @param b Output blue component
103+
* @param is_foreground Output: true if this is a foreground color
104+
* @return true if parsed successfully
105+
*/
106+
inline bool parse_ansi_256(const std::string &ansi, uint8_t &r, uint8_t &g, uint8_t &b, bool &is_foreground)
107+
{
108+
// Convert color index to RGB
109+
auto color_to_rgb = [](int color_index, uint8_t &r, uint8_t &g, uint8_t &b) -> bool
110+
{
111+
// Standard colors (0-15): Use basic color mapping
112+
if (color_index < 16)
113+
{
114+
static const uint8_t basic_colors[16][3] = {
115+
{0, 0, 0}, // 0 Black
116+
{128, 0, 0}, // 1 Red
117+
{0, 128, 0}, // 2 Green
118+
{128, 128, 0}, // 3 Yellow
119+
{0, 0, 128}, // 4 Blue
120+
{128, 0, 128}, // 5 Magenta
121+
{0, 128, 128}, // 6 Cyan
122+
{192, 192, 192}, // 7 White
123+
{128, 128, 128}, // 8 Bright Black
124+
{255, 0, 0}, // 9 Bright Red
125+
{0, 255, 0}, // 10 Bright Green
126+
{255, 255, 0}, // 11 Bright Yellow
127+
{0, 0, 255}, // 12 Bright Blue
128+
{255, 0, 255}, // 13 Bright Magenta
129+
{0, 255, 255}, // 14 Bright Cyan
130+
{255, 255, 255} // 15 Bright White
131+
};
132+
r = basic_colors[color_index][0];
133+
g = basic_colors[color_index][1];
134+
b = basic_colors[color_index][2];
135+
return true;
136+
}
137+
// Color cube (16-231): 6x6x6 RGB
138+
else if (color_index < 232)
139+
{
140+
int idx = color_index - 16;
141+
int ri = idx / 36;
142+
int gi = (idx % 36) / 6;
143+
int bi = idx % 6;
144+
r = ri ? (ri * 40 + 55) : 0;
145+
g = gi ? (gi * 40 + 55) : 0;
146+
b = bi ? (bi * 40 + 55) : 0;
147+
return true;
148+
}
149+
// Grayscale (232-255): 24 shades
150+
else if (color_index < 256)
151+
{
152+
int gray = (color_index - 232) * 10 + 8;
153+
r = g = b = static_cast<uint8_t>(gray);
154+
return true;
155+
}
156+
return false;
157+
};
158+
159+
// Match ESC[38;5;Nm (foreground only)
160+
std::regex fg_only_regex(R"(\x1b\[38;5;(\d+)m)");
161+
std::smatch match;
162+
if (std::regex_search(ansi, match, fg_only_regex) && match.prefix().str().empty())
163+
{
164+
int color_index = std::stoi(match[1].str());
165+
is_foreground = true;
166+
return color_to_rgb(color_index, r, g, b);
167+
}
168+
169+
// Match ESC[48;5;Nm (background only)
170+
std::regex bg_only_regex(R"(\x1b\[48;5;(\d+)m)");
171+
if (std::regex_search(ansi, match, bg_only_regex) && match.prefix().str().empty())
172+
{
173+
int color_index = std::stoi(match[1].str());
174+
is_foreground = false;
175+
return color_to_rgb(color_index, r, g, b);
176+
}
177+
178+
return false;
179+
}
180+
181+
/**
182+
* @brief Parse combined ANSI 256-color escape sequence with both fg and bg
183+
* @param ansi ANSI escape sequence like "\033[38;5;FG;48;5;BGm"
184+
* @param fg_r, fg_g, fg_b Output foreground RGB
185+
* @param bg_r, bg_g, bg_b Output background RGB
186+
* @param has_fg, has_bg Output: which colors were found
187+
* @return true if parsed successfully
188+
*/
189+
inline bool parse_ansi_256_combined(const std::string &ansi,
190+
uint8_t &fg_r, uint8_t &fg_g, uint8_t &fg_b,
191+
uint8_t &bg_r, uint8_t &bg_g, uint8_t &bg_b,
192+
bool &has_fg, bool &has_bg)
193+
{
194+
has_fg = has_bg = false;
195+
196+
// Convert color index to RGB
197+
auto color_to_rgb = [](int color_index, uint8_t &r, uint8_t &g, uint8_t &b) -> bool
198+
{
199+
if (color_index < 16)
200+
{
201+
static const uint8_t basic_colors[16][3] = {
202+
{0, 0, 0}, {128, 0, 0}, {0, 128, 0}, {128, 128, 0}, {0, 0, 128}, {128, 0, 128}, {0, 128, 128}, {192, 192, 192}, {128, 128, 128}, {255, 0, 0}, {0, 255, 0}, {255, 255, 0}, {0, 0, 255}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255}};
203+
r = basic_colors[color_index][0];
204+
g = basic_colors[color_index][1];
205+
b = basic_colors[color_index][2];
206+
return true;
207+
}
208+
else if (color_index < 232)
209+
{
210+
int idx = color_index - 16;
211+
r = (idx / 36) ? ((idx / 36) * 40 + 55) : 0;
212+
g = ((idx % 36) / 6) ? (((idx % 36) / 6) * 40 + 55) : 0;
213+
b = (idx % 6) ? ((idx % 6) * 40 + 55) : 0;
214+
return true;
215+
}
216+
else if (color_index < 256)
217+
{
218+
int gray = (color_index - 232) * 10 + 8;
219+
r = g = b = static_cast<uint8_t>(gray);
220+
return true;
221+
}
222+
return false;
223+
};
224+
225+
// Match combined fg+bg: ESC[38;5;FG;48;5;BGm
226+
std::regex combined_regex(R"(\x1b\[38;5;(\d+);48;5;(\d+)m)");
227+
std::smatch match;
228+
if (std::regex_search(ansi, match, combined_regex))
229+
{
230+
int fg_idx = std::stoi(match[1].str());
231+
int bg_idx = std::stoi(match[2].str());
232+
has_fg = color_to_rgb(fg_idx, fg_r, fg_g, fg_b);
233+
has_bg = color_to_rgb(bg_idx, bg_r, bg_g, bg_b);
234+
return has_fg || has_bg;
235+
}
236+
237+
// Also match reverse order: ESC[48;5;BG;38;5;FGm
238+
std::regex combined_rev_regex(R"(\x1b\[48;5;(\d+);38;5;(\d+)m)");
239+
if (std::regex_search(ansi, match, combined_rev_regex))
240+
{
241+
int bg_idx = std::stoi(match[1].str());
242+
int fg_idx = std::stoi(match[2].str());
243+
has_fg = color_to_rgb(fg_idx, fg_r, fg_g, fg_b);
244+
has_bg = color_to_rgb(bg_idx, bg_r, bg_g, bg_b);
245+
return has_fg || has_bg;
246+
}
247+
248+
return false;
249+
}
250+
97251
/**
98252
* @brief Strip all ANSI escape codes from a string
99253
*/
@@ -346,7 +500,7 @@ namespace pythonic
346500

347501
std::string escape = line.substr(start, i - start);
348502

349-
// Parse RGB color
503+
// Try parsing 24-bit RGB color first
350504
uint8_t r, g, b;
351505
if (parse_ansi_rgb(escape, r, g, b))
352506
{
@@ -361,14 +515,51 @@ namespace pythonic
361515
has_bg = true;
362516
}
363517
}
364-
// Reset code
365-
else if (escape.find("[0m") != std::string::npos ||
366-
escape.find("[m") != std::string::npos)
518+
// Try parsing combined 256-color (fg+bg in one escape)
519+
else
367520
{
368-
current_fg = RGB(255, 255, 255);
369-
current_bg = RGB(0, 0, 0);
370-
has_fg = false;
371-
has_bg = false;
521+
uint8_t fg_r, fg_g, fg_b, bg_r, bg_g, bg_b;
522+
bool got_fg, got_bg;
523+
if (parse_ansi_256_combined(escape, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, got_fg, got_bg))
524+
{
525+
if (got_fg)
526+
{
527+
current_fg = RGB(fg_r, fg_g, fg_b);
528+
has_fg = true;
529+
}
530+
if (got_bg)
531+
{
532+
current_bg = RGB(bg_r, bg_g, bg_b);
533+
has_bg = true;
534+
}
535+
}
536+
// Try parsing single 256-color
537+
else
538+
{
539+
bool is_foreground;
540+
if (parse_ansi_256(escape, r, g, b, is_foreground))
541+
{
542+
if (is_foreground)
543+
{
544+
current_fg = RGB(r, g, b);
545+
has_fg = true;
546+
}
547+
else
548+
{
549+
current_bg = RGB(r, g, b);
550+
has_bg = true;
551+
}
552+
}
553+
// Reset code
554+
else if (escape.find("[0m") != std::string::npos ||
555+
escape.find("[m") != std::string::npos)
556+
{
557+
current_fg = RGB(255, 255, 255);
558+
current_bg = RGB(0, 0, 0);
559+
has_fg = false;
560+
has_bg = false;
561+
}
562+
}
372563
}
373564
continue;
374565
}

include/pythonic/pythonicPrint.hpp

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
#include <iostream>
44
#include <iomanip>
55
#include <chrono>
6+
#include <thread>
7+
#include <atomic>
8+
#if __cplusplus >= 201703L && __has_include(<filesystem>)
9+
#include <filesystem>
10+
#endif
611
#include "pythonicVars.hpp"
712
#include "pythonicDraw.hpp"
813
#include "pythonicMedia.hpp"
@@ -1113,15 +1118,30 @@ namespace pythonic
11131118

11141119
std::string fps_str = std::to_string(actual_fps);
11151120

1116-
// Create temp directory for frames
1117-
std::string temp_dir = "/tmp/pythonic_video_export_" +
1118-
std::to_string(std::hash<std::string>{}(input_path));
1121+
// Create temp directory for frames using a simpler, more reliable approach
1122+
// Use timestamp + process id for uniqueness
1123+
auto now = std::chrono::steady_clock::now().time_since_epoch().count();
1124+
std::string temp_dir = "/tmp/pythonic_export_" + std::to_string(now % 1000000);
1125+
1126+
// Use filesystem to create directory (more reliable than system call)
1127+
#if __cplusplus >= 201703L && __has_include(<filesystem>)
1128+
try
1129+
{
1130+
std::filesystem::create_directories(temp_dir);
1131+
}
1132+
catch (...)
1133+
{
1134+
// Fallback to system call
1135+
#endif
11191136
#ifdef _WIN32
1120-
std::string mkdir_cmd = "mkdir \"" + temp_dir + "\" 2>nul";
1137+
std::string mkdir_cmd = "mkdir \"" + temp_dir + "\" 2>nul";
11211138
#else
11221139
std::string mkdir_cmd = "mkdir -p \"" + temp_dir + "\"";
11231140
#endif
1124-
std::system(mkdir_cmd.c_str());
1141+
std::system(mkdir_cmd.c_str());
1142+
#if __cplusplus >= 201703L && __has_include(<filesystem>)
1143+
}
1144+
#endif
11251145

11261146
// Get estimated frame count for preprocessing progress
11271147
double video_duration = get_video_duration(actual_path);
@@ -1171,23 +1191,15 @@ namespace pythonic
11711191
if (is_temp_video)
11721192
std::remove(actual_path.c_str());
11731193

1174-
if (result != 0)
1175-
{
1176-
std::cout << "\n\033[31mError: Failed to extract frames from video\033[0m\n";
1177-
#ifdef _WIN32
1178-
std::string rm_cmd = "rmdir /s /q \"" + temp_dir + "\"";
1179-
#else
1180-
std::string rm_cmd = "rm -rf \"" + temp_dir + "\"";
1181-
#endif
1182-
std::system(rm_cmd.c_str());
1183-
return false;
1184-
}
1185-
1186-
// Count total frames
1194+
// Count extracted frames - this is more reliable than exit code
1195+
// FFmpeg may return non-zero even when frames are extracted successfully
11871196
size_t total_frames = count_frames(temp_dir, "frame_");
1197+
11881198
if (total_frames == 0)
11891199
{
1190-
std::cout << "\n\033[31mError: No frames extracted from video\033[0m\n";
1200+
// Only fail if no frames were extracted at all
1201+
std::cout << "\n\033[31mError: Failed to extract frames from video (exit code: "
1202+
<< result << ")\033[0m\n";
11911203
#ifdef _WIN32
11921204
std::string rm_cmd = "rmdir /s /q \"" + temp_dir + "\"";
11931205
#else
@@ -1297,6 +1309,12 @@ namespace pythonic
12971309
{
12981310
auto hw_encoders = pythonic::accel::detect_hw_encoders();
12991311
encoder = hw_encoders.best_h264_encoder();
1312+
1313+
// If GPU was requested but not available, inform user
1314+
if (encoder == "libx264")
1315+
{
1316+
std::cout << "\n\033[33mNote: GPU requested but no hardware encoder found, falling back to CPU\033[0m\n";
1317+
}
13001318
}
13011319

13021320
// Log encoder being used
@@ -1306,7 +1324,7 @@ namespace pythonic
13061324
}
13071325
else if (!use_gpu)
13081326
{
1309-
std::cout << "\n\033[90mUsing CPU encoder (GPU disabled)\033[0m\n";
1327+
std::cout << "\n\033[90mUsing CPU encoder (GPU disabled by user)\033[0m\n";
13101328
}
13111329

13121330
// Build encoder-specific options

test_bw_parse

621 KB
Binary file not shown.

test_bw_raw.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Error: Could not convert image. Install ImageMagick.

0 commit comments

Comments
 (0)