AWS MediaLiveチャンネルの複製方法

#はじめに
本記事では、AWS MediaLiveチャンネルの複製方法について記載します。

既に作成済みのチャンネルと同じ設定のチャンネルが必要になるケースがあるかと思います。
AWSマネジメントコンソールで手動でポチポチと設定するのは面倒なので、下記の方法が有効と考えています。

おそらく、もっと効率的な方法はあるかと思いますが、
備忘録として記載しておきます。

#チャンネル作成方法
改めて整理しますと、MediaLiveチャンネルの作成方法には、
以下の3つの方法があります。

1. AWSマネジメントコンソール
2. AWS CLI
3. CloudFormation

今回は、**1の方法**で作成したMediaLiveチャンネルを
**2の方法**で複製する場合を想定しています。

##JSONダウンロード

まず、AWSマネジメントコンソールから、
MediaLiveチャンネルのJSONファイルをダウンロードします。

- MediaLiveチャンネルを選択 - 右上の**Channel Actions**をクリック - **Download custom templete**を選択

<img width="339" alt="ScreenShot 2020-05-04 9.46.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277934/ee7ec857-2ef9-3129-139a-4f145911718a.png">

すると、「**[Channel Name].json**」というファイル名で保存されます。

```demo-live-channel.json
{
  "name": "demo-live-channel",
  "id": "5700952",
  "arn": "arn:aws:medialive:ap-northeast-1:XXXXXXXXXXXX:channel:5700952",
  "inputAttachments": [
    {
      "inputId": "5814266",
      "inputAttachmentName": "demo-input",
      "inputSettings": {
        "sourceEndBehavior": "CONTINUE",
        "inputFilter": "AUTO",
        "filterStrength": 1,
        "deblockFilter": "DISABLED",
        "denoiseFilter": "DISABLED",
        "audioSelectors": [],
        "captionSelectors": []
      }
    }
  ],
  "state": "IDLE",
  "pipelinesRunningCount": 0,
  "destinations": [
    {
      "id": "hlsdest",
      "settings": [
        {
          "url": "mediastoressl://XXXXXXXXXXXXXX.data.mediastore.ap-northeast-1.amazonaws.com/live/testA"
        },
        {
          "url": "mediastoressl://XXXXXXXXXXXXXX.data.mediastore.ap-northeast-1.amazonaws.com/live/testB"
        }
      ],
      "mediaPackageSettings": []
    }
  ],
  "egressEndpoints": [
    {
      "sourceIp": "XXX.XXX.XXX.XXX"
    },
    {
      "sourceIp": "XXX.XXX.XXX.XXX"
    }
  ],
  "encoderSettings": {
    "audioDescriptions": [
      {
        "codecSettings": {
          "aacSettings": {
            "inputType": "NORMAL",
            "bitrate": 128000,
            "codingMode": "CODING_MODE_2_0",
            "rawFormat": "NONE",
            "spec": "MPEG4",
            "profile": "LC",
            "rateControlMode": "CBR",
            "sampleRate": 48000
          }
        },
        "audioTypeControl": "FOLLOW_INPUT",
        "languageCodeControl": "FOLLOW_INPUT",
        "name": "audio_2000k"
      }
    ],
    "captionDescriptions": [],
    "outputGroups": [
      {
        "outputGroupSettings": {
          "hlsGroupSettings": {
            "adMarkers": [],
            "captionLanguageSetting": "OMIT",
            "captionLanguageMappings": [],
            "hlsCdnSettings": {
              "hlsMediaStoreSettings": {
                "numRetries": 10,
                "connectionRetryInterval": 1,
                "restartDelay": 15,
                "filecacheDuration": 300,
                "mediaStoreStorageClass": "TEMPORAL"
              }
            },
            "inputLossAction": "PAUSE_OUTPUT",
            "manifestCompression": "NONE",
            "destination": {
              "destinationRefId": "hlsdest"
            },
            "ivInManifest": "INCLUDE",
            "ivSource": "FOLLOWS_SEGMENT_NUMBER",
            "clientCache": "ENABLED",
            "tsFileMode": "SEGMENTED_FILES",
            "manifestDurationFormat": "FLOATING_POINT",
            "segmentationMode": "USE_SEGMENT_DURATION",
            "redundantManifest": "ENABLED",
            "outputSelection": "MANIFESTS_AND_SEGMENTS",
            "streamInfResolution": "INCLUDE",
            "iFrameOnlyPlaylists": "DISABLED",
            "indexNSegments": 15,
            "programDateTime": "EXCLUDE",
            "programDateTimePeriod": 600,
            "keepSegments": 30,
            "segmentLength": 2,
            "timedMetadataId3Frame": "PRIV",
            "timedMetadataId3Period": 10,
            "hlsId3SegmentTagging": "DISABLED",
            "codecSpecification": "RFC_4281",
            "directoryStructure": "SINGLE_DIRECTORY",
            "segmentsPerSubdirectory": 10000,
            "mode": "LIVE"
          }
        },
        "name": "HLS Group",
        "outputs": [
          {
            "outputSettings": {
              "hlsOutputSettings": {
                "nameModifier": "_2000k",
                "hlsSettings": {
                  "standardHlsSettings": {
                    "m3u8Settings": {
                      "audioFramesPerPes": 4,
                      "audioPids": "492-498",
                      "nielsenId3Behavior": "NO_PASSTHROUGH",
                      "pcrControl": "PCR_EVERY_PES_PACKET",
                      "pmtPid": "480",
                      "programNum": 1,
                      "scte35Pid": "500",
                      "scte35Behavior": "NO_PASSTHROUGH",
                      "timedMetadataPid": "502",
                      "timedMetadataBehavior": "NO_PASSTHROUGH",
                      "videoPid": "481"
                    },
                    "audioRenditionSets": "program_audio"
                  }
                },
                "h265PackagingType": "HVC1"
              }
            },
            "outputName": "2000k",
            "videoDescriptionName": "video_2000k",
            "audioDescriptionNames": [
              "audio_2000k"
            ],
            "captionDescriptionNames": []
          }
        ]
      }
    ],
    "timecodeConfig": {
      "source": "EMBEDDED"
    },
    "videoDescriptions": [
      {
        "codecSettings": {
          "h264Settings": {
            "afdSignaling": "NONE",
            "colorMetadata": "INSERT",
            "adaptiveQuantization": "MEDIUM",
            "bitrate": 2000000,
            "entropyEncoding": "CABAC",
            "flickerAq": "ENABLED",
            "forceFieldPictures": "DISABLED",
            "framerateControl": "SPECIFIED",
            "framerateNumerator": 30,
            "framerateDenominator": 1,
            "gopBReference": "DISABLED",
            "gopClosedCadence": 1,
            "gopNumBFrames": 2,
            "gopSize": 60,
            "gopSizeUnits": "FRAMES",
            "subgopLength": "FIXED",
            "scanType": "PROGRESSIVE",
            "level": "H264_LEVEL_AUTO",
            "lookAheadRateControl": "MEDIUM",
            "numRefFrames": 1,
            "parControl": "INITIALIZE_FROM_SOURCE",
            "profile": "MAIN",
            "rateControlMode": "CBR",
            "syntax": "DEFAULT",
            "sceneChangeDetect": "ENABLED",
            "spatialAq": "ENABLED",
            "temporalAq": "ENABLED",
            "timecodeInsertion": "DISABLED"
          }
        },
        "height": 720,
        "name": "video_2000k",
        "respondToAfd": "NONE",
        "sharpness": 50,
        "scalingBehavior": "DEFAULT",
        "width": 1280
      }
    ]
  },
  "roleArn": "arn:aws:iam::XXXXXXXXXXXX:role/MediaLiveAccessRole",
  "inputSpecification": {
    "codec": "AVC",
    "resolution": "HD",
    "maximumBitrate": "MAX_20_MBPS"
  },
  "logLevel": "INFO",
  "tags": {},
  "channelClass": "STANDARD",
  "pipelineDetails": []
}
```

