1+ using System ;
12using System . Collections . ObjectModel ;
3+ using System . IO ;
4+ using System . Linq ;
25using System . Threading . Tasks ;
6+ using System . Xml ;
7+ using Avalonia . Controls ;
8+ using Avalonia . Platform . Storage ;
39using CommunityToolkit . Mvvm . ComponentModel ;
410using CommunityToolkit . Mvvm . Input ;
511using ChapterTool . Util ;
12+ using ChapterTool . Util . ChapterData ;
613
714namespace ChapterTool . Avalonia . ViewModels ;
815
@@ -14,28 +21,315 @@ public partial class MainWindowViewModel : ViewModelBase
1421 [ ObservableProperty ]
1522 private string _statusMessage = "Ready" ;
1623
24+ [ ObservableProperty ]
25+ private string _windowTitle = "ChapterTool - Modern Edition" ;
26+
27+ [ ObservableProperty ]
28+ private bool _autoGenName = false ;
29+
30+ [ ObservableProperty ]
31+ private int _selectedExportFormat = 0 ;
32+
33+ [ ObservableProperty ]
34+ private string _expressionText = "x" ;
35+
1736 public ObservableCollection < ChapterViewModel > Chapters { get ; } = new ( ) ;
37+ public ObservableCollection < string > ExportFormats { get ; } = new ( )
38+ {
39+ "OGM Text (.txt)" ,
40+ "Matroska XML (.xml)" ,
41+ "QPFile (.qpf)" ,
42+ "JSON (.json)" ,
43+ "CUE Sheet (.cue)"
44+ } ;
45+
46+ private ChapterInfo ? _currentChapterInfo ;
47+ private Window ? _mainWindow ;
48+
49+ public void SetMainWindow ( Window window )
50+ {
51+ _mainWindow = window ;
52+ }
1853
1954 [ RelayCommand ]
2055 private async Task LoadFile ( )
2156 {
22- // TODO: Implement file loading with file dialog
23- StatusMessage = "Loading file..." ;
24-
25- // Example of using Core library
26- Logger . Log ( "File loading initiated from Avalonia UI" ) ;
57+ if ( _mainWindow == null ) return ;
58+
59+ try
60+ {
61+ var filePickerOptions = new FilePickerOpenOptions
62+ {
63+ Title = "Open Chapter File" ,
64+ AllowMultiple = false ,
65+ FileTypeFilter = new [ ]
66+ {
67+ new FilePickerFileType ( "All Supported Files" )
68+ {
69+ Patterns = new [ ] { "*.mpls" , "*.xml" , "*.txt" , "*.ifo" , "*.mkv" , "*.mka" ,
70+ "*.tak" , "*.flac" , "*.cue" , "*.xpl" , "*.mp4" , "*.m4a" ,
71+ "*.m4v" , "*.vtt" }
72+ } ,
73+ new FilePickerFileType ( "Blu-ray Playlist" ) { Patterns = new [ ] { "*.mpls" } } ,
74+ new FilePickerFileType ( "XML Chapter" ) { Patterns = new [ ] { "*.xml" } } ,
75+ new FilePickerFileType ( "OGM Text" ) { Patterns = new [ ] { "*.txt" } } ,
76+ new FilePickerFileType ( "DVD IFO" ) { Patterns = new [ ] { "*.ifo" } } ,
77+ new FilePickerFileType ( "Matroska" ) { Patterns = new [ ] { "*.mkv" , "*.mka" } } ,
78+ new FilePickerFileType ( "Audio with CUE" ) { Patterns = new [ ] { "*.tak" , "*.flac" , "*.cue" } } ,
79+ new FilePickerFileType ( "HD DVD XPL" ) { Patterns = new [ ] { "*.xpl" } } ,
80+ new FilePickerFileType ( "MP4 Files" ) { Patterns = new [ ] { "*.mp4" , "*.m4a" , "*.m4v" } } ,
81+ new FilePickerFileType ( "WebVTT" ) { Patterns = new [ ] { "*.vtt" } } ,
82+ new FilePickerFileType ( "All Files" ) { Patterns = new [ ] { "*" } }
83+ }
84+ } ;
85+
86+ var files = await _mainWindow . StorageProvider . OpenFilePickerAsync ( filePickerOptions ) ;
87+ if ( files == null || files . Count == 0 ) return ;
88+
89+ var file = files [ 0 ] ;
90+ FilePath = file . Path . LocalPath ;
91+
92+ StatusMessage = "Loading file..." ;
93+ Logger . Log ( $ "Loading file: { FilePath } ") ;
94+
95+ await LoadChapterFile ( FilePath ) ;
96+
97+ StatusMessage = $ "Loaded: { Path . GetFileName ( FilePath ) } - { Chapters . Count } chapters";
98+ WindowTitle = $ "ChapterTool - { Path . GetFileName ( FilePath ) } ";
99+ }
100+ catch ( Exception ex )
101+ {
102+ StatusMessage = $ "Error: { ex . Message } ";
103+ Logger . Log ( $ "Error loading file: { ex . Message } ") ;
104+ }
105+ }
106+
107+ private async Task LoadChapterFile ( string filePath )
108+ {
109+ await Task . Run ( ( ) =>
110+ {
111+ try
112+ {
113+ var extension = Path . GetExtension ( filePath ) ? . ToLowerInvariant ( ) . TrimStart ( '.' ) ;
114+ ChapterInfo ? chapterInfo = null ;
115+
116+ switch ( extension )
117+ {
118+ case "mpls" :
119+ var mplsData = new MplsData ( filePath ) ;
120+ var mplsChapters = mplsData . GetChapters ( ) ;
121+ chapterInfo = mplsChapters . FirstOrDefault ( ) ;
122+ break ;
123+
124+ case "xml" :
125+ var xmlDoc = new XmlDocument ( ) ;
126+ xmlDoc . Load ( filePath ) ;
127+ chapterInfo = XmlData . ParseXml ( xmlDoc ) . FirstOrDefault ( ) ;
128+ break ;
129+
130+ case "txt" :
131+ var txtContent = File . ReadAllBytes ( filePath ) . GetUTFString ( ) ;
132+ chapterInfo = OgmData . GetChapterInfo ( txtContent ?? string . Empty ) ;
133+ break ;
134+
135+ case "ifo" :
136+ chapterInfo = IfoData . GetStreams ( filePath ) . FirstOrDefault ( ) ;
137+ break ;
138+
139+ case "mkv" :
140+ case "mka" :
141+ var mkvData = new MatroskaData ( ) ;
142+ var mkvXml = mkvData . GetXml ( filePath ) ;
143+ chapterInfo = XmlData . ParseXml ( mkvXml ) . FirstOrDefault ( ) ;
144+ break ;
145+
146+ case "cue" :
147+ case "tak" :
148+ case "flac" :
149+ var cueSheet = new CueData ( filePath , Logger . Log ) ;
150+ chapterInfo = cueSheet . Chapter ;
151+ break ;
152+
153+ case "xpl" :
154+ chapterInfo = XplData . GetStreams ( filePath ) . FirstOrDefault ( ) ;
155+ break ;
156+
157+ case "mp4" :
158+ case "m4a" :
159+ case "m4v" :
160+ var mp4Data = new Mp4Data ( filePath ) ;
161+ chapterInfo = mp4Data . Chapter ;
162+ break ;
163+
164+ case "vtt" :
165+ var vttContent = File . ReadAllBytes ( filePath ) . GetUTFString ( ) ;
166+ chapterInfo = VTTData . GetChapterInfo ( vttContent ?? string . Empty ) ;
167+ break ;
168+
169+ default :
170+ throw new Exception ( $ "Unsupported file format: { extension } ") ;
171+ }
172+
173+ if ( chapterInfo != null && chapterInfo . Chapters . Count > 0 )
174+ {
175+ _currentChapterInfo = chapterInfo ;
176+ UpdateChapterDisplay ( ) ;
177+ }
178+ else
179+ {
180+ throw new Exception ( "No chapters found in file" ) ;
181+ }
182+ }
183+ catch ( Exception ex )
184+ {
185+ Logger . Log ( $ "Error parsing file: { ex . Message } ") ;
186+ throw ;
187+ }
188+ } ) ;
189+ }
190+
191+ private void UpdateChapterDisplay ( )
192+ {
193+ Chapters . Clear ( ) ;
27194
28- await Task . Delay ( 100 ) ; // Placeholder for async operation
29- StatusMessage = "File loaded successfully" ;
195+ if ( _currentChapterInfo == null ) return ;
196+
197+ var nameGenerator = ChapterName . GetChapterName ( ) ;
198+ int index = 1 ;
199+
200+ foreach ( var chapter in _currentChapterInfo . Chapters . Where ( c => c . Time != TimeSpan . MinValue ) )
201+ {
202+ Chapters . Add ( new ChapterViewModel
203+ {
204+ Number = chapter . Number > 0 ? chapter . Number : index ,
205+ Time = chapter . Time ,
206+ TimeString = chapter . Time2String ( _currentChapterInfo ) ,
207+ Name = AutoGenName ? nameGenerator ( ) : chapter . Name ,
208+ FramesInfo = chapter . FramesInfo
209+ } ) ;
210+ index ++ ;
211+ }
30212 }
31213
32214 [ RelayCommand ]
33215 private async Task ExportChapters ( )
34216 {
35- // TODO: Implement chapter export functionality
36- StatusMessage = "Exporting chapters..." ;
37- await Task . Delay ( 100 ) ; // Placeholder for async operation
38- StatusMessage = "Export completed" ;
217+ if ( _mainWindow == null || _currentChapterInfo == null )
218+ {
219+ StatusMessage = "No chapters loaded" ;
220+ return ;
221+ }
222+
223+ try
224+ {
225+ var suggestedName = Path . GetFileNameWithoutExtension ( FilePath ) ;
226+ var extension = SelectedExportFormat switch
227+ {
228+ 0 => ".txt" ,
229+ 1 => ".xml" ,
230+ 2 => ".qpf" ,
231+ 3 => ".json" ,
232+ 4 => ".cue" ,
233+ _ => ".txt"
234+ } ;
235+
236+ var filePickerOptions = new FilePickerSaveOptions
237+ {
238+ Title = "Save Chapter File" ,
239+ SuggestedFileName = $ "{ suggestedName } _chapters{ extension } ",
240+ DefaultExtension = extension . TrimStart ( '.' ) ,
241+ FileTypeChoices = new [ ]
242+ {
243+ new FilePickerFileType ( ExportFormats [ SelectedExportFormat ] )
244+ {
245+ Patterns = new [ ] { $ "*{ extension } " }
246+ }
247+ }
248+ } ;
249+
250+ var file = await _mainWindow . StorageProvider . SaveFilePickerAsync ( filePickerOptions ) ;
251+ if ( file == null ) return ;
252+
253+ var savePath = file . Path . LocalPath ;
254+ StatusMessage = "Exporting chapters..." ;
255+ Logger . Log ( $ "Exporting to: { savePath } ") ;
256+
257+ await Task . Run ( ( ) =>
258+ {
259+ switch ( SelectedExportFormat )
260+ {
261+ case 0 : // OGM Text
262+ var text = _currentChapterInfo . GetText ( AutoGenName ) ;
263+ File . WriteAllText ( savePath , text , new System . Text . UTF8Encoding ( true ) ) ;
264+ break ;
265+ case 1 : // XML
266+ _currentChapterInfo . SaveXml ( savePath , "und" , AutoGenName ) ;
267+ break ;
268+ case 2 : // QPFile
269+ var qpfile = _currentChapterInfo . GetQpfile ( ) ;
270+ File . WriteAllLines ( savePath , qpfile ) ;
271+ break ;
272+ case 3 : // JSON
273+ var json = _currentChapterInfo . GetJson ( AutoGenName ) ;
274+ File . WriteAllText ( savePath , json . ToString ( ) ) ;
275+ break ;
276+ case 4 : // CUE
277+ var cue = _currentChapterInfo . GetCue ( Path . GetFileName ( FilePath ) , AutoGenName ) ;
278+ File . WriteAllText ( savePath , cue . ToString ( ) , new System . Text . UTF8Encoding ( false ) ) ;
279+ break ;
280+ }
281+ } ) ;
282+
283+ StatusMessage = $ "Exported to: { Path . GetFileName ( savePath ) } ";
284+ Logger . Log ( $ "Export completed successfully") ;
285+ }
286+ catch ( Exception ex )
287+ {
288+ StatusMessage = $ "Export failed: { ex . Message } ";
289+ Logger . Log ( $ "Export error: { ex . Message } ") ;
290+ }
291+ }
292+
293+ [ RelayCommand ]
294+ private void ApplyExpression ( )
295+ {
296+ if ( _currentChapterInfo == null || string . IsNullOrWhiteSpace ( ExpressionText ) )
297+ {
298+ StatusMessage = "No expression to apply" ;
299+ return ;
300+ }
301+
302+ try
303+ {
304+ _currentChapterInfo . Expr = new Expression ( ExpressionText ) ;
305+ UpdateChapterDisplay ( ) ;
306+ StatusMessage = $ "Expression applied: { ExpressionText } ";
307+ Logger . Log ( $ "Applied expression: { ExpressionText } ") ;
308+ }
309+ catch ( Exception ex )
310+ {
311+ StatusMessage = $ "Expression error: { ex . Message } ";
312+ Logger . Log ( $ "Expression error: { ex . Message } ") ;
313+ }
314+ }
315+
316+ [ RelayCommand ]
317+ private void ShowLog ( )
318+ {
319+ // Log viewer - show current log content
320+ var logText = Logger . LogText ;
321+ StatusMessage = $ "Log has { logText . Split ( '\n ' ) . Length } lines";
322+ }
323+
324+ [ RelayCommand ]
325+ private void ShowAbout ( )
326+ {
327+ StatusMessage = "ChapterTool - Modern Edition | .NET 8 + Avalonia UI" ;
328+ }
329+
330+ partial void OnAutoGenNameChanged ( bool value )
331+ {
332+ UpdateChapterDisplay ( ) ;
39333 }
40334}
41335
@@ -47,6 +341,9 @@ public partial class ChapterViewModel : ObservableObject
47341 [ ObservableProperty ]
48342 private int _number ;
49343
344+ [ ObservableProperty ]
345+ private TimeSpan _time ;
346+
50347 [ ObservableProperty ]
51348 private string _timeString = "00:00:00.000" ;
52349
0 commit comments