1- using Microsoft . AspNetCore . Mvc ;
2- using Microsoft . VisualBasic . FileIO ;
3- using Xabe . FFmpeg ;
1+ using MaiChartManager . Utils ;
2+ using Microsoft . AspNetCore . Mvc ;
43
54namespace MaiChartManager . Controllers . Music ;
65
76[ ApiController ]
87[ Route ( "MaiChartManagerServlet/[action]Api/{assetDir}/{id:int}" ) ]
9- public class MovieConvertController ( StaticSettings settings , ILogger < MovieConvertController > logger ) : ControllerBase
8+ public class MovieConvertController ( ILogger < MovieConvertController > logger ) : ControllerBase
109{
11- public enum HardwareAccelerationStatus
12- {
13- Pending ,
14- Enabled ,
15- Disabled
16- }
17-
18- public static HardwareAccelerationStatus HardwareAcceleration { get ; private set ; } = HardwareAccelerationStatus . Pending ;
19- public static string H264Encoder { get ; private set ; } = "libx264" ;
20-
21- public static async Task CheckHardwareAcceleration ( )
22- {
23- var tmpDir = Directory . CreateTempSubdirectory ( ) ;
24- try
25- {
26- var blankPath = Path . Combine ( tmpDir . FullName , "blank.ivf" ) ;
27- await FFmpeg . Conversions . New ( )
28- . SetOutputTime ( TimeSpan . FromSeconds ( 2 ) )
29- . SetInputFormat ( Format . lavfi )
30- . AddParameter ( "-i color=c=black:s=720x720:r=1" )
31- . AddParameter ( "-c:v vp9_qsv" )
32- . UseMultiThread ( true )
33- . SetOutput ( blankPath )
34- . Start ( ) ;
35- HardwareAcceleration = HardwareAccelerationStatus . Enabled ;
36- }
37- catch ( Exception e )
38- {
39- Console . WriteLine ( e ) ;
40- HardwareAcceleration = HardwareAccelerationStatus . Disabled ;
41- }
42-
43- foreach ( var encoder in ( ( string [ ] ) [ "h264_nvenc" , "h264_qsv" , "h264_vaapi" , "h264_amf" , "h264_mf" , "h264_vulkan" ] ) )
44- {
45- try
46- {
47- var blankPath = Path . Combine ( tmpDir . FullName , $ "{ encoder } .mp4") ;
48- await FFmpeg . Conversions . New ( )
49- . SetOutputTime ( TimeSpan . FromSeconds ( 2 ) )
50- . SetInputFormat ( Format . lavfi )
51- . AddParameter ( "-i color=c=black:s=720x720:r=1" )
52- . AddParameter ( $ "-c:v { encoder } ")
53- . UseMultiThread ( true )
54- . SetOutput ( blankPath )
55- . Start ( ) ;
56- H264Encoder = encoder ;
57- break ;
58- }
59- catch ( Exception e )
60- {
61- Console . WriteLine ( e ) ;
62- }
63- }
64-
65- Console . WriteLine ( $ "H264 encoder: { H264Encoder } ") ;
66- }
67-
68- private static string Vp9Encoding => HardwareAcceleration == HardwareAccelerationStatus . Enabled ? "vp9_qsv" : "vp9" ;
69-
7010 public enum SetMovieEventType
7111 {
7212 Progress ,
7313 Success ,
7414 Error
7515 }
7616
77- private static IConversion Concatenate ( string vf , params IMediaInfo [ ] mediaInfos )
78- {
79- var conversion = FFmpeg . Conversions . New ( ) ;
80- foreach ( var inputVideo in mediaInfos )
81- {
82- conversion . AddParameter ( "-i " + inputVideo . Path . Escape ( ) + " " ) ;
83- }
84-
85- conversion . AddParameter ( "-filter_complex \" " ) ;
86- for ( var index = 0 ; index < mediaInfos . Length ; ++ index )
87- conversion . AddParameter ( $ "[{ index } :v]setsar=1[{ index } s];") ;
88- for ( var index = 0 ; index < mediaInfos . Length ; ++ index )
89- conversion . AddParameter ( $ "[{ index } s] ") ;
90- conversion . AddParameter ( $ "concat=n={ mediaInfos . Length } :v=1 [v]; [v]{ vf } [vout]\" -map \" [vout]\" ") ;
91-
92- conversion . AddParameter ( "-aspect 1:1" ) ;
93- return conversion ;
94- }
95-
9617 [ HttpPut ]
9718 [ DisableRequestSizeLimit ]
9819 public async Task SetMovie ( int id , [ FromForm ] double padding , IFormFile file , [ FromForm ] bool noScale , [ FromForm ] bool h264 , [ FromForm ] bool yuv420p , string assetDir )
@@ -102,7 +23,7 @@ public async Task SetMovie(int id, [FromForm] double padding, IFormFile file, [F
10223 if ( Path . GetExtension ( file . FileName ) . Equals ( ".dat" , StringComparison . InvariantCultureIgnoreCase ) )
10324 {
10425 var targetPath = Path . Combine ( StaticSettings . StreamingAssets , assetDir , $@ "MovieData\{ id : 000000} .dat") ;
105- Directory . CreateDirectory ( Path . GetDirectoryName ( targetPath ) ) ;
26+ Directory . CreateDirectory ( Path . GetDirectoryName ( targetPath ) ! ) ;
10627 await using var stream = System . IO . File . Open ( targetPath , FileMode . Create ) ;
10728 await file . CopyToAsync ( stream ) ;
10829 StaticSettings . MovieDataMap [ id ] = targetPath ;
@@ -111,139 +32,61 @@ public async Task SetMovie(int id, [FromForm] double padding, IFormFile file, [F
11132
11233 if ( IapManager . License != IapManager . LicenseStatus . Active ) return ;
11334 Response . Headers . Append ( "Content-Type" , "text/event-stream" ) ;
35+
11436 var tmpDir = Directory . CreateTempSubdirectory ( ) ;
11537 logger . LogInformation ( "Temp dir: {tmpDir}" , tmpDir . FullName ) ;
116- // Convert vp9
117- var outVideoPath = Path . Combine ( tmpDir . FullName , h264 ? "out.mp4" : "out.ivf" ) ;
38+
11839 try
11940 {
41+ // 保存上传的文件到临时目录
12042 var srcFilePath = Path . Combine ( tmpDir . FullName , Path . GetFileName ( file . FileName ) ) ;
121- var srcFileStream = System . IO . File . OpenWrite ( srcFilePath ) ;
122- await file . CopyToAsync ( srcFileStream ) ;
123- await srcFileStream . DisposeAsync ( ) ;
124-
125- var srcMedia = await FFmpeg . GetMediaInfo ( srcFilePath ) ;
126- var firstStream = srcMedia . VideoStreams . First ( ) . SetCodec ( h264 ? H264Encoder : Vp9Encoding ) ;
127- var conversion = FFmpeg . Conversions . New ( )
128- . AddStream ( firstStream ) ;
129- if ( file . ContentType . StartsWith ( "image/" ) )
130- {
131- padding = 0 ;
132- conversion . AddParameter ( "-r 1 -t 2" ) ;
133- conversion . AddParameter ( "-loop 1" , ParameterPosition . PreInput ) ;
134- }
135-
136- if ( padding is > 0 and < 0.05 )
137- {
138- padding = 0 ;
139- }
140-
141- var vf = "" ;
142- var scale = h264 ? 2160 : 1080 ;
143- if ( ! noScale )
43+ await using ( var srcFileStream = System . IO . File . OpenWrite ( srcFilePath ) )
14444 {
145- vf = $ "scale= { scale } :-1,pad= { scale } : { scale } :( { scale } -iw)/2:( { scale } -ih)/2:black" ;
45+ await file . CopyToAsync ( srcFileStream ) ;
14646 }
14747
148- if ( padding < 0 )
149- {
150- conversion . SetSeek ( TimeSpan . FromSeconds ( - padding ) ) ;
151- }
152- else if ( padding > 0 )
153- {
154- var blankPath = Path . Combine ( tmpDir . FullName , "blank.mp4" ) ;
155- var blank = FFmpeg . Conversions . New ( )
156- . SetOutputTime ( TimeSpan . FromSeconds ( padding ) )
157- . SetInputFormat ( Format . lavfi )
158- . AddParameter ( $ "-i color=c=black:s={ srcMedia . VideoStreams . First ( ) . Width } x{ srcMedia . VideoStreams . First ( ) . Height } :r=30")
159- . UseMultiThread ( true )
160- . SetOutput ( blankPath ) ;
161- logger . LogInformation ( "About to run FFMpeg with params: {params}" , blank . Build ( ) ) ;
162- await blank . Start ( ) ;
163- var blankVideoInfo = await FFmpeg . GetMediaInfo ( blankPath ) ;
164- conversion = Concatenate ( vf , blankVideoInfo , srcMedia ) ;
165- conversion . AddParameter ( $ "-c:v { ( h264 ? "h264" : Vp9Encoding ) } ") ;
166- }
48+ // 目标路径
49+ var targetPath = Path . Combine ( StaticSettings . StreamingAssets , assetDir , $@ "MovieData\{ id : 000000} .{ ( h264 ? "mp4" : "dat" ) } ") ;
16750
168- conversion
169- . SetOutput ( outVideoPath )
170- . AddParameter ( "-hwaccel dxva2" , ParameterPosition . PreInput )
171- . UseMultiThread ( true ) ;
172- if ( ! h264 )
173- {
174- conversion . AddParameter ( "-cpu-used 5" ) ;
175- if ( yuv420p )
176- conversion . AddParameter ( "-pix_fmt yuv420p" ) ;
177- }
178- if ( ! noScale && padding <= 0 )
51+ // 使用工具类转换视频
52+ await VideoConvert . ConvertVideo ( new VideoConvert . VideoConvertOptions
17953 {
180- conversion . AddParameter ( $ "-vf { vf } ") ;
181- }
54+ InputPath = srcFilePath ,
55+ OutputPath = targetPath ,
56+ NoScale = noScale ,
57+ UseH264 = h264 ,
58+ UseYuv420p = yuv420p ,
59+ Padding = padding ,
60+ ContentType = file . ContentType ,
61+ OnProgress = async percent =>
62+ {
63+ await Response . WriteAsync ( $ "event: { SetMovieEventType . Progress } \n data: { percent } \n \n ") ;
64+ await Response . Body . FlushAsync ( ) ;
65+ }
66+ } ) ;
18267
183- logger . LogInformation ( "About to run FFMpeg with params: {params}" , conversion . Build ( ) ) ;
184- conversion . OnProgress += async ( sender , args ) =>
185- {
186- await Response . WriteAsync ( $ "event: { SetMovieEventType . Progress } \n data: { args . Percent } \n \n ") ;
187- await Response . Body . FlushAsync ( ) ;
188- } ;
189- await conversion . Start ( ) ;
68+ StaticSettings . MovieDataMap [ id ] = targetPath ;
69+ await Response . WriteAsync ( $ "event: { SetMovieEventType . Success } \n data: { SetMovieEventType . Success } \n \n ") ;
70+ await Response . Body . FlushAsync ( ) ;
19071 }
19172 catch ( Exception e )
19273 {
19374 logger . LogError ( e , "Failed to convert video" ) ;
19475 SentrySdk . CaptureException ( e ) ;
195- await Response . WriteAsync ( $ "event: { SetMovieEventType . Error } \n data: 视频转换为 VP9 失败:{ e . Message } \n \n ") ;
196- await Response . Body . FlushAsync ( ) ;
197- return ;
198- }
199-
200- // Convert ivf to usm
201- if ( ! System . IO . File . Exists ( outVideoPath ) || new FileInfo ( outVideoPath ) . Length == 0 )
202- {
203- await Response . WriteAsync ( $ "event: { SetMovieEventType . Error } \n data: 视频转换为 VP9 失败:输出文件不存在\n \n ") ;
76+ await Response . WriteAsync ( $ "event: { SetMovieEventType . Error } \n data: 转换失败:{ e . Message } \n \n ") ;
20477 await Response . Body . FlushAsync ( ) ;
205- return ;
20678 }
207-
208- var outputFile = Path . Combine ( tmpDir . FullName , "out.usm" ) ;
209- if ( h264 )
79+ finally
21080 {
211- outputFile = outVideoPath ;
212- }
213- else
81+ // 清理临时目录
21482 try
21583 {
216- WannaCRI . WannaCRI . CreateUsm ( outVideoPath ) ;
217- if ( ! System . IO . File . Exists ( outputFile ) || new FileInfo ( outputFile ) . Length == 0 )
218- {
219- throw new Exception ( "Output file not found or empty" ) ;
220- }
84+ tmpDir . Delete ( true ) ;
22185 }
222- catch ( Exception e )
86+ catch
22387 {
224- logger . LogError ( e , "Failed to convert ivf to usm" ) ;
225- SentrySdk . CaptureException ( e ) ;
226- await Response . WriteAsync ( $ "event: { SetMovieEventType . Error } \n data: 视频转换为 USM 失败:{ e . Message } \n \n ") ;
227- await Response . Body . FlushAsync ( ) ;
228- return ;
88+ // 忽略清理错误
22989 }
230-
231- try
232- {
233- var targetPath = Path . Combine ( StaticSettings . StreamingAssets , assetDir , $@ "MovieData\{ id : 000000} .{ ( h264 ? "mp4" : "dat" ) } ") ;
234- Directory . CreateDirectory ( Path . GetDirectoryName ( targetPath ) ) ;
235- FileSystem . CopyFile ( outputFile , targetPath , true ) ;
236-
237- StaticSettings . MovieDataMap [ id ] = targetPath ;
238- await Response . WriteAsync ( $ "event: { SetMovieEventType . Success } \n data: { SetMovieEventType . Success } \n \n ") ;
239- await Response . Body . FlushAsync ( ) ;
240- }
241- catch ( Exception e )
242- {
243- logger . LogError ( e , "Failed to copy movie data" ) ;
244- SentrySdk . CaptureException ( e ) ;
245- await Response . WriteAsync ( $ "event: { SetMovieEventType . Error } \n data: 复制文件失败:{ e . Message } \n \n ") ;
246- await Response . Body . FlushAsync ( ) ;
24790 }
24891 }
24992}
0 commit comments