From 3781d605a40adaa6a8df759a49e48ac25435b8e2 Mon Sep 17 00:00:00 2001 From: jinyangyang222 Date: Fri, 13 Dec 2024 17:25:52 +0800 Subject: [PATCH] feat(live/transcoding): transcoding resource support new fields --- docs/resources/live_transcoding.md | 71 ++++++++++- ...ource_huaweicloud_live_transcoding_test.go | 94 +++++++++++---- .../resource_huaweicloud_live_transcoding.go | 113 ++++++++++++++++-- 3 files changed, 243 insertions(+), 35 deletions(-) diff --git a/docs/resources/live_transcoding.md b/docs/resources/live_transcoding.md index f0e4701b86..b437098dda 100644 --- a/docs/resources/live_transcoding.md +++ b/docs/resources/live_transcoding.md @@ -50,15 +50,22 @@ Changing this parameter will create a new resource. * `video_encoding` - (Required, String) Specifies the video codec. The valid values are **H264** and **H265**. -* `templates` - (Required, List) Specifies the video quality templates. -The [object](#templates_resource) structure is documented below. A maximum of `4` templates can be added. -For resolution and bitrate settings in the presets, -please refer to the [document](https://support.huaweicloud.com/intl/en-us/usermanual-live/live01000802.html). +* `templates` - (Required, List) Specifies the video quality templates. A maximum of `4` templates can be added. + The [templates](#transcoding_templates) structure is documented below. + For resolution and bitrate settings in the presets, + please refer to the [document](https://support.huaweicloud.com/intl/en-us/usermanual-live/live01000802.html). + +* `trans_type` - (Optional, String) Specifies the transcoding stream trigger mode. + The valid values are as follows: + + **play**: Pull stream triggers transcoding. + + **publish**: Push stream triggers transcoding. + + Defaults to **play**. * `low_bitrate_hd` - (Optional, Bool) Specifies whether to enable low bitrate HD rates. If enabled the output media will have a lower bitrate with the same image quality. Defaults to **false**. - + The `templates` block supports: * `name` - (Required, String) Specifies the template name. The name can contain a maximum of 64 characters, and only @@ -77,6 +84,40 @@ contains letters, digits and hyphens (-). * `frame_rate` - (Optional, Int) Specifies the frame rate of the transcoded video, in fps. Value range: `0` ~ `30`. Value 0 indicates that the frame rate remains unchanged. +* `protocol` - (Optional, String) Specifies the protocol type supported for transcoding output. + The valid value is **RTMP**. Defaults to **RTMP**. + +* `i_frame_interval` - (Optional, String) Specifies the maximum I-frame interval in frames. + The value ranges from `0` to `500`, includes `0` and `500`. Defaults to `50`. + + -> If you want to set the i-frame interval through `i_frame_interval`, please set the `gop` to `0` or do not pass the + `gop` parameter. + +* `gop` - (Optional, String) Specifies the interval time for I-frames, in seconds. + The value ranges from `0` to `10`, includes `0` and `10`. Defaults to `2`. + + -> When `gop` is not `0`, the i-frame interval is set with the `gop` parameter, and the `i_frame_interval` field does + not take effect. + +* `bitrate_adaptive` - (Optional, String) Specifies the adaptive bitrate. + The valid values are as follows: + + **off**: Disable rate adaptation and output the target rate according to the set rate. + + **minimum**: Output the target bitrate based on the minimum value of the set bitrate and source file bitrate. + + **adaptive**: Adaptive output of target bitrate based on source file bitrate. + + Defaults to **off**. + +* `i_frame_policy` - (Optional, String) Specifies the encoding output I-frame strategy. + The valid values are as follows: + + **auto**: I-frame output according to the set `gop` duration. + + **strictSync**: The encoded output I-frame is completely consistent with the source, and the `gop` parameter is + invalid after setting this value. + + Defaults to **auto**. + + -> In multi bitrate scenarios, it is recommended to enable I-frame random source to ensure alignment of multi bitrate + I-frames. + ## Attribute Reference In addition to all arguments above, the following attributes are exported: @@ -86,8 +127,26 @@ separated by a slash. ## Import -Transcodings can be imported using the `domain_name` and `app_name`, separated by a slash. e.g. +Transcoding can be imported using the `domain_name` and `app_name`, separated by a slash. e.g. ```bash $ terraform import huaweicloud_live_transcoding.test play.example.demo.com/live ``` + +Note that the imported state may not be identical to your resource definition, due to some attributes missing from the +API response, security or some other reason. The missing attributes include: `trans_type`. +It is generally recommended running `terraform plan` after importing the resource. +You can then decide if changes should be applied to the resource, or the resource definition should be updated to align +with the resource. Also, you can ignore changes as below. + +```hcl +resource "huaweicloud_live_transcoding" "test" { + ... + + lifecycle { + ignore_changes = [ + trans_type, + ] + } +} +``` diff --git a/huaweicloud/services/acceptance/live/resource_huaweicloud_live_transcoding_test.go b/huaweicloud/services/acceptance/live/resource_huaweicloud_live_transcoding_test.go index d17a9949b9..26720993df 100644 --- a/huaweicloud/services/acceptance/live/resource_huaweicloud_live_transcoding_test.go +++ b/huaweicloud/services/acceptance/live/resource_huaweicloud_live_transcoding_test.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/chnsz/golangsdk" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/live/v1/model" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" @@ -19,19 +21,32 @@ func getTranscodingResourceFunc(conf *config.Config, state *terraform.ResourceSt return nil, fmt.Errorf("error creating Live v1 client: %s", err) } - domain := state.Primary.Attributes["domain_name"] - appName := state.Primary.Attributes["app_name"] - return client.ShowTranscodingsTemplate(&model.ShowTranscodingsTemplateRequest{ - Domain: domain, - AppName: &appName, - }) + var ( + domain = state.Primary.Attributes["domain_name"] + appName = state.Primary.Attributes["app_name"] + getOpts = &model.ShowTranscodingsTemplateRequest{ + Domain: domain, + AppName: &appName, + } + ) + resp, err := client.ShowTranscodingsTemplate(getOpts) + if err != nil { + return nil, fmt.Errorf("error retrieving Live transcoding: %s", err) + } + + if resp.Templates == nil || len(*resp.Templates) == 0 { + return nil, golangsdk.ErrDefault404{} + } + + return resp, nil } func TestAccTranscoding_basic(t *testing.T) { - var obj model.ShowTranscodingsTemplateResponse - - pushDomainName := fmt.Sprintf("%s.huaweicloud.com", acceptance.RandomAccResourceNameWithDash()) - rName := "huaweicloud_live_transcoding.test" + var ( + obj model.ShowTranscodingsTemplateResponse + pushDomainName = fmt.Sprintf("%s.huaweicloud.com", acceptance.RandomAccResourceNameWithDash()) + rName = "huaweicloud_live_transcoding.test" + ) rc := acceptance.InitResourceCheck( rName, &obj, @@ -50,12 +65,18 @@ func TestAccTranscoding_basic(t *testing.T) { resource.TestCheckResourceAttr(rName, "domain_name", pushDomainName), resource.TestCheckResourceAttr(rName, "app_name", "live"), resource.TestCheckResourceAttr(rName, "video_encoding", "H264"), + resource.TestCheckResourceAttr(rName, "trans_type", "publish"), resource.TestCheckResourceAttr(rName, "low_bitrate_hd", "false"), resource.TestCheckResourceAttr(rName, "templates.#", "1"), resource.TestCheckResourceAttr(rName, "templates.0.name", "t1"), resource.TestCheckResourceAttr(rName, "templates.0.width", "300"), resource.TestCheckResourceAttr(rName, "templates.0.height", "400"), resource.TestCheckResourceAttr(rName, "templates.0.bitrate", "300"), + resource.TestCheckResourceAttr(rName, "templates.0.protocol", "RTMP"), + resource.TestCheckResourceAttr(rName, "templates.0.i_frame_interval", "500"), + resource.TestCheckResourceAttr(rName, "templates.0.gop", "0"), + resource.TestCheckResourceAttr(rName, "templates.0.bitrate_adaptive", "adaptive"), + resource.TestCheckResourceAttr(rName, "templates.0.i_frame_policy", "strictSync"), ), }, { @@ -64,12 +85,22 @@ func TestAccTranscoding_basic(t *testing.T) { resource.TestCheckResourceAttr(rName, "domain_name", pushDomainName), resource.TestCheckResourceAttr(rName, "app_name", "live"), resource.TestCheckResourceAttr(rName, "video_encoding", "H264"), + resource.TestCheckResourceAttr(rName, "trans_type", "play"), resource.TestCheckResourceAttr(rName, "low_bitrate_hd", "true"), resource.TestCheckResourceAttr(rName, "templates.#", "2"), + resource.TestCheckResourceAttr(rName, "templates.0.protocol", "RTMP"), + resource.TestCheckResourceAttr(rName, "templates.0.i_frame_interval", "0"), + resource.TestCheckResourceAttr(rName, "templates.0.gop", "10"), + resource.TestCheckResourceAttr(rName, "templates.0.bitrate_adaptive", "minimum"), + resource.TestCheckResourceAttr(rName, "templates.0.i_frame_policy", "auto"), resource.TestCheckResourceAttr(rName, "templates.1.name", "t2"), resource.TestCheckResourceAttr(rName, "templates.1.width", "600"), resource.TestCheckResourceAttr(rName, "templates.1.height", "800"), resource.TestCheckResourceAttr(rName, "templates.1.bitrate", "300"), + resource.TestCheckResourceAttr(rName, "templates.1.protocol", "RTMP"), + resource.TestCheckResourceAttr(rName, "templates.1.gop", "10"), + resource.TestCheckResourceAttr(rName, "templates.1.bitrate_adaptive", "off"), + resource.TestCheckResourceAttr(rName, "templates.1.i_frame_policy", "auto"), ), }, { @@ -77,6 +108,9 @@ func TestAccTranscoding_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: fmt.Sprintf("%s/live", pushDomainName), + ImportStateVerifyIgnore: []string{ + "trans_type", + }, }, }, }) @@ -93,12 +127,18 @@ resource "huaweicloud_live_transcoding" "test" { domain_name = huaweicloud_live_domain.ingestDomain.name app_name = "live" video_encoding = "H264" + trans_type = "publish" templates { - name = "t1" - width = 300 - height = 400 - bitrate = 300 + name = "t1" + width = 300 + height = 400 + bitrate = 300 + protocol = "RTMP" + i_frame_interval = 500 + gop = 0 + bitrate_adaptive = "adaptive" + i_frame_policy = "strictSync" } } `, pushDomainName) @@ -115,20 +155,30 @@ resource "huaweicloud_live_transcoding" "test" { domain_name = huaweicloud_live_domain.ingestDomain.name app_name = "live" video_encoding = "H264" + trans_type = "play" low_bitrate_hd = true templates { - name = "t1" - width = 300 - height = 400 - bitrate = 300 + name = "t1" + width = 300 + height = 400 + bitrate = 300 + protocol = "RTMP" + i_frame_interval = 0 + gop = 10 + bitrate_adaptive = "minimum" + i_frame_policy = "auto" } templates { - name = "t2" - width = 600 - height = 800 - bitrate = 300 + name = "t2" + width = 600 + height = 800 + bitrate = 300 + protocol = "RTMP" + gop = 10 + bitrate_adaptive = "off" + i_frame_policy = "auto" } } `, pushDomainName) diff --git a/huaweicloud/services/live/resource_huaweicloud_live_transcoding.go b/huaweicloud/services/live/resource_huaweicloud_live_transcoding.go index 920d7670dd..63923e74cb 100644 --- a/huaweicloud/services/live/resource_huaweicloud_live_transcoding.go +++ b/huaweicloud/services/live/resource_huaweicloud_live_transcoding.go @@ -83,16 +83,44 @@ func ResourceTranscoding() *schema.Resource { Type: schema.TypeInt, Required: true, }, - "frame_rate": { Type: schema.TypeInt, Optional: true, Computed: true, }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "i_frame_interval": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "gop": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "bitrate_adaptive": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "i_frame_policy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, }, }, - + // This field is not returned by API, so the Computed attribute is not added. + "trans_type": { + Type: schema.TypeString, + Optional: true, + }, "low_bitrate_hd": { Type: schema.TypeBool, Optional: true, @@ -262,6 +290,39 @@ func buildTranscodingParams(d *schema.ResourceData) (*model.StreamTranscodingTem VideoFrameRate: utils.Int32(int32(template["frame_rate"].(int))), Codec: &codec, Hdlb: &hdlb, + IFrameInterval: convertStringValueToInt32(template["i_frame_interval"].(string)), + Gop: convertStringValueToInt32(template["gop"].(string)), + } + + if protocol := template["protocol"].(string); protocol == "RTMP" { + rtmpValue := model.GetQualityInfoProtocolEnum().RTMP + qualityInfo[i].Protocol = &rtmpValue + } + + if bitrateAdaptive := template["bitrate_adaptive"].(string); bitrateAdaptive != "" { + minimumValue := model.GetQualityInfoBitrateAdaptiveEnum().MINIMUM + adaptiveValue := model.GetQualityInfoBitrateAdaptiveEnum().ADAPTIVE + switch bitrateAdaptive { + case "off": + // There is no enumeration value for **off** in the HuaweiCloud SDK, but the default value for the API + // is **off**, so this processing is done here. + qualityInfo[i].BitrateAdaptive = nil + case "minimum": + qualityInfo[i].BitrateAdaptive = &minimumValue + case "adaptive": + qualityInfo[i].BitrateAdaptive = &adaptiveValue + } + } + + if iFramePolicy := template["i_frame_policy"].(string); iFramePolicy != "" { + autoValue := model.GetQualityInfoIFramePolicyEnum().AUTO + strictSyncValue := model.GetQualityInfoIFramePolicyEnum().STRICT_SYNC + switch iFramePolicy { + case "auto": + qualityInfo[i].IFramePolicy = &autoValue + case "strictSync": + qualityInfo[i].IFramePolicy = &strictSyncValue + } } } @@ -270,6 +331,17 @@ func buildTranscodingParams(d *schema.ResourceData) (*model.StreamTranscodingTem AppName: d.Get("app_name").(string), QualityInfo: qualityInfo, } + if transType := d.Get("trans_type").(string); transType != "" { + playValue := model.GetStreamTranscodingTemplateTransTypeEnum().PLAY + publishValue := model.GetStreamTranscodingTemplateTransTypeEnum().PUBLISH + switch transType { + case "play": + req.TransType = &playValue + case "publish": + req.TransType = &publishValue + } + } + return &req, nil } @@ -279,11 +351,16 @@ func setTemplatesToState(d *schema.ResourceData, qualityInfo *[]model.QualityInf rst := make([]map[string]interface{}, len(qualitys)) for i, v := range qualitys { rst[i] = map[string]interface{}{ - "name": v.TemplateName, - "width": v.Width, - "height": v.Height, - "bitrate": v.Bitrate, - "frame_rate": v.VideoFrameRate, + "name": v.TemplateName, + "width": v.Width, + "height": v.Height, + "bitrate": v.Bitrate, + "frame_rate": v.VideoFrameRate, + "protocol": v.Protocol.Value(), + "i_frame_interval": parseInt32ValueToString(v.IFrameInterval), + "gop": parseInt32ValueToString(v.Gop), + "bitrate_adaptive": v.BitrateAdaptive.Value(), + "i_frame_policy": v.IFramePolicy.Value(), } } @@ -311,3 +388,25 @@ func parseTranscodingId(id string) (domainName string, appName string, err error } return idArrays[0], idArrays[1], nil } + +func convertStringValueToInt32(value string) *int32 { + if value == "0" { + return utils.Int32(0) + } + + parsedValue := utils.StringToInt(&value) + if parsedValue != nil { + //nolint:gosec + return utils.Int32IgnoreEmpty(int32(*parsedValue)) + } + + return nil +} + +func parseInt32ValueToString(value *int32) string { + if value != nil { + return fmt.Sprintf("%v", *value) + } + + return "" +}