From ce7d5541853623e3c4356d38eed2243bfd055f64 Mon Sep 17 00:00:00 2001 From: Dapeng Li Date: Wed, 10 Apr 2024 13:19:52 +0800 Subject: [PATCH 1/2] Add video dubbing client tool and API client sample code. --- .../CommandParser/ArgumentAttribute.cs | 174 +++ .../CommandParser/CommandLineParser.cs | 1282 +++++++++++++++++ .../CommandParser/CommentAttribute.cs | 46 + .../CommonLib/CommandParser/ConsoleApp.cs | 54 + .../CommonLib/CommandParser/ExitCode.cs | 14 + .../CommonLib/CommandParser/InOutType.cs | 12 + .../Common/CommonLib/CommonLib.csproj | 17 + .../CommonLib/CustomContractResolver.cs | 138 ++ .../DataContracts/PaginatedResources.cs | 13 + .../StatefulResourceBase.cs | 11 + .../StatelessResourceBase.cs | 22 + .../CommonLib/DataContracts/ResponseBase.cs | 8 + .../DataContracts/StatefulResourceBase.cs | 16 + .../DataContracts/StatelessResourceBase.cs | 18 + .../DataContracts/VoiceGeneralTaskBrief.cs | 7 + .../VoiceGeneralTaskInputFileBase.cs | 20 + .../CommonLib/Enums/DeploymentEnvironment.cs | 140 ++ .../Common/CommonLib/Enums/OneApiState.cs | 19 + .../Enums/VideoTranslationFileKind.cs | 10 + ...TranslationMergeParagraphAudioAlignKind.cs | 13 + .../Enums/VideoTranslationVoiceKind.cs | 14 + .../Extensions/FileNameExtensions.cs | 294 ++++ .../CommonLib/Extensions/StringExtensions.cs | 49 + .../Common/CommonLib/HttpClientBase.cs | 249 ++++ .../APIClientTool/Common/CommonLib/Readme.txt | 2 + .../CommonLib/Util/ConsoleMaskSasHelper.cs | 25 + .../Common/CommonLib/Util/EnumExtensions.cs | 53 + .../CommonLib/Util/EnvironmentMetadatas.cs | 351 +++++ .../Common/CommonLib/Util/ExceptionHelper.cs | 228 +++ .../Common/CommonLib/Util/Sha256Helper.cs | 61 + .../Common/CommonLib/Util/TaskNameHelper.cs | 56 + .../VideoTranslation/Arguments.cs | 331 +++++ .../VideoTranslation/Mode.cs | 36 + .../VideoTranslation/Program.cs | 401 ++++++ .../VideoTranslation/VideoTranslation.csproj | 20 + .../VideoTranslationApiSampleCode.sln | 37 + .../DataContracts/DTOs/Translation.cs | 20 + .../DataContracts/DTOs/TranslationBase.cs | 34 + .../DataContracts/DTOs/TranslationBrief.cs | 9 + .../DataContracts/DTOs/TranslationCreate.cs | 37 + .../DTOs/TranslationTargetLocale.cs | 16 + .../DTOs/TranslationTargetLocaleBase.cs | 5 + .../DTOs/TranslationTargetLocaleCreate.cs | 6 + .../DataContracts/DTOs/VideoFileCreate.cs | 17 + .../DataContracts/DTOs/VideoFileMetadata.cs | 27 + .../DTOs/VideoFileTargetLocale.cs | 13 + .../DTOs/VideoFileTargetLocaleBrief.cs | 22 + ...eoFileTargetLocaleBriefPortalProperties.cs | 12 + .../DTOs/VideoTranslationFeatureMetadata.cs | 23 + .../DTOs/VideoTranslationMetadata.cs | 24 + ...eoTranslationPersonalVoiceModelMetadata.cs | 10 + .../DTOs/VideoTranslationProfileMetadata.cs | 13 + ...ranslationReleaseHistoryVersionMetadata.cs | 16 + .../VideoTranslationTargetLocaleMetadata.cs | 8 + .../DataContracts/DTOs/WebVttFileMetadata.cs | 13 + .../Utility/VideoTranslationPoolInputArgs.cs | 50 + .../VideoTranslationPoolOutputResult.cs | 30 + ...ranslationReleaseHistoryVersionMetadata.cs | 16 + .../VideoTranslationSourceLocaleMetadata.cs | 9 + ...VideoTranslationWebVttFilePlainTextKind.cs | 16 + .../Enum/VideoTranslationWebvttSourceKind.cs | 17 + .../VideoTranslationLib/TargetLocaleClient.cs | 83 ++ .../VideoTranslationLib/VideoFileClient.cs | 268 ++++ .../VideoTranslationClient.cs | 278 ++++ .../VideoTranslationClientBase.cs | 13 + .../VideoTranslationConstant.cs | 22 + .../VideoTranslationLib.csproj | 20 + .../VideoTranslationMetadataClient.cs | 40 + .../VideoTranslationPool.cs | 159 ++ .../CSharp/APIClientTool/nuget.config | 15 + VideoDubbing/readme.md | 19 + 71 files changed, 5621 insertions(+) create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ArgumentAttribute.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommandLineParser.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommentAttribute.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ConsoleApp.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ExitCode.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/InOutType.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommonLib.csproj create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CustomContractResolver.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/PaginatedResources.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatefulResourceBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatelessResourceBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/ResponseBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatefulResourceBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatelessResourceBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskBrief.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskInputFileBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/DeploymentEnvironment.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/OneApiState.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationFileKind.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationMergeParagraphAudioAlignKind.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationVoiceKind.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/FileNameExtensions.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/StringExtensions.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/HttpClientBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Readme.txt create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ConsoleMaskSasHelper.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnumExtensions.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnvironmentMetadatas.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ExceptionHelper.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/Sha256Helper.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/TaskNameHelper.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Arguments.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Mode.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Program.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/VideoTranslation.csproj create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationApiSampleCode.sln create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/Translation.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBrief.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationCreate.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocale.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleCreate.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileCreate.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocale.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBrief.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBriefPortalProperties.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationFeatureMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationPersonalVoiceModelMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationProfileMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationReleaseHistoryVersionMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationTargetLocaleMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/WebVttFileMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolInputArgs.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolOutputResult.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationReleaseHistoryVersionMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationSourceLocaleMetadata.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebVttFilePlainTextKind.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebvttSourceKind.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/TargetLocaleClient.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoFileClient.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClient.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClientBase.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationLib.csproj create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationMetadataClient.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationPool.cs create mode 100644 VideoDubbing/CSharp/APIClientTool/nuget.config create mode 100644 VideoDubbing/readme.md diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ArgumentAttribute.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ArgumentAttribute.cs new file mode 100644 index 00000000..af86215b --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ArgumentAttribute.cs @@ -0,0 +1,174 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser; + +using System; +using System.Globalization; + +[AttributeUsage(AttributeTargets.Field, Inherited = false)] +public sealed class ArgumentAttribute : Attribute +{ + private readonly string flag; + private string description = string.Empty; + private string usagePlaceholder; + private bool optional; + private bool hidden; + private InOutType inoutType; + private string requiredModes; + private string optionalModes; + + /// + /// Initializes a new instance of the class. + /// + /// Flag string for this attribute. + public ArgumentAttribute(string optionName) + { + if (optionName == null) + { + throw new ArgumentNullException(nameof(optionName)); + } + + this.flag = optionName; + } + + /// + /// Gets The parse recognising flag. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ignore.")] + public string OptionName + { + get { return this.flag.ToLower(CultureInfo.InvariantCulture); } + } + + /// + /// Gets or sets Description will display in the PrintUsage method. + /// + public string Description + { + get + { + return this.description; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.description = value; + } + } + + /// + /// Gets or sets In the PrintUsage method this will display a place hold for a parameter. + /// + public string UsagePlaceholder + { + get + { + return this.usagePlaceholder; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.usagePlaceholder = value; + } + } + + /// + /// Gets or sets a value indicating whether (optional = true) means not necessarily in the command-line. + /// + public bool Optional + { + get { return this.optional; } + set { this.optional = value; } + } + + /// + /// Gets or sets a value indicating whether (Hidden = true) means this option will not be printed in the command-line. + /// While one option is set with Hidden, the Optional must be true. + /// + public bool Hidden + { + get { return this.hidden; } + set { this.hidden = value; } + } + + /// + /// Gets or sets The in/out type of argument. + /// + public InOutType InOutType + { + get { return this.inoutType; } + set { this.inoutType = value; } + } + + /// + /// Gets or sets The modes require this argument. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ignore.")] + public string RequiredModes + { + get + { + return this.requiredModes; + } + + set + { + this.requiredModes = value?.ToLower(CultureInfo.InvariantCulture); + } + } + + /// + /// Gets or sets The modes optionally require this argument. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ignore.")] + public string OptionalModes + { + get + { + return this.optionalModes; + } + + set + { + this.optionalModes = value?.ToLower(CultureInfo.InvariantCulture); + } + } + + /// + /// Get required modes in an array. + /// + /// Mode array. + public string[] GetRequiredModeArray() + { + string[] modes = null; + if (!string.IsNullOrEmpty(this.requiredModes)) + { + modes = this.requiredModes.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); + } + + return modes; + } + + /// + /// Get optional modes in an array. + /// + /// Mode array. + public string[] GetOptionalModeArray() + { + string[] modes = null; + if (!string.IsNullOrEmpty(this.optionalModes)) + { + modes = this.optionalModes.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); + } + + return modes; + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommandLineParser.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommandLineParser.cs new file mode 100644 index 00000000..4a279562 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommandLineParser.cs @@ -0,0 +1,1282 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; + +public static class CommandLineParser +{ + public static void Parse(string[] args, object target) + { + ClpHelper.CheckTarget(target); + ClpHelper.CheckArgs(args, target); + + InternalFlags internalTarget = new InternalFlags(); + ClpHelper helper = new ClpHelper(target, internalTarget); + + helper.ParseArgs(args); + + if (!string.IsNullOrEmpty(internalTarget.ConfigFile)) + { + args = ClpHelper.GetStringsFromConfigFile(internalTarget.ConfigFile); + helper.ParseArgs(args); + } + + if (internalTarget.NeedHelp) + { + throw new CommandLineParseException(string.Empty, "help"); + } + + helper.CheckAllRequiredDestination(); + } + + public static void PrintUsage(object target) + { + string usage = BuildUsage(target); + Console.WriteLine(); + Console.WriteLine(usage); + } + + public static void HandleException(object target, Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); + if (!string.IsNullOrEmpty(exception.Message)) + { + ArgumentNullException.ThrowIfNull(exception.Message); + } + else + { + PrintUsage(target); + } + } + + public static string BuildUsage(object target) + { + ClpHelper.CheckTarget(target); + + CommentAttribute[] ca = (CommentAttribute[])target.GetType().GetCustomAttributes(typeof(CommentAttribute), false); + + StringBuilder sb = new StringBuilder(); + + if (ca.Length == 1 && !string.IsNullOrEmpty(ca[0].HeadComment)) + { + sb.AppendLine(ca[0].HeadComment); + } + + sb.AppendLine(); + + Assembly entryAssm = Assembly.GetEntryAssembly(); + + // entryAssm is a null reference when a managed assembly has been loaded + // from an unmanaged application; Currently we don't allow such calling. + // But when calling by our Nunit test framework, this value is null + if (entryAssm != null) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "Version {0}", entryAssm.GetName().Version.ToString()); + sb.AppendLine(); + } + + sb.Append(ClpHelper.BuildUsageLine(target)); + sb.Append(ClpHelper.BuildOptionsString(target)); + + if (ca.Length == 1 && !string.IsNullOrEmpty(ca[0].RearComment)) + { + sb.AppendLine(); + sb.AppendLine(ca[0].RearComment); + } + + return sb.ToString(); + } + + /// + /// Command line parser helper class. + /// + private class ClpHelper + { + public const BindingFlags AllFieldBindingFlags = + BindingFlags.Instance | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + + public const string Mode = "mode"; + + // const members. + private const int MaxCommandLineStringNumber = 800; + private const int MaxConfigFileSize = 32 * 1024; // 32k + + private string modeString; + + // class members. + private object clpTarget; + private InternalFlags internalTarget; + private Dictionary destMap = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Target object to reflect usage information. + /// Internal flags. + public ClpHelper(object target, InternalFlags internalTarget) + { + this.clpTarget = target; + this.internalTarget = internalTarget; // interal flags class, include "-h","-?","-help","-C" + + this.ParseTheDestination(target); + } + + /// + /// Check the target objcet, which is to save the value, to avoid misuse. + /// + /// Target object to reflect usage information. + public static void CheckTarget(object target) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (!target.GetType().IsClass) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Object target is not a class."), "target"); + } + + // Check each field of the target class to ensure that every field, which wanted to be + // filled, has defined a static TryParse(string, out Value) funtion. In the parsing time, + // the CLP class will use this static function to parse the string to value. + foreach (FieldInfo fieldInfo in target.GetType().GetFields(ClpHelper.AllFieldBindingFlags)) + { + if (fieldInfo.IsDefined(typeof(ArgumentAttribute), false)) + { + Type type = fieldInfo.FieldType; + if (type.IsArray) + { + type = type.GetElementType(); + } + + // string Type don't need a TryParse function, so skip check it. + if (type == typeof(string)) + { + continue; + } + + Type reftype = Type.GetType(type.ToString() + "&"); + if (reftype == null) + { + throw new ArgumentException( + "This Type does not exist in this assembly GetType(" + type + ")failed.", + fieldInfo.ToString()); + } + + MethodInfo mi = type.GetMethod("TryParse", new Type[] { typeof(string), reftype }); + if (mi == null) + { + throw new ArgumentException( + "Type " + type + " don't have a TryParse(string, out Value) method.", + fieldInfo.ToString()); + } + } + } + } + + /// + /// Check args from static Main() function, to avoid misuse this library. + /// + /// Argument string array. + /// Target object to reflect usage information. + public static void CheckArgs(string[] args, object target) + { + if (args == null) + { + string message = string.Format(CultureInfo.InvariantCulture, "Empty parameters."); + throw new CommandLineParseException(message); + } + + int requiredArgumentCount = GetRequiredArgumentCount(target); + if (args.Length == 0) + { + // if there is no parameter given + if (requiredArgumentCount > 0) + { + // some parameters are required + throw new CommandLineParseException(string.Empty, "help"); + } + + // run the application with default option values + } + + if (args.Length > MaxCommandLineStringNumber) + { + throw new CommandLineParseException(string.Format(CultureInfo.InvariantCulture, "Input parameter number is larger than {0}.", MaxCommandLineStringNumber), "args"); + } + + for (int i = 0; i < args.Length; ++i) + { + if (string.IsNullOrEmpty(args[i])) + { + string message = string.Format(CultureInfo.InvariantCulture, "The {0}(th) parameter in the command line could not be null or empty.", i + 1); + throw new CommandLineParseException(message); + } + } + } + + /// + /// Parse the configuration file into a string[], this string[] will be send to the + /// ParseArgs(string[] args). This function will do some simple check of the + /// Command line, the first character the config line in the file must '-', + /// Otherwise, this line will not be parsed. + /// + /// Configuration file path. + /// Configuration strings. + public static string[] GetStringsFromConfigFile(string filePath) + { + if (!File.Exists(filePath)) + { + string message = string.Format(CultureInfo.InvariantCulture, "The configuration file [{0}] can not found.", filePath); + throw new CommandLineParseException(message, filePath); + } + + FileInfo fileInfo = new FileInfo(filePath); + + if (fileInfo.Length > MaxConfigFileSize) + { + string message = string.Format(CultureInfo.InvariantCulture, "Not supported configuration file [{0}], for the size of it is bigger than {1} byte.", filePath, MaxConfigFileSize); + throw new CommandLineParseException(message, filePath); + } + + string[] lines; + using (StreamReader streamFile = new StreamReader(filePath)) + { + lines = streamFile.ReadToEnd().Split(Environment.NewLine.ToCharArray()); + } + + List strList = new List(); + + // Go through the file, and expand the listed parameters + // into the List of existing parameters. + foreach (string line in lines) + { + string trimedLine = line.Trim(); + + if (trimedLine.IndexOf('-') == 0) + { + string[] strArray = trimedLine.Split(new char[] { ' ', '\t' }); + foreach (string str in strArray) + { + if (!string.IsNullOrEmpty(str)) + { + strList.Add(str); + } + } + } + } + + return strList.ToArray(); + } + + /// + /// Count the number of required arguments. + /// + /// Target object to reflect usage information. + /// The number of required arguments. + public static int GetRequiredArgumentCount(object target) + { + int count = 0; + foreach (FieldInfo field in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(field); + if (argument == null) + { + continue; // skip those field that don't define the ArgumentAttribute. + } + + if (!argument.Optional) + { + count++; + } + } + + return count; + } + + /// + /// Build the useage line. First print the file name of current execution files. + /// And then, print the each flag of these options. + /// + /// Target object to reflect usage information. + /// Useage string. + public static string BuildUsageLine(object target) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "Usage:{0}", Environment.NewLine); + + string[] allModes = GetAllModes(target); + if (allModes != null) + { + foreach (string mode in allModes) + { + sb.AppendFormat(CultureInfo.InvariantCulture, @" Mode ""{0}"" has following usage: {1}", mode, Environment.NewLine); + sb.AppendFormat(CultureInfo.InvariantCulture, " {0} -mode {1}", AppDomain.CurrentDomain.FriendlyName, mode); + + foreach (FieldInfo field in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(field); + if (argument == null) + { + continue; // skip those field that don't define the ArgumentAttribute. + } + + if (argument.OptionName == ClpHelper.Mode) + { + continue; + } + + string[] optionalModes = argument.GetOptionalModeArray(); + string[] requiredModes = argument.GetRequiredModeArray(); + if (requiredModes == null && optionalModes == null) + { + // should not print out hidden argument + if (!argument.Hidden) + { + if (argument.Optional) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " [{0}]", GetFlagAndPlaceHolderString(argument)); + } + else + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}", GetFlagAndPlaceHolderString(argument)); + } + } + } + else + { + if ((optionalModes != null) && IsInArray(optionalModes, mode)) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " [{0}]", GetFlagAndPlaceHolderString(argument)); + } + else if (requiredModes != null && IsInArray(requiredModes, mode)) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}", GetFlagAndPlaceHolderString(argument)); + } + } + } + + sb.AppendLine(string.Empty); + sb.AppendLine(string.Empty); + } + } + else + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}", AppDomain.CurrentDomain.FriendlyName); + + foreach (FieldInfo field in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(field); + if (argument == null) + { + continue; // skip those field that don't define the ArgumentAttribute. + } + + string optionLine = BuildOptionLine(argument); + sb.Append(optionLine); + } + } + + sb.AppendLine(); + sb.AppendLine(); + + return sb.ToString(); + } + + /// + /// Print flag and description of each options. + /// + /// Target object to reflect usage information. + /// Flag and description string of each options. + public static string BuildOptionsString(object target) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(" Options\tDescriptions"); + sb.Append(BuildOptionsString(target, null)); + return sb.ToString(); + } + + /// + /// Parse the args string from the static Main() or from configuration file. + /// + /// Argument string array. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ignore.")] + public void ParseArgs(string[] args) + { + Destination destination = null; + + foreach (string str in args) + { + Destination dest = this.IsFlagStringAndGetTheDestination(str); + + // Is a flag string + if (dest != null) + { + if (destination != null) + { + destination.Save(this.clpTarget); + } + + destination = dest; + + if (destination.AlreadySaved) + { + string message = string.Format(CultureInfo.InvariantCulture, "The option flag [-{0}] could not be dupalicated.", destination.Argument.OptionName); + throw new CommandLineParseException(message, str); + } + } + else + { + if (destination == null) + { + destination = this.SaveValueStringToEmptyFlag(str); + } + else + { + if (!destination.TryToAddValue(this.clpTarget, str)) + { + destination.Save(this.clpTarget); + destination = this.SaveValueStringToEmptyFlag(str); + } + } + + if (destination != null) + { + if (destination.Argument.OptionName == ClpHelper.Mode) + { + this.modeString = str.ToLower(CultureInfo.InvariantCulture); + } + } + } + } + + // deal with the last flag + if (destination != null) + { + destination.Save(this.clpTarget); + } + } + + /// + /// By the end of the command line parsing, we must make sure that all non-optional + /// Flags have been given by the tool user. + /// + public void CheckAllRequiredDestination() + { + string[] allModes = GetAllModes(this.clpTarget); + foreach (Destination destination in this.destMap.Values) + { + bool requiredMissing = false; + if (destination.InternalTarget != null) + { + continue; + } + + if (allModes != null) + { + Debug.Assert(this.destMap.ContainsKey(Mode), "Failed"); + if (!string.IsNullOrEmpty(this.modeString)) + { + string[] requireModes = destination.Argument.GetRequiredModeArray(); + string[] optionalModes = destination.Argument.GetOptionalModeArray(); + if (requireModes == null) + { + if (optionalModes == null) + { + // if required modes and optional modes are all empty + // Means the argument is commonly optional or not in all modes. + // we can use the Optional flag to simplify + requiredMissing = !destination.Argument.Optional && !destination.AlreadySaved; + } + } + else + { + if (IsInArray(requireModes, this.modeString)) + { + requiredMissing = !destination.AlreadySaved; + } + } + + if (destination.AlreadySaved && (optionalModes != null || requireModes != null)) + { + if ((requireModes == null && !IsInArray(optionalModes, this.modeString)) || + (optionalModes == null && !IsInArray(requireModes, this.modeString)) || + (requireModes != null && optionalModes != null && + !IsInArray(optionalModes, this.modeString) && !IsInArray(requireModes, this.modeString))) + { + string message = string.Format(CultureInfo.InvariantCulture, "Parameter [{0}] is not needed for mode [{1}].", destination.Argument.OptionName, this.modeString); + throw new CommandLineParseException(message); + } + } + } + else + { + string message = string.Format(CultureInfo.InvariantCulture, "The mode option is required for the command."); + throw new CommandLineParseException(message); + } + } + else + { + requiredMissing = !destination.Argument.Optional && !destination.AlreadySaved; + } + + if (requiredMissing) + { + string optionLine = BuildOptionLine(destination.Argument); + string message = string.Format(CultureInfo.InvariantCulture, "The option '{0}' is required for the command.", optionLine.Trim()); + throw new CommandLineParseException(message, "-" + destination.Argument.OptionName); + } + } + } + + /// + /// Check if a value is in array. + /// + /// Array. + /// Value. + /// Boolean. + private static bool IsInArray(string[] arr, string value) + { + bool found = false; + for (int i = 0; i < arr.Length; i++) + { + if (arr[i] == value) + { + found = true; + break; + } + } + + return found; + } + + private static void CheckModeArray(string[] totalModes, string[] modes) + { + ArgumentNullException.ThrowIfNull(totalModes); + if (modes == null) + { + return; + } + + string msg = "Mode {0} should be listed in mode argument's Modes string."; + if (modes != null) + { + for (int i = 0; i < modes.Length; i++) + { + if (!IsInArray(totalModes, modes[i])) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, msg, modes[i])); + } + } + } + } + + private static string BuildOptionsString(object target, string mode) + { + StringBuilder sb = new StringBuilder(); + foreach (FieldInfo field in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(field); + if (argument == null) + { + continue; + } + + if (!string.IsNullOrEmpty(mode)) + { + string[] modeArray = argument.GetRequiredModeArray(); + if (modeArray != null) + { + bool found = IsInArray(modeArray, mode); + if (!found) + { + continue; + } + } + } + + if (!argument.Hidden) + { + string str = field.FieldType.ToString(); + int i = str.LastIndexOf('.'); + str = str.Substring(i == -1 ? 0 : i + 1); + + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}{1}\t\t({3}) {2}", GetFlagAndPlaceHolderString(argument), Environment.NewLine, argument.Description, str); + if (argument.InOutType != InOutType.Unknown) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " [{0}]", Enum.GetName(typeof(InOutType), argument.InOutType)); + } + + sb.Append(Environment.NewLine); + } + else + { + if (!argument.Optional) + { + string message = string.Format(CultureInfo.InvariantCulture, "Argument for {0} can be hidden but can not be optional at the meantime.", field.Name); + Debug.Assert(argument.Optional, message); + throw new ArgumentException(message); + } + } + } + + return sb.ToString(); + } + + private static string[] GetAllModes(object target) + { + ArgumentAttribute modeArgument = null; + foreach (FieldInfo field in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(field); + if (argument == null) + { + continue; + } + + if (argument.OptionName == ClpHelper.Mode) + { + modeArgument = argument; + break; + } + } + + if (modeArgument == null) + { + return null; + } + + string[] modeArray = modeArgument.GetRequiredModeArray(); + if (modeArray == null || modeArray.Length == 0) + { + return null; + } + + foreach (FieldInfo fieldInfo in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(fieldInfo); + if (argument == null) + { + continue; + } + + string[] requiredModes = argument.GetRequiredModeArray(); + string[] optionalModes = argument.GetOptionalModeArray(); + CheckModeArray(modeArray, requiredModes); + CheckModeArray(modeArray, optionalModes); + if (requiredModes != null && optionalModes != null) + { + for (int i = 0; i < requiredModes.Length; i++) + { + if (IsInArray(optionalModes, requiredModes[i])) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Required modes {0} is conflicted with optional modes {1}", argument.RequiredModes, argument.OptionalModes)); + } + } + } + } + + return modeArray; + } + + private static string BuildOptionLine(ArgumentAttribute argument) + { + StringBuilder sb = new StringBuilder(); + if (!argument.Hidden) + { + if (argument.Optional) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " [{0}]", GetFlagAndPlaceHolderString(argument)); + } + else + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}", GetFlagAndPlaceHolderString(argument)); + } + } + else + { + if (!argument.Optional) + { + string message = string.Format(CultureInfo.InvariantCulture, "Argument for -{0} can be hidden but can not be optional at the meantime.", argument.OptionName); + Debug.Assert(argument.Optional, message); + throw new ArgumentException(message); + } + } + + return sb.ToString(); + } + + /// + /// Get the ArgumentAttribute from the field. If the field don't define this + /// Custom attribute, it will return null. + /// + /// Field information. + /// Argument attribute associated with the field. + private static ArgumentAttribute GetFieldArgumentAttribute(FieldInfo fieldInfo) + { + ArgumentAttribute[] argument = + (ArgumentAttribute[])fieldInfo.GetCustomAttributes(typeof(ArgumentAttribute), false); + + return argument.Length == 1 ? argument[0] : null; + } + + /// + /// When output the usage, this function will generate the flag string + /// Such as "-time n1 n2..." string. + /// + /// Argument attribute. + /// Argument presentation on command line. + private static string GetFlagAndPlaceHolderString(ArgumentAttribute argument) + { + return (!string.IsNullOrEmpty(argument.OptionName) ? "-" : string.Empty) + + argument.OptionName + + (string.IsNullOrEmpty(argument.UsagePlaceholder) ? + string.Empty : " " + argument.UsagePlaceholder); + } + + /// + /// Call by the GetFlagAndPlaceHolderString() function, and generate the frendly + /// Name of each parameter in command line, such as "n1 n2 ..." string. + /// + /// Field information. + /// Field name of the argument. + private static string GetFieldFriendlyTypeName(FieldInfo fieldInfo) + { + Type type = fieldInfo.FieldType.IsArray ? + fieldInfo.FieldType.GetElementType() : fieldInfo.FieldType; + + string str; + if (type == typeof(bool)) + { + str = string.Empty; + } + else + { + // Use the Type name's first character, + // for example: System.int -> i, System.double -> d + str = type.ToString(); + int i = str.LastIndexOf('.'); + i = i == -1 ? 0 : i + 1; + str = char.ToLower(str[i], CultureInfo.CurrentCulture).ToString(); + } + + return fieldInfo.FieldType.IsArray ? str + "1 " + str + "2 ..." : str; + } + + /// + /// Check and parse the internal target and external target, then push the result + /// Of parsing into the DestMap. + /// Internal target class has predefined some flags, such as "-h", "-C" + /// External target class are defined by the library users. + /// + /// Target object to reflect usage information. + private void ParseTheDestination(object target) + { + // Check and parse the internal target, so use Debug.Assert to catch the error. + foreach (FieldInfo fieldInfo in typeof(InternalFlags).GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(fieldInfo); + if (string.IsNullOrEmpty(argument.UsagePlaceholder)) + { + argument.UsagePlaceholder = GetFieldFriendlyTypeName(fieldInfo); + } + + Debug.Assert(argument != null, "Failed"); + + Destination destination = new Destination(fieldInfo, argument, this.internalTarget); + + Debug.Assert(destination.Argument.OptionName.Length != 0, "Failed"); + Debug.Assert(char.IsLetter(destination.Argument.OptionName[0]) || destination.Argument.OptionName[0] == '?', "Failed"); + + // Assert there is no duplicate flag in the user defined argument class. + Debug.Assert(!this.destMap.ContainsKey(destination.Argument.OptionName), "Failed"); + + this.destMap.Add(destination.Argument.OptionName, destination); + } + + // Check and parse the external target, so use throw exception + // to handle the unexpect target difine. + foreach (FieldInfo fieldInfo in target.GetType().GetFields(AllFieldBindingFlags)) + { + ArgumentAttribute argument = GetFieldArgumentAttribute(fieldInfo); + if (argument == null) + { + continue; + } + + Destination destination = new Destination(fieldInfo, argument, null); + + // Assert user don't define a non-letter as a flag in the user defined argument class. + if (destination.Argument.OptionName.Length > 0 && !char.IsLetter(destination.Argument.OptionName[0])) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "User can't define a non-letter flag ({0}).", destination.Argument.OptionName[0]), destination.Argument.OptionName[0].ToString()); + } + + // Assert there is no duplicate flag in the user defined argument class. + if (this.destMap.ContainsKey(destination.Argument.OptionName)) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Duplicate flag are defined in the argument class."), destination.Argument.OptionName); + } + + this.destMap.Add(destination.Argument.OptionName, destination); + } + } + + /// + /// Check the given string is a flag, if so, get the corresponding destination class of the flag. + /// + /// String to test. + /// Destination. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ignore.")] + private Destination IsFlagStringAndGetTheDestination(string str) + { + Debug.Assert(!string.IsNullOrEmpty(str), "Failed"); + + if (str.Length < 2 || str[0] != '-' + || (!char.IsLetter(str[1]) && str[1] != '?')) + { + return null; + } + + str = str.Substring(1).ToLower(CultureInfo.InvariantCulture); + return this.destMap.ContainsKey(str) ? this.destMap[str] : null; + } + + /// + /// Save the given string to the empty flag("") destination class. + /// + /// Flag string to save, not the realy null Flag, is the "" Flag. + /// Destination. + private Destination SaveValueStringToEmptyFlag(string str) + { + Destination destination = this.destMap.ContainsKey(string.Empty) ? this.destMap[string.Empty] : null; + + if (destination == null || destination.AlreadySaved || + !destination.TryToAddValue(this.clpTarget, str)) + { + StringBuilder sb = new StringBuilder(); + sb.Append($"Unrecognized command {str}. "); + + Assembly assembly = Assembly.GetEntryAssembly(); + if (assembly != null) + { + sb.Append($"Run '{Path.GetFileName(assembly.Location)} -?' for help."); + } + + throw new CommandLineParseException(sb.ToString(), str); + } + + return destination; + } + } + + /// + /// Private class, to hold the information of the target object. + /// + private class Destination + { + private FieldInfo fieldInfo; + private ArgumentAttribute argument; + + // Hold the internal target, it distinguish + private InternalFlags internalTarget; + private bool alreadySaved; + + // A internal target to a external target. If it is external, this member is null. + private ArrayList parameterList; + + /// + /// Initializes a new instance of the class. + /// + /// Field information. + /// Argument attribute. + /// Internal flags. + public Destination(FieldInfo fieldInfo, ArgumentAttribute argument, InternalFlags internalTarget) + { + this.fieldInfo = fieldInfo; + this.argument = argument; + this.internalTarget = internalTarget; + + // _AlreadySaved = false; + this.parameterList = fieldInfo.FieldType.IsArray ? new ArrayList() : null; + } + + /// + /// Gets internal target. + /// + public InternalFlags InternalTarget + { + get { return this.internalTarget; } + } + + /// + /// Gets Argument attribute. + /// + public ArgumentAttribute Argument + { + get { return this.argument; } + } + + /// + /// Gets a value indicating whether Value already saved. + /// + public bool AlreadySaved + { + get { return this.alreadySaved; } + } + + /// + /// Parse the string to given type of value. + /// + /// Type of value. + /// String to parse. + /// Result value. + public static object TryParseStringToValue(Type type, string str) + { + object obj = null; + + if (type == typeof(string)) + { + // string to string, don't need parse. + obj = str; + } + else if (type == typeof(sbyte) || type == typeof(byte) || + type == typeof(short) || type == typeof(ushort) || + type == typeof(int) || type == typeof(uint) || + type == typeof(long) || type == typeof(ulong)) + { + // Use the dec style to parse the string into integer value frist. + // If it failed, then use the hex sytle to parse it again. + obj = TryParse(str, type, NumberStyles.Integer | NumberStyles.AllowThousands); + if (obj == null && str.Substring(0, 2) == "0x") + { + obj = TryParse(str.Substring(2), type, NumberStyles.HexNumber); + } + } + else if (type == typeof(double) || type == typeof(float)) + { + // Use float style to parse the string into float value. + obj = TryParse(str, type, NumberStyles.Float | NumberStyles.AllowThousands); + } + else + { + // Use the default style to parse the string. + obj = TryParse(str, type); + } + + return obj; + } + + /// + /// Try to and a value to the target. Frist prase the string form parameter + /// To a given value. And then, save the value to a target field or a value + /// List. + /// + /// Target object to reflect usage information. + /// String value to add. + /// True if succeeded, otherwise false. + public bool TryToAddValue(object target, string str) + { + if (this.alreadySaved) + { + return false; + } + + if (this.internalTarget != null) + { + target = this.internalTarget; + } + + // If this field is an array, it will save the prased value into an value list. + // Otherwise, it will save the parse value to the field of the target directly. + if (this.fieldInfo.FieldType.IsArray) + { + object value = TryParseStringToValue(this.fieldInfo.FieldType.GetElementType(), str); + if (value == null) + { + return false; + } + + this.parameterList.Add(value); + } + else + { + object value = TryParseStringToValue(this.fieldInfo.FieldType, str); + if (value == null) + { + return false; + } + + this.fieldInfo.SetValue(target, value); + this.alreadySaved = true; + } + + return true; + } + + /// + /// Save function will do some cleanup of the value save. + /// + /// Target object to reflect usage information. + public void Save(object target) + { + if (this.internalTarget != null) + { + target = this.internalTarget; + } + + if (this.fieldInfo.FieldType.IsArray) + { + // When the filed is an array, this function will save all values in the ParameterList + // into the array field. + Debug.Assert(!this.alreadySaved, "Failed"); + Array array = (Array)this.fieldInfo.GetValue(target); + if (array != null && array.Length != this.parameterList.Count) + { + string message = string.Format(CultureInfo.InvariantCulture, "For option flag -{0}, the parameter number is {1}, which is not as expected {2}.", this.argument.OptionName, this.parameterList.Count, array.Length); + throw new CommandLineParseException(message, "-" + this.argument.OptionName); + } + + this.fieldInfo.SetValue(target, this.parameterList.ToArray(this.fieldInfo.FieldType.GetElementType())); + } + else if (this.fieldInfo.FieldType == typeof(bool)) + { + if (!this.alreadySaved) + { + bool b = (bool)this.fieldInfo.GetValue(target); + b = !b; + this.fieldInfo.SetValue(target, b); + } + } + else if (!this.alreadySaved) + { + // Other types do nothing, only check its already saved, + // beacuse the value must be saved in the TryToAddValue(); + string message = string.Format(CultureInfo.InvariantCulture, "The option flag [-{0}] needs {1} parameter.", this.argument.OptionName, this.argument.UsagePlaceholder); + throw new CommandLineParseException(message, "-" + this.argument.OptionName); + } + + this.alreadySaved = true; + } + + /// + /// Use the given style to parse the string to given type of value. + /// + /// String to parse. + /// Type of value. + /// Number styles. + /// Result value. + private static object TryParse(string str, Type type, NumberStyles ns) + { + // Use reflection to dynamic load the TryParse function of given type. + Type[] typeArgs = new Type[] + { + typeof(string), + typeof(NumberStyles), + typeof(IFormatProvider), + Type.GetType(type.ToString() + "&"), + }; + + MethodInfo mi = type.GetMethod("TryParse", typeArgs); + + // Initilze these four parameters of the Tryparse funtion. + object[] objArgs = new object[] + { + str, + ns, + CultureInfo.InvariantCulture, + Activator.CreateInstance(type), + }; + + return DoTryParse(mi, objArgs); + } + + /// + /// Use the defalut style to parse the string to given type of value. + /// + /// String to parse. + /// Type of value. + /// Result value. + private static object TryParse(string str, Type type) + { + // Use reflection to dynamic load the TryParse function of given type. + MethodInfo mi = type.GetMethod("TryParse", new Type[] { typeof(string), Type.GetType(type.ToString() + "&") }); + + // Initilze these two parameters of the Tryparse funtion. + object[] objArgs = new object[] { str, Activator.CreateInstance(type) }; + + return DoTryParse(mi, objArgs); + } + + /// + /// Run the TryParse function by the given method and parameters. + /// + /// Method information. + /// Method arguments. + /// Result value. + private static object DoTryParse(MethodInfo methodInfo, object[] methodArgs) + { + object retVal = methodInfo.Invoke(null, methodArgs); + + if (!(retVal is bool)) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "TryParse() method must return a bool value."), methodArgs[methodArgs.Length - 1].GetType().ToString()); + } + + // the last parameter of TryParse method is a reference of a value. + // Therefore, it will return the last value of the parameter array. + // If the TryParse function failed when parsing, this DoTryParse will + // return null. + return (bool)retVal ? methodArgs[methodArgs.Length - 1] : null; + } + } + + /// + /// This class is defined to take the internal flags, such as -h, -?, -C, and etc. + /// When the parse begin to parse the target object, it will parse is class's object + /// First. So, the parse can first put the internal flags into the DestMap to avoid + /// Library user redifined those flags. And When finish parsed all flags, The library + /// Will check the property NeedHelp to determinated those flags are appeared in the + /// Command line. + /// + private sealed class InternalFlags + { + [Argument("h", Description = "Help", Optional = true)] + private bool needHelp1; + + [Argument("?", Description = "Help", Optional = true)] + private bool needHelp2; + + [Argument("help", Description = "Help", Optional = true)] + private bool needHelp3; + + [Argument("conf", Description = "Configuration file", Optional = true)] + private string configFile; // use internal instead of private to avoid unusing warning. + + /// + /// Initializes a new instance of the class. + /// + public InternalFlags() + { + this.needHelp1 = this.needHelp2 = this.needHelp3 = false; + } + + /// + /// Gets a value indicating whether Flag indicating whether user requires help. + /// + public bool NeedHelp + { + get { return this.needHelp1 || this.needHelp2 || this.needHelp3; } + } + + /// + /// Gets or sets Configuration file path. + /// + public string ConfigFile + { + get { return this.configFile; } + set { this.configFile = value; } + } + } +} + +/// +/// When the CommandLineParser meet an unacceptabile command line +/// Parameter, it will throw the CommandLineParseException. If the +/// CLP meet another arguments error by anaylse the target object, +/// It will throw the ArgumentException defined by .NET framework. +/// +[Serializable] +#pragma warning disable SA1402 // File may only contain a single type +public class CommandLineParseException : Exception +#pragma warning restore SA1402 // File may only contain a single type +{ + /// + /// The error string is "help". + /// + public const string ErrorStringHelp = "help"; + + private readonly string errorString; + + /// + /// Initializes a new instance of the class. + /// + /// Message. + /// Error string. + public CommandLineParseException(string message, string error) + : base(message) + { + this.errorString = string.IsNullOrEmpty(error) ? string.Empty : error; + } + + /// + /// Initializes a new instance of the class. + /// + public CommandLineParseException() + : base() + { + this.errorString = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// Message. + public CommandLineParseException(string message) + : base(message) + { + this.errorString = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// Message. + /// Inner exception. + public CommandLineParseException(string message, Exception inner) + : base(message, inner) + { + this.errorString = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization info. + /// Streaming context. + protected CommandLineParseException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + this.errorString = string.Empty; + } + + /// + /// Gets To save the error string. + /// + public string ErrorString + { + get { return this.errorString; } + } + + /// + /// This method is required by serialization. + /// + /// Serialization info. + /// Streaming context. + [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommentAttribute.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommentAttribute.cs new file mode 100644 index 00000000..a182c180 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/CommentAttribute.cs @@ -0,0 +1,46 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser; + +using System; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class CommentAttribute : Attribute +{ + private readonly string headComment; + private readonly string rearComment = "Copyright (C) Microsoft Corporation. All rights reserved."; + + public CommentAttribute(string headComment) + { + if (headComment == null) + { + throw new ArgumentNullException(nameof(headComment)); + } + + this.headComment = headComment; + } + + public CommentAttribute(string headComment, string rearComment) + { + if (headComment == null) + { + throw new ArgumentNullException(nameof(headComment)); + } + + if (rearComment == null) + { + throw new ArgumentNullException(nameof(rearComment)); + } + + this.headComment = headComment; + this.rearComment = rearComment; + } + + public string HeadComment + { + get { return this.headComment; } + } + + public string RearComment + { + get { return this.rearComment; } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ConsoleApp.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ConsoleApp.cs new file mode 100644 index 00000000..f28345e4 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ConsoleApp.cs @@ -0,0 +1,54 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser +{ + using System; + using System.IO; + using System.Text; + using System.Threading.Tasks; + + public static class ConsoleApp + where T : new() + { + public static async Task RunAsync(string[] arguments, Func> processAsync) + { + ArgumentNullException.ThrowIfNull(processAsync); + + ArgumentNullException.ThrowIfNull(arguments); + + int ret = ExitCode.NoError; + + T arg = new T(); + try + { + try + { + CommandLineParser.Parse(arguments, arg); + } + catch (CommandLineParseException cpe) + { + if (cpe.ErrorString == CommandLineParseException.ErrorStringHelp) + { + CommandLineParser.PrintUsage(arg); + } + else if (!string.IsNullOrEmpty(cpe.Message)) + { + Console.WriteLine(cpe.Message); + } + + return ExitCode.InvalidArgument; + } + + ret = await processAsync(arg).ConfigureAwait(false); + return ret; + } + catch (Exception) + { + if (ret != ExitCode.NoError) + { + return ret; + } + + throw; + } + } + } +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ExitCode.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ExitCode.cs new file mode 100644 index 00000000..73fc25b1 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/ExitCode.cs @@ -0,0 +1,14 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser; + +public sealed class ExitCode +{ + public const int NoError = 0; + + public const int InvalidArgument = -1; + + public const int GenericError = 999; + + private ExitCode() + { + } +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/InOutType.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/InOutType.cs new file mode 100644 index 00000000..9b18c63e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommandParser/InOutType.cs @@ -0,0 +1,12 @@ +namespace Microsoft.SpeechServices.CommonLib.CommandParser; + +public enum InOutType +{ + Unknown, + + In, + + Out, + + InOut, +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommonLib.csproj b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommonLib.csproj new file mode 100644 index 00000000..bd7ba814 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CommonLib.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + Microsoft.SpeechServices.CommonLib + Microsoft.SpeechServices.CommonLib + + + + + + + + + + + diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CustomContractResolver.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CustomContractResolver.cs new file mode 100644 index 00000000..96aaf16a --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/CustomContractResolver.cs @@ -0,0 +1,138 @@ +namespace Microsoft.SpeechServices.CommonLib; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.SpeechServices.CommonLib.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +public class CustomContractResolver : CamelCasePropertyNamesContractResolver +{ + public static readonly CustomContractResolver ReaderContractResolver = new CustomContractResolver(); + public static readonly CustomContractResolver WriterContractResolver = new CustomContractResolver(); + + public static JsonSerializerSettings WriterSettings { get; } = new JsonSerializerSettings + { + ContractResolver = WriterContractResolver, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + Converters = new List { new StringEnumConverter() { AllowIntegerValues = false } }, + DateFormatString = "yyyy-MM-ddTHH\\:mm\\:ss.fffZ", + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.Indented, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + + public static JsonSerializerSettings ReaderSettings { get; } = new JsonSerializerSettings + { + ContractResolver = ReaderContractResolver, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + Converters = new List { new StringEnumConverter() { AllowIntegerValues = true } }, + Formatting = Formatting.Indented + }; + + public static string GetResolvedPropertyName(PropertyInfo property) + { + ArgumentNullException.ThrowIfNull(property); + + string propertyName; + var jsonAttribute = property.GetCustomAttributes(typeof(JsonPropertyAttribute)).Cast().FirstOrDefault(); + if (jsonAttribute != null && !string.IsNullOrWhiteSpace(jsonAttribute.PropertyName)) + { + propertyName = jsonAttribute.PropertyName; + } + else + { + propertyName = ReaderContractResolver.GetResolvedPropertyName(property.Name); + } + + return propertyName; + } + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + if (!property.Writable) + { + var propertyInfo = member as PropertyInfo; + if (propertyInfo != null) + { + property.Writable = propertyInfo.CanWrite; + } + } + + const string createdDateTime = "CreatedDateTime"; + const string lastActionDateTime = "LastActionDateTime"; + const string status = "Status"; + const string timeToLive = "TimeToLive"; + const string duration = "Duration"; + const string customProperties = "CustomProperties"; + + if (property.PropertyType == typeof(DateTime) && property.PropertyName == this.ResolvePropertyName(createdDateTime)) + { + property.ShouldSerialize = + instance => + { + var value = (DateTime)instance.GetType().GetProperty(createdDateTime).GetValue(instance); + return value != default(DateTime); + }; + } + else if (property.PropertyType == typeof(DateTime) && property.PropertyName == this.ResolvePropertyName(lastActionDateTime)) + { + property.ShouldSerialize = + instance => + { + var value = (DateTime)instance.GetType().GetProperty(lastActionDateTime).GetValue(instance); + return value != default(DateTime); + }; + } + else if (property.PropertyType == typeof(OneApiState) && property.PropertyName == this.ResolvePropertyName(status)) + { + property.ShouldSerialize = + instance => + { + var value = (OneApiState)instance.GetType().GetProperty(status).GetValue(instance); + return value != default(OneApiState); + }; + } + else if (property.PropertyType == typeof(TimeSpan) && property.PropertyName == this.ResolvePropertyName(timeToLive)) + { + property.ShouldSerialize = + instance => + { + var value = (TimeSpan)instance.GetType().GetProperty(timeToLive).GetValue(instance); + return value != TimeSpan.Zero; + }; + } + else if (property.PropertyType == typeof(TimeSpan) && property.PropertyName == this.ResolvePropertyName(duration)) + { + property.ShouldSerialize = + instance => + { + var value = (TimeSpan)instance.GetType().GetProperty(duration).GetValue(instance); + return value != TimeSpan.Zero; + }; + } + else if (property.PropertyType == typeof(IReadOnlyDictionary) && property.PropertyName == this.ResolvePropertyName(customProperties)) + { + property.ShouldSerialize = + instance => + { + var value = (IReadOnlyDictionary)instance.GetType().GetProperty(customProperties).GetValue(instance); + return value != null && value.Count > 0; + }; + } + + return property; + } + + // do not javascriptify (camel case) dictionary keys. This would e.g. change + // key in artifact properties. + protected override string ResolveDictionaryKey(string dictionaryKey) + { + return dictionaryKey; + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/PaginatedResources.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/PaginatedResources.cs new file mode 100644 index 00000000..43b37eb2 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/PaginatedResources.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.DataContracts; + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +public class PaginatedResources +{ + public IEnumerable Value { get; set; } + + [JsonProperty(PropertyName = "@nextLink")] + public Uri NextLink { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatefulResourceBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatefulResourceBase.cs new file mode 100644 index 00000000..f14a78b3 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatefulResourceBase.cs @@ -0,0 +1,11 @@ +namespace Microsoft.SpeechServices.DataContracts.Deprecated; + +using System; +using Microsoft.SpeechServices.CommonLib.Enums; + +public abstract class StatefulResourceBase : StatelessResourceBase +{ + public OneApiState Status { get; set; } + + public DateTime LastActionDateTime { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatelessResourceBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatelessResourceBase.cs new file mode 100644 index 00000000..f0d5b29b --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/Public-2023-04-01-preview/StatelessResourceBase.cs @@ -0,0 +1,22 @@ +namespace Microsoft.SpeechServices.DataContracts.Deprecated; + +using System; +using System.ComponentModel.DataAnnotations; + +public abstract class StatelessResourceBase +{ + public Uri Self { get; set; } + + [Required] + public string DisplayName { get; set; } + + public string Description { get; set; } + + public DateTime CreatedDateTime { get; set; } + + public Guid ParseIdFromSelf() + { + var url = this.Self.OriginalString; + return Guid.Parse(url.Substring(url.LastIndexOf("/") + 1, 36)); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/ResponseBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/ResponseBase.cs new file mode 100644 index 00000000..9e52725a --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/ResponseBase.cs @@ -0,0 +1,8 @@ +namespace Microsoft.SpeechServices.DataContracts; + +using System; + +public abstract class ResponseBase +{ + public Uri Self { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatefulResourceBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatefulResourceBase.cs new file mode 100644 index 00000000..29219196 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatefulResourceBase.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public; + +using System; +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; + +public abstract class StatefulResourceBase : StatelessResourceBase +{ + public OneApiState Status { get; set; } + + public DateTime LastActionDateTime { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatelessResourceBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatelessResourceBase.cs new file mode 100644 index 00000000..c16d78c5 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/StatelessResourceBase.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public; + +using System; + +public abstract class StatelessResourceBase +{ + public string Id { get; set; } + + public string DisplayName { get; set; } + + public string Description { get; set; } + + public DateTime CreatedDateTime { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskBrief.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskBrief.cs new file mode 100644 index 00000000..3f6b4171 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskBrief.cs @@ -0,0 +1,7 @@ +using Microsoft.SpeechServices.DataContracts.Deprecated; + +namespace Microsoft.SpeechServices.DataContracts; + +public class VoiceGeneralTaskBrief : StatefulResourceBase +{ +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskInputFileBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskInputFileBase.cs new file mode 100644 index 00000000..930595ee --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/DataContracts/VoiceGeneralTaskInputFileBase.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public.VoiceGeneralTask; + +using Microsoft.SpeechServices.DataContracts; +using System; + +public class VoiceGeneralTaskInputFileBase : StatefulResourceBase +{ + // ID is used for client to know which file responsed. + public Guid Id { get; set; } + + public string FileContentSha256 { get; set; } + + public Uri Url { get; set; } + + public long? Version { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/DeploymentEnvironment.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/DeploymentEnvironment.cs new file mode 100644 index 00000000..56f7f6e4 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/DeploymentEnvironment.cs @@ -0,0 +1,140 @@ +namespace Microsoft.SpeechServices.CommonLib.Enums; + +using System; +using System.Runtime.Serialization; + +[DataContract] +public enum DeploymentEnvironment +{ + [EnumMember] + Default, + + [EnumMember] + Local, + + [EnumMember] + Develop, + + [EnumMember] + DevelopEUS, + + [EnumMember] + CanaryUSCX, + + [EnumMember] + ProductionAUE, + + [EnumMember] + ProductionBRS, + + [EnumMember] + ProductionCAC, + + [EnumMember] + ProductionUSW, + + [EnumMember] + ProductionUSW3, + + [EnumMember] + ProductionUSWC, + + [EnumMember] + ProductionEA, + + [EnumMember] + ProductionEUS, + + [EnumMember] + ProductionEUS2, + + [EnumMember] + ProductionFC, + + [EnumMember] + ProductionGWC, + + [EnumMember] + ProductionINC, + + [EnumMember] + ProductionJINW, + + [EnumMember] + ProductionJPE, + + [EnumMember] + ProductionJPW, + + [EnumMember] + ProductionKC, + + [EnumMember] + ProductionNEU, + + [EnumMember] + ProductionNOE, + + [EnumMember] + ProductionQAC, + + [EnumMember] + ProductionSAN, + + [EnumMember] + ProductionSEA, + + [EnumMember] + ProductionSEC, + + [EnumMember] + ProductionSWN, + + [EnumMember] + ProductionSWW, + + [EnumMember] + ProductionUAEN, + + [EnumMember] + ProductionUKS, + + [EnumMember] + ProductionUSC, + + [EnumMember] + ProductionUSNC, + + [EnumMember] + ProductionUSSC, + + [EnumMember] + ProductionWEU, + + [EnumMember] + ProductionWUS2, + + [EnumMember] + MooncakeChinaEast2, + + [EnumMember] + DevelopWEU, + + [EnumMember] + CanaryUSE2X, + + [EnumMember] + Internal, + + [EnumMember] + FairfaxDevOps, + + [EnumMember] + FairfaxVirginia, + + [EnumMember] + FairfaxArizona, + + [EnumMember] + MooncakeDevOps, +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/OneApiState.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/OneApiState.cs new file mode 100644 index 00000000..7a0b78cb --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/OneApiState.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.Serialization; + +namespace Microsoft.SpeechServices.CommonLib.Enums; + +[DataContract] +public enum OneApiState +{ + [Obsolete("Do not use directly - used to discover deserializer issues.")] + None = 0, + + NotStarted, + + Running, + + Succeeded, + + Failed, +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationFileKind.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationFileKind.cs new file mode 100644 index 00000000..1b1b512e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationFileKind.cs @@ -0,0 +1,10 @@ +namespace Microsoft.SpeechServices.CommonLib.Enums; + +public enum VideoTranslationFileKind +{ + None = 0, + + VideoFile, + + AudioFile, +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationMergeParagraphAudioAlignKind.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationMergeParagraphAudioAlignKind.cs new file mode 100644 index 00000000..628e7a05 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationMergeParagraphAudioAlignKind.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.CommonLib.Enums; + +using System; + +public enum VideoTranslationMergeParagraphAudioAlignKind +{ + [Obsolete("Do not use directly - used to discover serializer issues.")] + None = 0, + + TruncateIfExceed, + + SpeedUpIfExceed, +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationVoiceKind.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationVoiceKind.cs new file mode 100644 index 00000000..c7d499cc --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Enums/VideoTranslationVoiceKind.cs @@ -0,0 +1,14 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Common.Client; + +public enum VideoTranslationVoiceKind +{ + None = 0, + + PlatformVoice, + + PersonalVoice, +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/FileNameExtensions.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/FileNameExtensions.cs new file mode 100644 index 00000000..d243215f --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/FileNameExtensions.cs @@ -0,0 +1,294 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Common; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +public static class FileNameExtensions +{ + public const char FileExtensionDelimiter = '.'; + + public const string Mp3 = "mp3"; + + public const string Mp4 = "mp4"; + + public const string CloudAudioMetadataFile = "metadata"; + + public const string VideoTranslationDubbingMetricsReferenceYaml = "info.yaml"; + + public const string CloudAudioTsvFile = "tsv"; + + public const string Waveform = "wav"; + + public const string RawWave = "raw"; + + public const string Ogg = "ogg"; + + public const string Text = "txt"; + + public const string Tsv = "tsv"; + + public const string Xml = "xml"; + + public const string Yaml = "yaml"; + + public const string Configuration = "config"; + + public const string CsvFile = "csv"; + + public const string ExcelFile = "xlsx"; + + public const string ZipFile = "zip"; + + public const string HtmlFile = "html"; + + public const string PngFile = "png"; + + public const string JpegFile = "jpeg"; + + public const string JsonFile = "json"; + + public const string Zip7Z = "7z"; + + public const string IniFile = "ini"; + + public const string LgMarkdownFile = "lg"; + + public const string PdfFile = "pdf"; + + public const string PptxFile = "pptx"; + + public const string WaveformRaw = "raw"; + + public const string SubRipFile = "srt"; + + public const string WebVttFile = "vtt"; + + public const string WebmVideoFile = "webm"; + + public const string M4aAudioFile = "m4a"; + + public const string PitchF0File = "if0"; + + public static string EnsureExtensionWithoutDelimiter(this string extension) + { + string extensionWithoutDelimeter = string.Empty; + if (!string.IsNullOrEmpty(extension)) + { + if (extension[0] == FileExtensionDelimiter) + { + extensionWithoutDelimeter = extension.Substring(1); + } + else + { + extensionWithoutDelimeter = extension; + } + } + + return extensionWithoutDelimeter; + } + + public static string EnsureExtensionWithDelimiter(this string extension) + { + string extensionWithDelimiter = extension; + if (!string.IsNullOrEmpty(extension)) + { + if (extension[0] != FileExtensionDelimiter) + { + extensionWithDelimiter = FileExtensionDelimiter + extension; + } + else + { + extensionWithDelimiter = extension; + } + } + + return extensionWithDelimiter; + } + + public static string AppendExtensionName(this string file, string extensionName) + { + extensionName = extensionName ?? string.Empty; + return (string.IsNullOrEmpty(extensionName) || extensionName[0] == FileExtensionDelimiter) ? file + extensionName : file + FileExtensionDelimiter + extensionName; + } + + public static bool IsWithFileExtension(this string file, string extensionName) + { + if (string.IsNullOrEmpty(file)) + { + throw new ArgumentNullException(nameof(file)); + } + + if (string.IsNullOrEmpty(extensionName)) + { + throw new ArgumentNullException(nameof(extensionName)); + } + + if (extensionName[0] != FileExtensionDelimiter) + { + extensionName = FileExtensionDelimiter + extensionName; + } + + return file.EndsWith(extensionName, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsSameFileExtension(this string actualExtension, string expectedExtension) + { + if (string.IsNullOrEmpty(actualExtension)) + { + throw new ArgumentNullException(nameof(actualExtension)); + } + + if (string.IsNullOrEmpty(expectedExtension)) + { + throw new ArgumentNullException(nameof(expectedExtension)); + } + + string actualExtensionWithoutDelimeter = actualExtension; + if (actualExtension[0] == FileExtensionDelimiter) + { + actualExtensionWithoutDelimeter = actualExtension.Substring(1); + } + + string expectedExtensionWithoutDelimeter = expectedExtension; + if (expectedExtension[0] == FileExtensionDelimiter) + { + expectedExtensionWithoutDelimeter = expectedExtension.Substring(1); + } + + bool isSame = true; + if (string.CompareOrdinal(actualExtensionWithoutDelimeter, expectedExtensionWithoutDelimeter) != 0) + { + isSame = false; + } + + return isSame; + } + + public static bool IsSupportedFileExtension(this string actualExtension, IEnumerable supportedExtensions, StringComparison stringComparison = StringComparison.CurrentCulture) + { + ArgumentNullException.ThrowIfNull(actualExtension); + ArgumentNullException.ThrowIfNull(supportedExtensions); + + string actualExtensionWithoutDelimeter = actualExtension.EnsureExtensionWithoutDelimiter(); + var supportedExtensionsWithoutDelimeter = supportedExtensions.Select(extension => extension.EnsureExtensionWithoutDelimiter()); + + return supportedExtensionsWithoutDelimeter.Any(extenstion => actualExtensionWithoutDelimeter.Equals(extenstion, stringComparison)); + } + + public static string CreateSearchPatternWithFileExtension(this string fileExtension) + { + if (string.IsNullOrEmpty(fileExtension)) + { + throw new ArgumentNullException(nameof(fileExtension)); + } + + return "*".AppendExtensionName(fileExtension); + } + + public static string RemoveFilePathExtension(this string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentNullException(nameof(filePath)); + } + + return Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + } + + public static string ChangeFilePathExtension(this string filePath, string newFileNameExtension) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentNullException(nameof(filePath)); + } + + return Path.GetFileNameWithoutExtension(filePath).AppendExtensionName(newFileNameExtension); + } + + public static bool HasFileExtension(this string fileName) + { + try + { + return !string.IsNullOrWhiteSpace(fileName) && !string.IsNullOrWhiteSpace(fileName.GetFileExtension()); + } + catch (ArgumentException) + { + return false; + } + } + + public static string GetFileExtension(this string fileName, bool withDelimiter = true) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + throw new ArgumentException("The file name is either an empty string, null or whitespace.", nameof(fileName)); + } + + try + { + var fileInfo = new FileInfo(fileName); + + if (!withDelimiter) + { + if (fileInfo.Extension.StartsWith(FileExtensionDelimiter.ToString(), StringComparison.InvariantCultureIgnoreCase)) + { + return fileInfo.Extension.Substring(1); + } + } + + return fileInfo.Extension; + } + catch (ArgumentException) + { + return string.Empty; + } + } + + public static string GetLowerCaseFileNameExtensionWithoutDot(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + var extension = Path.GetExtension(fileName); + if (string.IsNullOrEmpty(extension)) + { + return extension; + } + + extension = extension.TrimStart('.'); +#pragma warning disable CA1308 // Normalize strings to uppercase + return extension.ToLowerInvariant(); +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + public static string GetFileNameExtensionFromCodec(string codec) + { + var fileExtension = FileNameExtensions.Waveform; + if (codec.IndexOf("riff", StringComparison.OrdinalIgnoreCase) >= 0) + { + fileExtension = FileNameExtensions.Waveform; + } + else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) >= 0) + { + fileExtension = FileNameExtensions.Mp3; + } + else if (codec.IndexOf("raw", StringComparison.OrdinalIgnoreCase) >= 0) + { + fileExtension = FileNameExtensions.RawWave; + } + else if (codec.IndexOf("json", StringComparison.OrdinalIgnoreCase) >= 0) + { + fileExtension = FileNameExtensions.JsonFile; + } + + return fileExtension; + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/StringExtensions.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/StringExtensions.cs new file mode 100644 index 00000000..2029aadc --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Extensions/StringExtensions.cs @@ -0,0 +1,49 @@ +namespace Microsoft.SpeechServices.CommonLib.Extensions; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +public static class StringExtensions +{ + public static string MaskSasToken(this string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + var sasRegexPattern = "(?sig=[\\w%]+)"; + var matches = Regex.Matches(str, sasRegexPattern); + foreach (Match match in matches) + { + str = str.Replace(match.Groups["signature"].Value, "SIGMASKED"); + } + + return str; + } + + public static IReadOnlyDictionary ToDictionaryWithDelimeter(this string value) + { + var headers = new Dictionary(); + if (!string.IsNullOrEmpty(value)) + { + var headerPairs = value.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var headerPair in headerPairs.Where(x => !string.IsNullOrEmpty(x))) + { + var delimeterIndex = headerPair.IndexOf('='); + if (delimeterIndex < 0) + { + throw new InvalidDataException($"Invalid argument format: {value}"); + } + + headers[headerPair.Substring(0, delimeterIndex)] = headerPair.Substring(delimeterIndex + 1); + } + } + + return headers; + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/HttpClientBase.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/HttpClientBase.cs new file mode 100644 index 00000000..1e10fc89 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/HttpClientBase.cs @@ -0,0 +1,249 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using Flurl; +using Flurl.Http; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CustomVoice.TtsLib.TtsUtil; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using Newtonsoft.Json; +using Polly; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +public abstract class HttpClientBase +{ + protected const string WatermarkDetectionsControllerName = "WatermarkDetections"; + protected const string Version20230401Preview = "2023-04-01-preview"; + private static string apiVersion = string.Empty; + + public HttpClientBase(DeploymentEnvironment environment, string subKey) + { + this.Environment = environment; + this.SubscriptionKey = subKey; + } + + public static string ApiVersion + { + get + { + if (!string.IsNullOrEmpty(apiVersion)) + { + return apiVersion; + } + + return Version20230401Preview; + } + + set + { + apiVersion = value; + } + } + + public virtual string RouteBase => "texttospeech"; + + public abstract string ControllerName { get; } + + public DeploymentEnvironment Environment { get; set; } + + public string SubscriptionKey { get; set; } + + public Uri BaseUrl + { + get + { + return EnvironmentMetadatas.DcMetadata.GetApiBaseUrl(this.Environment); + } + } + + public async Task DeleteByIdAsync( + Guid id, + IReadOnlyDictionary queryParams = null) + { + var url = this.BuildRequestBase(); + + url = url.AppendPathSegment(id); + + if (queryParams != null) + { + foreach (var (name, value) in queryParams) + { + url = url.SetQueryParam(name, value); + } + } + + return await this.RequestWithRetryAsync(async () => + { + return await url + .DeleteAsync() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task QueryByIdResponseStringAsync( + Guid id, + IReadOnlyDictionary additionalHeaders = null) + { + var url = this.BuildRequestBase(additionalHeaders) + .AppendPathSegment(id.ToString()); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .GetAsync() + .ReceiveString() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + protected async Task QueryByIdAsync( + Guid id, + IReadOnlyDictionary additionalHeaders = null) + { + var url = this.BuildRequestBase(additionalHeaders) + .AppendPathSegment(id.ToString()); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + protected IFlurlRequest BuildRequestBase(IReadOnlyDictionary additionalHeaders = null) + { + var url = this.BaseUrl + .AppendPathSegment("api") + .AppendPathSegment(RouteBase) + .AppendPathSegment(this.ControllerName) + .SetQueryParam("api-version", ApiVersion) + .WithHeader("Ocp-Apim-Subscription-Key", this.SubscriptionKey); + if (additionalHeaders != null) + { + foreach (var additionalHeader in additionalHeaders) + { + url.WithHeader(additionalHeader.Key, additionalHeader.Value); + } + } + + return url; + } + + public async Task QueryTaskByIdUntilTerminatedAsync( + Guid id, + IReadOnlyDictionary additionalHeaders = null, + bool printFirstQueryResult = false, + TimeSpan? timeout = null) + where T : StatefulResourceBase + { + var startTime = DateTime.Now; + OneApiState? state = null; + var firstTimePrinted = false; + + while (DateTime.Now - startTime < (timeout ?? TimeSpan.FromHours(3))) + { + var translation = await this.QueryByIdAsync(id, additionalHeaders).ConfigureAwait(false); + if (translation == null) + { + return null; + } + + var runPrinted = false; + if (printFirstQueryResult && !firstTimePrinted) + { + runPrinted = true; + firstTimePrinted = true; + ConsoleMaskSasHelper.WriteLineMaskSas(JsonConvert.SerializeObject( + translation, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + } + + if (new[] { OneApiState.Failed, OneApiState.Succeeded }.Contains(translation.Status)) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine($"Task completed with state: {translation.Status.AsString()}"); + if (!runPrinted) + { + ConsoleMaskSasHelper.WriteLineMaskSas(JsonConvert.SerializeObject( + translation, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + } + + return translation; + } + else + { + await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); + if (state == null || state != translation.Status) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine($"Task {translation.Status.AsString()}:"); + state = translation.Status; + } + else + { + Console.Write("."); + } + } + } + + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine($"Task run timeout after {(DateTime.Now - startTime).TotalMinutes.ToString("0.00", CultureInfo.InvariantCulture)} mins"); + return null; + } + + public async Task RequestWithRetryAsync(Func> requestAsyncFunc) + { + var policy = BuildRetryPolicy(); + + return await policy.ExecuteAsync(async () => + { + return await ExceptionHelper.PrintHandleExceptionAsync(async () => + { + return await requestAsyncFunc().ConfigureAwait(false); + }); + }); + } + + public static Polly.Retry.AsyncRetryPolicy BuildRetryPolicy() + { + var retryPolicy = Policy + .Handle(IsTransientError) + .WaitAndRetryAsync(5, retryAttempt => + { + var nextAttemptIn = TimeSpan.FromSeconds(5 * Math.Pow(2, retryAttempt)); + Console.WriteLine($"Retry attempt {retryAttempt} to make request. Next try on {nextAttemptIn.TotalSeconds} seconds."); + return nextAttemptIn; + }); + + return retryPolicy; + } + + protected static bool IsTransientError(FlurlHttpException exception) + { + int[] httpStatusCodesWorthRetrying = + { + (int)HttpStatusCode.RequestTimeout, // 408 + (int)HttpStatusCode.BadGateway, // 502 + (int)HttpStatusCode.ServiceUnavailable, // 503 + (int)HttpStatusCode.GatewayTimeout, // 504 + (int)HttpStatusCode.TooManyRequests, // 429 + }; + + Console.WriteLine($"Flurl exception status code: {exception.StatusCode}"); + return exception.StatusCode.HasValue && httpStatusCodesWorthRetrying.Contains(exception.StatusCode.Value); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Readme.txt b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Readme.txt new file mode 100644 index 00000000..c6f9e617 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Readme.txt @@ -0,0 +1,2 @@ +Nuget: + 1. Not upgrade Flurl to 4.0 due to 4.0 doesn't support NewtonJson for ReceiveJson. \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ConsoleMaskSasHelper.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ConsoleMaskSasHelper.cs new file mode 100644 index 00000000..a93e714e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ConsoleMaskSasHelper.cs @@ -0,0 +1,25 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using Microsoft.SpeechServices.CommonLib.Extensions; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +public static class ConsoleMaskSasHelper +{ + public static bool ShowSas { get; set; } + + static ConsoleMaskSasHelper() + { + ShowSas = false; + } + + public static void WriteLineMaskSas(string message) + { + Console.WriteLine(ShowSas ? message : message.MaskSasToken()); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnumExtensions.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnumExtensions.cs new file mode 100644 index 00000000..31cfd792 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnumExtensions.cs @@ -0,0 +1,53 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using System; +using System.Linq; +using System.Runtime.Serialization; + +public static class EnumExtensions +{ + public static string AsString(this TEnum enumValue) + where TEnum : Enum + { + if (typeof(TEnum).GetCustomAttributes(typeof(FlagsAttribute), false).Any()) + { + return enumValue.ToString(); + } + + var enumMemberName = Enum.GetName(typeof(TEnum), enumValue); + + var enumMember = typeof(TEnum).GetMember(enumMemberName).Single(); + var jsonPropertyAttribute = enumMember + .GetCustomAttributes(typeof(DataMemberAttribute), true) + .Cast() + .SingleOrDefault(); + + if (jsonPropertyAttribute != null) + { + return jsonPropertyAttribute.Name; + } + + return enumMemberName; + } + + public static TEnum AsEnumValue(this string value) + { + return value.AsEnumValue(false); + } + + public static TEnum AsEnumValue(this string value, bool ignoreCase) + { + var enumMembers = typeof(TEnum).GetMembers(); + var membersAndAttributes = enumMembers + .Select(m => (member: m, attribute: m.GetCustomAttributes(typeof(DataMemberAttribute), true).Cast().SingleOrDefault())) + .Where(m => m.attribute != null) + .Where(m => m.attribute.Name == value); + + if (membersAndAttributes.Any()) + { + value = membersAndAttributes.Single().member.Name; + } + + return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase); + } +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnvironmentMetadatas.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnvironmentMetadatas.cs new file mode 100644 index 00000000..a68b8a6e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/EnvironmentMetadatas.cs @@ -0,0 +1,351 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using Microsoft.SpeechServices.CommonLib.Enums; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +public class EnvironmentMetadatas +{ + public static ReadOnlyDictionary DcMetadatas => new ReadOnlyDictionary(new Dictionary() + { + { + DeploymentEnvironment.Local, + new DcMetadata() + { + Environment = DeploymentEnvironment.Local, + ApiHostName = "localhost", + ApiPort = 44311, + } + }, + { + DeploymentEnvironment.Develop, + new DcMetadata() + { + Environment = DeploymentEnvironment.Develop, + ApiHostName = "develop.customvoice.api.speech-test.microsoft.com", + } + }, + { + DeploymentEnvironment.DevelopEUS, + new DcMetadata() + { + Environment = DeploymentEnvironment.DevelopEUS, + ApiHostName = "developeus.customvoice.api.speech-test.microsoft.com", + } + }, + { + DeploymentEnvironment.CanaryUSCX, + new DcMetadata() + { + RegionIdentifier = "centraluseuap", + Environment = DeploymentEnvironment.CanaryUSCX, + } + }, + { + DeploymentEnvironment.ProductionAUE, + new DcMetadata() + { + RegionIdentifier = "australiaeast", + Environment = DeploymentEnvironment.ProductionAUE, + } + }, + { + DeploymentEnvironment.ProductionBRS, + new DcMetadata() + { + RegionIdentifier = "brazilsouth", + Environment = DeploymentEnvironment.ProductionBRS, + } + }, + { + DeploymentEnvironment.ProductionCAC, + new DcMetadata() + { + RegionIdentifier = "canadacentral", + Environment = DeploymentEnvironment.ProductionCAC, + } + }, + { + DeploymentEnvironment.ProductionEA, + new DcMetadata() + { + RegionIdentifier = "eastasia", + Environment = DeploymentEnvironment.ProductionEA, + } + }, + { + DeploymentEnvironment.ProductionEUS, + new DcMetadata() + { + RegionIdentifier = "eastus", + Environment = DeploymentEnvironment.ProductionEUS, + } + }, + { + DeploymentEnvironment.ProductionEUS2, + new DcMetadata() + { + RegionIdentifier = "eastus2", + Environment = DeploymentEnvironment.ProductionEUS2, + } + }, + { + DeploymentEnvironment.ProductionFC, + new DcMetadata() + { + RegionIdentifier = "francecentral", + Environment = DeploymentEnvironment.ProductionFC, + } + }, + { + DeploymentEnvironment.ProductionGWC, + new DcMetadata() + { + RegionIdentifier = "germanywestcentral", + Environment = DeploymentEnvironment.ProductionGWC, + } + }, + { + DeploymentEnvironment.ProductionINC, + new DcMetadata() + { + RegionIdentifier = "centralindia", + Environment = DeploymentEnvironment.ProductionINC, + } + }, + { + DeploymentEnvironment.ProductionJINW, + new DcMetadata() + { + RegionIdentifier = "jioindiawest", + Environment = DeploymentEnvironment.ProductionJINW, + } + }, + { + DeploymentEnvironment.ProductionJPE, + new DcMetadata() + { + RegionIdentifier = "japaneast", + Environment = DeploymentEnvironment.ProductionJPE, + } + }, + { + DeploymentEnvironment.ProductionJPW, + new DcMetadata() + { + RegionIdentifier = "japanwest", + Environment = DeploymentEnvironment.ProductionJPW, + } + }, + { + DeploymentEnvironment.ProductionKC, + new DcMetadata() + { + RegionIdentifier = "koreacentral", + Environment = DeploymentEnvironment.ProductionKC, + } + }, + { + DeploymentEnvironment.ProductionNEU, + new DcMetadata() + { + RegionIdentifier = "northeurope", + Environment = DeploymentEnvironment.ProductionNEU, + } + }, + { + DeploymentEnvironment.ProductionNOE, + new DcMetadata() + { + RegionIdentifier = "norwayeast", + Environment = DeploymentEnvironment.ProductionNOE, + } + }, + { + DeploymentEnvironment.ProductionQAC, + new DcMetadata() + { + RegionIdentifier = "qatarcentral", + Environment = DeploymentEnvironment.ProductionQAC, + } + }, + { + DeploymentEnvironment.ProductionUAEN, + new DcMetadata() + { + RegionIdentifier = "uaenorth", + Environment = DeploymentEnvironment.ProductionUAEN, + } + }, + { + DeploymentEnvironment.ProductionUSW3, + new DcMetadata() + { + RegionIdentifier = "westus3", + Environment = DeploymentEnvironment.ProductionUSW3, + } + }, + { + DeploymentEnvironment.ProductionSAN, + new DcMetadata() + { + RegionIdentifier = "southafricanorth", + Environment = DeploymentEnvironment.ProductionSAN, + } + }, + { + DeploymentEnvironment.ProductionSEA, + new DcMetadata() + { + RegionIdentifier = "southeastasia", + Environment = DeploymentEnvironment.ProductionSEA, + } + }, + { + DeploymentEnvironment.ProductionSWN, + new DcMetadata() + { + RegionIdentifier = "switzerlandnorth", + Environment = DeploymentEnvironment.ProductionSWN, + } + }, + { + DeploymentEnvironment.ProductionSWW, + new DcMetadata() + { + RegionIdentifier = "switzerlandwest", + Environment = DeploymentEnvironment.ProductionSWW, + } + }, + { + DeploymentEnvironment.ProductionUKS, + new DcMetadata() + { + RegionIdentifier = "uksouth", + Environment = DeploymentEnvironment.ProductionUKS, + } + }, + { + DeploymentEnvironment.ProductionUSC, + new DcMetadata() + { + RegionIdentifier = "centralus", + Environment = DeploymentEnvironment.ProductionUSC, + } + }, + { + DeploymentEnvironment.ProductionUSNC, + new DcMetadata() + { + RegionIdentifier = "northcentralus", + Environment = DeploymentEnvironment.ProductionUSNC, + } + }, + { + DeploymentEnvironment.ProductionUSSC, + new DcMetadata() + { + RegionIdentifier = "southcentralus", + Environment = DeploymentEnvironment.ProductionUSSC, + } + }, + { + DeploymentEnvironment.ProductionUSW, + new DcMetadata() + { + RegionIdentifier = "westus", + Environment = DeploymentEnvironment.ProductionUSW, + } + }, + { + DeploymentEnvironment.ProductionUSWC, + new DcMetadata() + { + RegionIdentifier = "westcentralus", + Environment = DeploymentEnvironment.ProductionUSWC, + } + }, + { + DeploymentEnvironment.ProductionWEU, + new DcMetadata() + { + RegionIdentifier = "westeurope", + Environment = DeploymentEnvironment.ProductionWEU, + } + }, + { + DeploymentEnvironment.ProductionWUS2, + new DcMetadata() + { + RegionIdentifier = "westus2", + Environment = DeploymentEnvironment.ProductionWUS2, + } + }, + { + DeploymentEnvironment.MooncakeChinaEast2, + new DcMetadata() + { + Environment = DeploymentEnvironment.MooncakeChinaEast2, + RegionIdentifier = "chinaeast2", + } + } + }); + + public class DcMetadata + { + private string apiHostName = null; + + public DeploymentEnvironment Environment { get; set; } + + public string PortalAddress + { + get + { + var address = ApiHostName; + if (!string.IsNullOrEmpty(address) && ApiPort != null) + { + address = $"{address}:{ApiPort.Value}"; + } + + return address; + } + } + + public int? ApiPort { get; set; } + + public string RegionIdentifier { get; set; } + + public string ApiHostName + { + get + { + if (!string.IsNullOrEmpty(apiHostName)) + { + return apiHostName; + } + else if (!string.IsNullOrEmpty(RegionIdentifier)) + { + return $"{RegionIdentifier}.customvoice.api.speech.microsoft.com"; + } + + return string.Empty; + } + set + { + apiHostName = value; + } + } + + public static Uri GetApiBaseUrl(DeploymentEnvironment environment) + { + Uri url = null; + if (!string.IsNullOrEmpty(DcMetadatas[environment].PortalAddress)) + { + url = new Uri($"https://{DcMetadatas[environment].PortalAddress}/"); + } + + return url; + } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ExceptionHelper.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ExceptionHelper.cs new file mode 100644 index 00000000..0a6b25af --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/ExceptionHelper.cs @@ -0,0 +1,228 @@ +namespace Microsoft.SpeechServices.CustomVoice.TtsLib.TtsUtil; + +using Flurl.Http; +using System; +using System.Text; +using System.Threading.Tasks; + +public class ExceptionHelper +{ + public static async Task PrintHandleExceptionAsync(Func> requestAsyncFunc) + { + ArgumentNullException.ThrowIfNull(requestAsyncFunc); + + try + { + return await requestAsyncFunc().ConfigureAwait(false); + } + catch (FlurlHttpTimeoutException e) + { + Console.WriteLine($"Timeout with error: {e.Message}"); + throw; + } + catch (FlurlHttpException ex) + { + Console.WriteLine($"{nameof(FlurlHttpException)}: {await ex.GetResponseStringAsync()}"); + throw; + } + catch (Exception ex) + { + Console.WriteLine($"{ex.GetType().Name}: {ex.Message}"); + throw; + } + } + + public static string BuildExceptionMessage(Exception exception) + { + return BuildExceptionMessage(exception, false); + } + + public static async Task<(bool success, string error, T result)> HasRunWithoutExceptionAsync(Func> func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + var result = await func().ConfigureAwait(false); + return (true, null, result); + } + catch (Exception e) + { + return (false, $"Failed to run function with exception: {BuildExceptionMessage(e, isAppendStackTrace)}", default(T)); + } + } + + public static async Task<(bool success, string error)> HasRunWithoutExceptionAsync(Func func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + await func().ConfigureAwait(false); + return (true, null); + } + catch (Exception e) + { + return (false, $"Failed to run function with exception: {BuildExceptionMessage(e, isAppendStackTrace)}"); + } + } + + public static string BuildExceptionMessage(Exception exception, bool isAppendStackTrace) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + var messageBuilder = new StringBuilder(); + for (Exception current = exception; current != null; current = current.InnerException) + { + if (current.InnerException != null) + { + messageBuilder.AppendLine(current.Message); + } + else + { + messageBuilder.Append(current.Message); + } + } + + if (isAppendStackTrace) + { + messageBuilder.Append(exception.StackTrace); + } + + return messageBuilder.ToString(); + } + +#pragma warning disable CA1031 + public static (bool success, string error) HasRunWithoutException(Func<(bool success, string error)> func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + return func(); + } + catch (Exception e) + { + return (false, BuildExceptionMessage(e, isAppendStackTrace)); + } + } + + public static (bool success, string error) HasRunWithoutException(Action action, bool isAppendStackTrace = false) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + action(); + return (true, null); + } + catch (Exception e) + { + return (false, BuildExceptionMessage(e, isAppendStackTrace)); + } + } + + public static (bool success, string error) HasRunWithoutExceptoin(Func<(bool success, string error)> func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + return func(); + } + catch (Exception e) + { + return (false, ExceptionHelper.BuildExceptionMessage(e, isAppendStackTrace)); + } + } + + public static (bool success, string error) HasRunWithoutExceptoin(Action action, bool isAppendStackTrace = false) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + action(); + return (true, null); + } + catch (Exception e) + { + return (false, ExceptionHelper.BuildExceptionMessage(e, isAppendStackTrace)); + } + } + + public static async Task<(bool success, string error)> HasRunWithoutExceptoinAsync(Func func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + await func().ConfigureAwait(false); + return (true, null); + } + catch (Exception e) + { + return (false, $"Failed to run function with exception: {BuildExceptionMessage(e, isAppendStackTrace)}"); + } + } + + public static async Task<(bool success, string error, T result)> HasRunWithoutExceptoinAsync(Func> func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + var result = await func().ConfigureAwait(false); + return (true, null, result); + } + catch (Exception e) + { + return (false, $"Failed to run function with exception: {ExceptionHelper.BuildExceptionMessage(e, isAppendStackTrace)}", default(T)); + } + } + + public static (bool success, string error, T result) HasRunWithoutExceptoin(Func func, bool isAppendStackTrace = false) + { + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } + + try + { + var result = func(); + return (true, null, result); + } + catch (Exception e) + { + return (false, $"Failed to run function with exception: {ExceptionHelper.BuildExceptionMessage(e, isAppendStackTrace)}", default(T)); + } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/Sha256Helper.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/Sha256Helper.cs new file mode 100644 index 00000000..39e855c3 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/Sha256Helper.cs @@ -0,0 +1,61 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using Microsoft.SpeechServices.Common; +using System; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +public static class Sha256Helper +{ + public static string GetSha256FromFile(string filename) + { + if (string.IsNullOrEmpty(filename)) + { + throw new ArgumentNullException(nameof(filename)); + } + + if (!File.Exists(filename)) + { + throw new FileNotFoundException(filename); + } + + using (var md5Hash = SHA256.Create()) + using (FileStream stream = File.OpenRead(filename)) + { + byte[] data = md5Hash.ComputeHash(stream); + var sb = new StringBuilder(); + + for (int i = 0; i < data.Length; i++) + { + sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture)); + } + + return sb.ToString(); + } + } + + public static string GetSha256WithExtensionFromFile(string filename) + { + return $"{GetSha256FromFile(filename).AppendExtensionName(Path.GetExtension(filename))}"; + } + + public static string GetSha256FromString(string value) + { + value = value ?? string.Empty; + using (var md5Hash = SHA256.Create()) + { + byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(value)); + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < data.Length; i++) + { + sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture)); + } + + return sb.ToString(); + } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/TaskNameHelper.cs b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/TaskNameHelper.cs new file mode 100644 index 00000000..57320351 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/Common/CommonLib/Util/TaskNameHelper.cs @@ -0,0 +1,56 @@ +namespace Microsoft.SpeechServices.CommonLib.Util; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +public static class TaskNameHelper +{ + public const int NameMaxCharLength = 256; + public const string IncompleteFileNamePrefix = "..."; + + public static string BuildAutoGeneratedFileName(IEnumerable fileNames) + { + ArgumentNullException.ThrowIfNull(fileNames); + + var fileName = string.Empty; + if (!fileNames.Any()) + { + fileName = "No file selected."; + } + else if (fileNames.Count() == 1) + { + fileName = $"{fileNames.First()}"; + } + else + { + fileName = TrimFileNameToDisplayText( + $"{fileNames.Count()} files: {string.Join(",", fileNames)}", + NameMaxCharLength); + } + + return fileName; + } + + public static string TrimFileNameToDisplayText(string fullFileName, int maxFileNameCharCount) + { + if (string.IsNullOrEmpty(fullFileName)) + { + return fullFileName; + } + + var sb = new StringBuilder(); + if (fullFileName.Length > maxFileNameCharCount && fullFileName.Length > IncompleteFileNamePrefix.Length) + { + sb.Append(fullFileName.Substring(fullFileName.Length - IncompleteFileNamePrefix.Length)); + sb.Append(IncompleteFileNamePrefix); + } + else + { + sb.Append(fullFileName); + } + + return sb.ToString(); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Arguments.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Arguments.cs new file mode 100644 index 00000000..99268151 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Arguments.cs @@ -0,0 +1,331 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.CommandParser; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CommonLib.Extensions; +using Microsoft.SpeechServices.VideoTranslation.Enums; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; + +[Comment("VideoTranslation tool.")] +public class Arguments +{ + [Argument( + "mode", + Description = "Specifies the execute modes.", + Optional = false, + UsagePlaceholder = "mode", + RequiredModes = "QueryMetadata,UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,QueryVideoOrAudioFiles,QueryTranslations,QueryVideoOrAudioFile,QueryTranslation,DeleteVideoOrAudioFile,DeleteTranslation,QueryTargetLocales,QueryTargetLocale,UpdateTargetLocaleEdittingWebvttFile,DeleteTargetLocale")] + private string modeString = string.Empty; + + [Argument( + "apiVersion", + Description = "Specifies the api version.", + Optional = true, + UsagePlaceholder = "apiVersion", + OptionalModes = "QueryMetadata,UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,QueryVideoOrAudioFiles,QueryTranslations,QueryVideoOrAudioFile,QueryTranslation,DeleteVideoOrAudioFile,DeleteTranslation,QueryTargetLocales,QueryTargetLocale,DeleteTargetLocale")] + private string apiVersion = string.Empty; + + [Argument( + "environment", + Description = "Specifies the environment: ProductionEUS/CanaryUSCX/Develop/Local", + Optional = false, + UsagePlaceholder = "ProductionEUS/CanaryUSCX/Develop/Local", + RequiredModes = "QueryMetadata,UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,QueryVideoOrAudioFiles,QueryVideoOrAudioFile,QueryTranslations,QueryTranslation,DeleteVideoOrAudioFile,DeleteTranslation,QueryTargetLocales,QueryTargetLocale,UpdateTargetLocaleEdittingWebvttFile,DeleteTargetLocale")] + private string environmentString = string.Empty; + + [Argument( + "subscriptionKey", + Description = "Specifies speech subscription key.", + Optional = false, + UsagePlaceholder = "subscriptionKey", + RequiredModes = "QueryMetadata,UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,QueryVideoOrAudioFiles,QueryVideoOrAudioFile,QueryTranslations,QueryTranslation,DeleteVideoOrAudioFile,DeleteTranslation,QueryTargetLocales,QueryTargetLocale,UpdateTargetLocaleEdittingWebvttFile,DeleteTargetLocale")] + private string subscriptionKey = string.Empty; + + [Argument( + "id", + Description = "Specifies the Guid format id", + Optional = true, + UsagePlaceholder = "id", + RequiredModes = "QueryVideoOrAudioFile,QueryTranslation,DeleteVideoOrAudioFile,DeleteTranslation")] + private Guid id = Guid.Empty; + + [Argument( + "sourceLocale", + Description = "Specifies the source locale: zh-CN/en-US/ru-RU/es-ES/pl-PL", + Optional = true, + UsagePlaceholder = "zh-CN/en-US/ru-RU/es-ES/pl-PL", + RequiredModes = "UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation")] + private string sourceLocaleString = string.Empty; + + [Argument( + "targetLocale", + Description = "Specifies the target locale: zh-CN/en-US/ru-RU/es-ES/pl-PL", + Optional = true, + UsagePlaceholder = "zh-CN/en-US/ru-RU/es-ES/pl-PL", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,UpdateTargetLocaleEdittingWebvttFile,DeleteTargetLocale,QueryTargetLocale")] + private string targetLocaleString = string.Empty; + + [Argument( + "targetLocales", + Description = "Specifies the target locales, for example: zh-CN,en-US,ru-RU,es-ES,pl-PL", + Optional = true, + UsagePlaceholder = "zh-CN,en-US,ru-RU,es-ES,pl-PL", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string targetLocalesString = string.Empty; + + [Argument( + "videoOrAudioFileId", + Description = "Specifies video or audio file ID.", + Optional = true, + UsagePlaceholder = "videoOrAudioFileId", + RequiredModes = "CreateTranslation,UpdateTargetLocaleEdittingWebvttFile,DeleteTargetLocale,QueryTargetLocale")] + private Guid videoOrAudioFileId = Guid.Empty; + + [Argument( + "sourceVideoOrAudioFilePath", + Description = "Specifies path of source video or audio file.", + Optional = true, + UsagePlaceholder = "sourceVideoOrAudioFilePath", + RequiredModes = "UploadVideoOrAudioFile,UploadVideoOrAudioFileIfNotExist,UploadVideoOrAudioFileAndCreateTranslation")] + private string sourceVideoOrAudioFilePath = string.Empty; + + [Argument( + "webvttSourceKind", + Description = "Specifies webvtt source kind: FileUpload(default)/TargetLocale .", + Optional = true, + UsagePlaceholder = "sourceLocaleWebvttFilePath", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string webvttSourceKind = string.Empty; + + [Argument( + "sourceLocaleWebvttFilePath", + Description = "Specifies file path of source locale webvtt.", + Optional = true, + UsagePlaceholder = "sourceLocaleWebvttFilePath", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string sourceLocaleWebvttFilePath = string.Empty; + + [Argument( + "targetLocaleWebvttFilePath", + Description = "Specifies file path of source locale webvtt.", + Optional = true, + UsagePlaceholder = "targetLocaleWebvttFilePath", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,UpdateTargetLocaleEdittingWebvttFile")] + private string targetLocaleWebvttFilePath = string.Empty; + + [Argument( + "voiceKind", + Description = "Specifies TTS synthesis voice kind.", + Optional = true, + UsagePlaceholder = "PlatformVoice/PersonalVoice", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string voiceKindString = string.Empty; + + [Argument( + "deleteAssociations", + Description = "Delete the video file and its associated translations.", + Optional = true, + OptionalModes = "DeleteVideoOrAudioFile,DeleteTargetLocale")] + private bool deleteAssociations = false; + + [Argument( + "reuseExistingVideoOrAudioFile", + Description = "Whether reuse existing audio.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation")] + private bool reuseExistingVideoOrAudioFile = false; + + [Argument( + "withoutSubtitleInTranslatedVideoFile", + Description = "Whether without subtitle in translated video file.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private bool withoutSubtitleInTranslatedVideoFile = false; + + [Argument( + "subtitleMaxCharCountPerSegment", + Description = "Subtitle max char per segment.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private int subtitleMaxCharCountPerSegment = 0; + + [Argument( + "exportPersonalVoicePromptAudioMetadata", + Description = "Whether export personal voice prompt audio metadata.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private bool exportPersonalVoicePromptAudioMetadata = false; + + [Argument( + "personalVoiceModelName", + Description = "Personal voice model name.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string personalVoiceModelName = string.Empty; + + [Argument( + "isAssociatedWithTargetLocale", + Description = "is associated with target locale.", + Optional = true, + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + public bool isAssociatedWithTargetLocale = false; + + [Argument( + "additionalHttpHeaders", + Description = "Specifies additional http headers.", + Optional = false, + UsagePlaceholder = "name1=value1,name2=value2", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,QueryTranslation,")] + private string additionalHttpHeaders = string.Empty; + + [Argument( + "enableFeatures", + Description = "Specifies feature list to be enabled, supported features: GptTextReformulation", + Optional = false, + UsagePlaceholder = "GptTextReformulation,[Others]", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string enableFeatures = string.Empty; + + [Argument( + "profileName", + Description = "Specifies profile to use.", + Optional = false, + UsagePlaceholder = "GptTextReformulation,[Others]", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation")] + private string profileName = string.Empty; + + [Argument( + "createTranslationAdditionalProperties", + Description = "Specifies additional properties when create translation.", + Optional = false, + UsagePlaceholder = "name1=value1,name2=value2", + OptionalModes = "UploadVideoOrAudioFileAndCreateTranslation,CreateTranslation,")] + private string createTranslationAdditionalProperties = string.Empty; + + public string EnableFeatures => this.enableFeatures; + + public string ProfileName => this.profileName; + + public string ApiVersion => this.apiVersion; + + public bool DeleteAssociations => this.deleteAssociations; + + public bool ReuseExistingVideoOrAudioFile => this.reuseExistingVideoOrAudioFile; + + public string SourceVideoOrAudioFilePath => this.sourceVideoOrAudioFilePath; + + public VideoTranslationWebvttSourceKind? TypedWebvttSourceKind => + string.IsNullOrWhiteSpace(this.webvttSourceKind) ? null : + Enum.Parse(this.webvttSourceKind); + + public string SpeechSubscriptionKey => this.subscriptionKey; + + public string SourceLocaleWebvttFilePath => this.sourceLocaleWebvttFilePath; + + public string TargetLocaleWebvttFilePath => this.targetLocaleWebvttFilePath; + + public bool WithoutSubtitleInTranslatedVideoFile => this.withoutSubtitleInTranslatedVideoFile; + + public int? SubtitleMaxCharCountPerSegment => this.subtitleMaxCharCountPerSegment; + + public bool ExportPersonalVoicePromptAudioMetadata => this.exportPersonalVoicePromptAudioMetadata; + + public string PersonalVoiceModelName => this.personalVoiceModelName; + + public bool IsAssociatedWithTargetLocale => this.isAssociatedWithTargetLocale; + + public Guid Id => this.id; + + public Guid VideoOrAudioFileId => this.videoOrAudioFileId; + + public CultureInfo TypedSourceLocale + { + get + { + if (string.IsNullOrEmpty(this.sourceLocaleString)) + { + return null; + } + + return CultureInfo.CreateSpecificCulture(this.sourceLocaleString); + } + } + + public IReadOnlyDictionary TypedAdditionalHttpHeaders => + this.additionalHttpHeaders.ToDictionaryWithDelimeter(); + + public IReadOnlyDictionary TypedCreateTranslationAdditionalProperties => + this.createTranslationAdditionalProperties.ToDictionaryWithDelimeter(); + + public IEnumerable TypedTargetLocales + { + get + { + var targetLocales = new List(); + if (!string.IsNullOrEmpty(this.targetLocalesString)) + { + foreach (var targetLocaleString in this.targetLocalesString.Split(",")) + { + targetLocales.Add(CultureInfo.CreateSpecificCulture(targetLocaleString)); + } + } + else if (!string.IsNullOrEmpty(this.targetLocaleString)) + { + targetLocales.Add(CultureInfo.CreateSpecificCulture(targetLocaleString)); + } + + return targetLocales; + } + } + + public VideoTranslationVoiceKind? VoiceKind + { + get + { + if (!string.IsNullOrEmpty(this.voiceKindString)) + { + if (Enum.TryParse(this.voiceKindString, true, out var voiceKind)) + { + return voiceKind; + } + else + { + throw new NotSupportedException(this.voiceKindString); + } + } + + return null; + } + } + + public DeploymentEnvironment Environment + { + get + { + if (!Enum.TryParse(this.environmentString, true, out var env)) + { + throw new ArgumentException($"Invalid environment arguments."); + } + + return env; + } + } + + public Mode Mode + { + get + { + if (!Enum.TryParse(this.modeString, true, out var mode)) + { + throw new ArgumentException($"Invalid mode arguments."); + } + + return mode; + } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Mode.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Mode.cs new file mode 100644 index 00000000..94e326fd --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Mode.cs @@ -0,0 +1,36 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +public enum Mode +{ + None = 0, + + QueryMetadata, + + UploadVideoOrAudioFile, + + UploadVideoOrAudioFileIfNotExist, + + UploadVideoOrAudioFileAndCreateTranslation, + + CreateTranslation, + + DeleteVideoOrAudioFile, + + QueryVideoOrAudioFile, + + QueryVideoOrAudioFiles, + + DeleteTranslation, + + QueryTranslation, + + QueryTranslations, + + QueryTargetLocales, + + QueryTargetLocale, + + UpdateTargetLocaleEdittingWebvttFile, + + DeleteTargetLocale, +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Program.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Program.cs new file mode 100644 index 00000000..0399ce5f --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/Program.cs @@ -0,0 +1,401 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using Microsoft.SpeechServices.CommonLib; +using Microsoft.SpeechServices.CommonLib.CommandParser; +using Microsoft.SpeechServices.CommonLib.Util; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using Microsoft.SpeechServices.VideoTranslation.Enums; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +public class Program +{ + static async Task Main(string[] args) + { + ConsoleMaskSasHelper.ShowSas = true; + return await ConsoleApp.RunAsync( + args, + ProcessAsync).ConfigureAwait(false); + } + + public static async Task ProcessAsync(Arguments args) + where TVideoFileMetadata : VideoFileMetadata + { + try + { + if (!VideoTranslationConstant.SupportedEnvironments.Contains(args.Environment)) + { + throw new NotSupportedException(args.Environment.AsString()); + } + + if (!string.IsNullOrEmpty(args.ApiVersion)) + { + HttpClientBase.ApiVersion = args.ApiVersion; + } + + var translationClient = new VideoTranslationClient(args.Environment, args.SpeechSubscriptionKey); + var fileClient = new VideoFileClient(args.Environment, args.SpeechSubscriptionKey); + var metadataClient = new VideoTranslationMetadataClient(args.Environment, args.SpeechSubscriptionKey); + var targetLocaleClient = new TargetLocaleClient(args.Environment, args.SpeechSubscriptionKey); + + switch (args.Mode) + { + case Mode.QueryMetadata: + { + var metadata = await metadataClient.QueryMetadataAsync().ConfigureAwait(false); + + Console.WriteLine(JsonConvert.SerializeObject( + metadata, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.QueryTargetLocales: + { + var targetLocales = await targetLocaleClient.QueryTargetLocalesAsync().ConfigureAwait(false); + + Console.WriteLine(JsonConvert.SerializeObject( + targetLocales, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.QueryTargetLocale: + { + var targetLocale = await fileClient.QueryTargetLocaleAsync( + args.VideoOrAudioFileId, + args.TypedTargetLocales.First()).ConfigureAwait(false); + + Console.WriteLine(JsonConvert.SerializeObject( + targetLocale, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.UpdateTargetLocaleEdittingWebvttFile: + { + var targetLocale = await targetLocaleClient.UpdateTargetLocaleEdittingWebvttFileAsync( + videoFileId: args.VideoOrAudioFileId, + targetLocale: args.TypedTargetLocales.First(), + kind: !string.IsNullOrEmpty(args.SourceLocaleWebvttFilePath) ? VideoTranslationWebVttFilePlainTextKind.SourceLocalePlainText : null, + webvttFilePath: !string.IsNullOrEmpty(args.SourceLocaleWebvttFilePath) ? + args.SourceLocaleWebvttFilePath : + args.TargetLocaleWebvttFilePath).ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + targetLocale, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.DeleteTargetLocale: + { + await fileClient.DeleteTargetLocaleAsync( + args.VideoOrAudioFileId, + args.TypedTargetLocales.First(), + args.DeleteAssociations).ConfigureAwait(false); + break; + } + + case Mode.UploadVideoOrAudioFile: + { + if (!File.Exists(args.SourceVideoOrAudioFilePath)) + { + throw new FileNotFoundException(args.SourceVideoOrAudioFilePath); + } + + Console.WriteLine($"Uploading file: {args.SourceVideoOrAudioFilePath}"); + var videoFile = await fileClient.UploadVideoFileAsync( + name: Path.GetFileName(args.SourceVideoOrAudioFilePath), + description: null, + locale: args.TypedSourceLocale, + speakerCount: null, + videoFilePath: args.SourceVideoOrAudioFilePath).ConfigureAwait(false); + + Console.WriteLine(JsonConvert.SerializeObject( + videoFile, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.UploadVideoOrAudioFileIfNotExist: + { + if (!File.Exists(args.SourceVideoOrAudioFilePath)) + { + throw new FileNotFoundException(args.SourceVideoOrAudioFilePath); + } + + var fileContentSha256 = Sha256Helper.GetSha256WithExtensionFromFile(args.SourceVideoOrAudioFilePath); + var videoFile = await fileClient.QueryVideoFileWithLocaleAndFileContentSha256Async( + args.TypedSourceLocale, + fileContentSha256).ConfigureAwait(false); + if (videoFile == null) + { + Console.WriteLine($"Uploading file: {args.SourceVideoOrAudioFilePath}"); + videoFile = await fileClient.UploadVideoFileAsync( + name: Path.GetFileName(args.SourceVideoOrAudioFilePath), + description: null, + locale: args.TypedSourceLocale, + speakerCount: null, + videoFilePath: args.SourceVideoOrAudioFilePath).ConfigureAwait(false); + } + + Console.WriteLine(JsonConvert.SerializeObject( + videoFile, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.UploadVideoOrAudioFileAndCreateTranslation: + { + if (string.IsNullOrEmpty(args.SourceVideoOrAudioFilePath)) + { + throw new ArgumentException($"Please provide at least one of {nameof(args.VideoOrAudioFileId)} or {nameof(args.SourceVideoOrAudioFilePath)}"); + } + + if (args.TypedSourceLocale == null || string.IsNullOrEmpty(args.TypedSourceLocale.Name)) + { + throw new ArgumentNullException(nameof(args.TypedSourceLocale)); + } + + if (!File.Exists(args.SourceVideoOrAudioFilePath)) + { + throw new FileNotFoundException(args.SourceVideoOrAudioFilePath); + } + + VideoFileMetadata videoOrAudioFile = null; + if (args.ReuseExistingVideoOrAudioFile) + { + var fileContentSha256 = Sha256Helper.GetSha256WithExtensionFromFile(args.SourceVideoOrAudioFilePath); + videoOrAudioFile = await fileClient.QueryVideoFileWithLocaleAndFileContentSha256Async( + args.TypedSourceLocale, + fileContentSha256).ConfigureAwait(false); + } + + if (videoOrAudioFile == null) + { + Console.WriteLine($"Uploading file: {args.SourceVideoOrAudioFilePath}"); + videoOrAudioFile = await fileClient.UploadVideoFileAsync( + name: Path.GetFileName(args.SourceVideoOrAudioFilePath), + description: null, + locale: args.TypedSourceLocale, + speakerCount: null, + videoFilePath: args.SourceVideoOrAudioFilePath).ConfigureAwait(false); + Console.WriteLine($"Uploaded new video file with ID {videoOrAudioFile.ParseIdFromSelf()} uploaded."); + } + else + { + Console.WriteLine($"Reuse existing video file with ID {videoOrAudioFile.ParseIdFromSelf()}."); + } + + Console.WriteLine(JsonConvert.SerializeObject( + videoOrAudioFile, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + + var translation = await DoCreateTranslationAsync( + client: translationClient, + videoOrAudioFile: videoOrAudioFile, + args: args).ConfigureAwait(false); + if (translation == null) + { + return ExitCode.GenericError; + } + + break; + } + + case Mode.QueryVideoOrAudioFiles: + { + var videoFiles = await fileClient.QueryVideoFilesAsync().ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + videoFiles, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.QueryVideoOrAudioFile: + { + var videoFile = await fileClient.QueryVideoFileAsync(args.Id).ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + videoFile, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.DeleteVideoOrAudioFile: + { + var response = await fileClient.DeleteVideoFileAsync(args.Id, args.DeleteAssociations).ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + ((HttpStatusCode)response.StatusCode).AsString()), + Formatting.Indented, + CustomContractResolver.WriterSettings); + break; + } + + case Mode.CreateTranslation: + { + var videoOrAudioFile = await fileClient.QueryVideoFileAsync( + args.VideoOrAudioFileId).ConfigureAwait(false); + if (videoOrAudioFile == null) + { + throw new InvalidDataException($"Failed to find video or audio file with ID {args.VideoOrAudioFileId}"); + } + + var translation = await DoCreateTranslationAsync( + client: translationClient, + videoOrAudioFile: videoOrAudioFile, + args: args).ConfigureAwait(false); + if (translation == null) + { + return ExitCode.GenericError; + } + + break; + } + + case Mode.QueryTranslations: + { + var translations = await translationClient.QueryTranslationsAsync().ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + translations, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + break; + } + + case Mode.QueryTranslation: + { + var translation = await translationClient.QueryTaskByIdUntilTerminatedAsync( + id: args.Id, + additionalHeaders: args.TypedAdditionalHttpHeaders, + printFirstQueryResult: true).ConfigureAwait(false); + if (translation == null) + { + Console.WriteLine($"Failed to find translation with ID: {args.Id}"); + } + + break; + } + + case Mode.DeleteTranslation: + { + var response = await translationClient.DeleteTranslationAsync(args.Id).ConfigureAwait(false); + Console.WriteLine(JsonConvert.SerializeObject( + ((HttpStatusCode)response.StatusCode).AsString()), + Formatting.Indented, + CustomContractResolver.WriterSettings); + break; + } + + default: + throw new NotSupportedException(args.Mode.AsString()); + } + } + catch (Exception e) + { + Console.WriteLine($"Failed to run with exception: {e.Message}"); + return ExitCode.GenericError; + } + + Console.WriteLine(); + Console.WriteLine("Process completed successfully."); + + return ExitCode.NoError; + } + + private static async Task DoCreateTranslationAsync( + VideoTranslationClient client, + VideoFileMetadata videoOrAudioFile, + Arguments args) + { + var targetLocaleDefinition = new TranslationTargetLocaleCreate(); + var filePaths = new Dictionary(); + var targetLocales = new Dictionary(); + + if (args.TypedTargetLocales.Count() == 1) + { + targetLocales[args.TypedTargetLocales.Single()] = targetLocaleDefinition; + + if (!string.IsNullOrEmpty(args.TargetLocaleWebvttFilePath)) + { + if (!File.Exists(args.TargetLocaleWebvttFilePath)) + { + throw new FileNotFoundException(args.TargetLocaleWebvttFilePath); + } + + targetLocaleDefinition.WebVttFileName = Path.GetFileName(args.TargetLocaleWebvttFilePath); + filePaths[targetLocaleDefinition.WebVttFileName] = args.TargetLocaleWebvttFilePath; + } + } + else + { + foreach (var targetLocale in args.TypedTargetLocales) + { + targetLocales[targetLocale] = null; + } + } + + var translationDefinition = new TranslationCreate() + { + VideoFileId = videoOrAudioFile.ParseIdFromSelf(), + VoiceKind = args.VoiceKind, + AlignKind = null, + DisplayName = $"{videoOrAudioFile.DisplayName} : {videoOrAudioFile.Locale.Name} => {string.Join(",", args.TypedTargetLocales.Select(x => x.Name))}", + TargetLocales = targetLocales, + EnableFeatures = args.EnableFeatures, + ProfileName = args.ProfileName, + PersonalVoiceModelName = args.PersonalVoiceModelName, + IsAssociatedWithTargetLocale = args.IsAssociatedWithTargetLocale, + WebvttSourceKind = args.TypedWebvttSourceKind, + }; + + translationDefinition.WithoutSubtitleInTranslatedVideoFile = args.WithoutSubtitleInTranslatedVideoFile; + + translationDefinition.ExportPersonalVoicePromptAudioMetadata = args.ExportPersonalVoicePromptAudioMetadata; + + var translation = await client.CreateTranslationAsync( + translation: translationDefinition, + sourceLocaleWebVttFilePath: args.SourceLocaleWebvttFilePath, + filePaths: filePaths, + additionalProperties: args.TypedCreateTranslationAdditionalProperties, + additionalHeaders: args.TypedAdditionalHttpHeaders).ConfigureAwait(false); + if (translation == null) + { + return translation; + } + + Console.WriteLine(); + Console.WriteLine($"New translation created with ID {translation.ParseIdFromSelf()}."); + + // Print resposne of creating instead of first query state to make sure resposne of creating correct. + Console.WriteLine(JsonConvert.SerializeObject( + translation, + Formatting.Indented, + CustomContractResolver.WriterSettings)); + var createdTranslation = await client.QueryTaskByIdUntilTerminatedAsync( + id: translation.ParseIdFromSelf(), + additionalHeaders: args.TypedAdditionalHttpHeaders, + printFirstQueryResult: false).ConfigureAwait(false); + if (createdTranslation == null) + { + Console.WriteLine($"Failed to find translation with ID: {translation.ParseIdFromSelf()}"); + return null; + } + + return createdTranslation; + } +} \ No newline at end of file diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/VideoTranslation.csproj b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/VideoTranslation.csproj new file mode 100644 index 00000000..1215618e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslation/VideoTranslation.csproj @@ -0,0 +1,20 @@ + + + + Exe + net7.0 + Microsoft.SpeechServices.VideoTranslation + Microsoft.SpeechServices.VideoTranslation + + + + + + + + + + + + + diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationApiSampleCode.sln b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationApiSampleCode.sln new file mode 100644 index 00000000..8e9112be --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationApiSampleCode.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33829.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoTranslationLib", "VideoTranslationLib\VideoTranslationLib.csproj", "{48AE0D58-EBDC-4517-A32B-058B17C193DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoTranslation", "VideoTranslation\VideoTranslation.csproj", "{AEDFC024-4991-4308-A195-F82134DC6CAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonLib", "..\Common\CommonLib\CommonLib.csproj", "{0C202E1B-705B-4DC4-8D89-39D4EC6DB6C5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48AE0D58-EBDC-4517-A32B-058B17C193DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48AE0D58-EBDC-4517-A32B-058B17C193DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48AE0D58-EBDC-4517-A32B-058B17C193DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48AE0D58-EBDC-4517-A32B-058B17C193DB}.Release|Any CPU.Build.0 = Release|Any CPU + {AEDFC024-4991-4308-A195-F82134DC6CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEDFC024-4991-4308-A195-F82134DC6CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEDFC024-4991-4308-A195-F82134DC6CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEDFC024-4991-4308-A195-F82134DC6CAD}.Release|Any CPU.Build.0 = Release|Any CPU + {0C202E1B-705B-4DC4-8D89-39D4EC6DB6C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C202E1B-705B-4DC4-8D89-39D4EC6DB6C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C202E1B-705B-4DC4-8D89-39D4EC6DB6C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C202E1B-705B-4DC4-8D89-39D4EC6DB6C5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6302A643-3DFD-46B4-955E-53CFF41A9F89} + EndGlobalSection +EndGlobal diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/Translation.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/Translation.cs new file mode 100644 index 00000000..8b155c09 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/Translation.cs @@ -0,0 +1,20 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System; +using System.Collections.Generic; +using System.Globalization; + +public class Translation : TranslationBase +{ + public Uri InputWebVttFileUrl { get; set; } + + public Uri OutputVideoSubtitleWebVttFileUrl { get; set; } + + public Uri ReportFileUrl { get; set; } + + public Uri IntermediateZipFileUrl { get; set; } + + public Uri CacheZipFileUrl { get; set; } + + public IReadOnlyDictionary TargetLocales { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBase.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBase.cs new file mode 100644 index 00000000..f38604f5 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBase.cs @@ -0,0 +1,34 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System; +using System.Collections.Generic; +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using Microsoft.SpeechServices.VideoTranslation.Enums; + +public class TranslationBase : StatefulResourceBase +{ + public Guid VideoFileId { get; set; } + + public VideoTranslationMergeParagraphAudioAlignKind? AudioAlignKind { get; set; } + + public VideoTranslationVoiceKind? VoiceKind { get; init; } + + public string EnableFeatures { get; set; } + + public string ProfileName { get; set; } + + public bool? WithoutSubtitleInTranslatedVideoFile { get; set; } + + public bool? ExportPersonalVoicePromptAudioMetadata { get; set; } + + public bool? IsAssociatedWithTargetLocale { get; set; } + + public string PersonalVoiceModelName { get; set; } + + public VideoTranslationWebvttSourceKind? WebvttSourceKind { get; set; } + + public IReadOnlyDictionary AdditionalProperties { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBrief.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBrief.cs new file mode 100644 index 00000000..adf29df9 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationBrief.cs @@ -0,0 +1,9 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System.Collections.Generic; +using System.Globalization; + +public class TranslationBrief : TranslationBase +{ + public IReadOnlyDictionary TargetLocales { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationCreate.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationCreate.cs new file mode 100644 index 00000000..cf6b9518 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationCreate.cs @@ -0,0 +1,37 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using Microsoft.SpeechServices.VideoTranslation.Enums; + +public class TranslationCreate : StatelessResourceBase +{ + public Guid VideoFileId { get; set; } + + public VideoTranslationMergeParagraphAudioAlignKind? AlignKind { get; set; } + + public VideoTranslationVoiceKind? VoiceKind { get; set; } + + public string EnableFeatures { get; set; } + + public string ProfileName { get; set; } + + public int? SubtitleMaxCharCountPerSegment { get; set; } + + public bool? WithoutSubtitleInTranslatedVideoFile { get; set; } + + public bool? ExportPersonalVoicePromptAudioMetadata { get; set; } + + public bool? IsAssociatedWithTargetLocale { get; set; } + + public string PersonalVoiceModelName { get; set; } + + public VideoTranslationWebvttSourceKind? WebvttSourceKind { get; set; } + + public Dictionary TargetLocales { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocale.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocale.cs new file mode 100644 index 00000000..a0b034c1 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocale.cs @@ -0,0 +1,16 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System; + +public class TranslationTargetLocale : TranslationTargetLocaleBase +{ + public Uri InputWebVttFileUrl { get; set; } + + public Uri OutputVideoSubtitleWebVttFileUrl { get; set; } + + public Uri OutputMetadataJsonWebVttFileUrl { get; set; } + + public Uri OutputVideoFileUrl { get; set; } + + public Uri Output24k16bitRiffAudioFileUrl { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleBase.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleBase.cs new file mode 100644 index 00000000..18821915 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleBase.cs @@ -0,0 +1,5 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +public class TranslationTargetLocaleBase +{ +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleCreate.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleCreate.cs new file mode 100644 index 00000000..8c90fc21 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/TranslationTargetLocaleCreate.cs @@ -0,0 +1,6 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +public class TranslationTargetLocaleCreate +{ + public string WebVttFileName { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileCreate.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileCreate.cs new file mode 100644 index 00000000..9bd50be6 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileCreate.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System.Globalization; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using Microsoft.SpeechServices.VideoTranslation; + +public class VideoFileCreate : StatelessResourceBase +{ + public CultureInfo Locale { get; set; } + + public int? SpeakerCount { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileMetadata.cs new file mode 100644 index 00000000..5a9e06be --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileMetadata.cs @@ -0,0 +1,27 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using System; +using System.Collections.Generic; +using System.Globalization; + +public class VideoFileMetadata : StatelessResourceBase +{ + public VideoTranslationFileKind FileKind { get; set; } + + public CultureInfo Locale { get; set; } + + public int? SpeakerCount { get; set; } + + public IEnumerable TargetLocales { get; set; } + + public Uri VideoFileUri { get; set; } + + public Uri AudioFileUri { get; set; } + + public TimeSpan? Duration { get; set; } + + public Uri SnapshotImageUrl { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocale.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocale.cs new file mode 100644 index 00000000..5ff1aa27 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocale.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using Microsoft.SpeechServices.Cris.Http.DTOs.Public; +using Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; +using Microsoft.SpeechServices.DataContracts; +using System; +using System.Globalization; + +// For query target locale list. +public class VideoFileTargetLocale : VideoFileTargetLocaleBrief +{ + public Uri EditingMetadataJsonWebvttFileUri { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBrief.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBrief.cs new file mode 100644 index 00000000..3d6efa76 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBrief.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; + +using System; +using System.Globalization; + +// For query target locale list. +public class VideoFileTargetLocaleBrief : StatefulResourceBase +{ + public CultureInfo SourceLocale { get; set; } + + public CultureInfo TargetLocale { get; set; } + + public Guid VideoFileId { get; set; } + + public Guid? LatestTranslationId { get; set; } + + public VideoFileTargetLocaleBriefPortalProperties PortalProperties { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBriefPortalProperties.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBriefPortalProperties.cs new file mode 100644 index 00000000..495b3431 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoFileTargetLocaleBriefPortalProperties.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; + +using System; + +public class VideoFileTargetLocaleBriefPortalProperties +{ + public Uri ImageUrl { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationFeatureMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationFeatureMetadata.cs new file mode 100644 index 00000000..926b8772 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationFeatureMetadata.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using Microsoft.SpeechServices.CommonLib.Enums; +using System.Collections.Generic; +using System.Globalization; + +public class VideoTranslationFeatureMetadata +{ + // Use this instead of enum to avoid exception. + public string FeatureKind { get; set; } + + public string Version { get; set; } + + public string Description { get; set; } + + public List SupportedSourceLocales { get; set; } + + public List SupportedTargetLocales { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationMetadata.cs new file mode 100644 index 00000000..4baf9f29 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationMetadata.cs @@ -0,0 +1,24 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; +using Microsoft.SpeechServices.DataContracts; +using System; +using System.Collections.Generic; +using System.Globalization; + +public class VideoTranslationMetadata : ResponseBase +{ + public Dictionary SupportedSourceLocales { get; set; } + + public Dictionary SupportedTargetLocales { get; set; } + + // Not use enum VideoTranslationFeatureMetadata as key to avoid client breaking when server add new enum value. + public List SupportedFeatures { get; set; } + + public List SupportedPersonalVoiceModels { get; set; } + + public List ReleaseHistoryVersions { get; set; } + + public List SupportedProfiles { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationPersonalVoiceModelMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationPersonalVoiceModelMetadata.cs new file mode 100644 index 00000000..e208f3c0 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationPersonalVoiceModelMetadata.cs @@ -0,0 +1,10 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +public class VideoTranslationPersonalVoiceModelMetadata +{ + public string ModelName { get; set; } + + public bool? IsDefault { get; set; } + + public string Description { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationProfileMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationProfileMetadata.cs new file mode 100644 index 00000000..23289a82 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationProfileMetadata.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +// No need expose optional features list to end user. +public class VideoTranslationProfileMetadata +{ + public string Name { get; set; } + + public bool? IsPrivate { get; set; } + + public bool? IsDefault { get; set; } + + public string Description { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationReleaseHistoryVersionMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationReleaseHistoryVersionMetadata.cs new file mode 100644 index 00000000..937dde6c --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationReleaseHistoryVersionMetadata.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using System; + +public class VideoTranslationReleaseHistoryVersionMetadata +{ + public string Version { get; set; } + + public DateTime Date { get; set; } + + public string Description { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationTargetLocaleMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationTargetLocaleMetadata.cs new file mode 100644 index 00000000..a766f08c --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/VideoTranslationTargetLocaleMetadata.cs @@ -0,0 +1,8 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +public class VideoTranslationTargetLocaleMetadata +{ + public bool IsTtsPersonalVoiceSupported { get; set; } + + public bool IsTtsPlatformVoiceSupported { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/WebVttFileMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/WebVttFileMetadata.cs new file mode 100644 index 00000000..a555c1e7 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/DTOs/WebVttFileMetadata.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.DataContracts.Deprecated; +using System; +using System.Globalization; + +public class WebVttFileMetadata : StatelessResourceBase +{ + public CultureInfo Locale { get; set; } + + public Uri FileUrl { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolInputArgs.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolInputArgs.cs new file mode 100644 index 00000000..3b52534e --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolInputArgs.cs @@ -0,0 +1,50 @@ +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.VideoTranslation.Enums; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.Utility +{ + public class VideoTranslationPoolInputArgs + { + public VideoTranslationPoolInputArgs() + { + this.AdditionalHeaders = new Dictionary(); + this.AdditionalProperties = new Dictionary(); + } + + public string VideoFilePath { get; set; } + + public CultureInfo SourceLocale { get; set; } + + public VideoTranslationVoiceKind? VoiceKind { get; set; } + + public string EnableFeatures { get; set; } + + public string ProfileName { get; set; } + + public bool? WithoutSubtitleInTranslatedVideoFile { get; set; } + + public int? SubtitleMaxCharCountPerSegment { get; set; } + + public bool? ExportPersonalVoicePromptAudioMetadata { get; set; } + + public string PersonalVoiceModelName { get; set; } + + public bool? IsAssociatedWithTargetLocale { get; set; } + + public VideoTranslationWebvttSourceKind? WebvttSourceKind { get; set; } + + public List TargetLocales { get; set; } + + public Dictionary AdditionalProperties { get; private set; } + + public Dictionary AdditionalHeaders { get; private set; } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolOutputResult.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolOutputResult.cs new file mode 100644 index 00000000..ab5eaa5f --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/Utility/VideoTranslationPoolOutputResult.cs @@ -0,0 +1,30 @@ +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SpeechServices.VideoTranslation.DataContracts.Utility +{ + public class VideoTranslationPoolOutputResult + { + public VideoTranslationPoolInputArgs InputArgs { get; set; } + + public string Error { get; set; } + + public VideoFileMetadata VideoFile { get; set; } + + public Translation Translation { get; set; } + + public bool Success + { + get + { + return string.IsNullOrWhiteSpace(this.Error) && + this.Translation?.Status == CommonLib.Enums.OneApiState.Succeeded; + } + } + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationReleaseHistoryVersionMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationReleaseHistoryVersionMetadata.cs new file mode 100644 index 00000000..12486bdc --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationReleaseHistoryVersionMetadata.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; + +using System; + +public class VideoTranslationReleaseHistoryVersionMetadata +{ + public string Version { get; set; } + + public DateTime Date { get; set; } + + public string Description { get; set; } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationSourceLocaleMetadata.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationSourceLocaleMetadata.cs new file mode 100644 index 00000000..6acdea34 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/DataContracts/VideoTranslationSourceLocaleMetadata.cs @@ -0,0 +1,9 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; + +public class VideoTranslationSourceLocaleMetadata +{ +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebVttFilePlainTextKind.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebVttFilePlainTextKind.cs new file mode 100644 index 00000000..db5ab68f --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebVttFilePlainTextKind.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.VideoTranslation.Enums; + +public enum VideoTranslationWebVttFilePlainTextKind +{ + None = 0, + + // User can upload webvtt with plain text format, the content of plain text is source locale. + SourceLocalePlainText, + + // User can upload webvtt with plain text format, the content of plain text is target locale. + TargetLocalePlainText, +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebvttSourceKind.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebvttSourceKind.cs new file mode 100644 index 00000000..2d8963ce --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/Enum/VideoTranslationWebvttSourceKind.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace Microsoft.SpeechServices.VideoTranslation.Enums; + +public enum VideoTranslationWebvttSourceKind +{ + None = 0, + + // In this kind, NOT associate with target locale with current translation. + FileUpload, + + // 1. In this kind, will associate with target local with current translation. + // 2. If no user editting file, translate without webvtt file instead of response error. + TargetLocale, +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/TargetLocaleClient.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/TargetLocaleClient.cs new file mode 100644 index 00000000..3aad7f54 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/TargetLocaleClient.cs @@ -0,0 +1,83 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using System.Xml.Linq; +using Flurl; +using Flurl.Http; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using Microsoft.SpeechServices.VideoTranslation.Enums; +using Microsoft.SpeechServices.CommonLib.Util; +using System.IO; +using Microsoft.SpeechServices.Cris.Http.DTOs.Public.VideoTranslation; + +public class TargetLocaleClient : VideoTranslationClientBase +{ + public TargetLocaleClient(DeploymentEnvironment environment, string subKey) + : base(environment, subKey) + { + } + + public override string ControllerName => "VideoFileTargetLocales"; + + public async Task> QueryTargetLocalesAsync( + int skip = 0, + int top = 100, + string orderby = "lastActionDateTime desc") + { + var url = this.BuildRequestBase(); + return await this.RequestWithRetryAsync(async () => + { + return await url + .SetQueryParam("skip", skip) + .SetQueryParam("top", top) + .SetQueryParam("orderby", orderby) + .GetAsync() + .ReceiveJson>() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task UpdateTargetLocaleEdittingWebvttFileAsync( + Guid videoFileId, + CultureInfo targetLocale, + VideoTranslationWebVttFilePlainTextKind? kind, + string webvttFilePath) + { + if (string.IsNullOrEmpty(webvttFilePath)) + { + throw new ArgumentNullException(webvttFilePath); + } + + if (!File.Exists(webvttFilePath)) + { + throw new FileNotFoundException(webvttFilePath); + } + + var url = this.BuildRequestBase() + .AppendPathSegment(videoFileId.ToString()) + .AppendPathSegment(targetLocale.Name) + .AppendPathSegment("webvtt"); + if ((kind ?? VideoTranslationWebVttFilePlainTextKind.None) != VideoTranslationWebVttFilePlainTextKind.None) + { + url = url.SetQueryParam("kind", kind.Value.AsString()); + } + + return await this.RequestWithRetryAsync(async () => + { + return await url + .PostMultipartAsync(mp => + { + mp.AddFile("webVttFile", webvttFilePath); + }); + }) + .ReceiveJson() + .ConfigureAwait(false); + } + +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoFileClient.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoFileClient.cs new file mode 100644 index 00000000..58f94d5f --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoFileClient.cs @@ -0,0 +1,268 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using System.Xml.Linq; +using Flurl; +using Flurl.Http; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; + +public class VideoFileClient : VideoTranslationClientBase +{ + public VideoFileClient(DeploymentEnvironment environment, string subKey) + : base(environment, subKey) + { + } + + public override string ControllerName => "VideoFiles"; + + public async Task QueryTargetLocaleAsync( + Guid videoOrAudioFileId, + CultureInfo targetLocale) + { + var url = this.BuildRequestBase(); + return await this.RequestWithRetryAsync(async () => + { + //var content = await url + // .AppendPathSegment(videoOrAudioFileId) + // .AppendPathSegment(targetLocale.Name) + // .GetAsync() + // .ReceiveString() + // .ConfigureAwait(false); + return await url + .AppendPathSegment(videoOrAudioFileId) + .AppendPathSegment(targetLocale.Name) + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task UploadVideoFileWithStringResponseAsync( + string name, + string description, + CultureInfo locale, + int? speakerCount, + string videoFilePath) + { + return await this.RequestWithRetryAsync(async () => + { + var response = this.PostUploadVideoFileWithResponseAsync( + name: name, + description: description, + locale: locale, + speakerCount: speakerCount, + videoFilePath: videoFilePath); + + return await response + .ReceiveString(); + }).ConfigureAwait(false); + } + + public async Task UploadVideoFileAsync( + string name, + string description, + CultureInfo locale, + int? speakerCount, + string videoFilePath) + where TVideoFileMetadata : VideoFileMetadata + { + return await this.RequestWithRetryAsync(async () => + { + var response = this.PostUploadVideoFileWithResponseAsync( + name: name, + description: description, + locale: locale, + speakerCount: speakerCount, + videoFilePath: videoFilePath); + + return await response + .ReceiveJson(); + }).ConfigureAwait(false); + } + + public async Task> QueryVideoFilesAsync() + { + var url = this.BuildRequestBase(); + return await this.RequestWithRetryAsync(async () => + { + return await url.GetAsync() + .ReceiveJson>() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task DeleteVideoFileAsync( + Guid videoFileId, + bool deleteAssociations) + { + var queryParams = new Dictionary(); + if (deleteAssociations) + { + queryParams["deleteAssociations"] = bool.TrueString; + } + + return await this.RequestWithRetryAsync(async () => + { + return await this.DeleteByIdAsync(videoFileId, queryParams).ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task> QueryTranslationsAsync(Guid videoFileId) + { + var url = this.BuildRequestBase() + .AppendPathSegment(videoFileId.ToString()) + .AppendPathSegment("translations"); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .GetAsync() + .ReceiveJson>() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task DeleteTargetLocaleAsync( + Guid videoFileId, + CultureInfo locale, + bool deleteAssociations) + { + var url = this.BuildRequestBase() + .AppendPathSegment(videoFileId.ToString()) + .AppendPathSegment(locale.Name); + if (deleteAssociations) + { + url = url.SetQueryParam("deleteAssociations", deleteAssociations); + } + + return await this.RequestWithRetryAsync(async () => + { + return await url + .DeleteAsync() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task QueryTargetLocaleWebVttAsync( + Guid videoFileId, + CultureInfo locale) + { + var url = this.BuildRequestBase() + .AppendPathSegment(videoFileId.ToString()) + .AppendPathSegment(locale.Name) + .AppendPathSegment("webvtt"); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task QueryVideoFileWithLocaleAndFileContentSha256Async( + CultureInfo locale, + string fileContentSha256) + { + if (locale == null || string.IsNullOrEmpty(locale.Name)) + { + throw new ArgumentNullException(nameof(locale)); + } + + if (string.IsNullOrEmpty(fileContentSha256)) + { + throw new ArgumentNullException(nameof(fileContentSha256)); + } + + var url = this.BuildRequestBase() + .AppendPathSegment("QueryByFileContentSha256") + .SetQueryParam("locale", locale.Name) + .SetQueryParam("fileContentSha256", fileContentSha256); + + return await this.RequestWithRetryAsync(async () => + { + try + { + return await url + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + } + catch (FlurlHttpException ex) + { + if (ex.StatusCode == (int)HttpStatusCode.NotFound) + { + return null; + } + + Console.Write($"Response failed with error: {await ex.GetResponseStringAsync().ConfigureAwait(false)}"); + throw; + } + }).ConfigureAwait(false); + } + + public async Task QueryVideoFileAsync(Guid id) + where TVideoFileMetadata : VideoFileMetadata + { + var url = this.BuildRequestBase(); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .AppendPathSegment(id.ToString()) + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + private async Task PostUploadVideoFileWithResponseAsync( + string name, + string description, + CultureInfo locale, + int? speakerCount, + string videoFilePath) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + var url = this.BuildRequestBase(); + + url.ConfigureRequest(settings => settings.Timeout = VideoTranslationConstant.UploadVideoOrAudioFileTimeout); + + return await url + .PostMultipartAsync(mp => + { + if (!string.IsNullOrWhiteSpace(name)) + { + mp.AddString(nameof(VideoFileMetadata.DisplayName), name); + } + + if (!string.IsNullOrWhiteSpace(description)) + { + mp.AddString(nameof(VideoFileMetadata.Description), description); + } + + if (locale != null && !string.IsNullOrEmpty(locale.Name)) + { + mp.AddString(nameof(VideoFileMetadata.Locale), locale.Name); + } + + if (speakerCount != null) + { + mp.AddString(nameof(VideoFileMetadata.SpeakerCount), speakerCount.Value.ToString(CultureInfo.InvariantCulture)); + } + + mp.AddFile("videoFile", videoFilePath); + }); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClient.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClient.cs new file mode 100644 index 00000000..042cf326 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClient.cs @@ -0,0 +1,278 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using Flurl; +using Flurl.Http; +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CommonLib.Util; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using Newtonsoft.Json; +using Polly; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +public class VideoTranslationClient : VideoTranslationClientBase +{ + public VideoTranslationClient(DeploymentEnvironment environment, string subKey) + : base(environment, subKey) + { + } + + public override string ControllerName => "VideoTranslations"; + + public async Task DeleteTranslationAsync( + Guid translationId) + { + var url = this.BuildRequestBase() + .AppendPathSegment(translationId.ToString()); + + return await this.RequestWithRetryAsync(async () => + { + return await url + .DeleteAsync() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task QueryTranslationAsync( + Guid id, + IReadOnlyDictionary additionalHeaders) + { + var url = this.BuildRequestBase(); + + url = url.AppendPathSegment(id.ToString()); + + if (additionalHeaders != null) + { + foreach (var additionalHeader in additionalHeaders) + { + url.WithHeader(additionalHeader.Key, additionalHeader.Value); + } + } + + return await this.RequestWithRetryAsync(async () => + { + try + { + return await url + .GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + } + catch (FlurlHttpException ex) + { + if (ex.StatusCode == (int)HttpStatusCode.NotFound) + { + return null; + } + + Console.Write($"Response failed with error: {await ex.GetResponseStringAsync().ConfigureAwait(false)}"); + throw; + } + }).ConfigureAwait(false); + } + + public async Task> QueryTranslationsAsync() + { + var url = this.BuildRequestBase(); + + return await this.RequestWithRetryAsync(async () => + { + // var responseJson = await url.GetStringAsync().ConfigureAwait(false); + return await url.GetAsync() + .ReceiveJson>() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task CreateTranslationAsync( + TranslationCreate translation, + string sourceLocaleWebVttFilePath, + IReadOnlyDictionary filePaths, + IReadOnlyDictionary additionalProperties = null, + IReadOnlyDictionary additionalHeaders = null) + { + return await this.RequestWithRetryAsync(async () => + { + var response = this.CreateTranslationWithResponseAsync( + translation: translation, + sourceLocaleWebVttFilePath: sourceLocaleWebVttFilePath, + filePaths: filePaths, + additionalProperties: additionalProperties, + additionalHeaders: additionalHeaders); + return await response.ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + public async Task CreateTranslationWithStringResponseAsync( + TranslationCreate translation, + string sourceLocaleWebVttFilePath, + IReadOnlyDictionary filePaths, + IReadOnlyDictionary additionalProperties = null, + IReadOnlyDictionary additionalHeaders = null) + { + return await this.RequestWithRetryAsync(async () => + { + var response = this.CreateTranslationWithResponseAsync( + translation: translation, + sourceLocaleWebVttFilePath: sourceLocaleWebVttFilePath, + filePaths: filePaths, + additionalProperties: additionalProperties, + additionalHeaders: additionalHeaders); + return await response.ReceiveString() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + private async Task CreateTranslationWithResponseAsync( + TranslationCreate translation, + string sourceLocaleWebVttFilePath, + IReadOnlyDictionary filePaths, + IReadOnlyDictionary additionalProperties = null, + IReadOnlyDictionary additionalHeaders = null) + { + ArgumentNullException.ThrowIfNull(translation); + + if (string.IsNullOrWhiteSpace(translation.DisplayName)) + { + throw new ArgumentNullException(nameof(translation.DisplayName)); + } + + if (translation.TargetLocales != null && filePaths != null) + { + foreach (var targetLocale in translation.TargetLocales.Where(x => !string.IsNullOrEmpty(x.Value?.WebVttFileName))) + { + if (!filePaths.ContainsKey(targetLocale.Value.WebVttFileName)) + { + throw new InvalidDataException($"Not found file {targetLocale.Value.WebVttFileName} for locale {targetLocale.Key}"); + } + + if (!File.Exists(filePaths[targetLocale.Value.WebVttFileName])) + { + throw new FileNotFoundException(filePaths[targetLocale.Value.WebVttFileName]); + } + } + } + + var url = this.BuildRequestBase(); + + if (additionalHeaders != null) + { + foreach (var additionalHeader in additionalHeaders) + { + url.WithHeader(additionalHeader.Key, additionalHeader.Value); + } + } + + return await url.PostMultipartAsync(mp => + { + if (!string.IsNullOrWhiteSpace(translation.DisplayName)) + { + mp.AddString(nameof(TranslationCreate.DisplayName), translation.DisplayName); + } + + if (!string.IsNullOrWhiteSpace(translation.Description)) + { + mp.AddString(nameof(TranslationCreate.Description), translation.Description); + } + + if (!string.IsNullOrEmpty(translation.EnableFeatures)) + { + mp.AddString(nameof(TranslationCreate.EnableFeatures), translation.EnableFeatures); + } + + if (!string.IsNullOrEmpty(translation.ProfileName)) + { + mp.AddString(nameof(TranslationCreate.ProfileName), translation.ProfileName); + } + + if (!string.IsNullOrEmpty(translation.ProfileName)) + { + mp.AddString(nameof(TranslationCreate.ProfileName), translation.ProfileName); + } + + if (translation.AlignKind != null) + { + mp.AddString(nameof(TranslationCreate.AlignKind), translation.AlignKind.Value.AsString()); + } + + if (translation.VoiceKind != null) + { + mp.AddString(nameof(TranslationCreate.VoiceKind), translation.VoiceKind.Value.AsString()); + } + + if (translation.WithoutSubtitleInTranslatedVideoFile ?? false) + { + mp.AddString(nameof(TranslationCreate.WithoutSubtitleInTranslatedVideoFile), + translation.WithoutSubtitleInTranslatedVideoFile.Value.ToString()); + } + + if (translation.SubtitleMaxCharCountPerSegment != null) + { + mp.AddString(nameof(TranslationCreate.SubtitleMaxCharCountPerSegment), + translation.SubtitleMaxCharCountPerSegment.Value.ToString()); + } + + if (translation.ExportPersonalVoicePromptAudioMetadata ?? false) + { + mp.AddString(nameof(TranslationCreate.ExportPersonalVoicePromptAudioMetadata), + translation.ExportPersonalVoicePromptAudioMetadata.Value.ToString()); + } + + if (!string.IsNullOrEmpty(translation.PersonalVoiceModelName)) + { + mp.AddString(nameof(TranslationCreate.PersonalVoiceModelName), + translation.PersonalVoiceModelName); + } + + if (translation.IsAssociatedWithTargetLocale ?? false) + { + mp.AddString(nameof(TranslationCreate.IsAssociatedWithTargetLocale), + translation.IsAssociatedWithTargetLocale.Value.ToString()); + } + + if (translation.WebvttSourceKind != null) + { + mp.AddString(nameof(TranslationCreate.WebvttSourceKind), + translation.WebvttSourceKind.Value.AsString()); + } + + if (additionalProperties != null) + { + foreach (var (name, value) in additionalProperties) + { + mp.AddString(name, value); + } + } + + if (!string.IsNullOrEmpty(sourceLocaleWebVttFilePath)) + { + if (!File.Exists(sourceLocaleWebVttFilePath)) + { + throw new FileNotFoundException(sourceLocaleWebVttFilePath); + } + + mp.AddFile("sourceLocaleWebVttFile", sourceLocaleWebVttFilePath, fileName: Path.GetFileName(sourceLocaleWebVttFilePath)); + } + + mp.AddJson(nameof(TranslationCreate.TargetLocales) + "JsonString", translation.TargetLocales); + + mp.AddString(nameof(TranslationCreate.VideoFileId), translation.VideoFileId.ToString()); + if (filePaths != null) + { + foreach (var (name, path) in filePaths.Where(x => translation.TargetLocales.Any(y => + string.Equals(x.Key, y.Value.WebVttFileName, StringComparison.Ordinal)))) + { + mp.AddFile("files", path, fileName: name); + } + } + }).ConfigureAwait(false); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClientBase.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClientBase.cs new file mode 100644 index 00000000..72971838 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationClientBase.cs @@ -0,0 +1,13 @@ +namespace Microsoft.SpeechServices.VideoTranslation; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CommonLib.Util; + +public abstract class VideoTranslationClientBase : HttpClientBase +{ + public VideoTranslationClientBase(DeploymentEnvironment environment, string subKey) + : base(environment, subKey) + { + } + + public override string RouteBase => "videotranslation"; +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs new file mode 100644 index 00000000..2b3b6e8c --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs @@ -0,0 +1,22 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using System; +using System.Collections.Generic; +using Microsoft.SpeechServices.CommonLib.Enums; + +public static class VideoTranslationConstant +{ + public readonly static TimeSpan UploadVideoOrAudioFileTimeout = TimeSpan.FromMinutes(10); + + public readonly static IEnumerable SupportedEnvironments = new[] + { + DeploymentEnvironment.Local, + DeploymentEnvironment.Develop, + DeploymentEnvironment.DevelopEUS, + DeploymentEnvironment.CanaryUSCX, + DeploymentEnvironment.ProductionEUS, + + // This region doesn't support GPT. + DeploymentEnvironment.ProductionWEU, + }; +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationLib.csproj b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationLib.csproj new file mode 100644 index 00000000..e234c164 --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationLib.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + Microsoft.SpeechServices.$(MSBuildProjectName) + Microsoft.SpeechServices.VideoTranslation + + + + + + + + + + + + + + diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationMetadataClient.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationMetadataClient.cs new file mode 100644 index 00000000..d07e297d --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationMetadataClient.cs @@ -0,0 +1,40 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using Flurl; +using Flurl.Http; +using Microsoft.SpeechServices.Common.Client; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CommonLib.Util; +using Microsoft.SpeechServices.DataContracts; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using Newtonsoft.Json; +using Polly; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +public class VideoTranslationMetadataClient : VideoTranslationClientBase +{ + public VideoTranslationMetadataClient(DeploymentEnvironment environment, string subKey) + : base(environment, subKey) + { + } + + public override string ControllerName => "VideoTranslationMetadata"; + + public async Task QueryMetadataAsync() + { + var url = this.BuildRequestBase(); + + return await this.RequestWithRetryAsync(async () => + { + return await url.GetAsync() + .ReceiveJson() + .ConfigureAwait(false); + }).ConfigureAwait(false); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationPool.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationPool.cs new file mode 100644 index 00000000..289581cb --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationPool.cs @@ -0,0 +1,159 @@ +namespace Microsoft.SpeechServices.VideoTranslation; + +using Flurl.Util; +using Microsoft.SpeechServices.CommonLib.Enums; +using Microsoft.SpeechServices.CommonLib.Util; +using Microsoft.SpeechServices.CustomVoice.TtsLib.TtsUtil; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.DTOs; +using Microsoft.SpeechServices.VideoTranslation.DataContracts.Utility; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; + +public class VideoTranslationPool + where TVideoFileMetadata : VideoFileMetadata +{ + private VideoTranslationPool() + { + this.TranslationResults = new ConcurrentDictionary(); + + this.waitTranslationExecutionActionBlock = new ActionBlock<(Guid translationId, IReadOnlyDictionary additionalHeaders)>( + async (args) => + { + try + { + var translation = await this.TranslationClient.QueryTaskByIdUntilTerminatedAsync( + id: args.translationId, + additionalHeaders: args.additionalHeaders, + printFirstQueryResult: false, + timeout: TimeSpan.FromHours(3)).ConfigureAwait(false); + this.TranslationResults[args.translationId].Translation = translation; + } + catch (Exception e) + { + Console.WriteLine(ExceptionHelper.BuildExceptionMessage(e, true)); + throw; + } + }, + new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 2 }); + + this.createTranslationActionBlock = new ActionBlock(async (args) => + { + var result = new VideoTranslationPoolOutputResult() + { + InputArgs = args, + }; + + try + { + var videoFileContentSha256 = Sha256Helper.GetSha256WithExtensionFromFile(args.VideoFilePath); + var videoFile = await this.FileClient.QueryVideoFileWithLocaleAndFileContentSha256Async( + args.SourceLocale, + videoFileContentSha256).ConfigureAwait(false); + if (videoFile == null) + { + Console.WriteLine($"Uploading file: {args.VideoFilePath}"); + videoFile = await this.FileClient.UploadVideoFileAsync( + name: Path.GetFileName(args.VideoFilePath), + description: null, + locale: args.SourceLocale, + speakerCount: null, + videoFilePath: args.VideoFilePath).ConfigureAwait(false); + } + + result.VideoFile = videoFile; + + var translationCreate = new TranslationCreate() + { + DisplayName = $"{videoFile.DisplayName} : {videoFile.Locale.Name} => {string.Join(",", args.TargetLocales.Select(x => x.Name))}", + Description = this.TranslatoinDescription, + VideoFileId = videoFile.ParseIdFromSelf(), + EnableFeatures = args.EnableFeatures, + ProfileName = args.ProfileName, + WithoutSubtitleInTranslatedVideoFile = args.WithoutSubtitleInTranslatedVideoFile, + SubtitleMaxCharCountPerSegment = args.SubtitleMaxCharCountPerSegment, + ExportPersonalVoicePromptAudioMetadata = args.ExportPersonalVoicePromptAudioMetadata, + PersonalVoiceModelName = args.PersonalVoiceModelName, + IsAssociatedWithTargetLocale = args.IsAssociatedWithTargetLocale, + WebvttSourceKind = args.WebvttSourceKind, + VoiceKind = args.VoiceKind, + TargetLocales = args.TargetLocales.ToDictionary(x => x, x => (TranslationTargetLocaleCreate)null), + }; + + result.Translation = await this.TranslationClient.CreateTranslationAsync( + translation: translationCreate, + sourceLocaleWebVttFilePath: null, + filePaths: null, + additionalProperties: args.AdditionalProperties, + additionalHeaders: args.AdditionalHeaders).ConfigureAwait(false); + + var translationId = result.Translation.ParseIdFromSelf(); + this.TranslationResults[translationId] = result; + + this.PostWaitTranslationAction(translationId, args.AdditionalHeaders); + } + catch (Exception e) + { + result.Error = $"Caught Exception with error: {e.Message}"; + Console.WriteLine(ExceptionHelper.BuildExceptionMessage(e, true)); + } + + }, + new ExecutionDataflowBlockOptions() + { + MaxDegreeOfParallelism = 50, + }); + } + + public void PostWaitTranslationAction(Guid translationId, IReadOnlyDictionary additionalHeaders) + { + this.waitTranslationExecutionActionBlock.Post((translationId, additionalHeaders)); + } + + public VideoTranslationClient TranslationClient { get; private set; } + + public VideoFileClient FileClient { get; private set; } + + public string TranslatoinDescription { get; set; } + + public ConcurrentDictionary TranslationResults { get; private set; } + + private ActionBlock createTranslationActionBlock; + + private ActionBlock<(Guid translationId, IReadOnlyDictionary additionalHeaders)> waitTranslationExecutionActionBlock; + + public VideoTranslationPool(DeploymentEnvironment environment, string subKey) + : this() + { + this.FileClient = new VideoFileClient(environment, subKey); + this.TranslationClient = new VideoTranslationClient(environment, subKey); + } + + public void Post(VideoTranslationPoolInputArgs args) + { + this.createTranslationActionBlock.Post(args); + } + + public async Task WaitTranslationExecutionCompletion() + { + Console.WriteLine("Before this.waitTranslationExecutionActionBlock.Complete()"); + + this.waitTranslationExecutionActionBlock.Complete(); + + Console.WriteLine("Before await waitTranslationExecutionActionBlock.Completion"); + + await this.waitTranslationExecutionActionBlock.Completion.ConfigureAwait(false); + Console.WriteLine("After await waitTranslationExecutionActionBlock.Completion"); + } + + public async Task CompleteAndWaitTranslationCreationCompletion() + { + this.createTranslationActionBlock.Complete(); + await this.createTranslationActionBlock.Completion.ConfigureAwait(false); + } +} diff --git a/VideoDubbing/CSharp/APIClientTool/nuget.config b/VideoDubbing/CSharp/APIClientTool/nuget.config new file mode 100644 index 00000000..8c61ab3d --- /dev/null +++ b/VideoDubbing/CSharp/APIClientTool/nuget.config @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/VideoDubbing/readme.md b/VideoDubbing/readme.md new file mode 100644 index 00000000..46f6a04a --- /dev/null +++ b/VideoDubbing/readme.md @@ -0,0 +1,19 @@ +# Video Dubbing + +Video dubbing client tool and API sample code + +# Solution: + [VideoTranslationApiSampleCode.sln](CSharp\APIClientTool\VideoTranslationApiSampleCode\VideoTranslationApiSampleCode.sln) + + +# API sample: + +## Video file API: + Metadata API: [VideoFileClient.cs](CSharp\APIClientTool\VideoTranslationApiSampleCode\VideoTranslationLib\VideoTranslationMetadataClient.cs) + + Video file API: [VideoFileClient.cs](CSharp\APIClientTool\VideoTranslationApiSampleCode\VideoTranslationLib\VideoFileClient.cs) + + Translation API: [VideoTranslationClient.cs](CSharp\APIClientTool\VideoTranslationApiSampleCode\VideoTranslationLib\VideoTranslationClient.cs) + + Target locale API: [TargetLocaleClient.cs](CSharp\APIClientTool\VideoTranslationApiSampleCode\VideoTranslationLib\TargetLocaleClient.cs) + From ecf43e0be71428068789c8a3aa407b26ca860713 Mon Sep 17 00:00:00 2001 From: Dapeng Li Date: Wed, 10 Apr 2024 13:36:02 +0800 Subject: [PATCH 2/2] i --- .../VideoTranslationLib/VideoTranslationConstant.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs index 2b3b6e8c..9c3efa95 100644 --- a/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs +++ b/VideoDubbing/CSharp/APIClientTool/VideoTranslationApiSampleCode/VideoTranslationLib/VideoTranslationConstant.cs @@ -15,8 +15,5 @@ public static class VideoTranslationConstant DeploymentEnvironment.DevelopEUS, DeploymentEnvironment.CanaryUSCX, DeploymentEnvironment.ProductionEUS, - - // This region doesn't support GPT. - DeploymentEnvironment.ProductionWEU, }; }