Wrong Aspect Ratio - Export Adds Padding Update

What is your operating system?
Windows 10 x64 20H2

What is your Shotcut version (see Help > About Shotcut)? Is it 32-bit?
21.03.21

Can you repeat the problem? If so, what are the steps?
This is a follow-up to Wrong Aspect Ratio - Export Adds Padding

I have a source video that is 720x406 resolution. It has a perfect 16:9 proportions using non-square pixels (PAR 406:405).

I want to clean it up with filters and export as h.264 lossless at the original resolution and scaling. Meaning no spatial interpolation (i.e. to preserve detail).

Shotcut exports it with the wrong aspect ratio. It also adds 2px padding to the right side. (meaning it shrunk the video 0.3% horizontally?).

I made a dummy mp4 source that is able to reproduce the problem. Used Avidemux to convert a 720x406 BMP to h.264. Used MyMP4Box to force PAR 406:405. I included ffprobe outputs of both the original and my dummy for comparison.

Video Mode is automatic

See attached for a sample project, screen shots, logs, etc.

720x406 padding.zip (66.7 KB)

1 Like

here’s another piece of the puzzle. I created a new video mode for 720x406, 16x9. The sample aspect values are wrong.

width=720
height=406
sample_aspect_num=721
sample_aspect_den=720
display_aspect_num=16
display_aspect_den=9
progressive=1
colorspace=601
frame_rate_num=30000
frame_rate_den=1000

It’s like it’s rounding the canvas size to integer pixels. The resolution, display aspect, and sample aspect are in conflict. If you back-calculate 720x406 with 721:720 SAR, the effective DAR is 15.9828:1. And padding pixels in the output is an artifact.

To test, I used Notepad to create a new video profile with the correct sample aspect values (i.e. calculated from the resolution and display aspect), and the export did not add padding.

width=720
height=406
sample_aspect_num=406
sample_aspect_den=405
display_aspect_num=16
display_aspect_den=9
progressive=1
colorspace=601
frame_rate_num=30000
frame_rate_den=1000

I’ve seen shotcut add padding with other resolutions / aspect ratios, too. Example: 848x480 16x9. Real PAR for this combination is 160:159, but Shotcut uses sample aspect as 853/848 (effective DAR 15.9938:1). Again, rounding the canvas size to integers, resulting in padding pixels in the output.

1 Like

@andrewk89 I have seen this video mode for 720x406, 16x9. But i think it is for knowing the value of operating system. :slightly_smiling_face:

Not sure what your comment means. I put my current OS and SC version in the original post.

I use SC to restore old video. Often, I’ve observed that SC adds a few pixels padding in the source --> project pipeline per the preview window (kind of rare) or project --> export pipeline per the SC output file (less rare, but not necessarily common). I’ve been struggling to find out why.

It happens at other resolutions, too. But this is the first time I’ve done a deep dive with a viable example and proof-of-concept for a partial work-around.

Mismatch of the sample aspect vs. the resolution and display aspect may be a clue that would help the developers. Not sure the usage of sample aspect. It’s not given in the UI for source videos, exports, or video modes. So, to me, it should be derived on-the-fly. And, in my counter-example, I showed a solution where different values of the sample aspect variables which are consistent with the resolution and display aspect produce the correct output (i.e. 1:1 correspondence between source pixel position and output pixel position).

Here is an occurrence of the issue:

The math is good, but the precision is not great enough.

In your case, SAR = 16/9 * 406/720 = 1.002469136

720/406 * (720 * 1.002469136)/720 = 1.777777778 (16:9)
720/406 * (721 * 1.000000000)/720 = 1.775862069 (15.xxx:9)

In this case, being off by one is enough error to create padding. With higher precision, basing all SARs against the video width would be fine. Without such precision, the full math would need to become:

SAR_Num = DAR_Num * Video_Height
SAR_Den = DAR_Den * Video_Width

Then distill SAR_Num and SAR_Den down to a reduced fraction, which after four rounds in this case would produce 406/405. The easiest place to reduce the SAR fraction would probably be here to catch most use cases:

A C implementation of fraction reduction can be found here:

https://snipplr.com/view/42917

@shotcut I would submit the code myself, but I don’t have an SDK lit up yet to test any changes.

Fantastic code find. I hope this will fix both issues.

Thanks @Austin. That works good for the custom video mode but not automatic. The code for that starts here:

Here is one alternate approach for that code:

int x = MAX(profile->width, profile->height);
int m = profile->display_aspect_num = x * profile->sample_aspect_num / profile->height;
int n = profile->display_aspect_den = x * profile->sample_aspect_den / profile->width;
int gcd, remainder;
while (n) {
	remainder = m % n;
	m = n;
	n = remainder;
}
gcd = m;
profile->display_aspect_num /= gcd;
profile->display_aspect_den /= gcd;

Do you have something better to suggest there?

I’m a little nervous about the division operation because it could truncate on odd dimensions.

For instance, if I run this code on a 1296x729 video with a 1:1 SAR and 16:9 DAR, the division causes extreme truncation:

x = 1296
m = 1296 * 1 / 729 = 1.777777778 = 1.0 after integer truncation
n = 1296 * 1 / 1296 = 1.0 (always 1.0 for landscape video with 1:1 SAR)
Final DAR = 1:1 (skewed)

Assuming integers will get 4 bytes on all platforms and not overflow, could this work instead?

int m = profile->display_aspect_num = profile->sample_aspect_num * profile->width;
int n = profile->display_aspect_den = profile->sample_aspect_den * profile->height;
if ( n > m ) {
    // Swap
    int x = m;
    m = n;
    n = x;
}
int gcd, remainder;
while (n) {
	remainder = m % n;
	m = n;
	n = remainder;
}
gcd = m;
profile->display_aspect_num /= gcd;
profile->display_aspect_den /= gcd;

This method follows the original code very closely, the only difference being the ratio is based on “full multiplication” rather than being calculated against the video height. Sample run with 1296x729:

m = 1 * 1296 = 1296
n = 1 * 729 = 729
gcd = 81
Final DAR = 16:9 (accurate)

The final DAR has potential to get unwieldy on malformed files with no greatest common divisor available. I’m not sure how significant of an issue that could pose in terms of overflowing the allocated DAR bits of a container header.

Since Shotcut now only supports 64-bit architectures, can’t we use INT64 or UINT64 (C __int 64, unsigned __int64) in place of “int” and improve the accuracy all around?

1 Like

It would take an image 1,000,000 pixels wide with a four-digit SAR to get even halfway to overflowing a 4-byte int. Int is 100% accurate provided it doesn’t overflow. 64-bit is unlikely to get utilized at all, although it doesn’t hurt to use it. My bigger concern was any compilers that default to short int (2-byte).

1 Like

Use INT64
Calculate in nanopixels (LOL)

  1. Left shift divisor 12 (x4K)
  2. Left shift dividend 16 (x64K)
  3. Divide
  4. (Quotient is now x16)
  5. The rounding error is in the bottom bits…
  6. Right shift 4 (/16)

Where is your rounding error now?

(…or am I missing something?)

You can never avoid the possibility of a wrong bottom bit. It is the nature of digitized anything.

Why the concern about division? Division never actually happens here except for the GCD at the end, which by definition will not produce a fraction. This is why we’re not concerned about accuracy or rounding (aside from overflow). The numerator and the denominator are both carried through the entire chain separately rather than being compacted (divided) into a single floating-point number that truncates or rounds, then mixes with other numbers. Same for frame rate, display AR, and pixel AR. The num/den are both retained and processed independently, which keeps them in integer space for highest precision. The only “error” is at the very end of the entire chain where we have to decide if 15.7 pixels is going to be 15 or 16. And the answer varies depending on the question, so this is acceptable error. There will also be a type cast to double when it’s time to ask that final question, so the size of int becomes moot.

1 Like

Thank you for the explanation.

I had, in fact, missed something.

I had focused on…

Ah, okay, well, you were absolutely right about that part. :slight_smile: That’s why I removed that code in my proposed alternative.

1 Like

This is fixed for the next version 21.04 or 21.05 TBD

1 Like

Thank you very much @Austin and @shotcut