From 988091ef8c1a2bb8872ec6c1f5279eeacdc5c7be Mon Sep 17 00:00:00 2001 From: Federico Cicciarella Date: Fri, 16 Sep 2022 10:19:25 +0200 Subject: [PATCH] upload data --- nuget.config | 9 ++ src/EthernaVideoImporter/.editorconfig | 114 ++++++++++++++++++ .../Dtos/MetadataVideoDto.cs | 44 +++++++ .../Dtos/MetadataVideoSource.cs | 24 ++++ .../Dtos/SwarmImageRaw.cs | 23 ++++ .../Dtos/VideoDataInfoDto.cs | 2 + .../Dtos/VideoDownloadInfo.cs | 21 ++++ .../EthernaVideoImporter.csproj | 20 ++- src/EthernaVideoImporter/Program.cs | 64 ++++++++-- .../Services/VideoImporterService.cs | 43 +++++-- .../Services/VideoUploaderService.cs | 74 ++++++++++++ 11 files changed, 412 insertions(+), 26 deletions(-) create mode 100644 nuget.config create mode 100644 src/EthernaVideoImporter/.editorconfig create mode 100644 src/EthernaVideoImporter/Dtos/MetadataVideoDto.cs create mode 100644 src/EthernaVideoImporter/Dtos/MetadataVideoSource.cs create mode 100644 src/EthernaVideoImporter/Dtos/SwarmImageRaw.cs create mode 100644 src/EthernaVideoImporter/Dtos/VideoDownloadInfo.cs create mode 100644 src/EthernaVideoImporter/Services/VideoUploaderService.cs diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..dd4edcc --- /dev/null +++ b/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/EthernaVideoImporter/.editorconfig b/src/EthernaVideoImporter/.editorconfig new file mode 100644 index 0000000..276bdf5 --- /dev/null +++ b/src/EthernaVideoImporter/.editorconfig @@ -0,0 +1,114 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +#### Define style #### + +# All files +[*] +indent_style = space + +# C# Project, JS and CSS files +[*.{csproj,js,ts,css,scss}] +indent_size = 2 + +#### Suppress warnings #### + +# C# files +[*.cs] + +# CA1034: Nested types should not be visible +dotnet_diagnostic.CA1034.severity = none # Asp.Net Core Pages uses nested models + +# CA1054: Uri parameters should not be strings +dotnet_diagnostic.CA1054.severity = none # Asp.Net Core Pages uses strings natively + +# CA1056: Uri properties should not be strings +dotnet_diagnostic.CA1056.severity = none # Asp.Net Core Pages uses strings natively + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = none # Don't need translated exceptions + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = none # I like underscores into constants name + +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = none # Doing extensive use of Dependency Injection + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = none # Don't like static members + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = none # Not needed with .Net Core. More info https://devblogs.microsoft.com/dotnet/configureawait-faq/ + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = none +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent diff --git a/src/EthernaVideoImporter/Dtos/MetadataVideoDto.cs b/src/EthernaVideoImporter/Dtos/MetadataVideoDto.cs new file mode 100644 index 0000000..b41cf63 --- /dev/null +++ b/src/EthernaVideoImporter/Dtos/MetadataVideoDto.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace Etherna.EthernaVideoImporter.Dtos +{ + internal class MetadataVideoDto + { + // Constructors. + public MetadataVideoDto( + string? batchId, + string description, + long duration, + long createdAt, + string originalQuality, + string ownerAddress, + IEnumerable sources, + SwarmImageRaw? thumbnail, + string title, + long? updatedAt) + { + BatchId = batchId; + Description = description; + Duration = duration; + CreatedAt = createdAt; + OriginalQuality = originalQuality; + OwnerAddress = ownerAddress; + Sources = sources; + Thumbnail = thumbnail; + Title = title; + UpdatedAt = updatedAt; + } + + // Properties. + public string? BatchId { get; } + public string Description { get; } + public long Duration { get; } + public long CreatedAt { get; } + public string OriginalQuality { get; } + public string OwnerAddress { get; } + public IEnumerable Sources { get; } + public SwarmImageRaw? Thumbnail { get; } + public string Title { get; } + public long? UpdatedAt { get; } + } +} diff --git a/src/EthernaVideoImporter/Dtos/MetadataVideoSource.cs b/src/EthernaVideoImporter/Dtos/MetadataVideoSource.cs new file mode 100644 index 0000000..0e65bc6 --- /dev/null +++ b/src/EthernaVideoImporter/Dtos/MetadataVideoSource.cs @@ -0,0 +1,24 @@ +namespace Etherna.EthernaVideoImporter.Dtos +{ + public class MetadataVideoSource + { + // Constructors. + public MetadataVideoSource( + int bitrate, + string quality, + string reference, + long size) + { + Bitrate = bitrate; + Quality = quality; + Reference = reference; + Size = size; + } + + // Properties. + public int Bitrate { get; } + public string Quality { get; } + public string Reference { get; } + public long Size { get; } + } +} diff --git a/src/EthernaVideoImporter/Dtos/SwarmImageRaw.cs b/src/EthernaVideoImporter/Dtos/SwarmImageRaw.cs new file mode 100644 index 0000000..1645c41 --- /dev/null +++ b/src/EthernaVideoImporter/Dtos/SwarmImageRaw.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Etherna.EthernaVideoImporter.Dtos +{ + public class SwarmImageRaw + { + // Constructors. + public SwarmImageRaw( + float aspectRatio, + string blurhash, + IReadOnlyDictionary sources) + { + AspectRatio = aspectRatio; + Blurhash = blurhash; + Sources = sources; + } + + // Properties. + public float AspectRatio { get; } + public string Blurhash { get; } + public IReadOnlyDictionary Sources { get; } + } +} diff --git a/src/EthernaVideoImporter/Dtos/VideoDataInfoDto.cs b/src/EthernaVideoImporter/Dtos/VideoDataInfoDto.cs index 802d60c..a9e5640 100644 --- a/src/EthernaVideoImporter/Dtos/VideoDataInfoDto.cs +++ b/src/EthernaVideoImporter/Dtos/VideoDataInfoDto.cs @@ -1,4 +1,5 @@ using CsvHelper.Configuration.Attributes; +using System.Collections.Generic; namespace EthernaVideoImporter.Dtos { @@ -7,6 +8,7 @@ internal class VideoDataInfoDto public string? Description { get; set; } [Optional] public string? DownloadedFileName { get; set; } + public string? DownloadedFilePath { get; set; } public int Duration { get; set; } public int Edition { get; set; } public string? Expertise { get; set; } diff --git a/src/EthernaVideoImporter/Dtos/VideoDownloadInfo.cs b/src/EthernaVideoImporter/Dtos/VideoDownloadInfo.cs new file mode 100644 index 0000000..80f6b06 --- /dev/null +++ b/src/EthernaVideoImporter/Dtos/VideoDownloadInfo.cs @@ -0,0 +1,21 @@ +namespace Etherna.EthernaVideoImporter.Dtos +{ + public class VideoDownloadInfo + { + // Constructors. + public VideoDownloadInfo( + int bitrate, + string quality, + long size) + { + Bitrate = bitrate; + Quality = quality; + Size = size; + } + + // Properties. + public int Bitrate { get; } + public string Quality { get; } + public long Size { get; } + } +} diff --git a/src/EthernaVideoImporter/EthernaVideoImporter.csproj b/src/EthernaVideoImporter/EthernaVideoImporter.csproj index 20c9fda..83ab80e 100644 --- a/src/EthernaVideoImporter/EthernaVideoImporter.csproj +++ b/src/EthernaVideoImporter/EthernaVideoImporter.csproj @@ -1,14 +1,28 @@ - + Exe net6.0 - enable - enable + true + Etherna.EthernaVideoImporter + + Etherna Sagl + A .Net console for upload video on Etherna + + 10.0 + enable + true + AllEnabledByDefault + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/EthernaVideoImporter/Program.cs b/src/EthernaVideoImporter/Program.cs index 5f4b3dd..f592dd0 100644 --- a/src/EthernaVideoImporter/Program.cs +++ b/src/EthernaVideoImporter/Program.cs @@ -1,7 +1,18 @@ using CsvHelper; +using Etherna.BeeNet; +using Etherna.BeeNet.Clients.DebugApi; +using Etherna.BeeNet.Clients.GatewayApi; +using Etherna.EthernaVideoImporter.Dtos; +using Etherna.EthernaVideoImporter.Services; using EthernaVideoImporter.Dtos; using EthernaVideoImporter.Services; +using System; +using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; using YoutubeDownloader.Clients; internal class Program @@ -11,7 +22,7 @@ static async Task Main(string[] args) string tmpFolder = "tmpData"; if (args is null || - args.Length < 0) + args.Length < 1) { Console.WriteLine("Missing read path"); return; @@ -21,6 +32,28 @@ static async Task Main(string[] args) Console.WriteLine($"File not found {args[0]}"); return; } + if (args.Length < 2) + { + Console.WriteLine("Missing beenode url"); + return; + } + if (args.Length < 3) + { + Console.WriteLine("Missing beenode port"); + return; + } + if (args.Length < 4) + { + Console.WriteLine("Missing beenode version"); + return; + } + var beeNodeUrl = args[1]; +#pragma warning disable CA1305 // Specify IFormatProvider + var beeNodePort = Convert.ToInt32(args[2]); +#pragma warning restore CA1305 // Specify IFormatProvider + var beeNodeDebugPort = beeNodePort + 1; + var beeNodeVersion = GatewayApiVersion.v3_0_2; + var beeNodeDebugVersion = DebugApiVersion.v3_0_2; if (!Directory.Exists(tmpFolder)) Directory.CreateDirectory(tmpFolder); @@ -36,8 +69,9 @@ static async Task Main(string[] args) Console.WriteLine($"Csv with {totalVideo} items to upload"); // Call import service for each video. - var youtubeDownloadClient = new YoutubeDownloadClient(); - var videoImporterService = new VideoImporterService(youtubeDownloadClient, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, tmpFolder)); + var tmpFolderFullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, tmpFolder); + var videoImporterService = new VideoImporterService(new YoutubeDownloadClient(), tmpFolderFullPath); + var videoUploaderService = new VideoUploaderService(new BeeNodeClient(beeNodeUrl, beeNodePort, beeNodeDebugPort, beeNodeVersion, beeNodeDebugVersion), tmpFolderFullPath); var videoCount = 0; foreach (var videoInfo in videoDataInfoDtos) @@ -45,20 +79,30 @@ static async Task Main(string[] args) try { Console.WriteLine($"Start processing video {++videoCount} of {totalVideo}"); - await videoImporterService.Start(videoInfo); + + // Download from youtube. + var downloadInfo = await videoImporterService.Start(videoInfo); + + if (downloadInfo != null) + // Upload on bee. + await videoUploaderService.Start(videoInfo, downloadInfo); + Console.WriteLine($"Video #{videoCount} processed"); } +#pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { Console.WriteLine($"{ex.Message} \n Unable to upload: {videoDataInfoDtos}"); videoInfo.VideoStatusNote = ex.Message; } + finally + { + // Save csv with results at every cycle. + using (var writer = new StreamWriter(args[0])) + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + await csv.WriteRecordsAsync(videoDataInfoDtos).ConfigureAwait(false); + } } - - // Save csv with results. - using (var writer = new StreamWriter(args[0])) - using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) - await csv.WriteRecordsAsync(videoDataInfoDtos); - } } \ No newline at end of file diff --git a/src/EthernaVideoImporter/Services/VideoImporterService.cs b/src/EthernaVideoImporter/Services/VideoImporterService.cs index a317899..2e6aea7 100644 --- a/src/EthernaVideoImporter/Services/VideoImporterService.cs +++ b/src/EthernaVideoImporter/Services/VideoImporterService.cs @@ -1,4 +1,10 @@ -using EthernaVideoImporter.Dtos; +using Etherna.EthernaVideoImporter.Dtos; +using EthernaVideoImporter.Dtos; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using YoutubeDownloader.Clients; namespace EthernaVideoImporter.Services @@ -18,45 +24,56 @@ public VideoImporterService( } // Public methods. - public async Task Start(VideoDataInfoDto videoDataInfoDto) + public async Task Start(VideoDataInfoDto videoDataInfoDto) { if (videoDataInfoDto.VideoStatus == VideoStatus.Processed) - return; + return null; if (string.IsNullOrWhiteSpace(videoDataInfoDto.YoutubeUrl)) throw new InvalidOperationException("Invalid YoutubeUrl"); - if (string.IsNullOrWhiteSpace(videoDataInfoDto.DownloadedFileName)) + try { // Take best video resolution. var videos = await youtubeDownloadClient.GetAllVideosAsync(videoDataInfoDto.YoutubeUrl); var videoWithAudio = videos - .Where(i => i.AudioBitrate != -1); + .Where(i => i.AudioBitrate != -1); var videoDownload = videoWithAudio .First(i => i.AudioBitrate == videoWithAudio.Max(j => j.AudioBitrate)); // Take best resolution Console.WriteLine($"Resolution: {videoDownload.Resolution}\tAudio Bitrate: {videoDownload.AudioBitrate}"); - //Start download + // Start download. videoDataInfoDto.VideoStatus = VideoStatus.Downloading; + videoDataInfoDto.DownloadedFilePath = Path.Combine(tmpFolder, videoDownload.FullName); await youtubeDownloadClient .CreateDownloadAsync( new Uri(videoDownload.Uri), - Path.Combine(tmpFolder, videoDownload.FullName), + videoDataInfoDto.DownloadedFilePath, new Progress>((Tuple v) => { var percent = (int)((v.Item1 * 100) / v.Item2); +#pragma warning disable CA1305 // Specify IFormatProvider Console.Write(string.Format("Downloading.. ( % {0} ) {1} / {2} MB\r", percent, (v.Item1 / (double)(1024 * 1024)).ToString("N"), (v.Item2 / (double)(1024 * 1024)).ToString("N"))); +#pragma warning restore CA1305 // Specify IFormatProvider })); Console.WriteLine(""); videoDataInfoDto.DownloadedFileName = videoDownload.FullName; videoDataInfoDto.VideoStatus = VideoStatus.Downloaded; videoDataInfoDto.VideoStatusNote = ""; - } - - // - //TODO UPLOAD - videoDataInfoDto.VideoStatus = VideoStatus.Processed; - return; + // Result VideoDownloadInfo. + var fileSize = new FileInfo(videoDataInfoDto.DownloadedFilePath).Length; + return new VideoDownloadInfo( + (int)Math.Ceiling(fileSize / (videoDataInfoDto.Duration / 60 * 0.0075)), //TODO it's OK? bitrate = file size / (number of minutes * .0075) + videoDownload.Resolution.ToString(CultureInfo.InvariantCulture) + "p", + fileSize); + } + catch (Exception) + { + videoDataInfoDto.DownloadedFileName = ""; + videoDataInfoDto.DownloadedFilePath = ""; + videoDataInfoDto.VideoStatus = VideoStatus.Downloading; + throw; + } } } } diff --git a/src/EthernaVideoImporter/Services/VideoUploaderService.cs b/src/EthernaVideoImporter/Services/VideoUploaderService.cs new file mode 100644 index 0000000..ac6363c --- /dev/null +++ b/src/EthernaVideoImporter/Services/VideoUploaderService.cs @@ -0,0 +1,74 @@ +using Etherna.BeeNet; +using Etherna.BeeNet.InputModels; +using Etherna.EthernaVideoImporter.Dtos; +using EthernaVideoImporter.Dtos; +using JavaScriptEngineSwitcher.Core.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using YoutubeDownloader.Dto; + +namespace Etherna.EthernaVideoImporter.Services +{ + internal class VideoUploaderService + { + private readonly BeeNodeClient beeNodeClient; + private readonly string tmpFolder; + + // Constractor. + public VideoUploaderService( + BeeNodeClient beeNodeClient, + string tmpFolder) + { + if (beeNodeClient is null) + throw new ArgumentNullException(nameof(beeNodeClient)); + if (string.IsNullOrWhiteSpace(tmpFolder)) + throw new ArgumentNullException(nameof(tmpFolder)); + + this.beeNodeClient = beeNodeClient; + this.tmpFolder = tmpFolder; + } + + // Public methods. + public async Task Start(VideoDataInfoDto videoDataInfoDto, VideoDownloadInfo videoDownloadInfo) + { + if (videoDataInfoDto.VideoStatus == VideoStatus.Processed) + return; + if (videoDataInfoDto.VideoStatus != VideoStatus.Downloaded) + throw new InvalidOperationException("Invalid Status"); + + + // Create batch. + var postageBatch = await beeNodeClient.DebugClient!.BuyPostageBatchAsync(1, 28); + await Task.Delay(90000); + + // Upload file. + var fileParameterInput = new FileParameterInput( + File.OpenRead(videoDataInfoDto.DownloadedFilePath!), + Path.GetFileName(videoDataInfoDto.DownloadedFilePath!), + MimeTypes.GetMimeType(Path.GetFileName(videoDataInfoDto.DownloadedFilePath!))); + var reference = await beeNodeClient.GatewayClient!.UploadFileAsync(postageBatch, files: new List { fileParameterInput }, swarmCollection: false); + + // Upload metadata. + var metadataVideoDto = new MetadataVideoDto( + postageBatch, + videoDataInfoDto.Description ?? "", + videoDataInfoDto.Duration, + -1L, //Created at + videoDownloadInfo.Quality, + "owner", + new List { new MetadataVideoSource(videoDownloadInfo.Bitrate, videoDownloadInfo.Quality, reference, videoDownloadInfo.Size) }, + null, + videoDataInfoDto.Title ?? "", + -1); //Updated at + + + videoDataInfoDto.VideoStatus = VideoStatus.Processed; + return; + } + + // Private methods. + + } +}