GetAllFile()
155 | {
156 | if(SourceFileItem != null)
157 | yield return SourceFileItem;
158 |
159 | if(SpriteVideoFileItem != null)
160 | yield return SpriteVideoFileItem;
161 |
162 | if(EncodedFileItems != null)
163 | foreach (FileItem fileItem in EncodedFileItems)
164 | yield return fileItem;
165 |
166 | if(SnapFileItem != null)
167 | yield return SnapFileItem;
168 |
169 | if(OverlayFileItem != null)
170 | yield return OverlayFileItem;
171 |
172 | if(SubtitleFileItem != null)
173 | yield return SubtitleFileItem;
174 | }
175 |
176 | public void CancelAll(string message)
177 | {
178 | foreach (var item in GetAllFile())
179 | {
180 | item.Cancel(message);
181 | }
182 | }
183 |
184 | public void CleanFilesIfEnd()
185 | {
186 | if(!Finished())
187 | return;
188 |
189 | foreach (FileItem item in GetAllFile())
190 | {
191 | TempFileManager.SafeDeleteTempFiles(item.FilesToDelete, item.IpfsHash);
192 | }
193 |
194 | if (Error())
195 | TempFileManager.SafeCopyError(OriginFilePath, SourceFileItem.IpfsHash);
196 |
197 | TempFileManager.SafeDeleteTempFile(OriginFilePath, SourceFileItem.IpfsHash);
198 | }
199 |
200 | public bool Finished()
201 | {
202 | return GetAllFile().All(f => f.Finished());
203 | }
204 |
205 | public bool Error()
206 | {
207 | return GetAllFile().Any(f => f.Error());
208 | }
209 |
210 | public DateTime LastActivityDateTime => Tools.Max(CreationDate, GetAllFile().Max(f => f.LastActivityDateTime));
211 | }
212 | }
--------------------------------------------------------------------------------
/Uploader.Core/Managers/Common/ProcessManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Text;
4 |
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace Uploader.Core.Managers.Common
8 | {
9 | public class ProcessManager
10 | {
11 | private ProcessStartInfo _processStartInfo;
12 |
13 | private ILogger Logger { get; set; }
14 |
15 | public bool HasTimeout { get; private set; }
16 |
17 | public int ExitCode { get; private set; }
18 |
19 | public StringBuilder DataOutput { get; private set; } = new StringBuilder();
20 |
21 | public StringBuilder ErrorOutput { get; private set; } = new StringBuilder();
22 |
23 | public ProcessManager(string fileName, string arguments, ILogger logger)
24 | {
25 | if(string.IsNullOrWhiteSpace(fileName))
26 | throw new ArgumentNullException(nameof(fileName));
27 | if(string.IsNullOrWhiteSpace(arguments))
28 | throw new ArgumentNullException(nameof(arguments));
29 | if(logger == null)
30 | throw new ArgumentNullException(nameof(logger));
31 |
32 | Logger = logger;
33 |
34 | _processStartInfo = new ProcessStartInfo();
35 | _processStartInfo.FileName = fileName;
36 | _processStartInfo.Arguments = arguments;
37 |
38 | _processStartInfo.RedirectStandardOutput = true;
39 | _processStartInfo.RedirectStandardError = true;
40 | _processStartInfo.WorkingDirectory = TempFileManager.GetTempDirectory();
41 |
42 | _processStartInfo.UseShellExecute = false;
43 | _processStartInfo.ErrorDialog = false;
44 | _processStartInfo.CreateNoWindow = true;
45 | _processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
46 | }
47 |
48 | public bool LaunchAsync(int timeout)
49 | {
50 | LogManager.Log(Logger, LogLevel.Information, _processStartInfo.FileName + " " + _processStartInfo.Arguments, "Launch command");
51 |
52 | try
53 | {
54 | using(Process process = Process.Start(_processStartInfo))
55 | {
56 | process.OutputDataReceived += new DataReceivedEventHandler(OutputDataReceived);
57 | process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataReceived);
58 |
59 | process.BeginOutputReadLine();
60 | process.BeginErrorReadLine();
61 |
62 | bool success = process.WaitForExit(timeout * 1000);
63 |
64 | HasTimeout = !success;
65 | ExitCode = process.ExitCode;
66 |
67 | if (HasTimeout)
68 | {
69 | LogManager.Log(Logger, LogLevel.Error, $"Le process n'a pas pu être exécuté dans le temps imparti.", "Timeout");
70 | return false;
71 | }
72 |
73 | if (ExitCode != 0)
74 | {
75 | LogManager.Log(Logger, LogLevel.Error, $"Le process n'a pas pu être exécuté correctement, erreur {process.ExitCode}.", "Error");
76 | return false;
77 | }
78 |
79 | return true;
80 | }
81 | }
82 | catch(Exception ex)
83 | {
84 | LogManager.Log(Logger, LogLevel.Critical, $"Exception : Le process n'a pas pu être exécuté correctement : {ex}.", "Exception");
85 | return false;
86 | }
87 | }
88 |
89 | public bool Launch(int timeout)
90 | {
91 | LogManager.Log(Logger, LogLevel.Information, _processStartInfo.FileName + " " + _processStartInfo.Arguments, "Launch command");
92 |
93 | try
94 | {
95 | using(Process process = Process.Start(_processStartInfo))
96 | {
97 | bool success = process.WaitForExit(timeout * 1000);
98 |
99 | DataOutput = DataOutput.Append(process.StandardOutput.ReadToEnd());
100 | ErrorOutput = ErrorOutput.Append(process.StandardError.ReadToEnd());
101 |
102 | LogManager.Log(Logger, LogLevel.Debug, DataOutput.ToString(), "DEBUG");
103 | LogManager.Log(Logger, LogLevel.Debug, ErrorOutput.ToString(), "DEBUG");
104 |
105 | HasTimeout = !success;
106 | ExitCode = process.ExitCode;
107 |
108 | if (HasTimeout)
109 | {
110 | LogManager.Log(Logger, LogLevel.Error, $"Le process n'a pas pu être exécuté dans le temps imparti.", "Timeout");
111 | return false;
112 | }
113 |
114 | if (ExitCode != 0)
115 | {
116 | LogManager.Log(Logger, LogLevel.Error, $"Le process n'a pas pu être exécuté correctement, erreur {process.ExitCode}.", "Error");
117 | return false;
118 | }
119 |
120 | return true;
121 | }
122 | }
123 | catch(Exception ex)
124 | {
125 | LogManager.Log(Logger, LogLevel.Critical, $"Exception : Le process n'a pas pu être exécuté correctement : {ex}.", "Exception");
126 | return false;
127 | }
128 | }
129 |
130 | public bool LaunchWithoutTracking()
131 | {
132 | LogManager.Log(Logger, LogLevel.Information, _processStartInfo.FileName + " " + _processStartInfo.Arguments, "Launch command");
133 |
134 | try
135 | {
136 | using(Process process = Process.Start(_processStartInfo))
137 | {
138 | return !process.HasExited || process.ExitCode == 0;
139 | }
140 | }
141 | catch(Exception ex)
142 | {
143 | LogManager.Log(Logger, LogLevel.Critical, $"Exception : Le process n'a pas pu être exécuté correctement : {ex}.", "Exception");
144 | return false;
145 | }
146 | }
147 |
148 | private void OutputDataReceived(object sender, DataReceivedEventArgs e)
149 | {
150 | string output = e.Data;
151 | if (string.IsNullOrWhiteSpace(output))
152 | return;
153 |
154 | LogManager.Log(Logger, LogLevel.Debug, output, "DEBUG");
155 | DataOutput.AppendLine(output);
156 | }
157 |
158 | private void ErrorDataReceived(object sender, DataReceivedEventArgs e)
159 | {
160 | string output = e.Data;
161 | if (string.IsNullOrWhiteSpace(output))
162 | return;
163 |
164 | LogManager.Log(Logger, LogLevel.Debug, output, "DEBUG");
165 | ErrorOutput.AppendLine(output);
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------
/Uploader.Web/wwwroot/scripts/fine-uploader-gallery.min.css:
--------------------------------------------------------------------------------
1 | .qq-gallery .qq-btn{float:right;border:none;padding:0;margin:0;box-shadow:none}.qq-gallery .qq-upload-button{display:inline;width:105px;padding:7px 10px;float:left;text-align:center;background:#00abc7;color:#fff;border-radius:2px;border:1px solid #37b7cc;box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 1px 0 rgba(0,0,0,.36),0 -2px 12px rgba(0,0,0,.08) inset}.qq-gallery .qq-upload-button-hover{background:#33b6cc}.qq-gallery .qq-upload-button-focus{outline:1px dotted #000}.qq-gallery.qq-uploader{position:relative;min-height:200px;max-height:490px;overflow-y:hidden;width:inherit;border-radius:6px;border:1px dashed #ccc;background-color:#fafafa;padding:20px}.qq-gallery.qq-uploader:before{content:attr(qq-drop-area-text) " ";position:absolute;font-size:200%;left:0;width:100%;text-align:center;top:45%;opacity:.25}.qq-gallery .qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#f9f9f9;border-radius:4px;text-align:center}.qq-gallery .qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-gallery .qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-gallery .qq-upload-drop-area-active{background:#fdfdfd;border-radius:4px}.qq-gallery .qq-upload-list{margin:0;padding:10px 0 0;list-style:none;max-height:450px;overflow-y:auto;clear:both;box-shadow:none}.qq-gallery .qq-upload-list li{display:inline-block;position:relative;max-width:120px;margin:0 25px 25px 0;padding:0;line-height:16px;font-size:13px;color:#424242;background-color:#fff;border-radius:2px;box-shadow:0 1px 1px 0 rgba(0,0,0,.22);vertical-align:top;height:186px}.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-failed-text,.qq-gallery .qq-upload-pause,.qq-gallery .qq-upload-retry,.qq-gallery .qq-upload-size,.qq-gallery .qq-upload-spinner{display:inline}.qq-gallery .qq-upload-continue:hover,.qq-gallery .qq-upload-delete:hover,.qq-gallery .qq-upload-pause:hover,.qq-gallery .qq-upload-retry:hover{background-color:transparent}.qq-gallery .qq-upload-cancel,.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-pause{cursor:pointer}.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-pause{border:none;background:0 0;color:#00a0ba;font-size:12px;padding:0}.qq-gallery .qq-upload-status-text{color:#333;font-size:12px;padding-left:3px;padding-top:2px;width:inherit;display:none;width:108px}.qq-gallery .qq-upload-fail .qq-upload-status-text{text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden;display:block}.qq-gallery .qq-upload-retrying .qq-upload-status-text{display:inline-block}.qq-gallery .qq-upload-retrying .qq-progress-bar-container{display:none}.qq-gallery .qq-upload-cancel{background-color:#525252;color:#f7f7f7;font-weight:700;font-family:Arial,Helvetica,sans-serif;border-radius:12px;border:none;height:22px;width:22px;padding:4px;position:absolute;right:-5px;top:-6px;margin:0;line-height:17px}.qq-gallery .qq-upload-cancel:hover{background-color:#525252}.qq-gallery .qq-upload-retry{cursor:pointer;position:absolute;top:30px;left:50%;margin-left:-31px;box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 4px 4px rgba(0,0,0,.5),0 -2px 12px rgba(0,0,0,.08) inset;padding:3px 4px;border:1px solid #d2ddc7;border-radius:2px;color:inherit;background-color:#ebf6e0;z-index:1}.qq-gallery .qq-upload-retry:hover{background-color:#f7ffec}.qq-gallery .qq-file-info{padding:10px 6px 4px;margin-top:-3px;border-radius:0 0 2px 2px;text-align:left;overflow:hidden}.qq-gallery .qq-file-info .qq-file-name{position:relative}.qq-gallery .qq-upload-file{display:block;margin-right:0;margin-bottom:3px;width:auto;text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden}.qq-gallery .qq-upload-spinner{display:inline-block;background:url(loading.gif);position:absolute;left:50%;margin-left:-7px;top:53px;width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-drop-processing{display:block}.qq-gallery .qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-gallery .qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-gallery .qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-upload-fail .qq-upload-failed-text{display:inline}.qq-gallery .qq-upload-retrying .qq-upload-failed-text{display:inline}.qq-gallery .qq-upload-list li.qq-upload-success{background-color:#f2f7ed}.qq-gallery .qq-upload-list li.qq-upload-fail{background-color:#f5eded;box-shadow:0 0 1px 0 red;border:0}.qq-gallery .qq-progress-bar{display:block;background:#00abc7;width:0%;height:15px;border-radius:6px;margin-bottom:3px}.qq-gallery .qq-total-progress-bar{height:25px;border-radius:9px}.qq-gallery .qq-total-progress-bar-container{margin-left:9px;display:inline;float:right;width:500px}.qq-gallery .qq-upload-size{float:left;font-size:11px;color:#929292;margin-bottom:3px;margin-right:0;display:inline-block}.qq-gallery INPUT.qq-edit-filename{position:absolute;opacity:0;z-index:-1}.qq-gallery .qq-upload-file.qq-editable{cursor:pointer;margin-right:20px}.qq-gallery .qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer;position:absolute;right:0;top:0}.qq-gallery INPUT.qq-edit-filename.qq-editing{position:static;height:28px;width:90px;width:-moz-available;padding:0 8px;margin-bottom:3px;border:1px solid #ccc;border-radius:2px;font-size:13px;opacity:1}.qq-gallery .qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-delete-icon{background:url(trash.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-retry-icon{background:url(retry.gif);width:15px;height:15px;vertical-align:sub;display:inline-block;float:none}.qq-gallery .qq-continue-icon{background:url(continue.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-pause-icon{background:url(pause.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-hide{display:none}.qq-gallery .qq-in-progress .qq-thumbnail-wrapper{opacity:.5}.qq-gallery .qq-thumbnail-wrapper{overflow:hidden;position:relative;height:120px;width:120px}.qq-gallery .qq-thumbnail-selector{border-radius:2px 2px 0 0;bottom:0;top:0;margin:auto;display:block}:root *>.qq-gallery .qq-thumbnail-selector{position:relative;top:50%;transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-webkit-transform:translateY(-50%)}.qq-gallery.qq-uploader DIALOG{display:none}.qq-gallery.qq-uploader DIALOG[open]{display:block}.qq-gallery.qq-uploader DIALOG{display:none}.qq-gallery.qq-uploader DIALOG[open]{display:block}.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons{text-align:center;padding-top:10px}.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons BUTTON{margin-left:5px;margin-right:5px}.qq-gallery.qq-uploader DIALOG .qq-dialog-message-selector{padding-bottom:10px}.qq-gallery .qq-uploader DIALOG::backdrop{background-color:rgba(0,0,0,.7)}/*# sourceMappingURL=fine-uploader-gallery.min.css.map */
--------------------------------------------------------------------------------
/Uploader.Core/Models/ProcessItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Extensions.Logging;
4 | using Uploader.Core.Managers.Common;
5 |
6 | namespace Uploader.Core.Models
7 | {
8 | internal class ProcessItem
9 | {
10 | public ProcessItem(FileItem fileItem, ILogger logger)
11 | {
12 | CurrentStep = ProcessStep.Init;
13 | FileItem = fileItem;
14 | Logger = logger;
15 | CreationDate = DateTime.UtcNow;
16 | }
17 |
18 | public FileItem FileItem
19 | {
20 | get;
21 | private set;
22 | }
23 |
24 | public ILogger Logger
25 | {
26 | get;
27 | private set;
28 | }
29 |
30 | public bool CantCascadeCancel
31 | {
32 | get;
33 | set;
34 | }
35 |
36 | public DateTime CreationDate
37 | {
38 | get;
39 | }
40 |
41 | ///
42 | /// Date d'inscription dans la queue à son enregistrement
43 | ///
44 | ///
45 | public DateTime DateInQueue
46 | {
47 | get;
48 | private set;
49 | }
50 |
51 | ///
52 | /// Numéro attribué dans la queue à son enregistrement
53 | ///
54 | ///
55 | public int PositionInQueue
56 | {
57 | get;
58 | private set;
59 | }
60 |
61 | ///
62 | /// Position d'attente dans la queue à son enregistrement
63 | ///
64 | ///
65 | public int OriginWaitingPositionInQueue
66 | {
67 | get;
68 | private set;
69 | }
70 |
71 | public long? WaitingTime
72 | {
73 | get
74 | {
75 | if(CurrentStep == ProcessStep.Init || CurrentStep == ProcessStep.Canceled)
76 | return null;
77 |
78 | if(CurrentStep == ProcessStep.Waiting)
79 | return (long)(DateTime.UtcNow - DateInQueue).TotalSeconds;
80 |
81 | return (long)(StartProcess - DateInQueue).TotalSeconds;
82 | }
83 | }
84 |
85 | public long? ProcessTime
86 | {
87 | get
88 | {
89 | if(CurrentStep < ProcessStep.Started)
90 | return null;
91 |
92 | if(CurrentStep == ProcessStep.Started)
93 | return (long)(DateTime.UtcNow - StartProcess).TotalSeconds;
94 |
95 | return (long)(EndProcess - StartProcess).TotalSeconds;
96 | }
97 | }
98 |
99 | public DateTime LastActivityDateTime => Tools.Max(CreationDate, DateInQueue, StartProcess, EndProcess);
100 |
101 | public DateTime StartProcess
102 | {
103 | get;
104 | private set;
105 | }
106 |
107 | public DateTime EndProcess
108 | {
109 | get;
110 | private set;
111 | }
112 |
113 | public string Progress
114 | {
115 | get;
116 | private set;
117 | }
118 |
119 | public DateTime? LastTimeProgressChanged
120 | {
121 | get;
122 | private set;
123 | }
124 |
125 | public ProcessStep CurrentStep
126 | {
127 | get;
128 | private set;
129 | }
130 |
131 | public string ErrorMessage
132 | {
133 | get;
134 | private set;
135 | }
136 |
137 | public void SavePositionInQueue(int totalAddToQueue, int currentPositionInQueue)
138 | {
139 | PositionInQueue = totalAddToQueue;
140 | OriginWaitingPositionInQueue = totalAddToQueue - currentPositionInQueue;
141 |
142 | DateInQueue = DateTime.UtcNow;
143 | CurrentStep = ProcessStep.Waiting;
144 | }
145 |
146 | public bool Unstarted()
147 | {
148 | return CurrentStep == ProcessStep.Init || CurrentStep == ProcessStep.Waiting;
149 | }
150 |
151 | public bool Finished()
152 | {
153 | return CurrentStep == ProcessStep.Success || CurrentStep == ProcessStep.Error || CurrentStep == ProcessStep.Canceled;
154 | }
155 |
156 | public bool CanProcess()
157 | {
158 | return !FileItem.FileContainer.MustAbort() && CurrentStep == ProcessStep.Waiting;
159 | }
160 |
161 | public void CancelCascade(string shortMessage, string longMessage)
162 | {
163 | Cancel(shortMessage, longMessage);
164 | FileItem.FileContainer.CancelAll(shortMessage);
165 | }
166 |
167 | public void Cancel(string shortMessage, string longMessage)
168 | {
169 | LogManager.Log(Logger, LogLevel.Warning, longMessage, "Annulation");
170 | Cancel(shortMessage);
171 | }
172 |
173 | public void CancelUnstarted(string message)
174 | {
175 | if(!Unstarted())
176 | return;
177 |
178 | Cancel(message);
179 | }
180 |
181 | private void Cancel(string message)
182 | {
183 | Progress = null;
184 | LastTimeProgressChanged = null;
185 | ErrorMessage = message;
186 | CurrentStep = ProcessStep.Canceled;
187 | }
188 |
189 | public void StartProcessDateTime()
190 | {
191 | SetProgress("0.00%");
192 |
193 | StartProcess = DateTime.UtcNow;
194 | CurrentStep = ProcessStep.Started;
195 | }
196 |
197 | public void SetProgress(string progress, bool initMessage = false)
198 | {
199 | Progress = progress;
200 |
201 | if(!initMessage)
202 | LastTimeProgressChanged = DateTime.UtcNow;
203 | }
204 |
205 | public void SetErrorMessage(string shortMessage, string longMessage)
206 | {
207 | LogManager.Log(Logger, LogLevel.Error, longMessage, "Error");
208 |
209 | ErrorMessage = shortMessage;
210 |
211 | EndProcess = DateTime.UtcNow;
212 | CurrentStep = ProcessStep.Error;
213 |
214 | FileItem.FileContainer.CancelAll(shortMessage);
215 | }
216 |
217 | public void SetErrorMessage(string shortMessage, string longMessage, Exception exception)
218 | {
219 | LogManager.Log(Logger, LogLevel.Critical, longMessage, "Exception", exception);
220 |
221 | ErrorMessage = shortMessage;
222 |
223 | EndProcess = DateTime.UtcNow;
224 | CurrentStep = ProcessStep.Error;
225 |
226 | FileItem.FileContainer.ExceptionDetail = exception.ToString();
227 | FileItem.FileContainer.CancelAll(shortMessage);
228 | }
229 |
230 | public void EndProcessDateTime()
231 | {
232 | SetProgress("100.00%");
233 |
234 | EndProcess = DateTime.UtcNow;
235 | CurrentStep = ProcessStep.Success;
236 | }
237 | }
238 | }
--------------------------------------------------------------------------------
/Uploader.Core/Managers/Front/OverlayManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | using Uploader.Core.Managers.Common;
7 | using Uploader.Core.Managers.Ipfs;
8 | using Uploader.Core.Models;
9 |
10 | namespace Uploader.Core.Managers.Front
11 | {
12 | public static class ImageManager
13 | {
14 | private static string _overlayImagePath = Path.Combine(Directory.GetCurrentDirectory(), "overlay.png");
15 |
16 | private static int _finalWidthSnap = 210;
17 | private static int _finalHeightSnap = 118;
18 |
19 | private static int _finalWidthOverlay = 640;
20 | private static int _finalHeightOverlay = 360;
21 |
22 | public static Guid ComputeImage(string sourceFilePath)
23 | {
24 | FileContainer fileContainer = FileContainer.NewImageContainer(sourceFilePath);
25 | FileItem sourceFile = fileContainer.SourceFileItem;
26 |
27 | ///////////////////////////////////////////////////////////////////////////////////////////////
28 | /// resize + crop source image
29 | ///////////////////////////////////////////////////////////////////////////////////////////////
30 |
31 | try
32 | {
33 | LogManager.AddImageMessage(LogLevel.Information, "SourceFileName " + Path.GetFileName(sourceFile.SourceFilePath), "Start Resize and Crop source");
34 | string arguments = $"{Path.GetFileName(sourceFile.SourceFilePath)} -resize \"{_finalWidthOverlay}x{_finalHeightOverlay}^\" -gravity Center -crop {_finalWidthOverlay}x{_finalHeightOverlay}+0+0 {Path.GetFileName(sourceFile.TempFilePath)}";
35 | var process = new ProcessManager(Path.Combine(GeneralSettings.Instance.ImageMagickPath, "convert"), arguments, LogManager.ImageLogger);
36 | bool success = process.Launch(5);
37 | if(!success)
38 | {
39 | LogManager.AddImageMessage(LogLevel.Error, "Erreur convert", "Erreur");
40 | fileContainer.CancelAll("Erreur resize and crop source");
41 | fileContainer.CleanFilesIfEnd();
42 | return fileContainer.ProgressToken;
43 | }
44 | sourceFile.ReplaceOutputPathWithTempPath();
45 | LogManager.AddImageMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(sourceFile.OutputFilePath), "End Resize and Crop source");
46 | }
47 | catch(Exception ex)
48 | {
49 | LogManager.AddImageMessage(LogLevel.Critical, "Exception non gérée resize crop source", "Exception", ex);
50 | fileContainer.CancelAll("Exception non gérée");
51 | fileContainer.CleanFilesIfEnd();
52 | return fileContainer.ProgressToken;
53 | }
54 |
55 | // remplacement de l'image source par l'image de sortie
56 | sourceFile.SetSourceFilePath(sourceFile.OutputFilePath);
57 |
58 | ///////////////////////////////////////////////////////////////////////////////////////////////
59 | /// Resize snap image
60 | ///////////////////////////////////////////////////////////////////////////////////////////////
61 |
62 | // changement de la source de SnapFileItem
63 | fileContainer.SnapFileItem.SetSourceFilePath(sourceFile.SourceFilePath);
64 |
65 | try
66 | {
67 | LogManager.AddImageMessage(LogLevel.Information, "SourceFileName " + Path.GetFileName(fileContainer.SnapFileItem.SourceFilePath), "Start Resize Snap");
68 | string arguments = $"{Path.GetFileName(fileContainer.SnapFileItem.SourceFilePath)} -resize \"{_finalWidthSnap}x{_finalHeightSnap}^\" {Path.GetFileName(fileContainer.SnapFileItem.TempFilePath)}";
69 | var process = new ProcessManager(Path.Combine(GeneralSettings.Instance.ImageMagickPath, "convert"), arguments, LogManager.ImageLogger);
70 | bool success = process.Launch(5);
71 | if(!success)
72 | {
73 | LogManager.AddImageMessage(LogLevel.Error, "Erreur snap", "Erreur");
74 | fileContainer.CancelAll("Erreur snap");
75 | fileContainer.CleanFilesIfEnd();
76 | return fileContainer.ProgressToken;
77 | }
78 | fileContainer.SnapFileItem.ReplaceOutputPathWithTempPath();
79 | LogManager.AddImageMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(fileContainer.SnapFileItem.OutputFilePath), "End Resize Snap");
80 | IpfsDaemon.Instance.Queue(fileContainer.SnapFileItem);
81 | }
82 | catch(Exception ex)
83 | {
84 | LogManager.AddImageMessage(LogLevel.Critical, "Exception non gérée snap", "Exception", ex);
85 | fileContainer.CancelAll("Exception non gérée");
86 | fileContainer.CleanFilesIfEnd();
87 | return fileContainer.ProgressToken;
88 | }
89 |
90 | ///////////////////////////////////////////////////////////////////////////////////////////////
91 | /// Overlay image
92 | ///////////////////////////////////////////////////////////////////////////////////////////////
93 |
94 | // changement de la source de OverlayFileItem
95 | fileContainer.OverlayFileItem.SetSourceFilePath(sourceFile.SourceFilePath);
96 |
97 | try
98 | {
99 | LogManager.AddImageMessage(LogLevel.Information, "SourceFileName " + Path.GetFileName(fileContainer.OverlayFileItem.SourceFilePath), "Start Overlay");
100 | string arguments = $"-gravity NorthEast {_overlayImagePath} {Path.GetFileName(fileContainer.OverlayFileItem.SourceFilePath)} {Path.GetFileName(fileContainer.OverlayFileItem.TempFilePath)}";
101 | var process = new ProcessManager(Path.Combine(GeneralSettings.Instance.ImageMagickPath, "composite"), arguments, LogManager.ImageLogger);
102 | bool success = process.Launch(5);
103 | if(!success)
104 | {
105 | LogManager.AddImageMessage(LogLevel.Error, "Erreur overlay", "Erreur");
106 | fileContainer.CancelAll("Erreur overlay");
107 | fileContainer.CleanFilesIfEnd();
108 | return fileContainer.ProgressToken;
109 | }
110 | fileContainer.OverlayFileItem.ReplaceOutputPathWithTempPath();
111 | LogManager.AddImageMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(fileContainer.OverlayFileItem.OutputFilePath), "End Overlay");
112 | IpfsDaemon.Instance.Queue(fileContainer.OverlayFileItem);
113 | }
114 | catch(Exception ex)
115 | {
116 | LogManager.AddImageMessage(LogLevel.Critical, "Exception non gérée overlay", "Exception", ex);
117 | fileContainer.CancelAll("Exception non gérée");
118 | fileContainer.CleanFilesIfEnd();
119 | return fileContainer.ProgressToken;
120 | }
121 |
122 | return fileContainer.ProgressToken;
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/Uploader.Web/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
81 |
82 |
83 |
96 |
97 | Video
98 |
99 |
111 |
112 | Image
113 |
114 |
126 |
127 | Subtitle
128 |
129 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/Uploader.Core/Managers/Video/EncodeManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Microsoft.Extensions.Logging;
5 | using Uploader.Core.Managers.Common;
6 | using Uploader.Core.Managers.Ipfs;
7 | using Uploader.Core.Models;
8 |
9 | namespace Uploader.Core.Managers.Video
10 | {
11 | internal static class EncodeManager
12 | {
13 | public static bool AudioVideoCpuEncoding(FileItem fileItem)
14 | {
15 | try
16 | {
17 | FileItem sourceFile = fileItem.FileContainer.SourceFileItem;
18 | LogManager.AddEncodingMessage(LogLevel.Information, "SourceFilePath " + Path.GetFileName(sourceFile.SourceFilePath) + " -> " + fileItem.VideoSize, "Start AudioVideoCpuEncoding");
19 | fileItem.AudioVideoCpuEncodeProcess.StartProcessDateTime();
20 |
21 | string size = GetSize(fileItem.VideoSize, sourceFile.VideoWidth.Value, sourceFile.VideoHeight.Value);
22 | string arguments = $"-y -i {Path.GetFileName(sourceFile.SourceFilePath)}";
23 | if(sourceFile.VideoPixelFormat != "yuv420p")
24 | arguments += " -pixel_format yuv420p";
25 |
26 | // si rotation 90 ou 270, inverser la largeur et la hauteur de la video
27 | if(sourceFile.VideoRotate.HasValue && (sourceFile.VideoRotate.Value == 90 || sourceFile.VideoRotate.Value == 270))
28 | {
29 | string[] sizes = size.Split(':');
30 | size = $"{sizes[1]}:{sizes[0]}";
31 | }
32 |
33 | arguments += $" -vf scale={size}";
34 |
35 | if(sourceFile.VideoCodec != "h264")
36 | arguments += " -vcodec libx264";
37 |
38 | if(sourceFile.AudioCodec != "aac")
39 | arguments += " -acodec aac -strict -2"; //-strict -2 pour forcer aac sur ubuntu
40 | else
41 | arguments += " -acodec copy";
42 |
43 | arguments += $" {Path.GetFileName(fileItem.TempFilePath)}";
44 |
45 | var ffmpegProcessManager = new FfmpegProcessManager(fileItem, fileItem.AudioVideoCpuEncodeProcess);
46 | ffmpegProcessManager.StartProcess(arguments, VideoSettings.Instance.EncodeTimeout);
47 |
48 | fileItem.ReplaceOutputPathWithTempPath();
49 | LogManager.AddEncodingMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(fileItem.OutputFilePath) + " / FileSize " + fileItem.FileSize + " / Format " + fileItem.VideoSize, "End AudioVideoCpuEncoding");
50 | fileItem.AudioVideoCpuEncodeProcess.EndProcessDateTime();
51 |
52 | return true;
53 | }
54 | catch (Exception ex)
55 | {
56 | string message = "Exception AudioVideoCpuEncoding : Video Duration " + fileItem.VideoDuration + " / FileSize " + fileItem.FileSize + " / Progress " + fileItem.AudioVideoCpuEncodeProcess.Progress;
57 | fileItem.AudioVideoCpuEncodeProcess.SetErrorMessage("Exception non gérée", message, ex);
58 | return false;
59 | }
60 | }
61 |
62 | public static bool AudioCpuEncoding(FileItem fileItem)
63 | {
64 | try
65 | {
66 | LogManager.AddEncodingMessage(LogLevel.Information, "SourceFilePath " + Path.GetFileName(fileItem.SourceFilePath), "Start AudioCpuEncoding");
67 | fileItem.AudioCpuEncodeProcess.StartProcessDateTime();
68 |
69 | if(fileItem.FileContainer.SourceFileItem.AudioCodec == "aac")
70 | {
71 | fileItem.AudioCpuEncodeProcess.StartProcessDateTime();
72 | fileItem.SetTempFilePath(fileItem.SourceFilePath);
73 | }
74 | else
75 | {
76 | if(fileItem.VideoCodec.ToLower() == "vp8" || fileItem.VideoCodec.ToLower() == "vp9")
77 | fileItem.SetTempFilePath(fileItem.TempFilePath.Replace(".mp4", ".mkv"));
78 |
79 | // encoding audio de la source
80 | string arguments = $"-y -i {Path.GetFileName(fileItem.SourceFilePath)} -vcodec copy -acodec aac -strict -2 {Path.GetFileName(fileItem.TempFilePath)}";
81 | var ffmpegProcessManager = new FfmpegProcessManager(fileItem, fileItem.AudioCpuEncodeProcess);
82 | ffmpegProcessManager.StartProcess(arguments, VideoSettings.Instance.EncodeTimeout);
83 | }
84 |
85 | fileItem.SetVideoAacTempFilePath(fileItem.TempFilePath);
86 | LogManager.AddEncodingMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(fileItem.VideoAacTempFilePath) + " / FileSize " + fileItem.FileSize + " / Format " + fileItem.VideoSize, "End AudioCpuEncoding");
87 | fileItem.AudioCpuEncodeProcess.EndProcessDateTime();
88 |
89 | return true;
90 | }
91 | catch (Exception ex)
92 | {
93 | string message = "Exception AudioCpuEncoding : Video Duration " + fileItem.VideoDuration + " / FileSize " + fileItem.FileSize + " / Progress " + fileItem.AudioCpuEncodeProcess.Progress;
94 | fileItem.AudioCpuEncodeProcess.SetErrorMessage("Exception non gérée", message, ex);
95 | return false;
96 | }
97 | }
98 |
99 | public static bool VideoGpuEncoding(FileItem fileItem)
100 | {
101 | try
102 | {
103 | LogManager.AddEncodingMessage(LogLevel.Information, "SourceFilePath " + Path.GetFileName(fileItem.VideoAacTempFilePath) + " -> 1:N formats", "Start VideoGpuEncoding");
104 | fileItem.VideoGpuEncodeProcess.StartProcessDateTime();
105 |
106 | // encoding video 1:N formats
107 | string arguments = $"-y -hwaccel cuvid -vcodec h264_cuvid -vsync 0 -i {Path.GetFileName(fileItem.VideoAacTempFilePath)}";
108 | if(VideoSettings.Instance.NVidiaCard != "QuadroP5000")
109 | arguments = arguments.Replace(" -hwaccel cuvid -vcodec h264_cuvid -vsync 0 ", " ");
110 |
111 | FileItem sourceFile = fileItem.FileContainer.SourceFileItem;
112 | foreach (FileItem item in fileItem.FileContainer.EncodedFileItems)
113 | {
114 | string size = GetSize(item.VideoSize, fileItem.VideoWidth.Value, fileItem.VideoHeight.Value);
115 | string maxRate = item.VideoSize.MaxRate;
116 |
117 | if(sourceFile.VideoPixelFormat != "yuv420p")
118 | arguments += " -pixel_format yuv420p";
119 |
120 | // si rotation 90 ou 270, inverser la largeur et la hauteur de la video
121 | if(sourceFile.VideoRotate.HasValue && (sourceFile.VideoRotate.Value == 90 || sourceFile.VideoRotate.Value == 270))
122 | {
123 | string[] sizes = size.Split(':');
124 | size = $"{sizes[1]}:{sizes[0]}";
125 | }
126 |
127 | arguments += $" -vf scale_npp={size} -b:v {maxRate} -maxrate {maxRate} -bufsize {maxRate} -vcodec h264_nvenc -acodec copy {Path.GetFileName(item.TempFilePath)}";
128 | if(VideoSettings.Instance.NVidiaCard != "QuadroP5000")
129 | arguments = arguments.Replace("scale_npp=", "scale=");
130 | }
131 |
132 | var ffmpegProcessManager = new FfmpegProcessManager(fileItem, fileItem.VideoGpuEncodeProcess);
133 | ffmpegProcessManager.StartProcess(arguments, VideoSettings.Instance.EncodeTimeout);
134 |
135 | foreach (var item in fileItem.FileContainer.EncodedFileItems)
136 | {
137 | item.ReplaceOutputPathWithTempPath();
138 | LogManager.AddEncodingMessage(LogLevel.Information, "OutputFileName " + Path.GetFileName(item.OutputFilePath) + " / FileSize " + item.FileSize + " / Format " + item.VideoSize, "End VideoGpuEncoding");
139 | }
140 | fileItem.VideoGpuEncodeProcess.EndProcessDateTime();
141 |
142 | return true;
143 | }
144 | catch (Exception ex)
145 | {
146 | string message = "Exception VideoGpuEncoding : Video Duration " + fileItem.VideoDuration + " / FileSize " + fileItem.FileSize + " / Progress " + fileItem.VideoGpuEncodeProcess.Progress;
147 | fileItem.VideoGpuEncodeProcess.SetErrorMessage("Exception non gérée", message, ex);
148 | return false;
149 | }
150 | }
151 |
152 | private static string GetSize(VideoSize videoSize, int width, int height)
153 | {
154 | Tuple finalSize = SizeHelper.GetSize(width, height, videoSize.Width, videoSize.Height);
155 | return $"{finalSize.Item1}:{finalSize.Item2}";
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Uploader.Core/Models/FileItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 |
6 | using Uploader.Core.Managers.Common;
7 | using Uploader.Core.Managers.Ipfs;
8 | using Uploader.Core.Managers.Video;
9 |
10 | namespace Uploader.Core.Models
11 | {
12 | internal class FileItem
13 | {
14 | public static FileItem NewSourceVideoFileItem(FileContainer fileContainer)
15 | {
16 | FileItem fileItem = new FileItem(fileContainer, fileContainer.OriginFilePath, TypeFile.SourceVideo);
17 | fileItem.VideoSize = null;
18 | fileItem.InfoSourceProcess = new ProcessItem(fileItem, LogManager.FfmpegLogger);
19 | return fileItem;
20 | }
21 |
22 | public static FileItem NewEncodedVideoFileItem(FileContainer fileContainer, VideoSize videoSize)
23 | {
24 | if (videoSize == null)
25 | throw new ArgumentNullException(nameof(videoSize));
26 |
27 | FileItem fileItem = new FileItem(fileContainer, fileContainer.SourceFileItem.SourceFilePath, TypeFile.EncodedVideo);
28 | fileItem.VideoSize = videoSize;
29 | fileItem.IpfsProcess = new ProcessItem(fileItem, LogManager.IpfsLogger);
30 | return fileItem;
31 | }
32 |
33 | public static FileItem NewSpriteVideoFileItem(FileContainer fileContainer)
34 | {
35 | FileItem fileItem = new FileItem(fileContainer, fileContainer.OriginFilePath, TypeFile.SpriteVideo);
36 | fileItem.VideoSize = null;
37 | fileItem.SpriteEncodeProcess = new ProcessItem(fileItem, LogManager.SpriteLogger);
38 | fileItem.IpfsProcess = new ProcessItem(fileItem, LogManager.IpfsLogger);
39 | return fileItem;
40 | }
41 |
42 | public static FileItem NewSourceImageFileItem(FileContainer fileContainer)
43 | {
44 | FileItem fileItem = new FileItem(fileContainer, fileContainer.OriginFilePath, TypeFile.SourceImage);
45 | return fileItem;
46 | }
47 |
48 | public static FileItem NewSnapImageFileItem(FileContainer fileContainer)
49 | {
50 | FileItem fileItem = new FileItem(fileContainer, fileContainer.SourceFileItem.SourceFilePath, TypeFile.SnapImage);
51 | fileItem.IpfsProcess = new ProcessItem(fileItem, LogManager.ImageLogger);
52 | return fileItem;
53 | }
54 |
55 | public static FileItem NewOverlayImageFileItem(FileContainer fileContainer)
56 | {
57 | FileItem fileItem = new FileItem(fileContainer, fileContainer.SourceFileItem.SourceFilePath, TypeFile.OverlayImage);
58 | fileItem.IpfsProcess = new ProcessItem(fileItem, LogManager.ImageLogger);
59 | return fileItem;
60 | }
61 |
62 | public static FileItem NewSubtitleFileItem(FileContainer fileContainer)
63 | {
64 | FileItem fileItem = new FileItem(fileContainer, null, TypeFile.SubtitleText);
65 | fileItem.IpfsProcess = new ProcessItem(fileItem, LogManager.SubtitleLogger);
66 | return fileItem;
67 | }
68 |
69 | private FileItem(FileContainer fileContainer, string sourceFilePath, TypeFile typeFile)
70 | {
71 | FileContainer = fileContainer;
72 | FilesToDelete = new List();
73 | CreationDate = DateTime.UtcNow;
74 | SetSourceFilePath(sourceFilePath);
75 | TypeFile = typeFile;
76 | switch(typeFile){
77 | case TypeFile.EncodedVideo:
78 | case TypeFile.SourceVideo:
79 | SetTempFilePath(Path.ChangeExtension(TempFileManager.GetNewTempFilePath(), ".mp4"));
80 | break;
81 |
82 | case TypeFile.SpriteVideo:
83 | SetTempFilePath(Path.ChangeExtension(TempFileManager.GetNewTempFilePath(), ".jpeg"));
84 | break;
85 |
86 | case TypeFile.SourceImage:
87 | case TypeFile.SnapImage:
88 | case TypeFile.OverlayImage:
89 | SetTempFilePath(Path.ChangeExtension(TempFileManager.GetNewTempFilePath(), ".png"));
90 | break;
91 |
92 | case TypeFile.SubtitleText:
93 | SetTempFilePath(Path.ChangeExtension(TempFileManager.GetNewTempFilePath(), ".vtt"));
94 | break;
95 | }
96 | }
97 |
98 | public void AddGpuEncodeProcess()
99 | {
100 | AudioCpuEncodeProcess = new ProcessItem(this, LogManager.FfmpegLogger);
101 | VideoGpuEncodeProcess = new ProcessItem(this, LogManager.FfmpegLogger);
102 | }
103 |
104 | public void AddCpuEncodeProcess()
105 | {
106 | AudioVideoCpuEncodeProcess = new ProcessItem(this, LogManager.FfmpegLogger);
107 | }
108 |
109 | public DateTime CreationDate
110 | {
111 | get;
112 | }
113 |
114 | public TypeFile TypeFile
115 | {
116 | get;
117 | }
118 |
119 | public bool IsSource => TypeFile == TypeFile.SourceVideo || TypeFile == TypeFile.SourceImage;
120 |
121 | public DateTime LastActivityDateTime => Tools.Max(CreationDate, GetAllProcess().Any() ? GetAllProcess().Max(p => p.LastActivityDateTime) : DateTime.MinValue);
122 |
123 | public long? FileSize { get; private set; }
124 |
125 | public string OutputFilePath { get { return _outputFilePath; } }
126 | private string _outputFilePath;
127 |
128 | public void SetOutputFilePath(string path)
129 | {
130 | _outputFilePath = path;
131 | SafeAddFileToDelete(path);
132 |
133 | if (OutputFilePath != null && File.Exists(OutputFilePath))
134 | FileSize = new FileInfo(OutputFilePath).Length;
135 | }
136 |
137 | private void SafeAddFileToDelete(string path)
138 | {
139 | if(string.IsNullOrWhiteSpace(path))
140 | return;
141 |
142 | if(!FilesToDelete.Contains(path) && path != FileContainer.OriginFilePath)
143 | FilesToDelete.Add(path);
144 | }
145 |
146 | public void ReplaceOutputPathWithTempPath()
147 | {
148 | SetOutputFilePath(TempFilePath);
149 | }
150 |
151 | public string SourceFilePath { get { return _sourceFilePath; } }
152 | private string _sourceFilePath;
153 |
154 | public void SetSourceFilePath(string path)
155 | {
156 | _sourceFilePath = path;
157 | SafeAddFileToDelete(path);
158 | }
159 |
160 | public string TempFilePath { get { return _tempFilePath; } }
161 | private string _tempFilePath;
162 |
163 | public void SetTempFilePath(string path)
164 | {
165 | _tempFilePath = path;
166 | SafeAddFileToDelete(path);
167 | }
168 |
169 | public string VideoAacTempFilePath { get { return _videoAacTempFilePath; } }
170 | private string _videoAacTempFilePath;
171 |
172 | public void SetVideoAacTempFilePath(string path)
173 | {
174 | _videoAacTempFilePath = path;
175 | SafeAddFileToDelete(path);
176 | }
177 |
178 | public FileContainer FileContainer
179 | {
180 | get;
181 | }
182 |
183 | public VideoSize VideoSize
184 | {
185 | get;
186 | private set;
187 | }
188 |
189 | public string VideoFrameRate
190 | {
191 | get;
192 | set;
193 | }
194 |
195 | public int? VideoBitRate
196 | {
197 | get;
198 | set;
199 | }
200 |
201 | public int? VideoRotate
202 | {
203 | get;
204 | set;
205 | }
206 |
207 | public int? VideoNbFrame
208 | {
209 | get;
210 | set;
211 | }
212 |
213 | public string VideoPixelFormat
214 | {
215 | get;
216 | set;
217 | }
218 |
219 | public string AudioCodec
220 | {
221 | get;
222 | set;
223 | }
224 |
225 | public string VideoCodec
226 | {
227 | get;
228 | set;
229 | }
230 |
231 | ///
232 | /// in seconds
233 | ///
234 | ///
235 | public int? VideoDuration
236 | {
237 | get;
238 | set;
239 | }
240 |
241 | public int? VideoHeight
242 | {
243 | get;
244 | set;
245 | }
246 |
247 | public int? VideoWidth
248 | {
249 | get;
250 | set;
251 | }
252 |
253 | ///
254 | /// Récupération durée et résolution de la vidéo source
255 | ///
256 | ///
257 | public ProcessItem InfoSourceProcess
258 | {
259 | get;
260 | private set;
261 | }
262 |
263 | ///
264 | /// Encode audio de la source
265 | ///
266 | ///
267 | public ProcessItem AudioCpuEncodeProcess
268 | {
269 | get;
270 | private set;
271 | }
272 |
273 | ///
274 | /// Encode video et audi des formats demandés par CPU
275 | ///
276 | ///
277 | public ProcessItem AudioVideoCpuEncodeProcess
278 | {
279 | get;
280 | private set;
281 | }
282 |
283 | ///
284 | /// Encode video des formats demandés sans l'audio par GPU
285 | ///
286 | ///
287 | public ProcessItem VideoGpuEncodeProcess
288 | {
289 | get;
290 | private set;
291 | }
292 |
293 | ///
294 | /// Sprite création d'une video
295 | ///
296 | ///
297 | public ProcessItem SpriteEncodeProcess
298 | {
299 | get;
300 | private set;
301 | }
302 |
303 | public ProcessItem IpfsProcess
304 | {
305 | get;
306 | private set;
307 | }
308 |
309 | public string IpfsHash
310 | {
311 | get;
312 | set;
313 | }
314 |
315 | public IList FilesToDelete { get; private set; }
316 |
317 | public bool SuccessGetSourceInfo()
318 | {
319 | return (VideoDuration??0) > 0
320 | && (VideoWidth??0) > 0
321 | && (VideoHeight??0) > 0
322 | //&& (VideoBitRate??0) > 0
323 | //&& (VideoNbFrame??0) > 0
324 | //&& !string.IsNullOrWhiteSpace(VideoFrameRate)
325 | && !string.IsNullOrWhiteSpace(VideoCodec)
326 | && !string.IsNullOrWhiteSpace(VideoPixelFormat)
327 | && !string.IsNullOrWhiteSpace(AudioCodec);
328 | }
329 |
330 | public bool HasReachMaxVideoDurationForEncoding()
331 | {
332 | return VideoDuration.HasValue ? VideoDuration.Value > VideoSettings.Instance.MaxVideoDurationForEncoding : false;
333 | }
334 |
335 | public void AddIpfsProcess(string sourceFilePath)
336 | {
337 | if(IpfsProcess == null)
338 | {
339 | IpfsProcess = new ProcessItem(this, LogManager.IpfsLogger);
340 | IpfsProcess.CantCascadeCancel = true;
341 | SetOutputFilePath(sourceFilePath);
342 | }
343 | }
344 |
345 | private IEnumerable GetAllProcess()
346 | {
347 | if(IpfsProcess != null)
348 | yield return IpfsProcess;
349 |
350 | if(AudioCpuEncodeProcess != null)
351 | yield return AudioCpuEncodeProcess;
352 |
353 | if(AudioVideoCpuEncodeProcess != null)
354 | yield return AudioVideoCpuEncodeProcess;
355 |
356 | if(VideoGpuEncodeProcess != null)
357 | yield return VideoGpuEncodeProcess;
358 |
359 | if(SpriteEncodeProcess != null)
360 | yield return SpriteEncodeProcess;
361 | }
362 |
363 | public void Cancel(string message)
364 | {
365 | if(!GetAllProcess().Any())
366 | return;
367 |
368 | foreach (ProcessItem item in GetAllProcess().Where(p => p.Unstarted() && !p.CantCascadeCancel))
369 | {
370 | item.CancelUnstarted(message);
371 | }
372 | }
373 |
374 | public bool Finished()
375 | {
376 | if(!GetAllProcess().Any())
377 | return true;
378 |
379 | return GetAllProcess().All(p => p.Finished());
380 | }
381 |
382 | public bool Error()
383 | {
384 | if(!GetAllProcess().Any())
385 | return false;
386 |
387 | return GetAllProcess().Any(p => p.CurrentStep == ProcessStep.Error);
388 | }
389 | }
390 | }
--------------------------------------------------------------------------------
/Uploader.Core/Managers/Front/ProgressManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | using Microsoft.Extensions.Logging;
10 |
11 | using Uploader.Core.Managers.Common;
12 | using Uploader.Core.Managers.Ipfs;
13 | using Uploader.Core.Managers.Video;
14 | using Uploader.Core.Models;
15 |
16 | namespace Uploader.Core.Managers.Front
17 | {
18 | public static class ProgressManager
19 | {
20 | public static string Version => GeneralSettings.Instance.Version;
21 |
22 | private static ConcurrentDictionary progresses = new ConcurrentDictionary();
23 |
24 | internal static void RegisterProgress(FileContainer fileContainer)
25 | {
26 | progresses.TryAdd(fileContainer.ProgressToken, fileContainer);
27 |
28 | // Supprimer le suivi progress après 1j
29 | var now = DateTime.UtcNow;
30 | var purgeList = progresses.Values.Where(f => (now - f.LastActivityDateTime).TotalHours >= 24).ToList();
31 | foreach (FileContainer toDelete in purgeList)
32 | {
33 | FileContainer thisFileContainer;
34 | progresses.TryRemove(toDelete.ProgressToken, out thisFileContainer);
35 | }
36 | }
37 |
38 | public static dynamic GetErrors()
39 | {
40 | return new
41 | {
42 | list = progresses.Values
43 | .Where(c => c.Error())
44 | .Select(c => GetResult(c, true))
45 | .ToList()
46 | };
47 | }
48 |
49 | public static dynamic GetStats(bool details)
50 | {
51 | return details ? GetDetailStats() : GetLightStats();
52 | }
53 |
54 | public static dynamic GetLightStats()
55 | {
56 | return new
57 | {
58 | version = Version,
59 | currentWaitingInQueue = GetCurrentWaitingInQueue()
60 | };
61 | }
62 |
63 | public static dynamic GetDetailStats()
64 | {
65 | try
66 | {
67 | var list = progresses.Values.ToList();
68 |
69 | var listVideoEncoded = new List();
70 | var listSpriteCreated = new List();
71 | var listIpfsAdded = new List();
72 |
73 | listVideoEncoded.AddRange(list.Select(l => l.SourceFileItem));
74 | listIpfsAdded.AddRange(list.Select(l => l.SourceFileItem).Where(f => f.IpfsProcess != null));
75 |
76 | var listSpriteFiles = list.Where(l => l.SpriteVideoFileItem != null).Select(l => l.SpriteVideoFileItem).ToList();
77 | listSpriteCreated.AddRange(listSpriteFiles);
78 | listIpfsAdded.AddRange(listSpriteFiles);
79 |
80 | var listEncodeFiles = list.Where(l => l.EncodedFileItems != null).SelectMany(l => l.EncodedFileItems).ToList();
81 | listVideoEncoded.AddRange(listEncodeFiles);
82 | listIpfsAdded.AddRange(listEncodeFiles.Where(f => f.IpfsProcess != null));
83 |
84 | return new
85 | {
86 | version = Version,
87 | currentWaitingInQueue = GetCurrentWaitingInQueue(),
88 |
89 | Init = GetStatByStep(ProcessStep.Init, listVideoEncoded, listSpriteCreated, listIpfsAdded),
90 | Waiting = GetStatByStep(ProcessStep.Waiting, listVideoEncoded, listSpriteCreated, listIpfsAdded),
91 | Canceled = GetStatByStep(ProcessStep.Canceled, listVideoEncoded, listSpriteCreated, listIpfsAdded),
92 | Started = GetStatByStep(ProcessStep.Started, listVideoEncoded, listSpriteCreated, listIpfsAdded),
93 | Error = GetStatByStep(ProcessStep.Error, listVideoEncoded, listSpriteCreated, listIpfsAdded),
94 | Success = GetStatByStep(ProcessStep.Success, listVideoEncoded, listSpriteCreated, listIpfsAdded)
95 | };
96 | }
97 | catch(Exception ex)
98 | {
99 | return new
100 | {
101 | version = Version,
102 | currentWaitingInQueue = GetCurrentWaitingInQueue(),
103 | exception = ex.ToString()
104 | };
105 | }
106 | }
107 |
108 | private static dynamic GetCurrentWaitingInQueue()
109 | {
110 | return new
111 | {
112 | audioCpuToEncode = AudioCpuEncodeDaemon.Instance.CurrentWaitingInQueue,
113 | videoGpuToEncode = VideoGpuEncodeDaemon.Instance.CurrentWaitingInQueue,
114 | audioVideoCpuToEncode = AudioVideoCpuEncodeDaemon.Instance.CurrentWaitingInQueue,
115 | spriteToCreate = SpriteDaemon.Instance.CurrentWaitingInQueue,
116 | ipfsToAdd = IpfsDaemon.Instance.CurrentWaitingInQueue
117 | };
118 | }
119 |
120 | private static dynamic GetStatByStep(ProcessStep step, List listVideoEncoded, List listSpriteCreated, List listIpfsAdded)
121 | {
122 | try
123 | {
124 | return new
125 | {
126 | audioCpuEncodeLast24h = GetProcessStats(listVideoEncoded.FindAll(f => f.AudioCpuEncodeProcess != null && f.AudioCpuEncodeProcess.CurrentStep == step).Select(f => f.AudioCpuEncodeProcess).ToList()),
127 | videoGpuEncodeLast24h = GetProcessStats(listVideoEncoded.FindAll(f => f.VideoGpuEncodeProcess != null && f.VideoGpuEncodeProcess.CurrentStep == step).Select(f => f.VideoGpuEncodeProcess).ToList()),
128 | audioVideoCpuEncodeLast24h = GetProcessStats(listVideoEncoded.FindAll(f => f.AudioVideoCpuEncodeProcess != null && f.AudioVideoCpuEncodeProcess.CurrentStep == step).Select(f => f.AudioVideoCpuEncodeProcess).ToList()),
129 | spriteCreationLast24h = GetProcessStats(listSpriteCreated.FindAll(f => f.SpriteEncodeProcess != null && f.SpriteEncodeProcess.CurrentStep == step).Select(f => f.SpriteEncodeProcess).ToList()),
130 | ipfsAddLast24h = GetProcessStats(listIpfsAdded.FindAll(f => f.IpfsProcess != null && f.IpfsProcess.CurrentStep == step).Select(f => f.IpfsProcess).ToList())
131 | };
132 | }
133 | catch(Exception ex)
134 | {
135 | return new
136 | {
137 | exception = ex.ToString()
138 | };
139 | }
140 | }
141 |
142 | private static dynamic GetProcessStats(List processItems)
143 | {
144 | if(processItems == null || !processItems.Any())
145 | return null;
146 |
147 | return new
148 | {
149 | nb = processItems.Count,
150 | waitingInQueue = GetInfo(processItems
151 | .Where(p => p.OriginWaitingPositionInQueue > 0)
152 | .Select(p => (long)p.OriginWaitingPositionInQueue)
153 | .ToList()),
154 | fileSize = GetFileSizeInfo(processItems
155 | .Where(p => p.FileItem.FileSize.HasValue)
156 | .Select(p => p.FileItem.FileSize.Value)
157 | .ToList()),
158 | waitingTime = GetTimeInfo(processItems
159 | .Where(p => p.WaitingTime.HasValue)
160 | .Select(p => p.WaitingTime.Value)
161 | .ToList()),
162 | processTime = GetTimeInfo(processItems
163 | .Where(p => p.ProcessTime.HasValue)
164 | .Select(p => p.ProcessTime.Value)
165 | .ToList())
166 | };
167 | }
168 |
169 | private static dynamic GetProcessWithSourceStats(List processItems)
170 | {
171 | var stats = GetProcessStats(processItems);
172 | if(stats == null)
173 | return null;
174 |
175 | return new
176 | {
177 | nb = stats.nb,
178 | waitingInQueue = stats.waitingInQueue,
179 | sourceDuration = GetTimeInfo(processItems
180 | .Where(p => p.FileItem.FileContainer.SourceFileItem.VideoDuration.HasValue)
181 | .Select(p => (long)p.FileItem.FileContainer.SourceFileItem.VideoDuration.Value)
182 | .ToList()),
183 | sourceFileSize = GetFileSizeInfo(processItems
184 | .Where(p => p.FileItem.FileContainer.SourceFileItem.FileSize.HasValue)
185 | .Select(p => (long)p.FileItem.FileContainer.SourceFileItem.FileSize.Value)
186 | .ToList()),
187 | fileSize = stats.fileSize,
188 | waitingTime = stats.waitingTime,
189 | processTime = stats.processTime
190 | };
191 | }
192 |
193 | private static dynamic GetInfo(List infos)
194 | {
195 | if(!infos.Any())
196 | return null;
197 | return new { min = infos.Min(), average = Math.Round(infos.Average(), 2), max = infos.Max() };
198 | }
199 |
200 | private static dynamic GetTimeInfo(List infos)
201 | {
202 | if(!infos.Any())
203 | return null;
204 | return new { min = Time(infos.Min()), average = Time((long)infos.Average()), max = Time(infos.Max()) };
205 | }
206 |
207 | private static dynamic GetFileSizeInfo(List infos)
208 | {
209 | if(!infos.Any())
210 | return null;
211 | return new { min = Filesize(infos.Min()), average = Filesize((long)infos.Average()), max = Filesize(infos.Max()) };
212 | }
213 |
214 | private static string Filesize(long octet)
215 | {
216 | if(octet < 1 * 1000)
217 | return octet + " o";
218 |
219 | if(octet < 1 * 1000000)
220 | return Math.Round(octet/1000d, 2) + " Ko";
221 |
222 | if(octet < 1 * 1000000000)
223 | return Math.Round(octet/1000000d, 2) + " Mo";
224 |
225 | if(octet < 1 * 1000000000000)
226 | return Math.Round(octet/1000000000d, 2) + " Go";
227 |
228 | return Math.Round(octet/1000000000000d, 2) + " To";
229 | }
230 |
231 | private static string Time(long seconds)
232 | {
233 | if(seconds < 1 * 60)
234 | return seconds + " second(s)";
235 |
236 | if(seconds < 1 * 3600)
237 | return Math.Round(seconds/60d, 2) + " minute(s)";
238 |
239 | return Math.Round(seconds/3600d, 2) + " heure(s)";
240 | }
241 |
242 | public static dynamic GetFileContainerByToken(Guid progressToken)
243 | {
244 | FileContainer fileContainer;
245 | progresses.TryGetValue(progressToken, out fileContainer);
246 |
247 | if(fileContainer != null)
248 | fileContainer.UpdateLastTimeProgressRequest();
249 | else
250 | return null;
251 |
252 | return GetResult(fileContainer);
253 | }
254 |
255 | public static dynamic GetFileContainerBySourceHash(string sourceHash)
256 | {
257 | FileContainer fileContainer = progresses.Values
258 | .Where(s => s.SourceFileItem.IpfsHash == sourceHash)
259 | .OrderByDescending(s => s.NumInstance)
260 | .FirstOrDefault();
261 |
262 | if(fileContainer != null)
263 | fileContainer.UpdateLastTimeProgressRequest();
264 | else
265 | return null;
266 |
267 | return GetResult(fileContainer);
268 | }
269 |
270 | private static dynamic GetResult(FileContainer fileContainer, bool error = false)
271 | {
272 | switch (fileContainer.TypeContainer)
273 | {
274 | case TypeContainer.Video:
275 | return new
276 | {
277 | finished = fileContainer.Finished(),
278 | debugInfo = error ? DebugInfo(fileContainer) : null,
279 | sourceAudioCpuEncoding = AudioCpuEncodeResultJson(fileContainer.SourceFileItem, error),
280 | sourceVideoGpuEncoding = VideoGpuEncodeResultJson(fileContainer.SourceFileItem, error),
281 | ipfsAddSourceVideo = IpfsResultJson(fileContainer.SourceFileItem, error),
282 | sprite = fileContainer.SpriteVideoFileItem == null ? null :
283 | new
284 | {
285 | spriteCreation = SpriteResultJson(fileContainer.SpriteVideoFileItem, error),
286 | ipfsAddSprite = IpfsResultJson(fileContainer.SpriteVideoFileItem, error)
287 | },
288 | encodedVideos = !fileContainer.EncodedFileItems.Any() ? null :
289 | fileContainer.EncodedFileItems.Select(e =>
290 | new
291 | {
292 | encode = AudioVideoCpuEncodeResultJson(e, error),
293 | ipfsAddEncodeVideo = IpfsResultJson(e, error)
294 | })
295 | .ToArray()
296 | };
297 |
298 | case TypeContainer.Image:
299 | return new
300 | {
301 | ipfsAddSource = IpfsResultJson(fileContainer.SnapFileItem, error),
302 | ipfsAddOverlay = IpfsResultJson(fileContainer.OverlayFileItem, error)
303 | };
304 |
305 | case TypeContainer.Subtitle:
306 | return new
307 | {
308 | ipfsAddSource = IpfsResultJson(fileContainer.SubtitleFileItem, error)
309 | };
310 | }
311 |
312 | LogManager.AddGeneralMessage(LogLevel.Critical, "Type container non géré " + fileContainer.TypeContainer, "Error");
313 | throw new InvalidOperationException("type container non géré");
314 | }
315 |
316 | private static dynamic DebugInfo(FileContainer fileContainer)
317 | {
318 | string hash = fileContainer?.SourceFileItem.IpfsHash;
319 | return new
320 | {
321 | originFileName = Path.GetFileName(fileContainer.OriginFilePath),
322 | ipfsUrl = hash == null ? null : "https://ipfs.io/ipfs/" + hash,
323 | exceptionDetail = fileContainer.ExceptionDetail,
324 | sourceInfo = SourceInfo(fileContainer.SourceFileItem)
325 | };
326 | }
327 |
328 | private static dynamic SourceInfo(FileItem sourceFileItem)
329 | {
330 | if (sourceFileItem == null || sourceFileItem.InfoSourceProcess == null)
331 | return null;
332 |
333 | return new
334 | {
335 | sourceFileItem.FileSize,
336 | sourceFileItem.VideoCodec,
337 | sourceFileItem.VideoDuration,
338 | sourceFileItem.VideoWidth,
339 | sourceFileItem.VideoHeight,
340 | sourceFileItem.VideoPixelFormat,
341 | sourceFileItem.VideoFrameRate,
342 | sourceFileItem.VideoBitRate,
343 | sourceFileItem.VideoNbFrame,
344 | sourceFileItem.VideoRotate,
345 | sourceFileItem.AudioCodec,
346 | sourceFileItem.InfoSourceProcess.ErrorMessage
347 | };
348 | }
349 |
350 | private static dynamic IpfsResultJson(FileItem fileItem, bool error)
351 | {
352 | var result = ProcessResultJson(fileItem?.IpfsProcess, error, IpfsDaemon.Instance.CurrentPositionInQueue);
353 | if(result == null)
354 | return null;
355 |
356 | return new
357 | {
358 | progress = result.progress,
359 | encodeSize = result.encodeSize,
360 | lastTimeProgress = result.lastTimeProgress,
361 | errorMessage = result.errorMessage,
362 | step = result.step,
363 | positionInQueue = result.positionInQueue,
364 |
365 | hash = fileItem.IpfsHash,
366 | fileSize = fileItem.FileSize
367 | };
368 | }
369 |
370 | private static dynamic SpriteResultJson(FileItem fileItem, bool error)
371 | {
372 | return ProcessResultJson(fileItem?.SpriteEncodeProcess, error, SpriteDaemon.Instance.CurrentPositionInQueue);
373 | }
374 |
375 | private static dynamic AudioCpuEncodeResultJson(FileItem fileItem, bool error)
376 | {
377 | return ProcessResultJson(fileItem?.AudioCpuEncodeProcess, error, AudioCpuEncodeDaemon.Instance.CurrentPositionInQueue);
378 | }
379 |
380 | private static dynamic AudioVideoCpuEncodeResultJson(FileItem fileItem, bool error)
381 | {
382 | return ProcessResultJson(fileItem?.AudioVideoCpuEncodeProcess, error, AudioVideoCpuEncodeDaemon.Instance.CurrentPositionInQueue);
383 | }
384 |
385 | private static dynamic VideoGpuEncodeResultJson(FileItem fileItem, bool error)
386 | {
387 | return ProcessResultJson(fileItem?.VideoGpuEncodeProcess, error, VideoGpuEncodeDaemon.Instance.CurrentPositionInQueue);
388 | }
389 |
390 | private static dynamic ProcessResultJson(ProcessItem processItem, bool error, int daemonCurrentPositionInQUeue)
391 | {
392 | if (processItem == null)
393 | return null;
394 | if(error && processItem.CurrentStep != ProcessStep.Error)
395 | return null;
396 |
397 | return new
398 | {
399 | progress = processItem.Progress,
400 | encodeSize = processItem.FileItem.VideoSize.VideoSizeString(),
401 | lastTimeProgress = processItem.LastTimeProgressChanged,
402 | errorMessage = processItem.ErrorMessage,
403 | step = processItem.CurrentStep.ToString(),
404 | positionInQueue = Position(processItem, daemonCurrentPositionInQUeue)
405 | };
406 | }
407 |
408 | private static string VideoSizeString(this VideoSize videoSize)
409 | {
410 | return videoSize == null ? "source" : videoSize.Height.ToString() + "p";
411 | }
412 |
413 | private static int? Position(ProcessItem processItem, int daemonCurrentPositionInQueue)
414 | {
415 | if (processItem.CurrentStep != ProcessStep.Waiting)
416 | return null;
417 |
418 | return processItem.PositionInQueue - daemonCurrentPositionInQueue;
419 | }
420 | }
421 | }
--------------------------------------------------------------------------------