77namespace LottieViewConvert . Helper . Convert
88{
99 /// <summary>
10- /// AVIF format converter using FFmpeg.
10+ /// AVIF format converter using FFmpeg with proper transparency support .
1111 /// </summary>
1212 public class AvifConverter : IFormatConverter
1313 {
@@ -28,30 +28,101 @@ public async Task<bool> ConvertAsync(
2828 IProgress < TimeSpan > ? progress = null ,
2929 CancellationToken cancellationToken = default )
3030 {
31+ try
32+ {
33+ // First pass
34+ var firstPassArgs = GetEncodingArgs ( options , outputPath , 1 ) ;
35+ bool firstPassSuccess = await _commandExecutor . ExecuteAsync (
36+ "ffmpeg" ,
37+ firstPassArgs ,
38+ inputDirectory ,
39+ progress ,
40+ cancellationToken ) ;
41+
42+ if ( ! firstPassSuccess )
43+ throw new Exception ( "First pass AVIF encoding failed" ) ;
44+
45+ // Second pass
46+ var secondPassArgs = GetEncodingArgs ( options , outputPath , 2 ) ;
47+ bool secondPassSuccess = await _commandExecutor . ExecuteAsync (
48+ "ffmpeg" ,
49+ secondPassArgs ,
50+ inputDirectory ,
51+ progress ,
52+ cancellationToken ) ;
53+
54+ if ( ! secondPassSuccess )
55+ throw new Exception ( "Second pass AVIF encoding failed" ) ;
56+
57+ return true ;
58+ }
59+ catch ( Exception ex )
60+ {
61+ throw new Exception ( "Error while converting AVIF" , ex ) ;
62+ }
63+ }
64+
65+ private List < string > GetEncodingArgs (
66+ ConversionOptions options ,
67+ string outputPath ,
68+ int passNumber )
69+ {
70+ // Get quality parameters
71+ int crf = GetCrfValue ( options . Quality ) ;
72+ int crfAlpha = Math . Min ( crf + 20 , 63 ) ; // Higher CRF for alpha (less quality needed for transparency)
73+ int cpuUsed = GetCpuUsed ( options . Quality ) ;
74+
3175 var args = new List < string >
3276 {
3377 "-hide_banner" ,
3478 "-y" ,
3579 "-r" , options . Fps . ToString ( ) ,
3680 "-i" , "%05d.png" ,
81+ "-color_range" , "tv" ,
82+ "-pix_fmt:0" , "yuv420p" ,
83+ "-pix_fmt:1" , "gray8" ,
84+
85+ // Key part: proper alpha channel handling
86+ "-filter_complex" ,
87+ "[0:v]format=pix_fmts=yuva444p[main]; [main]split[main][alpha]; [alpha]alphaextract[alpha]" ,
88+
89+ // Map the main video and alpha channels separately
90+ "-map" , "[main]:v" ,
91+ "-map" , "[alpha]:v" ,
92+
93+ // No audio
94+ "-an" ,
95+
96+ // AV1 codec settings
3797 "-c:v" , "libaom-av1" ,
38- "-crf" , GetCrfValue ( options . Quality ) . ToString ( ) ,
39- "-b:v" , "0" , // Use CRF mode
40- "-cpu-used" , GetCpuUsed ( options . Quality ) . ToString ( ) ,
41- "-row-mt" , "1" , // Enable row-based multithreading
42- "-tiles" , GetTileConfiguration ( options . Quality ) ,
43- "-pix_fmt" , "yuv420p" ,
44- "-movflags" , "+faststart" ,
98+ "-cpu-used" , cpuUsed . ToString ( ) ,
99+ "-crf" , crf . ToString ( ) ,
100+ "-crf:1" , crfAlpha . ToString ( ) ,
101+
102+ // Two-pass encoding
103+ "-pass" , passNumber . ToString ( ) ,
104+
105+ // Add progress reporting for the progress bar
45106 "-progress" , "pipe:1" ,
46107 "-nostats"
47108 } ;
48-
109+
49110 // Add quality-specific optimizations
50111 AddQualityOptimizations ( args , options . Quality ) ;
51-
52- args . Add ( outputPath ) ;
53-
54- return await _commandExecutor . ExecuteAsync ( "ffmpeg" , args , inputDirectory , progress , cancellationToken ) ;
112+
113+ // For first pass, output to null
114+ if ( passNumber == 1 )
115+ {
116+ args . Add ( "-f" ) ;
117+ args . Add ( "null" ) ;
118+ args . Add ( Environment . OSVersion . Platform == PlatformID . Win32NT ? "NUL" : "/dev/null" ) ;
119+ }
120+ else
121+ {
122+ args . Add ( outputPath ) ;
123+ }
124+
125+ return args ;
55126 }
56127
57128 /// <summary>
@@ -96,23 +167,6 @@ private static int GetCpuUsed(int quality)
96167 } ;
97168 }
98169
99- /// <summary>
100- /// Gets the tile configuration for parallel encoding.
101- /// More tiles = faster encoding but slightly reduced efficiency.
102- /// </summary>
103- /// <param name="quality">Quality percentage (0-100)</param>
104- /// <returns>Tile configuration string</returns>
105- private static string GetTileConfiguration ( int quality )
106- {
107- return quality switch
108- {
109- >= 80 => "2x1" , // Fewer tiles for better compression
110- >= 60 => "2x2" , // Balanced
111- >= 40 => "3x2" , // More tiles for faster encoding
112- _ => "4x2" // Maximum tiles for speed
113- } ;
114- }
115-
116170 /// <summary>
117171 /// Adds quality-specific optimization parameters.
118172 /// </summary>
@@ -123,32 +177,29 @@ private static void AddQualityOptimizations(List<string> args, int quality)
123177 if ( quality >= 80 )
124178 {
125179 // High quality optimizations
126- args . AddRange ( new [ ]
127- {
180+ args . AddRange ( [
128181 "-aom-params" , "enable-chroma-deltaq=1:enable-qm=1:qm-min=0:qm-max=15"
129- } ) ;
182+ ] ) ;
130183 }
131184 else if ( quality >= 60 )
132185 {
133186 // Medium quality optimizations
134- args . AddRange ( new [ ]
135- {
187+ args . AddRange ( [
136188 "-aom-params" , "enable-chroma-deltaq=1"
137- } ) ;
189+ ] ) ;
138190 }
139191 else if ( quality < 40 )
140192 {
141193 // Low quality - prioritize speed
142- args . AddRange ( new [ ]
143- {
194+ args . AddRange ( [
144195 "-aom-params" , "enable-cdef=0:enable-restoration=0"
145- } ) ;
196+ ] ) ;
146197 }
147198
148199 // Animation-specific tuning
149200 if ( quality >= 70 )
150201 {
151- args . AddRange ( new [ ] { "-tune" , "psnr" } ) ;
202+ args . AddRange ( [ "-tune" , "psnr" ] ) ;
152203 }
153204 }
154205 }
0 commit comments