1 /*****************************************************************************
2
3 FmtAvs.cpp
4 Author: Laurent de Soras, 2021
5
6 --- Legal stuff ---
7
8 This program is free software. It comes without any warranty, to
9 the extent permitted by applicable law. You can redistribute it
10 and/or modify it under the terms of the Do What The Fuck You Want
11 To Public License, Version 2, as published by Sam Hocevar. See
12 http://www.wtfpl.net/ for more details.
13
14 *Tab=3***********************************************************************/
15
16
17
18 #if defined (_MSC_VER)
19 #pragma warning (1 : 4130 4223 4705 4706)
20 #pragma warning (4 : 4355 4786 4800)
21 #endif
22
23
24
25 /*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
26
27 #include "avsutl/fnc.h"
28 #include "fstb/fnc.h"
29 #include "fmtcavs/FmtAvs.h"
30 #include "avisynth.h"
31
32 #include <stdexcept>
33
34 #include <cassert>
35 #include <cctype>
36
37
38
39 namespace fmtcavs
40 {
41
42
43
44 /*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
45
46
47
48 constexpr int FmtAvs::_max_css;
49
50
51
FmtAvs(std::string fmt_str)52 FmtAvs::FmtAvs (std::string fmt_str)
53 {
54 if (conv_from_str (fmt_str) != 0)
55 {
56 throw std::runtime_error ("Bad video format string");
57 }
58 }
59
60
61
FmtAvs(const VideoInfo & vi)62 FmtAvs::FmtAvs (const VideoInfo &vi) noexcept
63 {
64 conv_from_vi (vi);
65 }
66
67
68
invalidate()69 void FmtAvs::invalidate () noexcept
70 {
71 _bitdepth = -1;
72 _col_fam = fmtcl::ColorFamily_INVALID;
73 _planar_flag = false;
74 _alpha_flag = false;
75 _subspl_h = -1;
76 _subspl_v = -1;
77 }
78
79
80
81 // Converts from an Avisynth colorspace
82 // http://avisynth.nl/index.php/Convert
83 // Returns 0 if everything is OK.
conv_from_str(std::string fmt_str)84 int FmtAvs::conv_from_str (std::string fmt_str)
85 {
86 invalidate ();
87
88 fmt_str = remove_outer_spaces (fmt_str);
89 fstb::conv_to_lower_case (fmt_str);
90
91 if (is_eq_leftstr_and_eat (fmt_str, "rgb"))
92 {
93 _col_fam = fmtcl::ColorFamily_RGB;
94 _subspl_h = 0; // Required for consistency
95 _subspl_v = 0;
96 if (fmt_str.empty ())
97 {
98 return -1; // Nothing specified after "RGB"
99 }
100
101 if (fmt_str.front () == 'a')
102 {
103 _alpha_flag = true;
104 fmt_str.erase (0, 1);
105 if (fmt_str.empty ())
106 {
107 return -1; // We need the bitdepth
108 }
109 }
110
111 // Planar
112 if (check_planar_bits_and_eat (fmt_str, _bitdepth))
113 {
114 _planar_flag = true;
115 if (_bitdepth < 0)
116 {
117 return -1; // Ill-formed string or invalid bitdepth
118 }
119 }
120
121 // Interleaved
122 else
123 {
124 if (_planar_flag)
125 {
126 return -1; // Interleaved format is always RGBxx, not RGBAxx
127 }
128 size_t pos = 0;
129 const int val_bits = std::stoi (fmt_str, &pos, 10);
130 if (fmt_str.length () != pos)
131 {
132 return -1; // Garbage after RGBxx
133 }
134 switch (val_bits)
135 {
136 case 24: _bitdepth = 8; _alpha_flag = false; break;
137 case 32: _bitdepth = 8; _alpha_flag = true; break;
138 case 48: _bitdepth = 16; _alpha_flag = false; break;
139 case 64: _bitdepth = 16; _alpha_flag = true; break;
140 default: return -1; // Invalid bitdepth/alpha combination
141 }
142 }
143 }
144
145 else if (is_eq_leftstr_and_eat (fmt_str, "yuy2"))
146 {
147 _col_fam = fmtcl::ColorFamily_YUV;
148 _bitdepth = 8;
149 _subspl_h = 1;
150 _subspl_v = 0;
151
152 if (! fmt_str.empty ())
153 {
154 return -1; // Garbage after YUY2
155 }
156 }
157
158 else if (is_eq_leftstr_and_eat (fmt_str, "yuv"))
159 {
160 _col_fam = fmtcl::ColorFamily_YUV;
161 _planar_flag = true;
162
163 if (fmt_str.front () == 'a')
164 {
165 _alpha_flag = true;
166 fmt_str.erase (0, 1);
167 if (fmt_str.empty ())
168 {
169 return -1; // We need chroma subsampling + planar bitdepth
170 }
171 }
172
173 size_t pos = 0;
174 const int css = std::stoi (fmt_str, &pos, 10);
175 fmt_str.erase (0, pos);
176 switch (css)
177 {
178 case 420: _subspl_h = 1; _subspl_v = 1; break;
179 case 422: _subspl_h = 1; _subspl_v = 0; break;
180 case 444: _subspl_h = 0; _subspl_v = 0; break;
181 default: return -1; // Invalid chroma subsampling
182 }
183
184 if (! check_planar_bits_and_eat (fmt_str, _bitdepth))
185 {
186 return -1;
187 }
188 if (_bitdepth < 0)
189 {
190 return -1; // Ill-formed string or invalid bitdepth
191 }
192 }
193
194 else if (is_eq_leftstr_and_eat (fmt_str, "yv"))
195 {
196 _col_fam = fmtcl::ColorFamily_YUV;
197 _planar_flag = true;
198 _bitdepth = 8;
199 size_t pos = 0;
200 const int val_bits = std::stoi (fmt_str, &pos, 10);
201 if (fmt_str.length () != pos)
202 {
203 return -1; // Garbage after YVxx
204 }
205 switch (val_bits)
206 {
207 case 12: _subspl_h = 1; _subspl_v = 1; break;
208 case 16: _subspl_h = 1; _subspl_v = 0; break;
209 case 24: _subspl_h = 0; _subspl_v = 0; break;
210 case 411: _subspl_h = 2; _subspl_v = 0; break;
211 default: return -1; // Invalid chroma subsampling
212 }
213 }
214
215 else if (is_eq_leftstr_and_eat (fmt_str, "y"))
216 {
217 _col_fam = fmtcl::ColorFamily_GRAY;
218 _planar_flag = true;
219 _subspl_h = 0; // Required for consistency
220 _subspl_v = 0;
221 _bitdepth = check_bits_and_eat (fmt_str, false);
222 if (_bitdepth < 0)
223 {
224 return -1; // Ill-formed or unsupported bitdepth
225 }
226 }
227
228 return 0;
229 }
230
231
232
233 // Assumes pixel_type is valid
conv_from_vi(const VideoInfo & vi)234 void FmtAvs::conv_from_vi (const VideoInfo &vi)
235 {
236 invalidate ();
237
238 _bitdepth = vi.BitsPerComponent ();
239 _planar_flag = vi.IsPlanar ();
240 _alpha_flag = avsutl::has_alpha (vi);
241
242 if (avsutl::is_rgb (vi))
243 {
244 _col_fam = fmtcl::ColorFamily_RGB;
245 _subspl_h = 0; // Required for consistency
246 _subspl_v = 0;
247 }
248 else if (vi.IsY ())
249 {
250 _col_fam = fmtcl::ColorFamily_GRAY;
251 _subspl_h = 0; // Required for consistency
252 _subspl_v = 0;
253 }
254 else
255 {
256 assert (vi.IsYUV () || vi.IsYUVA ());
257 _col_fam = fmtcl::ColorFamily_YUV;
258 _subspl_h = ((vi.pixel_type >> VideoInfo::CS_Shift_Sub_Width ) + 1) & 3;
259 _subspl_v = ((vi.pixel_type >> VideoInfo::CS_Shift_Sub_Height) + 1) & 3;
260 }
261 }
262
263
264
265 // Returns 0 if conversion is OK.
conv_to_vi(VideoInfo & vi) const266 int FmtAvs::conv_to_vi (VideoInfo &vi) const
267 {
268 assert (is_valid ());
269
270 int pixel_type = 0;
271
272 switch (_bitdepth)
273 {
274 case 8: pixel_type |= VideoInfo::CS_Sample_Bits_8; break;
275 case 10: pixel_type |= VideoInfo::CS_Sample_Bits_10; break;
276 case 12: pixel_type |= VideoInfo::CS_Sample_Bits_12; break;
277 case 14: pixel_type |= VideoInfo::CS_Sample_Bits_14; break;
278 case 16: pixel_type |= VideoInfo::CS_Sample_Bits_16; break;
279 case 32: pixel_type |= VideoInfo::CS_Sample_Bits_32; break;
280 default: return -1; // Bitdepth not supported
281 }
282
283 if (_col_fam == fmtcl::ColorFamily_GRAY)
284 {
285 // Chroma subsampling is ignored
286 if (_alpha_flag)
287 {
288 return -1;
289 }
290 pixel_type |= VideoInfo::CS_GENERIC_Y;
291 }
292
293 else if (_col_fam == fmtcl::ColorFamily_RGB)
294 {
295 // Chroma subsampling is ignored
296 pixel_type |= VideoInfo::CS_BGR;
297 pixel_type |= (_alpha_flag)
298 ? VideoInfo::CS_RGBA_TYPE
299 : VideoInfo::CS_RGB_TYPE;
300
301 if (_planar_flag)
302 {
303 pixel_type |= VideoInfo::CS_PLANAR;
304 }
305 else
306 {
307 if (_bitdepth != 8 && _bitdepth != 16)
308 {
309 return -1;
310 }
311 pixel_type |= VideoInfo::CS_INTERLEAVED;
312 }
313 }
314
315 else if (_col_fam == fmtcl::ColorFamily_YUV)
316 {
317 if (_planar_flag)
318 {
319 pixel_type |= VideoInfo::CS_PLANAR;
320 pixel_type |= VideoInfo::CS_VPlaneFirst;
321 pixel_type |= (_alpha_flag) ? VideoInfo::CS_YUVA : VideoInfo::CS_YUV;
322
323 switch ((_subspl_v << 4) | _subspl_h)
324 {
325 case 0x00:
326 pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_1;
327 break;
328 case 0x01:
329 pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_2;
330 break;
331 case 0x11:
332 pixel_type |= VideoInfo::CS_Sub_Height_2 | VideoInfo::CS_Sub_Width_2;
333 break;
334 case 0x02:
335 pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_4;
336 if (_alpha_flag || _bitdepth != 8)
337 {
338 return -1; // Only YV411 in 4:1:1
339 }
340 break;
341 /**** To do: should we allow 4:1:0 (YUV9)? It is in the API but it
342 seems Avisynth filter don't use it.
343 http://avisynth.nl/index.php/Avisynthplus_color_formats
344 ***/
345 default:
346 return -1; // Invalid chroma subsampling
347 }
348 }
349 else
350 {
351 // Only YUY2 allowed with interleaved formats
352 if (_bitdepth != 8 || _alpha_flag || _subspl_h != 1 || _subspl_v != 0)
353 {
354 return -1;
355 }
356 pixel_type = VideoInfo::CS_YUY2;
357 }
358 }
359
360 else
361 {
362 return -1;
363 }
364
365 vi.pixel_type = pixel_type;
366
367 return 0;
368 }
369
370
371
372 // Makes sure all the field are initialised, but doesn't guarantee
373 // that it corresponds to a valid avisynth colorspace.
is_valid() const374 bool FmtAvs::is_valid () const noexcept
375 {
376 return (
377 _bitdepth > 0 && _col_fam >= 0
378 && _subspl_h >= 0 && _subspl_v >= 0);
379 }
380
381
382
set_bitdepth(int bitdepth)383 void FmtAvs::set_bitdepth (int bitdepth) noexcept
384 {
385 assert (is_bitdepth_valid (bitdepth));
386
387 _bitdepth = bitdepth;
388 }
389
390
391
get_bitdepth() const392 int FmtAvs::get_bitdepth () const noexcept
393 {
394 assert (is_valid ());
395
396 return _bitdepth;
397 }
398
399
400
is_float() const401 bool FmtAvs::is_float () const noexcept
402 {
403 assert (is_valid ());
404
405 return (_bitdepth >= 32);
406 }
407
408
409
set_col_fam(fmtcl::ColorFamily col_fam)410 void FmtAvs::set_col_fam (fmtcl::ColorFamily col_fam) noexcept
411 {
412 assert (col_fam >= 0);
413 assert (col_fam < fmtcl::ColorFamily_NBR_ELT);
414 assert (col_fam != fmtcl::ColorFamily_YCGCO);
415
416 _col_fam = col_fam;
417 }
418
419
420
get_col_fam() const421 fmtcl::ColorFamily FmtAvs::get_col_fam () const noexcept
422 {
423 assert (is_valid ());
424
425 return _col_fam;
426 }
427
428
429
is_planar() const430 bool FmtAvs::is_planar () const noexcept
431 {
432 assert (is_valid ());
433
434 return _planar_flag;
435 }
436
437
438
has_alpha() const439 bool FmtAvs::has_alpha () const noexcept
440 {
441 assert (is_valid ());
442
443 return _alpha_flag;
444 }
445
446
447
set_subspl_h(int ss)448 void FmtAvs::set_subspl_h (int ss) noexcept
449 {
450 assert (ss >= 0);
451 assert (ss <= _max_css);
452
453 _subspl_h = ss;
454 }
455
456
457
get_subspl_h() const458 int FmtAvs::get_subspl_h () const noexcept
459 {
460 assert (is_valid ());
461
462 return _subspl_h;
463 }
464
465
466
set_subspl_v(int ss)467 void FmtAvs::set_subspl_v (int ss) noexcept
468 {
469 assert (ss >= 0);
470 assert (ss <= _max_css);
471
472 _subspl_v = ss;
473 }
474
475
476
get_subspl_v() const477 int FmtAvs::get_subspl_v () const noexcept
478 {
479 assert (is_valid ());
480
481 return _subspl_v;
482 }
483
484
485
get_nbr_comp_non_alpha() const486 int FmtAvs::get_nbr_comp_non_alpha () const noexcept
487 {
488 assert (is_valid ());
489
490 return (_col_fam == fmtcl::ColorFamily_GRAY) ? 1 : 3;
491 }
492
493
494
is_bitdepth_valid(int bitdepth)495 bool FmtAvs::is_bitdepth_valid (int bitdepth) noexcept
496 {
497 return (
498 bitdepth == 8
499 || bitdepth == 10
500 || bitdepth == 12
501 || bitdepth == 14
502 || bitdepth == 16
503 || bitdepth == 32
504 );
505 }
506
507
508
509 /*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
510
511
512
513 /*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
514
515
516
remove_outer_spaces(std::string str)517 std::string FmtAvs::remove_outer_spaces (std::string str)
518 {
519 while (! str.empty () && std::isspace (str.back ()) != 0)
520 {
521 str.pop_back ();
522 }
523 while (! str.empty () && std::isspace (str.front ()) != 0)
524 {
525 str.erase (0, 1);
526 }
527
528 return str;
529 }
530
531
532
is_eq_leftstr_and_eat(std::string & str,std::string stest)533 bool FmtAvs::is_eq_leftstr_and_eat (std::string &str, std::string stest)
534 {
535 const auto len = stest.length ();
536 if (str.substr (0, len) == stest)
537 {
538 str.erase (0, len);
539 return true;
540 }
541
542 return false;
543 }
544
545
546
547 // str must be lower-case
548 // If the string is ill-formed, returns true and res < 0
549 // As the pxx value should be last, the string is considered ill-formed
550 // if there are characters after the resolution.
check_planar_bits_and_eat(std::string & str,int & res)551 bool FmtAvs::check_planar_bits_and_eat (std::string &str, int &res)
552 {
553 if (! str.empty () && str.front () == 'p')
554 {
555 str.erase (0, 1);
556 if (str.empty ())
557 {
558 res = -1;
559 return true;
560 }
561 res = check_bits_and_eat (str, true);
562 return true;
563 }
564
565 return false;
566 }
567
568
569
check_bits_and_eat(std::string & str,bool allow_s_flag)570 int FmtAvs::check_bits_and_eat (std::string &str, bool allow_s_flag)
571 {
572 int res = -1;
573
574 if (str.empty ())
575 {
576 return -1;
577 }
578
579 if (str [0] == 's')
580 {
581 if (! allow_s_flag)
582 {
583 return -1;
584 }
585 res = 32;
586 str.erase (0, 1);
587 }
588 else
589 {
590 size_t pos = 0;
591 res = std::stoi (str, &pos, 10);
592 switch (res)
593 {
594 case 8:
595 case 10:
596 case 12:
597 case 14:
598 case 16:
599 case 32:
600 break;
601 default:
602 return -1;
603 }
604 str.erase (0, pos);
605 }
606
607 if (! str.empty ())
608 {
609 return -1;
610 }
611
612 return res;
613 }
614
615
616
617 } // namespace fmtcavs
618
619
620
621 /*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
622