260 lines
9.3 KiB
C#
260 lines
9.3 KiB
C#
|
namespace TimelapseApi;
|
|||
|
using FlashCap;
|
|||
|
using FlashCap.Utilities;
|
|||
|
using FlashCap.Devices;
|
|||
|
using SixLabors.ImageSharp;
|
|||
|
using SixLabors.ImageSharp.PixelFormats;
|
|||
|
using Eto.Forms;
|
|||
|
using TimelapseApi.ClassExtensions;
|
|||
|
using Newtonsoft.Json;
|
|||
|
|
|||
|
public class GuiData
|
|||
|
{
|
|||
|
public async Task Set()
|
|||
|
{
|
|||
|
if(Instance ==null) return;
|
|||
|
var s= Instance._extSettings;
|
|||
|
ExtensionLoader.Data=this;
|
|||
|
s.Add((
|
|||
|
()=>{
|
|||
|
return new TimelapseSettings(Instance);
|
|||
|
},"Settings",TimelapseExtension.Null
|
|||
|
));
|
|||
|
foreach(var ext in ExtensionLoader.GetTimelapseExtensions())
|
|||
|
{
|
|||
|
ext.Instance=Instance;
|
|||
|
Instance.Extensions.Add(ext);
|
|||
|
await ext._Create();
|
|||
|
}
|
|||
|
}
|
|||
|
public void FSChanged()
|
|||
|
{
|
|||
|
if(Instance != null)
|
|||
|
{
|
|||
|
if(Instance.Project != null)
|
|||
|
{
|
|||
|
Instance.Project=null;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
public IEnumerable<(Func<IEnumerable<Image<Rgb24>>,string,CancellationToken,Task> Export,string Text,TimelapseExtension Extension,FileFilter[] SaveDialogFilters)>? Export {get {
|
|||
|
if(Instance == null) return null;
|
|||
|
return Instance._export;
|
|||
|
}}
|
|||
|
|
|||
|
public IEnumerable<(Func<Dialog> Dialog,string Text,TimelapseExtension Extension)>? ExtensionSettings {get {if(Instance == null) return null; return Instance._extSettings;}}
|
|||
|
public Api? Instance {get;set;}
|
|||
|
|
|||
|
public List<(TimelapseFileSystem FileSystem,string Text,TimelapseExtension)>? FileSystems {get { if(Instance==null)return null; return Instance._fs;}}
|
|||
|
public TimelapseFileSystem? CurrentFileSystem {get {var fs=FileSystems; if(fs==null) return null; return fs[CurrentFSIndex].FileSystem;} }
|
|||
|
|
|||
|
public IEnumerable<(Func<Window,string,Task> ShareActionAsync,string Text,TimelapseExtension extension)>? Share {get {if(Instance==null) return null;return Instance._share;}}
|
|||
|
public int CurrentFSIndex {get;set;}
|
|||
|
}
|
|||
|
public class Api
|
|||
|
{
|
|||
|
public TimelapseSettingsModel Model = LoadModel();
|
|||
|
internal static string ModelLocation = GetInternalFile("config.json");
|
|||
|
private static TimelapseSettingsModel LoadModel()
|
|||
|
{
|
|||
|
|
|||
|
Directory.CreateDirectory(GetInternalFile("ExtensionBinaries"));
|
|||
|
Directory.CreateDirectory(GetInternalFile("ExtensionData"));
|
|||
|
if(File.Exists(ModelLocation))
|
|||
|
{
|
|||
|
TimelapseSettingsModel? model= JsonConvert.DeserializeObject<TimelapseSettingsModel>(File.ReadAllText(ModelLocation));
|
|||
|
if(model != null) return model;
|
|||
|
}
|
|||
|
return new TimelapseSettingsModel();
|
|||
|
}
|
|||
|
public void SaveModel()
|
|||
|
{
|
|||
|
Directory.CreateDirectory(GetInternalFile());
|
|||
|
|
|||
|
File.WriteAllText(ModelLocation, JsonConvert.SerializeObject(Model));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
private static string Get()
|
|||
|
{
|
|||
|
if(File.Exists("appdir.txt") )
|
|||
|
return File.ReadAllText("appdir.txt");
|
|||
|
else
|
|||
|
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),"TimelapseNow");
|
|||
|
}
|
|||
|
public static string GetInternalFile(params string[] paths)
|
|||
|
{
|
|||
|
if(paths.Length == 0) return Get();
|
|||
|
return Path.Combine(Get(),Path.Combine(paths));
|
|||
|
}
|
|||
|
public event EventHandler<EventArgs>? ProjectClosed;
|
|||
|
|
|||
|
public event EventHandler<EventArgs>? ProjectOpened;
|
|||
|
public Api(GuiData data)
|
|||
|
{
|
|||
|
|
|||
|
Gui=data;
|
|||
|
}
|
|||
|
|
|||
|
private TimelapseProject? _proj=null;
|
|||
|
public TimelapseProject? Project {get {return _proj;} set {
|
|||
|
|
|||
|
if(value != null)
|
|||
|
{
|
|||
|
if(_proj != null)
|
|||
|
{
|
|||
|
ProjectClosed?.Invoke(this,EventArgs.Empty);
|
|||
|
}
|
|||
|
_proj=value;
|
|||
|
ProjectOpened?.Invoke(this,EventArgs.Empty);
|
|||
|
}else{
|
|||
|
ProjectClosed?.Invoke(this,EventArgs.Empty);
|
|||
|
_proj=value;
|
|||
|
}
|
|||
|
|
|||
|
}}
|
|||
|
public event EventHandler<EventArgs>? RecordingChanged;
|
|||
|
|
|||
|
public event EventHandler<EventArgs>? RealTimeChanged;
|
|||
|
|
|||
|
bool _rec,_real;
|
|||
|
|
|||
|
public bool Recording {get {
|
|||
|
if(Project == null) return false;
|
|||
|
return _rec;} set {
|
|||
|
_rec=value;
|
|||
|
RecordingChanged?.Invoke(this,EventArgs.Empty);
|
|||
|
}}
|
|||
|
|
|||
|
public bool RealTime { get{if(Project == null) return false; return _real;} set{
|
|||
|
_real =value;
|
|||
|
RealTimeChanged?.Invoke(this,EventArgs.Empty);
|
|||
|
}}
|
|||
|
|
|||
|
|
|||
|
public async Task RecordFrame(Image<Rgb24> img)
|
|||
|
{
|
|||
|
|
|||
|
if(Project !=null && Recording)
|
|||
|
{
|
|||
|
var interval = RealTime ? TimeSpan.FromSeconds(1) : (TimeSpan)Project.Interval;
|
|||
|
var now=DateTime.Now;
|
|||
|
if((Project.LastFrameTaken + (interval/10)) <= now)
|
|||
|
{
|
|||
|
Project.LastFrameTaken=now;
|
|||
|
await Project.Save(img);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
public async Task SendFrame(Image<Rgb24> img)
|
|||
|
{
|
|||
|
bool canRecordFrame=true;
|
|||
|
foreach(var item in _frameHandlers)
|
|||
|
{
|
|||
|
if(ExtensionAllowedToBeFrameHandler(item.Extension))
|
|||
|
{
|
|||
|
if(item.Handler != null)
|
|||
|
{
|
|||
|
var res=await item.Handler(img);
|
|||
|
if(!res && ExtensionsCanBlockFrames(item.Extension)) canRecordFrame=false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if(canRecordFrame)
|
|||
|
{
|
|||
|
await RecordFrame(img);
|
|||
|
}
|
|||
|
}
|
|||
|
internal bool ExtensionsCanBlockFrames(TimelapseExtension ext)
|
|||
|
{
|
|||
|
if(!Model.canBlockFrames) return false;
|
|||
|
|
|||
|
return !Model.deniedBlockExtensions.Contains( ext.Id);
|
|||
|
}
|
|||
|
internal bool ExtensionAllowedToBeFrameHandler(TimelapseExtension ext)
|
|||
|
{
|
|||
|
if(!Model.canOverlayVideo) return false;
|
|||
|
|
|||
|
return !Model.deniedOverlayExtensions.Contains( ext.Id);
|
|||
|
|
|||
|
}
|
|||
|
private GuiData Gui;
|
|||
|
public int CurrentFileSystemIndex {get {return Gui.CurrentFSIndex;}}
|
|||
|
|
|||
|
internal List<(TimelapseFileSystem FileSystem,string Text,TimelapseExtension Extension)> _fs = CreateFileSystemList();
|
|||
|
|
|||
|
internal List<IDisposable> Extensions =new List<IDisposable>();
|
|||
|
private static List<(TimelapseFileSystem FileSystem,string Text,TimelapseExtension Extension)> CreateFileSystemList()
|
|||
|
{
|
|||
|
List<(TimelapseFileSystem FileSystem,string Text,TimelapseExtension Extension)> fs=new List<(TimelapseFileSystem FileSystem,string Text,TimelapseExtension Extension)>();
|
|||
|
fs.Add((new NativeFileSystem(),"Native",TimelapseExtension.Null));
|
|||
|
return fs;
|
|||
|
}
|
|||
|
|
|||
|
internal List<(Func<Image<Rgb24>,Task<bool>> Handler,string HandlerName, TimelapseExtension Extension)> _frameHandlers=new List<(Func<Image<Rgb24>, Task<bool>> Handler,string HandlerName, TimelapseExtension Extension)>();
|
|||
|
|
|||
|
internal List<(Func<Dialog> Dialog,string Text,TimelapseExtension Extension)> _extSettings= new List<(Func<Dialog> Dialog,string Text,TimelapseExtension Extension)>();
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
public async Task<string?> Export(Window w,int exportId)
|
|||
|
{
|
|||
|
if(Project == null) return null;
|
|||
|
var e=_export[exportId];
|
|||
|
using(var sfd = new SaveFileDialog())
|
|||
|
{
|
|||
|
foreach(var item in e.SaveDialogFilters)
|
|||
|
{
|
|||
|
sfd.Filters.Add(item);
|
|||
|
}
|
|||
|
if(sfd.ShowDialog(w) == DialogResult.Ok)
|
|||
|
{
|
|||
|
try{
|
|||
|
using(var cts=new CancellationTokenSource())
|
|||
|
{
|
|||
|
using(ExportForm frm=new ExportForm(Project,cts))
|
|||
|
{
|
|||
|
frm.Show();
|
|||
|
await e.Export(frm,sfd.FileName,cts.Token);
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
return sfd.FileName;
|
|||
|
}catch(Exception ex)
|
|||
|
{
|
|||
|
_=ex;
|
|||
|
}
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal List<(Func<IEnumerable<Image<Rgb24>>,string,CancellationToken,Task> Export,string Text,TimelapseExtension Extension,FileFilter[] SaveDialogFilters)> _export=GetExports();
|
|||
|
|
|||
|
private static List<(Func<IEnumerable<Image<Rgb24>>, string, CancellationToken, Task> Export, string Text, TimelapseExtension Extension, FileFilter[] SaveDialogFilters)> GetExports()
|
|||
|
{
|
|||
|
var exp=new List<(Func<IEnumerable<Image<Rgb24>>,string,CancellationToken,Task> Export,string Text,TimelapseExtension Extension,FileFilter[] SaveDialogFilters)>();
|
|||
|
Func<IEnumerable<Image<Rgb24>>,string,CancellationToken,Task> callback=async(images,fname,token)=>{
|
|||
|
using(var vfc=new VideoFileCreator(fname))
|
|||
|
{
|
|||
|
foreach(var img in images)
|
|||
|
{
|
|||
|
await vfc.AddAsync(img);
|
|||
|
if(token.IsCancellationRequested)
|
|||
|
{
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
exp.Add((callback,"Export (FFmpeg)",TimelapseExtension.Null,new FileFilter[]{new FileFilter("MPEG-4",".mp4"),new FileFilter("MKV",".mkv")}));
|
|||
|
return exp;
|
|||
|
}
|
|||
|
|
|||
|
internal List<(Func<Window,string,Task> ShareActionAsync,string Text,TimelapseExtension Extension)> _share=new List<(Func<Window,string, Task> ShareActionAsync, string Text,TimelapseExtension Extension)>();
|
|||
|
|
|||
|
|
|||
|
}
|