aws / aws-sdk

Landing page for the AWS SDKs on GitHub
https://aws.amazon.com/tools/
Other
72 stars 14 forks source link

C# and .NET version of MediaConvert, GetJobAsync method - documentation issue #571

Closed Bellarmine-Head closed 1 year ago

Bellarmine-Head commented 1 year ago

Describe the issue

The C# Amazon.MediaConvert.AmazonMediaConvertClient.GetJobAsync method says in its comments:-

        //
        // Summary:
        //     Retrieve the JSON for a specific completed transcoding job.
        //

I would like to emphasize the word "completed" here.

I was worried about this, because I wanted to call GetJobAsync every 2 or 3 seconds to get an updated status... but the summary suggests that the method will only work for completed jobs.

I tried it anyway, and got "PROGRESSING" a few times before I got "COMPLETED".

So method works for completed and also ongoing transcoding jobs! Perhaps the documentation could be updated to reflect that.

Links

Can't find a link to a .NET page, but here's one that's very similar and illustrates the problem....

https://docs.aws.amazon.com/mediaconvert/latest/apireference/jobs-id.html#jobs-idget

ashishdhingra commented 1 year ago

@Bellarmine-Head Good morning. Thanks for reporting the issue. The said AWS SDK for .NET API documentation is generated from the service model https://github.com/aws/aws-sdk-net/blob/ddb9b60e8b804f561ffb5415eb729ecc3ca2582a/generator/ServiceModels/mediaconvert/mediaconvert-2017-08-29.docs.json#L17C6-L17C12

Looks like as pointed by you, the same issue is present in Media Convert API reference at https://docs.aws.amazon.com/mediaconvert/latest/apireference/jobs-id.html#jobs-idget. I was able to reproduce your use case following below example (snippet taken from https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/MediaConvert/Scenarios/CreateJob): MediaConvertWrapper.cs

using System;
using Amazon.MediaConvert;
using Amazon.MediaConvert.Model;

namespace MediaConvertTest
{
    public class MediaConvertWrapper
    {
        private readonly IAmazonMediaConvert _amazonMediaConvert;

        /// <summary>
        /// Constructor for the MediaConvert wrapper.
        /// </summary>
        /// <param name="amazonMediaConvert">The injected MediaConvert client.</param>
        public MediaConvertWrapper(IAmazonMediaConvert amazonMediaConvert)
        {
            _amazonMediaConvert = amazonMediaConvert;
        }

        // snippet-start:[MediaConvert.dotnetv3.CreateJob]

