twitch vod downloading done

ffmpeg essentials

fix

Project reorganized

git lfs

ffmpeg removed

ffmpeg added
This commit is contained in:
2024-02-14 02:07:22 +01:00
Unverified
parent 91f9b645bd
commit e3ec5c3a48
264 changed files with 6239 additions and 4014 deletions

View 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
}
}

View File

@@ -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
}
}

View 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
}
}

View 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 class DownloadTaskNotInitializedException : Exception
{
#region CONSTRUCTORS
public DownloadTaskNotInitializedException() : base() { }
public DownloadTaskNotInitializedException(string message) : base(message) { }
public DownloadTaskNotInitializedException(string message, Exception inner) : base(message, inner) { }
#endregion
}
}

View 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,
}
}

View File

@@ -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>