ちなみに、ここでの設定は、過去記事を元にしています。

[ 【AWS】ライブ配信 お手軽設定のススメ](https://qiita.com/ktsuchi/items/bf53a9054c5c0fd83bbe)

## AWS CLIにて実行すると…



このJSONファイルを使って、AWS CLIにてチャンネル作成（create-channel）を実施すると、
下記のようなエラーが出力されてしまいます。

```
$ aws medialive create-channel --region ap-northeast-1 --cli-input-json file://demo-live-channel.json

Parameter validation failed:
Unknown parameter in input: "inputAttachments", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "pipelineDetails", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "name", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "tags", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "logLevel", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "roleArn", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "state", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "egressEndpoints", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "inputSpecification", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "pipelinesRunningCount", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "channelClass", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "encoderSettings", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "id", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "arn", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "destinations", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in EncoderSettings.VideoDescriptions[0].CodecSettings.H264Settings: "ForceFieldPictures", must be one of: AdaptiveQuantization, AfdSignaling, Bitrate, BufFillPct, BufSize, ColorMetadata, ColorSpaceSettings, EntropyEncoding, FixedAfd, FlickerAq, FramerateControl, FramerateDenominator, FramerateNumerator, GopBReference, GopClosedCadence, GopNumBFrames, GopSize, GopSizeUnits, Level, LookAheadRateControl, MaxBitrate, MinIInterval, NumRefFrames, ParControl, ParDenominator, ParNumerator, Profile, QvbrQualityLevel, RateControlMode, ScanType, SceneChangeDetect, Slices, Softness, SpatialAq, SubgopLength, Syntax, TemporalAq, TimecodeInsertion
```

JSONファイルの中身をよく見ると、
本来は大文字で始まらないといけないパラメータの最初の文字が全て**小文字**になっています。

これが原因で、Parameter validation failedのエラーとなっているようです。
（2020年5月6日現在も、パラメータの先頭文字が小文字になる事象は変わっていません）

## JSONファイルの修正

### 1. 先頭文字を大文字に変換させる

全てのパラメータを1つずつ大文字に替えるのは面倒で、時間がかかってしまうので、
ややアナログなやり方ですが、今回はExcelを使う方法を採用します。

Excelファイルが貼り付けられないので、簡単に説明します。

#### Excel作成方法

・B1セル：=MID(A1,1,1)
・C1セル：=MID(A1,2,1)
・D1セル：=PROPER(C1)
・E1セル：=MID(A1,3,500)
・F1セル：=B1&D1&E1

A1~F1を選択して、下の行にコピーする。
（500行くらいまでコピーすれば十分かと思います）

#### Excel使用方法

1. JSONファイルのテキストを全選択して、コピーする。
2. ExcelのA1セルを選択する。そこで、1のテキストをペーストする。
3. 先頭文字が大文字に変換されているF列のテキストをコピーする。
4. 新しいテキストファイルにペーストして、保存する。

#####補足
- MID関数：文字列の任意の位置から指定された文字数の文字を抽出する
- PROPER関数：文字列の英字の先頭文字を大文字に変換する

##### 注意点

「AudioDescriptionNames」というパラメータの設定値が、
大文字に変換させる際に、一緒に大文字になってしまうので、必要に応じて手動で適切に変更してください。

######例：

- Before

            "audioDescriptionNames": [
              "audio_2000k"
            ],

- After

            "AudioDescriptionNames": [
              "Audio_2000k"
            ],

### 2. パラメータを削除する

この時点で、AWS CLIにてチャンネル作成（create-channel）を実行すると、
下記のようなエラーとなります。

```
Parameter validation failed:
Unknown parameter in input: "PipelineDetails", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "State", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "EgressEndpoints", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "PipelinesRunningCount", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "Id", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in input: "Arn", must be one of: ChannelClass, Destinations, EncoderSettings, InputAttachments, InputSpecification, LogLevel, Name, RequestId, Reserved, RoleArn, Tags
Unknown parameter in EncoderSettings.VideoDescriptions[0].CodecSettings.H264Settings: "ForceFieldPictures", must be one of: AdaptiveQuantization, AfdSignaling, Bitrate, BufFillPct, BufSize, ColorMetadata, ColorSpaceSettings, EntropyEncoding, FixedAfd, FlickerAq, FramerateControl, FramerateDenominator, FramerateNumerator, GopBReference, GopClosedCadence, GopNumBFrames, GopSize, GopSizeUnits, Level, LookAheadRateControl, MaxBitrate, MinIInterval, NumRefFrames, ParControl, ParDenominator, ParNumerator, Profile, QvbrQualityLevel, RateControlMode, ScanType, SceneChangeDetect, Slices, Softness, SpatialAq, SubgopLength, Syntax, TemporalAq, TimecodeInsertion
```

ここに表示されている、Unknownなパラメータを削除する必要があります。
下記の通りです。

* Id
* Arn
* State
* PipelinesRunningCount
* EgressEndpoints
* PipelineDetails
* ForceFieldPictures

###3. パラメータを修正する

また、設定値を修正しないといけないパラメータがあります。

既存のMediaLive情報と重複させないため、**チャンネル名**と**MediaLive input**の設定値を変更します。
（複製するチャンネルにアタッチするinputは、事前に作成しておいてください）

   - Name
   - InputId
   - InputAttachmentName

###4. パラメータを追加する

複製元のMediaLiveチャンネルで「Audio Selectors」を設定していない場合、
下記のようなエラーが出ているかと思います。

```
Missing required parameter in EncoderSettings.AudioDescriptions[0]: "AudioSelectorName"
```

必須パラメータが足りていないことによるエラーなので、
JSONに下記のパラメータおよび設定値を追記する必要があります。

* AudioSelectorName

JSONファイル内の **EncoderSettings** に追記します。

######例：

> "AudioSelectorName": "test",

```
"EncoderSettings": {
"AudioDescriptions": [
{
"AudioSelectorName": "test",
"CodecSettings": {
"AacSettings": {
"InputType": "NORMAL",
"Bitrate": 128000,
"CodingMode": "CODING_MODE_2_0",
"RawFormat": "NONE",
"Spec": "MPEG4",
"Profile": "LC",
"RateControlMode": "CBR",
"SampleRate": 48000
}
},
```

##修正後のJSONファイル

- 先頭の小文字 → 大文字
- パラメータ削除・変更・追加

これらを修正した後のJSONファイルは下記のようになります。

```demo-live-channel-2.json
{
  "Name": "demo-live-channel-2",
  "InputAttachments": [
  {
  "InputId": "7275460",
  "InputAttachmentName": "demo-input-2",
  "InputSettings": {
  "SourceEndBehavior": "CONTINUE",
  "InputFilter": "AUTO",
  "FilterStrength": 1,
  "DeblockFilter": "DISABLED",
  "DenoiseFilter": "DISABLED",
  "AudioSelectors": [],
  "CaptionSelectors": []
  }
  }
  ],
  "Destinations": [
  {
  "Id": "hlsdest",
  "Settings": [
  {
  "Url": "mediastoressl://XXXXXXXXXXXXXX.data.mediastore.ap-northeast-1.amazonaws.com/live/testA"
  },
  {
  "Url": "mediastoressl://XXXXXXXXXXXXXX.data.mediastore.ap-northeast-1.amazonaws.com/live/testB"
  }
  ],
  "MediaPackageSettings": []
  }
  ],
  "EncoderSettings": {
  "AudioDescriptions": [
  {
  "AudioSelectorName": "test",
  "CodecSettings": {
  "AacSettings": {
  "InputType": "NORMAL",
  "Bitrate": 128000,
  "CodingMode": "CODING_MODE_2_0",
  "RawFormat": "NONE",
  "Spec": "MPEG4",
  "Profile": "LC",
  "RateControlMode": "CBR",
  "SampleRate": 48000
  }
  },
  "AudioTypeControl": "FOLLOW_INPUT",
  "LanguageCodeControl": "FOLLOW_INPUT",
  "Name": "audio_2000k"
  }
  ],
  "CaptionDescriptions": [],
  "OutputGroups": [
  {
  "OutputGroupSettings": {
  "HlsGroupSettings": {
  "AdMarkers": [],
  "CaptionLanguageSetting": "OMIT",
  "CaptionLanguageMappings": [],
  "HlsCdnSettings": {
  "HlsMediaStoreSettings": {
  "NumRetries": 10,
  "ConnectionRetryInterval": 1,
  "RestartDelay": 15,
  "FilecacheDuration": 300,
  "MediaStoreStorageClass": "TEMPORAL"
  }
  },
  "InputLossAction": "PAUSE_OUTPUT",
  "ManifestCompression": "NONE",
  "Destination": {
  "DestinationRefId": "hlsdest"
  },
  "IvInManifest": "INCLUDE",
  "IvSource": "FOLLOWS_SEGMENT_NUMBER",
  "ClientCache": "ENABLED",
  "TsFileMode": "SEGMENTED_FILES",
  "ManifestDurationFormat": "FLOATING_POINT",
  "SegmentationMode": "USE_SEGMENT_DURATION",
  "RedundantManifest": "ENABLED",
  "OutputSelection": "MANIFESTS_AND_SEGMENTS",
  "StreamInfResolution": "INCLUDE",
  "IFrameOnlyPlaylists": "DISABLED",
  "IndexNSegments": 15,
  "ProgramDateTime": "EXCLUDE",
  "ProgramDateTimePeriod": 600,
  "KeepSegments": 30,
  "SegmentLength": 2,
  "TimedMetadataId3Frame": "PRIV",
  "TimedMetadataId3Period": 10,
  "HlsId3SegmentTagging": "DISABLED",
  "CodecSpecification": "RFC_4281",
  "DirectoryStructure": "SINGLE_DIRECTORY",
  "SegmentsPerSubdirectory": 10000,
  "Mode": "LIVE"
  }
  },
  "Name": "HLS Group",
  "Outputs": [
  {
  "OutputSettings": {
  "HlsOutputSettings": {
  "NameModifier": "_2000k",
  "HlsSettings": {
  "StandardHlsSettings": {
  "M3u8Settings": {
  "AudioFramesPerPes": 4,
  "AudioPids": "492-498",
  "NielsenId3Behavior": "NO_PASSTHROUGH",
  "PcrControl": "PCR_EVERY_PES_PACKET",
  "PmtPid": "480",
  "ProgramNum": 1,
  "Scte35Pid": "500",
  "Scte35Behavior": "NO_PASSTHROUGH",
  "TimedMetadataPid": "502",
  "TimedMetadataBehavior": "NO_PASSTHROUGH",
  "VideoPid": "481"
  },
  "AudioRenditionSets": "program_audio"
  }
  },
  "H265PackagingType": "HVC1"
  }
  },
  "OutputName": "2000k",
  "VideoDescriptionName": "video_2000k",
  "AudioDescriptionNames": [
  "audio_2000k"
  ],
  "CaptionDescriptionNames": []
  }
  ]
  }
  ],
  "TimecodeConfig": {
  "Source": "EMBEDDED"
  },
  "VideoDescriptions": [
  {
  "CodecSettings": {
  "H264Settings": {
  "AfdSignaling": "NONE",
  "ColorMetadata": "INSERT",
  "AdaptiveQuantization": "MEDIUM",
  "Bitrate": 2000000,
  "EntropyEncoding": "CABAC",
  "FlickerAq": "ENABLED",
  "FramerateControl": "SPECIFIED",
  "FramerateNumerator": 30,
  "FramerateDenominator": 1,
  "GopBReference": "DISABLED",
  "GopClosedCadence": 1,
  "GopNumBFrames": 2,
  "GopSize": 60,
  "GopSizeUnits": "FRAMES",
  "SubgopLength": "FIXED",
  "ScanType": "PROGRESSIVE",
  "Level": "H264_LEVEL_AUTO",
  "LookAheadRateControl": "MEDIUM",
  "NumRefFrames": 1,
  "ParControl": "INITIALIZE_FROM_SOURCE",
  "Profile": "MAIN",
  "RateControlMode": "CBR",
  "Syntax": "DEFAULT",
  "SceneChangeDetect": "ENABLED",
  "SpatialAq": "ENABLED",
  "TemporalAq": "ENABLED",
  "TimecodeInsertion": "DISABLED"
  }
  },
  "Height": 720,
  "Name": "video_2000k",
  "RespondToAfd": "NONE",
  "Sharpness": 50,
  "ScalingBehavior": "DEFAULT",
  "Width": 1280
  }
  ]
  },
  "RoleArn": "arn:aws:iam::XXXXXXXXXXXX:role/MediaLiveAccessRole",
  "InputSpecification": {
  "Codec": "AVC",
  "Resolution": "HD",
  "MaximumBitrate": "MAX_20_MBPS"
  },
  "LogLevel": "INFO",
  "Tags": {},
  "ChannelClass": "STANDARD"
  }
```

##再度、AWS CLIで実行する

```
$ aws medialive create-channel --region ap-northeast-1 --cli-input-json file://demo-live-channel-2.json
```

#おわりに
初めてJSONファイルからチャンネル複製したときには、
難所が多くて、ハマってしまったので、この記事を書きました。

使いやすいJSONファイルが生成されるよう、AWS側でUpdateしてくれるといいなと思います。