        /// <summary>
        /// Create a job to convert a media file.
        /// </summary>
        /// <param name="mediaConvertRole">The Amazon Resource Name (ARN) of the media convert role, as specified here:
        /// https://docs.aws.amazon.com/mediaconvert/latest/ug/creating-the-iam-role-in-mediaconvert-configured.html</param>
        /// <param name="fileInput">The Amazon Simple Storage Service (Amazon S3) location of the input media file.</param>
        /// <param name="fileOutput">The Amazon S3 location for the output media file.</param>
        /// <returns>The ID of the new job.</returns>
        public async Task<string> CreateJob(string mediaConvertRole, string fileInput,
            string fileOutput)
        {
            CreateJobRequest createJobRequest = new CreateJobRequest
            {
                Role = mediaConvertRole
            };

            createJobRequest.UserMetadata.Add("Customer", "Amazon");

            JobSettings jobSettings = new JobSettings
            {
                AdAvailOffset = 0,
                TimecodeConfig = new TimecodeConfig
                {
                    Source = TimecodeSource.EMBEDDED
                }
            };
            createJobRequest.Settings = jobSettings;

            #region OutputGroup

            OutputGroup ofg = new OutputGroup
            {
                Name = "File Group",
                OutputGroupSettings = new OutputGroupSettings
                {
                    Type = OutputGroupType.FILE_GROUP_SETTINGS,
                    FileGroupSettings = new FileGroupSettings
                    {
                        Destination = fileOutput
                    }
                }
            };

            Output output = new Output
            {
                NameModifier = "_1"
            };

            #region VideoDescription

            VideoDescription vdes = new VideoDescription
            {
                ScalingBehavior = ScalingBehavior.DEFAULT,
                TimecodeInsertion = VideoTimecodeInsertion.DISABLED,
                AntiAlias = AntiAlias.ENABLED,
                Sharpness = 50,
                AfdSignaling = AfdSignaling.NONE,
                DropFrameTimecode = DropFrameTimecode.ENABLED,
                RespondToAfd = RespondToAfd.NONE,
                ColorMetadata = ColorMetadata.INSERT,
                CodecSettings = new VideoCodecSettings
                {
                    Codec = VideoCodec.H_264
                }
            };
            output.VideoDescription = vdes;

            H264Settings h264 = new H264Settings
            {
                InterlaceMode = H264InterlaceMode.PROGRESSIVE,
                NumberReferenceFrames = 3,
                Syntax = H264Syntax.DEFAULT,
                Softness = 0,
                GopClosedCadence = 1,
                GopSize = 90,
                Slices = 1,
                GopBReference = H264GopBReference.DISABLED,
                SlowPal = H264SlowPal.DISABLED,
                SpatialAdaptiveQuantization = H264SpatialAdaptiveQuantization.ENABLED,
                TemporalAdaptiveQuantization = H264TemporalAdaptiveQuantization.ENABLED,
                FlickerAdaptiveQuantization = H264FlickerAdaptiveQuantization.DISABLED,
                EntropyEncoding = H264EntropyEncoding.CABAC,
                Bitrate = 5000000,
                FramerateControl = H264FramerateControl.SPECIFIED,
                RateControlMode = H264RateControlMode.CBR,
                CodecProfile = H264CodecProfile.MAIN,
                Telecine = H264Telecine.NONE,
                MinIInterval = 0,
                AdaptiveQuantization = H264AdaptiveQuantization.HIGH,
                CodecLevel = H264CodecLevel.AUTO,
                FieldEncoding = H264FieldEncoding.PAFF,
                SceneChangeDetect = H264SceneChangeDetect.ENABLED,
                QualityTuningLevel = H264QualityTuningLevel.SINGLE_PASS,
                FramerateConversionAlgorithm =
                    H264FramerateConversionAlgorithm.DUPLICATE_DROP,
                UnregisteredSeiTimecode = H264UnregisteredSeiTimecode.DISABLED,
                GopSizeUnits = H264GopSizeUnits.FRAMES,
                ParControl = H264ParControl.SPECIFIED,
                NumberBFramesBetweenReferenceFrames = 2,
                RepeatPps = H264RepeatPps.DISABLED,
                FramerateNumerator = 30,
                FramerateDenominator = 1,
                ParNumerator = 1,
                ParDenominator = 1
            };
            output.VideoDescription.CodecSettings.H264Settings = h264;

            #endregion VideoDescription

            #region AudioDescription

            AudioDescription ades = new AudioDescription
            {
                LanguageCodeControl = AudioLanguageCodeControl.FOLLOW_INPUT,
                // This name matches one specified in the following Inputs.
                AudioSourceName = "Audio Selector 1",
                CodecSettings = new AudioCodecSettings
                {
                    Codec = AudioCodec.AAC
                }
            };

            AacSettings aac = new AacSettings
            {
                AudioDescriptionBroadcasterMix = AacAudioDescriptionBroadcasterMix.NORMAL,
                RateControlMode = AacRateControlMode.CBR,
                CodecProfile = AacCodecProfile.LC,
                CodingMode = AacCodingMode.CODING_MODE_2_0,
                RawFormat = AacRawFormat.NONE,
                SampleRate = 48000,
                Specification = AacSpecification.MPEG4,
                Bitrate = 64000
            };
            ades.CodecSettings.AacSettings = aac;
            output.AudioDescriptions.Add(ades);

            #endregion AudioDescription

            #region Mp4 Container

            output.ContainerSettings = new ContainerSettings
            {
                Container = ContainerType.MP4
            };
            Mp4Settings mp4 = new Mp4Settings
            {
                CslgAtom = Mp4CslgAtom.INCLUDE,
                FreeSpaceBox = Mp4FreeSpaceBox.EXCLUDE,
                MoovPlacement = Mp4MoovPlacement.PROGRESSIVE_DOWNLOAD
            };
            output.ContainerSettings.Mp4Settings = mp4;

            #endregion Mp4 Container

            ofg.Outputs.Add(output);
            createJobRequest.Settings.OutputGroups.Add(ofg);

            #endregion OutputGroup

            #region Input

            Input input = new Input
            {
                FilterEnable = InputFilterEnable.AUTO,
                PsiControl = InputPsiControl.USE_PSI,
                FilterStrength = 0,
                DeblockFilter = InputDeblockFilter.DISABLED,
                DenoiseFilter = InputDenoiseFilter.DISABLED,
                TimecodeSource = InputTimecodeSource.EMBEDDED,
                FileInput = fileInput
            };

            AudioSelector audsel = new AudioSelector
            {
                Offset = 0,
                DefaultSelection = AudioDefaultSelection.NOT_DEFAULT,
                ProgramSelection = 1,
                SelectorType = AudioSelectorType.TRACK
            };
            audsel.Tracks.Add(1);
            input.AudioSelectors.Add("Audio Selector 1", audsel);

            input.VideoSelector = new VideoSelector
            {
                ColorSpace = ColorSpace.FOLLOW
            };

            createJobRequest.Settings.Inputs.Add(input);

            #endregion Input

            var jobId = "";
            try
            {
                CreateJobResponse createJobResponse =
                    await _amazonMediaConvert.CreateJobAsync(createJobRequest);

                jobId = createJobResponse.Job.Id;
            }
            catch (BadRequestException bre)
            {
                // If the endpoint was bad.
                if (bre.Message.StartsWith("You must use the customer-"))
                {
                    // The exception contains the correct endpoint; extract it.
                    var mediaConvertEndpoint = bre.Message.Split('\'')[1];
                    Console.WriteLine(
                        $"Request failed, please use endpoint {mediaConvertEndpoint}.");
                }
                else
                    throw;
            }

            return jobId;
        }
        // snippet-end:[MediaConvert.dotnetv3.CreateJob]

