using System.Net; using System.Net.Sockets; using Eto.Drawing; using Eto.Forms; using FlashCap; using FlashCap.Utilities; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Timelapse.Desktop; using TimelapseApi; using Image = SixLabors.ImageSharp.Image; using System.Reflection; public class MainForm : Form { TimelapseWebServer? svr; string[] args; public GuiData Gui {get;set;} public Api Instance {get;set;} public SynchronizationContext? synchContext; // Constructed capture device. private CaptureDevice? captureDevice; private PictureBox d; private CheckCommand record; private CheckCommand oneX; public MainForm(string[] args) { var asm=Assembly.GetExecutingAssembly(); var strm=asm.GetManifestResourceStream("Timelapse.ServerFiles.favicon.ico"); if(strm != null) { this.Icon = new Icon(strm); } record=new CheckCommand{ MenuText="&Record", ToolBarText="Record" }; oneX = new CheckCommand{ MenuText="&Real Time", ToolBarText="Real Time" }; this.args=args; Gui=new GuiData(); Instance=new Api(Gui); Gui.Instance=Instance; Gui.CurrentFSIndex=0; Gui.Set().Wait(); d=new PictureBox(); } public void CreateMenu() { Command close = new Command((sender,e)=>{Environment.Exit(0);}); close.MenuText="&Exit"; Instance.RealTimeChanged += RealTime_Changed; Instance.RecordingChanged += Recording_Changed; Instance.ProjectOpened += ProjectOpened; Instance.ProjectClosed += ProjectClosed; record.CheckedChanged += Record_CheckedChanged; oneX.CheckedChanged += RealTime_CheckedChanged; NewFrame += NewFrameEvent; Command export=new Command(async(sender,e)=>{ using(ExportWindow window = new ExportWindow(this,Instance,Gui,false)) { await window.ShowModalAsync(this); } }){ MenuText="&Export Project", ToolBarText="Export" }; Command share = new Command(async(sender,e)=>{ using(ExportWindow window = new ExportWindow(this,Instance,Gui,true)) { await window.ShowModalAsync(this); } }){ MenuText="&Share Project", ToolBarText="Share" }; Command changeProjectSettings = new Command(async(sender,e)=>{ var p = Instance.Project; if(p != null) { Instance.Project=null; using(TimelapseProjectSettings settings=new TimelapseProjectSettings(p)) await settings.ShowModalAsync(this); p.Save(); Instance.Project=p; } }){ MenuText="&Project Settings", ToolBarText="Project" }; Command newProject=new Command(async(sender,e)=>{ var cfs=Gui.CurrentFileSystem; if(cfs != null) { string? path=cfs.ShowSaveDialog(this,new FileFilter[]{new FileFilter("Timelapse Project",".tlnp")}); if(!string.IsNullOrWhiteSpace(path)) { string? changed=Path.ChangeExtension(path,""); if(!string.IsNullOrWhiteSpace(changed)) { var ss= cfs+changed.Substring(0,changed.Length-1); if(ss != null) { var proj=new TimelapseProject(); proj.ProjectLocation=ss; using(TimelapseProjectSettings settings=new TimelapseProjectSettings(proj)) await settings.ShowModalAsync(this); proj.Save(); Instance.Project = proj; } } } } }){ MenuText="&New Project", ToolBarText="New" }; Command openProject=new Command((sender,e)=>{ var cfs=Gui.CurrentFileSystem; if(cfs != null) { string? path=cfs.ShowOpenDialog(this,new FileFilter[]{new FileFilter("Timelapse Project",".tlnp")}); if(!string.IsNullOrWhiteSpace(path)) { if(cfs.FileExists(path)) { string? changed=Path.ChangeExtension(path,""); if(!string.IsNullOrWhiteSpace(changed)) { var ss= cfs+changed.Substring(0,changed.Length-1); if(ss != null) { Instance.Project = TimelapseProject.Open(ss); } } } } } }){ MenuText="&Open Project", ToolBarText="Open" }; var FsItem = new ButtonMenuItem { Text="File System", }; var gfs = Gui.FileSystems; if(gfs != null){ int i=0; foreach(var fs in gfs) { int j=i++; var rb =new RadioMenuItem(); rb.Text=fs.Text; rb.CheckedChanged+=(sender,e)=>{ if(rb.Checked) { Gui.CurrentFSIndex=j; Gui.FSChanged(); } }; FsItem.Items.Add(rb); } } var ExtSettings = new ButtonMenuItem { Text="Extension Settings" }; var exts = Gui.ExtensionSettings; Dictionary> extBtns=new Dictionary>(); if(exts != null) { foreach(var item in exts) { if(!extBtns.ContainsKey(item.Extension)) { extBtns.Add(item.Extension,new List()); } var btn=new ButtonMenuItem{Text=item.Text}; btn.Click += async(sender,e)=>{ try{ using(var dlg = item.Dialog()) { await dlg.ShowModalAsync(this); } }catch(Exception ex) { _=ex; } }; extBtns[item.Extension].Add(btn); } foreach(var item in extBtns) { var btn = new ButtonMenuItem() {Text=item.Key.Name}; foreach(var item2 in item.Value) { btn.Items.Add(item2); } ExtSettings.Items.Add(btn); } } ToolBar = new ToolBar{ Items = { new ButtonToolItem(newProject), new ButtonToolItem(openProject), new SeparatorToolItem(), new ButtonToolItem(export), new ButtonToolItem(share), new SeparatorToolItem(), new ButtonToolItem(changeProjectSettings), new CheckToolItem(record), new CheckToolItem(oneX) } }; Menu = new MenuBar { QuitItem=new ButtonMenuItem(close), ApplicationItems={ new ButtonMenuItem(newProject), new ButtonMenuItem(openProject), new SeparatorMenuItem(), new ButtonMenuItem(export), new ButtonMenuItem(share), }, Items={ new ButtonMenuItem { Text="&Project", Items={ new CheckMenuItem(record), new CheckMenuItem(oneX), new ButtonMenuItem(changeProjectSettings) } }, new ButtonMenuItem { Text="&Options", Items={ FsItem, ExtSettings } } } }; } private void NewFrameEvent(object? sender, NewFrameEventArgs e) { if(synchContext != null) synchContext.Post(_ => { // HACK: on .NET Core, will be leaked (or delayed GC?) // So we could release manually before updates. this.d.Image=e.Image; d.Image.Dispose(); }, null); } protected override async void OnShown(EventArgs e) { this.synchContext = SynchronizationContext.Current; await SetCamera(); CreateMenu(); this.Content=d; this.SizeChanged+=(sender,e)=>{ d.Height = ClientSize.Height- d.Location.Y; d.Width = ClientSize.Width-d.Location.X; }; } public event EventHandler? NewFrame; public async Task FrameChanged(Image image) { await Instance.SendFrame(image); NewFrame?.Invoke(this,new NewFrameEventArgs(image)); } public async Task SetCamera() { TimelapseCamera? c=null; using(SelectCamera camera=new SelectCamera()) { c=await camera.ShowModalAsync(this); } svr=new TimelapseWebServer(this); Thread t = new Thread(()=>{ svr.Listen(); }); t.Start(); if(c == null) { MessageBox.Show("You will need to restart app to record"); return; } //////////////////////////////////////////////// // Initialize and start capture device // Enumerate capture devices: // Use first device. var descriptor0 =c.DeviceDescriptor; if (descriptor0 != null) { var characteristics = c.VideoCharacteristics; // Show status. // Open capture device: if(characteristics != null){ this.captureDevice = await descriptor0.OpenAsync( characteristics, this.OnPixelBufferArrived); // Start capturing. this.captureDevice.Start(); } } } private async void OnPixelBufferArrived(PixelBufferScope bufferScope) { //////////////////////////////////////////////// // Pixel buffer has arrived. // NOTE: Perhaps this thread context is NOT UI thread. // Or, refer image data binary directly. // (Advanced manipulation, see README.) ArraySegment image = bufferScope.Buffer.ReferImage(); // Convert to Stream (using FlashCap.Utilities) using (var stream = image.AsStream()) { // Decode image data to a bitmap: Image img = Image.Load(stream); await FrameChanged(img); // `bitmap` is copied, so we can release pixel buffer now. bufferScope.ReleaseNow(); // Switch to UI thread. // HACK: Here is using `SynchronizationContext.Post()` instead of `Control.Invoke()`. // Because in sensitive states when the form is closing, // `Control.Invoke()` can fail with exception. } } private void Record_CheckedChanged(object? sender,EventArgs args) { Instance.RecordingChanged -= Recording_Changed; Instance.Recording = record.Checked; Instance.RecordingChanged += Recording_Changed; } private void RealTime_CheckedChanged(object? sender,EventArgs args) { Instance.RealTimeChanged -= RealTime_Changed; Instance.RealTime = oneX.Checked; Instance.RealTimeChanged += RealTime_Changed; } private void Recording_Changed(object? sender,EventArgs args) { record.CheckedChanged -= Record_CheckedChanged; record.Checked = Instance.Recording; record.CheckedChanged += Record_CheckedChanged; } private void RealTime_Changed(object? sender,EventArgs args) { oneX.CheckedChanged -= RealTime_CheckedChanged; oneX.Checked = Instance.RealTime; oneX.CheckedChanged += RealTime_CheckedChanged; } public void ProjectClosed(object? sender,EventArgs args) { } public void ProjectOpened(object? sender,EventArgs args) { if(Instance.Project != null) { Instance.Recording=false; } } }