/// <summary> /// Adds an action. /// </summary> /// <param name="action">The action to be added.</param> /// <param name="bindEventHandlers">Whether to register event handlers for this action.</param> internal void AddAction(Action action, bool bindEventHandlers = true) { this.currentActions.Add(action); if (bindEventHandlers) { BindEventHandlers(action); } }
/// <inheritdoc /> /// <summary> /// Control initializer /// </summary> /// <param name="action">Action instance</param> public ActionControl(Action action) { SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.CacheText | ControlStyles.UserMouse | ControlStyles.StandardClick | ControlStyles.StandardDoubleClick, true); Action = action; InitializeComponent(); try { this.actionNameLabel.Text = Application.PluginManager.Actions.First(p => p.Type.Name == action.GetType().Name).ToString(); } catch (InvalidOperationException) { // no such action? this.actionNameLabel.Text = Resources.PluginManager_DefaultActionName; } UpdateAction(); UpdateThumbnail(); // bind action events Action.OnStatusChanged += (s, e) => this.dispatcher.Invoke(UpdateAction); if (Action is IReportsProgress reportingProgressAction) { this.currentProgress = 0; reportingProgressAction.OnProgressChanged += OnActionProgressChanged; } foreach (Control control in Controls) { control.MouseMove += OnControlMouseMove; control.MouseLeave += OnControlMouseLeave; } }
internal void Start() { this.videoProvider = VideoProviderFactory.Create(this.region); this.actions = this.task.Actions.Select(a => { try { // set options if needed if (a.Options != null && Application.PluginManager.Actions.First(a2 => a2.Type.Name == a.ActionType).Configurable) { // create uninitialized instance var action = FormatterServices.GetSafeUninitializedObject( Type.GetType(a.ActionType, true, true) ?? throw new InvalidOperationException(Resources.TaskHelper_NoSuchActionMessage)) as Action; // set options property a.GetType().GetProperty("Options")?.SetValue(action, a.Options); // call parameterless constructor action?.GetType() .GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) ?.Invoke(action, new object[] { this.codec }); } return(Activator.CreateInstance(Type.GetType(a.ActionType) ?? throw new InvalidOperationException( Resources.TaskHelper_NoSuchActionMessage), this.codec) as Action); } catch (Exception exception) { Log.WriteLine(LogLevel.Warning, $"error initializing action {a.ActionType}: {exception}"); // create dummy action for displaying error var action = new Action(this.codec); action.SetStatus(ActionStatus.Failed, new Exception(Resources.TaskHelper_ActionInitializationFailedCaption, exception)); return(action); } }) .Where(a => a != null) .ToList(); this.stream = new MultiStream(); this.actions.ForEach(a => { this.stream.Add(a); Application.ActionManager.AddAction(a); }); if (this.codec is ID3D11Codec acceleratedCodec && this.videoProvider is ID3D11VideoProvider acceleratedVideoProvider && acceleratedVideoProvider.SurfacePointer != IntPtr.Zero) { acceleratedCodec.SurfacePointer = acceleratedVideoProvider.SurfacePointer; this.isAcceleratedEncoding = true; Log.WriteLine(LogLevel.Informational, "performing hardware-assisted encoding"); } this.codec?.Initialize(this.videoProvider.CaptureBounds.Size, this.stream); this.recordingThread = new Thread(Record) { Priority = ThreadPriority.Highest }; this.recordingThread.SetApartmentState(ApartmentState.MTA); this.recordingThread.Start(); State = RecordingState.Recording; Application.TrayIcon.AnimateIndicator(IndicatorStatus.Recording, 500); }
/// <summary> /// Captures an still image, encodes it and executes each bound action. /// </summary> /// <param name="task">Task to be run.</param> /// <param name="rect">Rectangle to be captured.</param> /// <returns>An enumeration of the <see cref="Common.Action" />s running.</returns> private static void StartScreenshotTask(Task task, Rectangle rect) { IBitmapVideoProvider provider = null; BitmapData bmpData = default; Bitmap thumbnail; try { // create video provider provider = VideoProviderFactory.Create(rect); // acquire frame and lock frame bits for reading so that codecs can work over it provider.AcquireFrame(); bmpData = provider.LockFrameBitmap(); // generate thumbnail thumbnail = new Bitmap(bmpData.Width, bmpData.Height); // TODO: debug this System.Drawing.Imaging.BitmapData data = thumbnail.LockBits(new Rectangle(Point.Empty, thumbnail.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Utilities.CopyMemory(data.Scan0, bmpData.Scan0, bmpData.Height * bmpData.Stride); thumbnail.UnlockBits(data); using (var tempBmp = new Bitmap(4 * (Resources.NeutralResultOverlay.Width - 48), 4 * (Resources.NeutralResultOverlay.Height - 48))) { using (Graphics graphics = Graphics.FromImage(tempBmp)) { graphics.DrawImage(thumbnail, new Rectangle(Point.Empty, tempBmp.Size), new Rectangle(Point.Empty, thumbnail.Size), GraphicsUnit.Pixel); } thumbnail.Dispose(); thumbnail = tempBmp.Clone() as Bitmap; } } catch (Exception exception) { Log.WriteLine(LogLevel.Error, $"video provider exception: ${exception}"); if (bmpData.Scan0 == default) { provider?.UnlockFrameBitmap(bmpData); } provider?.ReleaseFrame(); provider?.Dispose(); throw new TaskException(Resources.TaskHelper_CaptureFailedCaption, Resources.TaskHelper_CaptureFailedContent, exception); } // initialize codec IStillImageCodec codec; try { // get uninitialized object in case we need to set Options property first codec = Activator.CreateInstance(Type.GetType(task.Codec.CodecType, true, true) ?? throw new InvalidOperationException("No such codec loaded.")) as IStillImageCodec; if (task.Codec.Options is Dictionary <string, object> userOptions && codec is IHasOptions configurableObject) { // set user options configurableObject.Options = configurableObject.Options ?? new Dictionary <string, object>(); foreach (KeyValuePair <string, object> pair in userOptions) { configurableObject.Options[pair.Key] = pair.Value; } } } catch (Exception exception) { Log.WriteLine(LogLevel.Error, $"error initializing codec {task.Codec.CodecType}: {exception}"); throw new TaskException(Resources.TaskHelper_EncodingFailedCaption, Resources.TaskHelper_EncodingInitializationFailedContent, exception); } // create temporary memory stream for holding the encoded data var stream = new MemoryStream(); try { // encode the still image codec?.Encode(bmpData, stream); } catch (Exception exception) { Log.WriteLine(LogLevel.Error, $"error encoding still image: {exception}"); stream.Dispose(); provider.UnlockFrameBitmap(bmpData); provider.ReleaseFrame(); provider.Dispose(); thumbnail?.Dispose(); throw new TaskException(Resources.TaskHelper_EncodingFailedCaption, Resources.TaskHelper_EncodingFailedContent, exception); } List <Action> actions = task.Actions.Select(a => { try { // set options if needed if (a.Options != null && Application.PluginManager.Actions.First(a2 => a2.Type.Name == a.ActionType).Configurable) { // create uninitialized instance var action = FormatterServices.GetSafeUninitializedObject( Type.GetType(a.ActionType, true, true) ?? throw new InvalidOperationException(Resources.TaskHelper_NoSuchActionMessage)) as Action; // set options property a.GetType().GetProperty("Options")?.SetValue(action, a.Options); // call parameterless constructor action?.GetType() .GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) ?.Invoke(action, new object[] { codec }); } return(Activator.CreateInstance(Type.GetType(a.ActionType) ?? throw new InvalidOperationException( Resources.TaskHelper_NoSuchActionMessage), codec) as Action); } catch (Exception exception) { Log.WriteLine(LogLevel.Warning, $"error initializing action {a.ActionType}: {exception}"); // create dummy action for displaying error var action = new Action(codec); action.SetStatus(ActionStatus.Failed, new Exception(Resources.TaskHelper_ActionInitializationFailedCaption, exception)); return(action); } }) .Where(a => a != null) .ToList(); actions.ForEach(a => { Application.ActionManager.AddAction(a); void Release() { if (a == actions.Last()) { // this was the last action, release the temporary stream. It's not disposed unless the last stream // fails to initialize // ReSharper disable once AccessToDisposedClosure Log.WriteLine(LogLevel.Debug, "releasing resources"); stream.Dispose(); provider.UnlockFrameBitmap(bmpData); provider.ReleaseFrame(); provider.Dispose(); GC.Collect(); } } // set preview bitmap a.Thumbnail = thumbnail; try { if (a is IFiltered filteredAction) { if (!filteredAction.GetMediaAcceptance(task.TaskType, codec, task.Codec.Options as ICodecParameters)) { Log.WriteLine(LogLevel.Warning, "media filter did not accept this capture"); throw new Exception(Resources.TaskHelper_UnsupportedMediaMessage); } } if (a is IPreBitmapEncodingAction preEncodingAction) { // set bitmap data instead of copying to the stream Log.WriteLine(LogLevel.Debug, $"setting bitmap data for {a.GetType().Name}"); preEncodingAction.SetBitmapData(bmpData); Release(); a.SetStatus(ActionStatus.Success); Log.WriteLine(LogLevel.Informational, $"action {a.GetType().Name} is done"); } else { // copy data to the action stream Log.WriteLine(LogLevel.Debug, $"writing {stream.Length} bytes to {a.GetType().Name}"); a.SetLength(stream.Length); new Thread(() => { using (var target = new BufferedStream(a, a.BufferSize)) { stream.WriteTo(target); } a.Flush(); a.Dispose(); Release(); Log.WriteLine(LogLevel.Informational, $"action {a.GetType().Name} is ready"); }).Start(); } } catch (Exception exception) { Release(); Log.WriteLine(LogLevel.Warning, $"error copying to action stream {a.GetType().Name}: {exception}"); a.SetStatus(ActionStatus.Failed, new Exception(Resources.TaskHelper_ActionFailedCaption, exception)); } }); }
/// <summary> /// Binds application event handlers for an action. /// </summary> /// <param name="action">The action.</param> private void BindEventHandlers(Action action) { action.OnStatusChanged += OnBoundActionStatusChanged; }