        // snippet-start:[MediaConvert.dotnetv3.ListJobs]
        /// <summary>
        /// List all of the jobs with a particular status using a paginator.
        /// </summary>
        /// <param name="status">The status to use when listing jobs.</param>
        /// <returns>The list of jobs matching the status.</returns>
        public async Task<List<Job>> ListAllJobsByStatus(JobStatus? status = null)
        {
            var returnedJobs = new List<Job>();

            var paginatedJobs = _amazonMediaConvert.Paginators.ListJobs(
                    new ListJobsRequest
                    {
                        Status = status
                    });

            // Get the entire list using the paginator.
            await foreach (var job in paginatedJobs.Jobs)
            {
                returnedJobs.Add(job);
            }

            return returnedJobs;
        }
        // snippet-end:[MediaConvert.dotnetv3.ListJobs]

        // snippet-start:[MediaConvert.dotnetv3.GetJob]
        /// <summary>
        /// Get the job information for a job by its ID.
        /// </summary>
        /// <param name="jobId">The ID of the job.</param>
        /// <returns>The Job object.</returns>
        public async Task<Job> GetJobById(string jobId)
        {
            var jobResponse = await _amazonMediaConvert.GetJobAsync(
                    new GetJobRequest
                    {
                        Id = jobId
                    });

            return jobResponse.Job;
        }
        // snippet-end:[MediaConvert.dotnetv3.GetJob]
    }
}

Program.cs

using Amazon.MediaConvert;
using Amazon.MediaConvert.Model;
using MediaConvertTest;

string fileInput = "s3://some-bucket/some-file.mov";
string fileOutput = "s3://some-bucket/some-file_converted.mp4";
string mediaConvertRole = "arn:aws:iam::<<account-id>>:role/testmediaconvertrole";

Console.WriteLine("Welcome to the MediaConvert Create Job example.");
Console.WriteLine("Getting customer-specific MediaConvert endpoint.");
AmazonMediaConvertClient client = new AmazonMediaConvertClient();
DescribeEndpointsRequest describeRequest = new DescribeEndpointsRequest();
DescribeEndpointsResponse describeResponse = await client.DescribeEndpointsAsync(describeRequest);
string mediaConvertEndpoint = describeResponse.Endpoints[0].Url;

Console.WriteLine(new string('-', 80));
Console.WriteLine($"Using endpoint {mediaConvertEndpoint}.");
Console.WriteLine(new string('-', 80));
// Because you have a service URL for MediaConvert, you don't
// need to set RegionEndpoint. If you do, the ServiceURL will
// be overwritten.
AmazonMediaConvertConfig mcConfig = new AmazonMediaConvertConfig
{
    ServiceURL = mediaConvertEndpoint,
};

AmazonMediaConvertClient mcClient = new AmazonMediaConvertClient(mcConfig);

var wrapper = new MediaConvertWrapper(mcClient);

Console.WriteLine(new string('-', 80));
Console.WriteLine($"Creating job for input file {fileInput}.");
var jobId = await wrapper.CreateJob(mediaConvertRole!, fileInput!, fileOutput!);
Console.WriteLine($"Created job with Job ID: {jobId}");
Console.WriteLine(new string('-', 80));
// snippet-end:[MediaConvert.dotnetv3.CreateJobSetup]

// snippet-start:[MediaConvert.dotnetv3.GetJobSetup]
Console.WriteLine(new string('-', 80));
Console.WriteLine($"Getting job information for Job ID {jobId}");
var job = await wrapper.GetJobById(jobId);
Console.WriteLine($"Job {job.Id} created on {job.CreatedAt:d} has status {job.Status}.");
Console.WriteLine(new string('-', 80));
// snippet-end:[MediaConvert.dotnetv3.GetJobSetup]

// snippet-start:[MediaConvert.dotnetv3.ListJobsSetup]
Console.WriteLine(new string('-', 80));
Console.WriteLine($"Listing all complete jobs.");
var completeJobs = await wrapper.ListAllJobsByStatus(JobStatus.COMPLETE);
completeJobs.ForEach(j =>
{
    Console.WriteLine($"Job {j.Id} created on {j.CreatedAt:d} has status {j.Status}.");
});
// snippet-end:[MediaConvert.dotnetv3.ListJobsSetup]

Console.WriteLine(new string('-', 80));
Console.WriteLine("MediaConvert Create Job example complete.");
Console.WriteLine(new string('-', 80));
Welcome to the MediaConvert Create Job example.
Getting customer-specific MediaConvert endpoint.
--------------------------------------------------------------------------------
Using endpoint https://<<some-id>>.mediaconvert.us-east-2.amazonaws.com.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Creating job for input file s3://some-bucket/some-file.mov.
Created job with Job ID: 1691087883440-u4o7yv
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Getting job information for Job ID 1691087883440-u4o7yv
Job 1691087883440-u4o7yv created on 8/3/2023 has status PROGRESSING.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Listing all complete jobs.
--------------------------------------------------------------------------------
MediaConvert Create Job example complete.
--------------------------------------------------------------------------------

I will open an issue with MediaConvert service team for review.

Thanks, Ashish

ashishdhingra commented 1 year ago

P95945073

ashishdhingra commented 1 year ago

@Bellarmine-Head This appears to have been fixed at the following places:

github-actions[bot] commented 1 year ago

This issue is now closed.

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.