Help us understand the problem. What is going on with this article?

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を選択

ScreenShot 2020-05-04 9.46.18.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】ライブ配信 お手軽設定のススメ

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してくれるといいなと思います。

ktsuchi
動画配信について色々と勉強中。
cloudpack
Amazon Web Services (AWS) の導入設計、環境構築、運用・保守をサポートするマネジドホスティングサービス
https://cloudpack.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした