timelapsenow/Timelapse/Program.cs

443 lines
14 KiB
C#

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<TimelapseExtension,List<ButtonMenuItem>> extBtns=new Dictionary<TimelapseExtension, List<ButtonMenuItem>>();
if(exts != null)
{
foreach(var item in exts)
{
if(!extBtns.ContainsKey(item.Extension))
{
extBtns.Add(item.Extension,new List<ButtonMenuItem>());
}
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<NewFrameEventArgs>? NewFrame;
public async Task FrameChanged(Image<Rgb24> 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();
Instance.HasCamera=c !=null;
if(c == null)
{
Thread t2=new Thread(async()=>{
while(true)
{
Image<Rgb24> rgb=new Image<Rgb24>(Instance.Model.blankCameraWidth,Instance.Model.blankCameraHeight,new Rgb24(0,0,0));
await FrameChanged(rgb);
Thread.Sleep(50);
}
});
t2.Start();
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<byte> image = bufferScope.Buffer.ReferImage();
// Convert to Stream (using FlashCap.Utilities)
using (var stream = image.AsStream())
{
// Decode image data to a bitmap:
Image<Rgb24> img = Image.Load<Rgb24>(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;
}
}
}