1+ using System . IO . Compression ;
2+ using BmsToOsu . Converter ;
3+ using BmsToOsu . Entity ;
4+ using BmsToOsu . Utils ;
5+ using CommandLine ;
6+ using CommandLine . Text ;
7+ using log4net ;
8+
9+ namespace BmsToOsu . Launcher ;
10+
11+ public static class CliArgsLauncher
12+ {
13+ private static readonly ILog Log = LogManager . GetLogger ( nameof ( CliArgsLauncher ) ) ;
14+
15+ private static readonly string [ ] AvailableBmsExt =
16+ {
17+ ".bms" , ".bml" , ".bme" , ".bmx"
18+ } ;
19+
20+ public static void Launch ( string [ ] args )
21+ {
22+ var argsParser = new Parser ( with =>
23+ {
24+ with . AutoVersion = false ;
25+ with . AutoHelp = true ;
26+ with . HelpWriter = null ;
27+ } ) ;
28+
29+ var result = argsParser . ParseArguments < Option > ( args ) ;
30+
31+ result . WithParsed ( Convert ) ;
32+
33+ result . WithNotParsed ( _ =>
34+ {
35+ var helpText = HelpText . AutoBuild ( result , h =>
36+ {
37+ h . AutoHelp = true ;
38+ h . AutoVersion = false ;
39+ h . AutoVersion = false ;
40+ h . AdditionalNewLineAfterOption = false ;
41+ h . AddNewLineBetweenHelpSections = false ;
42+ h . Heading = "" ;
43+ h . Copyright = Const . Copyright ;
44+ return HelpText . DefaultParsingErrorsHandler ( result , h ) ;
45+ } , e => e ) ;
46+ Console . WriteLine ( helpText ) ;
47+ } ) ;
48+ }
49+
50+ public static void Convert ( Option o )
51+ {
52+ o . OutPath = Path . GetFullPath ( o . OutPath ) ;
53+ o . InputPath = Path . GetFullPath ( o . InputPath ) ;
54+ o . Ffmpeg = string . IsNullOrEmpty ( o . Ffmpeg ) ? "" : Path . GetFullPath ( o . Ffmpeg ) ;
55+
56+ var osz = o . OutPath + ".osz" ;
57+
58+ #region check options
59+
60+ // avoid removing existing folder
61+ if ( Directory . Exists ( o . OutPath ) && ! o . NoRemove )
62+ {
63+ Log . Warn ( $ "{ o . OutPath } exists, `--no-remove` will be appended to the parameter") ;
64+ o . NoRemove = true ;
65+ }
66+
67+ if ( o . NoCopy && o . GenerateMp3 )
68+ {
69+ Log . Error ( $ "`--no-copy` is conflict with `--generate-mp3`") ;
70+ return ;
71+ }
72+
73+ // avoid removing after generation
74+ if ( o . NoZip && ! o . NoRemove )
75+ {
76+ Log . Warn ( "`--no-remove` is appended to the parameter" ) ;
77+ o . NoRemove = true ;
78+ }
79+
80+ // avoid duplication
81+ if ( File . Exists ( osz ) )
82+ {
83+ Log . Warn ( $ "{ osz } exists, ignoring...") ;
84+ return ;
85+ }
86+
87+ #endregion
88+
89+ #region parse & convert
90+
91+ var converter = new Converter ( o ) ;
92+
93+ var bms = Directory
94+ . GetFiles ( o . InputPath , "*.*" , SearchOption . AllDirectories )
95+ . Where ( f => AvailableBmsExt . Any ( ext => f . EndsWith ( ext , StringComparison . OrdinalIgnoreCase ) ) ) ;
96+
97+ var skippedFileList = new List < string > ( ) ;
98+ var generationFailedList = new List < string > ( ) ;
99+
100+ void Proc ( params string [ ] path )
101+ {
102+ try
103+ {
104+ converter . Convert ( path ) ;
105+ }
106+ catch ( BmsParserException e )
107+ {
108+ lock ( skippedFileList ) skippedFileList . AddRange ( e . FailedList ) ;
109+ }
110+ catch ( GenerationFailedException e )
111+ {
112+ lock ( generationFailedList ) generationFailedList . AddRange ( e . FailedList ) ;
113+ }
114+ }
115+
116+ Parallel . ForEach ( bms . GroupBy ( Path . GetDirectoryName ) , groupedBms =>
117+ {
118+ if ( o . GenerateMp3 )
119+ {
120+ Proc ( groupedBms . ToArray ( ) ) ;
121+ }
122+ else
123+ {
124+ Parallel . ForEach ( groupedBms , g => Proc ( g ) ) ;
125+ }
126+ } ) ;
127+
128+ #endregion
129+
130+ #region after convertion (e.g. remove temp files)
131+
132+ if ( ! o . NoCopy )
133+ {
134+ Log . Info ( "Copying files" ) ;
135+ Parallel . ForEach ( converter . FilesToCopy , c =>
136+ {
137+ var dest = c . Replace ( o . InputPath , o . OutPath ) ;
138+ dest = Path . Join ( Path . GetDirectoryName ( dest ) , Path . GetFileName ( dest ) . Escape ( ) ) ;
139+
140+ if ( ! File . Exists ( dest ) )
141+ {
142+ File . Copy ( c , dest , true ) ;
143+ }
144+ } ) ;
145+ }
146+
147+ if ( ! o . NoZip && Directory . Exists ( o . OutPath ) )
148+ {
149+ Log . Info ( $ "Creating { osz } ") ;
150+ ZipFile . CreateFromDirectory ( o . OutPath , osz , CompressionLevel . Fastest , false ) ;
151+ }
152+
153+ if ( ! o . NoRemove )
154+ {
155+ Log . Info ( $ "Removing { o . OutPath } ") ;
156+ Directory . Delete ( o . OutPath , true ) ;
157+
158+ if ( o . OutPath . EndsWith ( ".osz" , StringComparison . OrdinalIgnoreCase ) )
159+ {
160+ File . Move ( osz , o . OutPath ) ;
161+ }
162+ }
163+
164+ if ( skippedFileList . Any ( ) )
165+ {
166+ Log . Info ( new string ( '-' , 60 ) ) ;
167+ Log . Info ( "Skipped List:" ) ;
168+
169+ skippedFileList . ForEach ( path => Log . Info ( path ) ) ;
170+ }
171+
172+ if ( generationFailedList . Any ( ) )
173+ {
174+ Log . Info ( new string ( '-' , 60 ) ) ;
175+ Log . Info ( "Generation Failed List:" ) ;
176+
177+ generationFailedList . ForEach ( path => Log . Info ( path ) ) ;
178+ }
179+
180+ #endregion
181+ }
182+ }
183+
184+ internal class GenerationFailedException : Exception
185+ {
186+ public readonly List < string > FailedList ;
187+
188+ public GenerationFailedException ( IEnumerable < string > generationFailedList )
189+ {
190+ FailedList = generationFailedList . ToList ( ) ;
191+ }
192+ }
193+
194+ internal class BmsParserException : GenerationFailedException
195+ {
196+ public BmsParserException ( IEnumerable < string > generationFailedList ) : base ( generationFailedList )
197+ {
198+ }
199+ }
200+
201+ internal class Converter
202+ {
203+ private readonly Option _option ;
204+ private readonly SampleToMp3 _mp3Generator ;
205+ private readonly ILog _log ;
206+
207+ public readonly List < Task > Tasks = new ( ) ;
208+
209+ public Converter ( Option option )
210+ {
211+ _option = option ;
212+ _mp3Generator = new SampleToMp3 ( _option ) ;
213+ _log = LogManager . GetLogger ( nameof ( Converter ) ) ;
214+ }
215+
216+ public readonly HashSet < string > FilesToCopy = new ( ) ;
217+
218+ private void ConvertOne ( BmsFileData data , string mp3Path , HashSet < Sample > excludingSounds )
219+ {
220+ var bmsDir = Path . GetDirectoryName ( data . BmsPath ) ?? "" ;
221+ var outputDir = bmsDir . Replace ( _option . InputPath , _option . OutPath ) ;
222+
223+ Directory . CreateDirectory ( outputDir ) ;
224+
225+ foreach ( var includePlate in new [ ] { true , false } )
226+ {
227+ var ( osuBeatmap , ftc ) = data . ToOsuBeatMap ( excludingSounds , _option . NoSv , mp3Path , includePlate ) ;
228+
229+ foreach ( var c in ftc )
230+ lock ( FilesToCopy )
231+ FilesToCopy . Add ( Path . Join ( bmsDir , c ) ) ;
232+
233+ var plate = includePlate ? " (7+1K)" : "" ;
234+
235+ var osuName = $ "{ data . Metadata . Title } - { data . Metadata . Artist } - BMS Converted{ plate } - { Path . GetFileNameWithoutExtension ( data . BmsPath ) } .osu";
236+
237+ File . WriteAllText ( Path . Join ( outputDir , osuName . MakeValidFileName ( ) ) , osuBeatmap ) ;
238+ }
239+ }
240+
241+ public void Convert ( string [ ] bmsFiles )
242+ {
243+ bmsFiles = bmsFiles . OrderBy ( s => s ) . ToArray ( ) ;
244+
245+ if ( ! bmsFiles . Any ( ) ) return ;
246+
247+ List < Sample > ? soundFileList = null ;
248+
249+ var dataList = new List < BmsFileData > ( ) ;
250+ var parseErrorList = new List < string > ( ) ;
251+
252+ var workPath = Path . GetDirectoryName ( bmsFiles [ 0 ] ) ! ;
253+
254+ foreach ( var bmsFilePath in bmsFiles )
255+ {
256+ _log . Info ( $ "Processing { bmsFilePath } ") ;
257+
258+ BmsFileData data ;
259+
260+ try
261+ {
262+ data = BmsFileData . FromFile ( bmsFilePath ) ;
263+ }
264+ catch ( InvalidDataException )
265+ {
266+ parseErrorList . Add ( bmsFilePath ) ;
267+ continue ;
268+ }
269+
270+ dataList . Add ( data ) ;
271+
272+ if ( ! _option . GenerateMp3 ) continue ;
273+
274+ var soundFiles = data . GetSoundFileList ( ) ;
275+
276+ soundFileList ??= soundFiles ;
277+ soundFileList = soundFileList . Intersect ( soundFiles , Sample . Comparer ) . ToList ( ) ;
278+ }
279+
280+ if ( ! dataList . Any ( ) ) return ;
281+
282+ var filename = _option . GenerateMp3 && soundFileList != null && soundFileList . Any ( )
283+ ? $ "{ dataList [ 0 ] . Metadata . Title } - { dataList [ 0 ] . Metadata . Artist } .mp3". MakeValidFileName ( )
284+ : "" ;
285+
286+ foreach ( var data in dataList )
287+ {
288+ try
289+ {
290+ ConvertOne ( data , filename , new HashSet < Sample > ( soundFileList ?? new List < Sample > ( ) ) ) ;
291+ }
292+ catch ( InvalidDataException )
293+ {
294+ parseErrorList . Add ( data . BmsPath ) ;
295+ }
296+ }
297+
298+ var mp3 = Path . Join (
299+ Path . GetDirectoryName ( dataList [ 0 ] . BmsPath ) !
300+ . Replace ( _option . InputPath , _option . OutPath )
301+ , filename
302+ ) ;
303+
304+ if ( _option . GenerateMp3 )
305+ {
306+ try
307+ {
308+ if ( File . Exists ( mp3 ) )
309+ {
310+ _log . Warn ( $ "{ workPath } : { mp3 } exists, skipping...") ;
311+ }
312+ else
313+ {
314+ if ( soundFileList != null && soundFileList . Any ( ) )
315+ {
316+ _mp3Generator . GenerateMp3 ( soundFileList , workPath , mp3 ) ;
317+ }
318+ else
319+ {
320+ _log . Warn ( $ "{ workPath } : The sampling intersection of the same song is too small, use hit sound instead.") ;
321+ }
322+ }
323+ }
324+ catch
325+ {
326+ throw new GenerationFailedException ( bmsFiles ) ;
327+ }
328+ }
329+
330+ if ( parseErrorList . Any ( ) )
331+ {
332+ throw new BmsParserException ( parseErrorList ) ;
333+ }
334+ }
335+ }
0 commit comments