twitch vod downloading done
ffmpeg essentials fix Project reorganized git lfs ffmpeg removed ffmpeg added
This commit is contained in:
229
VDownload.Core/VDownload.Core.Tasks/DownloadTask.cs
Normal file
229
VDownload.Core/VDownload.Core.Tasks/DownloadTask.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using VDownload.Models;
|
||||
using VDownload.Services.Utility.FFmpeg;
|
||||
using VDownload.Services.Data.Configuration;
|
||||
using VDownload.Services.Data.Settings;
|
||||
using Windows.System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Windows.Storage;
|
||||
using System.IO;
|
||||
using VDownload.Services.UI.Notifications;
|
||||
using VDownload.Services.UI.StringResources;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VDownload.Core.Tasks
|
||||
{
|
||||
public partial class DownloadTask : ObservableObject
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
protected readonly IConfigurationService _configurationService;
|
||||
protected readonly ISettingsService _settingsService;
|
||||
protected readonly IFFmpegService _ffmpegService;
|
||||
protected readonly IStringResourcesService _stringResourcesService;
|
||||
protected readonly INotificationsService _notificationsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
private Task _downloadTask;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
[ObservableProperty]
|
||||
protected Guid _id;
|
||||
|
||||
[ObservableProperty]
|
||||
protected Video _video;
|
||||
|
||||
[ObservableProperty]
|
||||
protected VideoDownloadOptions _downloadOptions;
|
||||
|
||||
[ObservableProperty]
|
||||
protected DownloadTaskStatus _status;
|
||||
|
||||
[ObservableProperty]
|
||||
protected DateTime _createDate;
|
||||
|
||||
[ObservableProperty]
|
||||
protected double _progress;
|
||||
|
||||
[ObservableProperty]
|
||||
protected string? _error;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
internal DownloadTask(Video video, VideoDownloadOptions downloadOptions, IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, IStringResourcesService stringResourcesService, INotificationsService notificationsService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_settingsService = settingsService;
|
||||
_ffmpegService = ffmpegService;
|
||||
_stringResourcesService = stringResourcesService;
|
||||
_notificationsService = notificationsService;
|
||||
|
||||
_video = video;
|
||||
_downloadOptions = downloadOptions;
|
||||
_id = Guid.NewGuid();
|
||||
_status = DownloadTaskStatus.Idle;
|
||||
_createDate = DateTime.Now;
|
||||
_progress = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void Enqueue()
|
||||
{
|
||||
DownloadTaskStatus[] statuses =
|
||||
[
|
||||
DownloadTaskStatus.Idle,
|
||||
DownloadTaskStatus.EndedUnsuccessfully,
|
||||
DownloadTaskStatus.EndedSuccessfully,
|
||||
DownloadTaskStatus.EndedCancelled,
|
||||
];
|
||||
if (statuses.Contains(Status))
|
||||
{
|
||||
Status = DownloadTaskStatus.Queued;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.Initializing);
|
||||
|
||||
_downloadTask = Download();
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
if (_cancellationTokenSource is not null)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
await _downloadTask;
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async Task Download()
|
||||
{
|
||||
CancellationToken token = _cancellationTokenSource.Token;
|
||||
|
||||
await _settingsService.Load();
|
||||
|
||||
string tempDirectory = $"{_settingsService.Data.Common.TempDirectory}\\{_configurationService.Common.Path.Temp.TasksDirectory}\\{_id}";
|
||||
Directory.CreateDirectory(tempDirectory);
|
||||
|
||||
List<string> content = new List<string>()
|
||||
{
|
||||
$"{_stringResourcesService.NotificationsResources.Get("Title")}: {Video.Title}",
|
||||
$"{_stringResourcesService.NotificationsResources.Get("Author")}: {Video.Author}"
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
IProgress<double> onProgressDownloading = new Progress<double>((value) =>
|
||||
{
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.Downloading);
|
||||
UpdateProgressWithDispatcher(value);
|
||||
});
|
||||
|
||||
VideoStreamDownloadResult downloadResult = await DownloadOptions.SelectedStream.Download(tempDirectory, onProgressDownloading, token, DownloadOptions.TrimStart, DownloadOptions.TrimEnd);
|
||||
|
||||
Action<double> onProgressProcessing = (value) =>
|
||||
{
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.Processing);
|
||||
UpdateProgressWithDispatcher(value);
|
||||
};
|
||||
string outputPath = $"{tempDirectory}\\processed.{DownloadOptions.Extension}";
|
||||
|
||||
FFmpegBuilder ffmpegBuilder = _ffmpegService.CreateBuilder();
|
||||
if (downloadResult.NewTrimStart != TimeSpan.Zero)
|
||||
{
|
||||
ffmpegBuilder.TrimStart(downloadResult.NewTrimStart);
|
||||
}
|
||||
if (downloadResult.NewTrimEnd != downloadResult.NewDuration)
|
||||
{
|
||||
ffmpegBuilder.TrimEnd(downloadResult.NewTrimEnd);
|
||||
}
|
||||
ffmpegBuilder.SetMediaType(DownloadOptions.MediaType)
|
||||
.JoinCancellationToken(token)
|
||||
.JoinProgressReporter(onProgressProcessing, downloadResult.NewDuration);
|
||||
await ffmpegBuilder.RunAsync(downloadResult.File, outputPath);
|
||||
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.Finalizing);
|
||||
|
||||
string destination = $"{DownloadOptions.Directory}\\{DownloadOptions.Filename}.{DownloadOptions.Extension}";
|
||||
File.Copy(outputPath, destination, true);
|
||||
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedSuccessfully);
|
||||
|
||||
if (_settingsService.Data.Common.Notifications.OnSuccessful)
|
||||
{
|
||||
string title = _stringResourcesService.NotificationsResources.Get("OnSuccessfulTitle");
|
||||
_notificationsService.SendNotification(title, content);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedCancelled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateErrorWithDispatcher(ex.Message);
|
||||
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedUnsuccessfully);
|
||||
|
||||
if (_settingsService.Data.Common.Notifications.OnUnsuccessful)
|
||||
{
|
||||
string title = _stringResourcesService.NotificationsResources.Get("OnSuccessfulTitle");
|
||||
content.Add($"{_stringResourcesService.NotificationsResources.Get("Message")}: {ex.Message}");
|
||||
_notificationsService.SendNotification(title, content);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Status != DownloadTaskStatus.EndedUnsuccessfully || _settingsService.Data.Common.DeleteTempOnError)
|
||||
{
|
||||
Directory.Delete(tempDirectory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatusWithDispatcher(DownloadTaskStatus status) => _dispatcherQueue.TryEnqueue(() => Status = status);
|
||||
|
||||
private void UpdateProgressWithDispatcher(double progress) => _dispatcherQueue.TryEnqueue(() => Progress = progress);
|
||||
|
||||
private void UpdateErrorWithDispatcher(string message) => _dispatcherQueue.TryEnqueue(() => Error = message);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Models;
|
||||
using VDownload.Services.Data.Configuration;
|
||||
using VDownload.Services.Data.Settings;
|
||||
using VDownload.Services.UI.Notifications;
|
||||
using VDownload.Services.UI.StringResources;
|
||||
using VDownload.Services.Utility.FFmpeg;
|
||||
|
||||
namespace VDownload.Core.Tasks
|
||||
{
|
||||
public interface IDownloadTaskFactoryService
|
||||
{
|
||||
DownloadTask Create(Video video, VideoDownloadOptions downloadOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class DownloadTaskFactoryService : IDownloadTaskFactoryService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
protected readonly IConfigurationService _configurationService;
|
||||
protected readonly ISettingsService _settingsService;
|
||||
protected readonly IFFmpegService _ffmpegService;
|
||||
protected readonly IStringResourcesService _stringResourcesService;
|
||||
protected readonly INotificationsService _notificationsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTaskFactoryService(IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, IStringResourcesService stringResourcesService, INotificationsService notificationsService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_settingsService = settingsService;
|
||||
_ffmpegService = ffmpegService;
|
||||
_stringResourcesService = stringResourcesService;
|
||||
_notificationsService = notificationsService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public DownloadTask Create(Video video, VideoDownloadOptions downloadOptions)
|
||||
{
|
||||
return new DownloadTask(video, downloadOptions, _configurationService, _settingsService, _ffmpegService, _stringResourcesService, _notificationsService);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
168
VDownload.Core/VDownload.Core.Tasks/DownloadTaskManager.cs
Normal file
168
VDownload.Core/VDownload.Core.Tasks/DownloadTaskManager.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using VDownload.Models;
|
||||
using VDownload.Services.Data.Settings;
|
||||
|
||||
namespace VDownload.Core.Tasks
|
||||
{
|
||||
public interface IDownloadTaskManager
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
ReadOnlyObservableCollection<DownloadTask> Tasks { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
event NotifyCollectionChangedEventHandler TaskCollectionChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions);
|
||||
void RemoveTask(DownloadTask task);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class DownloadTaskManager : IDownloadTaskManager
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
protected readonly IDownloadTaskFactoryService _downloadTaskFactoryService;
|
||||
protected readonly ISettingsService _settingsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private readonly Thread _taskMonitorThread;
|
||||
|
||||
private readonly ObservableCollection<DownloadTask> _tasksMain;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public ReadOnlyObservableCollection<DownloadTask> Tasks { get; protected set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
public event NotifyCollectionChangedEventHandler TaskCollectionChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTaskManager(IDownloadTaskFactoryService downloadTaskFactoryService, ISettingsService settingsService)
|
||||
{
|
||||
_downloadTaskFactoryService = downloadTaskFactoryService;
|
||||
_settingsService = settingsService;
|
||||
|
||||
_tasksMain = new ObservableCollection<DownloadTask>();
|
||||
_tasksMain.CollectionChanged += Tasks_CollectionChanged;
|
||||
Tasks = new ReadOnlyObservableCollection<DownloadTask>(_tasksMain);
|
||||
|
||||
_taskMonitorThread = new Thread(TaskMonitor)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
_taskMonitorThread.Start();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions)
|
||||
{
|
||||
DownloadTask task = _downloadTaskFactoryService.Create(video, downloadOptions);
|
||||
_tasksMain.Add(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void RemoveTask(DownloadTask task)
|
||||
{
|
||||
_tasksMain.Remove(task);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async void TaskMonitor()
|
||||
{
|
||||
await _settingsService.Load();
|
||||
|
||||
DownloadTaskStatus[] pendingStatuses =
|
||||
[
|
||||
DownloadTaskStatus.Initializing,
|
||||
DownloadTaskStatus.Downloading,
|
||||
DownloadTaskStatus.Processing,
|
||||
DownloadTaskStatus.Finalizing
|
||||
];
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IEnumerable<DownloadTask> pendingTasks = Tasks.Where(x => pendingStatuses.Contains(x.Status));
|
||||
int freeSlots = _settingsService.Data.Common.MaxNumberOfRunningTasks - pendingTasks.Count();
|
||||
if (freeSlots > 0)
|
||||
{
|
||||
IEnumerable<DownloadTask> queuedTasks = Tasks.Where(x => x.Status == DownloadTaskStatus.Queued).OrderBy(x => x.CreateDate).Take(freeSlots);
|
||||
foreach (DownloadTask queuedTask in queuedTasks)
|
||||
{
|
||||
queuedTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Console.WriteLine("TaskMonitor: Collection locked - skipping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENT HANDLERS
|
||||
|
||||
private void Tasks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
TaskCollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Tasks
|
||||
{
|
||||
public class DownloadTaskNotInitializedException : Exception
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DownloadTaskNotInitializedException() : base() { }
|
||||
|
||||
public DownloadTaskNotInitializedException(string message) : base(message) { }
|
||||
|
||||
public DownloadTaskNotInitializedException(string message, Exception inner) : base(message, inner) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
21
VDownload.Core/VDownload.Core.Tasks/DownloadTaskStatus.cs
Normal file
21
VDownload.Core/VDownload.Core.Tasks/DownloadTaskStatus.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VDownload.Core.Tasks
|
||||
{
|
||||
public enum DownloadTaskStatus
|
||||
{
|
||||
Idle,
|
||||
EndedSuccessfully,
|
||||
EndedUnsuccessfully,
|
||||
EndedCancelled,
|
||||
Queued,
|
||||
Initializing,
|
||||
Finalizing,
|
||||
Processing,
|
||||
Downloading,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDownload.Core.Tasks</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\VDownload.Models\VDownload.Models.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Settings\VDownload.Services.Data.Settings.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.Notifications\VDownload.Services.UI.Notifications.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StringResources\VDownload.Services.UI.StringResources.csproj" />
|
||||
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.FFmpeg\VDownload.Services.Utility.FFmpeg.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user