├── .dockerignore
├── .gitignore
├── packages
├── transcoder
│ └── transcoder.go
├── database
│ ├── migration.go
│ └── postgres.go
├── mediaInfo
│ └── medfiainfo.go
├── ffmpeg
│ ├── executer.go
│ ├── core.go
│ ├── tools.go
│ └── ffmpeg.go
├── s3
│ └── minio.go
└── cache
│ └── redis.go
├── wait-for-it
├── .gitignore
├── test
│ ├── requirements.txt
│ ├── README.md
│ ├── container-runners.py
│ └── wait-for-it.py
├── .travis.yml
├── composer.json
├── LICENSE
├── README.md
└── wait-for-it.sh
├── .vscode
└── settings.json
├── images
├── logo.png
└── cover.png
├── transcoder
├── config.go
├── options.go
├── ffmpeg
│ ├── config.go
│ ├── progress.go
│ ├── options.go
│ ├── metadata.go
│ └── ffmpeg.go
├── progress.go
├── transcoder.go
├── utils
│ └── utils.go
└── metadata.go
├── .env
├── Dockerfile
├── models
├── frame-types.go
├── file-types.go
├── splash-type.go
└── api-types.go
├── interfaces
├── file-manager
│ └── filemanager.go
├── splash
│ └── splash.go
└── frame
│ └── frame.go
├── docker-compose.DEV.yml
├── config
└── config.go
├── go.mod
├── docker-compose.yml
├── main.go
├── controller
├── frame.controller.go
├── generator-controller.go
└── splash-controller.go
├── README.md
└── go.sum
/.dockerignore:
--------------------------------------------------------------------------------
1 | data
2 | sample-files/*.*
3 | .vscode
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | data/*
2 | sample-files/*.*
3 | .vscode
4 |
--------------------------------------------------------------------------------
/packages/transcoder/transcoder.go:
--------------------------------------------------------------------------------
1 | package executer
2 |
--------------------------------------------------------------------------------
/wait-for-it/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.pyc
2 | .pydevproject
3 | /vendor/
4 |
--------------------------------------------------------------------------------
/wait-for-it/test/requirements.txt:
--------------------------------------------------------------------------------
1 | docker>=4.0.0
2 | parameterized>=0.7.0
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "luma"
4 | ]
5 | }
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amupxm/go-video-concat/HEAD/images/logo.png
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amupxm/go-video-concat/HEAD/images/cover.png
--------------------------------------------------------------------------------
/transcoder/config.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Config ...
4 | type Config interface{}
5 |
--------------------------------------------------------------------------------
/wait-for-it/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 |
5 | script:
6 | - python test/wait-for-it.py
7 |
8 |
--------------------------------------------------------------------------------
/transcoder/options.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Options ...
4 | type Options interface {
5 | GetStrArguments() []string
6 | }
7 |
--------------------------------------------------------------------------------
/transcoder/ffmpeg/config.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | // Config ...
4 | type Config struct {
5 | FfmpegBinPath string
6 | FfprobeBinPath string
7 | ProgressEnabled bool
8 | Verbose bool
9 | }
10 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | dbhost=127.0.0.1
2 | dbname=postgres
3 | dbpass=1202212022AaAa
4 | dbport=5432
5 | dbusername=postgres
6 | Minio_user=AKIAIOSFODNN7EXAMPLE
7 | Minio_host=127.0.0.1:9000
8 | Minio_password=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
--------------------------------------------------------------------------------
/transcoder/progress.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Progress ...
4 | type Progress interface {
5 | GetFramesProcessed() string
6 | GetCurrentTime() string
7 | GetCurrentBitrate() string
8 | GetProgress() float64
9 | GetSpeed() string
10 | }
11 |
--------------------------------------------------------------------------------
/wait-for-it/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vishnubob/wait-for-it",
3 | "description": "Pure bash script to test and wait on the availability of a TCP host and port",
4 | "type": "library",
5 | "license": "MIT",
6 | "bin": ["wait-for-it.sh"]
7 | }
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.16.4
2 |
3 | WORKDIR /go/src/app/amupxm
4 |
5 | RUN apt-get update && apt-get install -y \
6 | ffmpeg \
7 | libmediainfo-dev \
8 | zlib* \
9 | gcc && rm -rf /var/lib/apt/lists/*
10 | COPY . .
11 |
12 | RUN go get .
13 |
14 |
15 | RUN go build -v .
16 |
17 | CMD [ "./go-video-concat" ]
--------------------------------------------------------------------------------
/packages/database/migration.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/amupxm/go-video-concat/models"
7 | )
8 |
9 | func AutoMigration() {
10 | if err := PostgresConnection.DBCli.AutoMigrate(
11 | &models.Frame{},
12 | &models.Splash{},
13 | ); err != nil {
14 | log.Panicln(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/models/frame-types.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type (
4 | Frame struct {
5 | ID uint `gorm:"primaryKey;index"`
6 | Name string `json:"name" binding:"required"`
7 | FileCode string `json:"file_code" binding:"required" gorm:"uniqueIndex;not null"`
8 | Height int `json:"height" binding:"required"`
9 | Width int `json:"width" binding:"required"`
10 | StartOffset int `json:"start_offset" binding:"required"`
11 | }
12 | )
13 |
--------------------------------------------------------------------------------
/models/file-types.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Suite int
4 |
5 | type (
6 | fileType uint
7 | )
8 |
9 | const (
10 | _ fileType = iota
11 | uploadFile
12 | downloadFile
13 | chunkFile
14 | templatesFrameFile
15 | outputFile
16 | )
17 |
18 | var (
19 | fileTypes = map[fileType]string{
20 | uploadFile: "upload",
21 | downloadFile: "download",
22 | chunkFile: "chunk",
23 | templatesFrameFile: "template",
24 | outputFile: "output",
25 | }
26 | )
27 |
--------------------------------------------------------------------------------
/transcoder/transcoder.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | // Transcoder ...
8 | type Transcoder interface {
9 | Start(opts Options) (<-chan Progress, error)
10 | Input(i string) Transcoder
11 | InputPipe(w *io.WriteCloser, r *io.ReadCloser) Transcoder
12 | Output(o string) Transcoder
13 | OutputPipe(w *io.WriteCloser, r *io.ReadCloser) Transcoder
14 | WithOptions(opts Options) Transcoder
15 | WithAdditionalOptions(opts Options) Transcoder
16 | GetMetadata() (Metadata, error)
17 | }
18 |
--------------------------------------------------------------------------------
/transcoder/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | // DurToSec ...
9 | func DurToSec(dur string) (sec float64) {
10 | durAry := strings.Split(dur, ":")
11 | var secs float64
12 | if len(durAry) != 3 {
13 | return
14 | }
15 | hr, _ := strconv.ParseFloat(durAry[0], 64)
16 | secs = hr * (60 * 60)
17 | min, _ := strconv.ParseFloat(durAry[1], 64)
18 | secs += min * (60)
19 | second, _ := strconv.ParseFloat(durAry[2], 64)
20 | secs += second
21 | return secs
22 | }
23 |
--------------------------------------------------------------------------------
/wait-for-it/test/README.md:
--------------------------------------------------------------------------------
1 | # Tests for wait-for-it
2 |
3 | * wait-for-it.py - pytests for wait-for-it.sh
4 | * container-runners.py - Runs wait-for-it.py tests in multiple containers
5 | * requirements.txt - pip requirements for container-runners.py
6 |
7 | To run the basic tests:
8 |
9 | ```
10 | python wait-for-it.py
11 | ```
12 |
13 | Many of the issues encountered have been related to differences between operating system versions. The container-runners.py script provides an easy way to run the python wait-for-it.py tests against multiple system configurations:
14 |
15 | ```
16 | pip install -r requirements.txt
17 | python container-runners.py
18 | ```
19 |
--------------------------------------------------------------------------------
/packages/mediaInfo/medfiainfo.go:
--------------------------------------------------------------------------------
1 | package mediainfo
2 |
3 | import mediainfo "github.com/lbryio/go_mediainfo"
4 |
5 | type (
6 | MediaInfoStruct struct {
7 | Height int64
8 | Width int64
9 | Duration int64
10 | }
11 | MediaInfoInterface interface {
12 | getVideoInfo() error
13 | }
14 | )
15 |
16 | func (m *MediaInfoStruct) GetVideoInfo(path string) error {
17 | mi := mediainfo.NewMediaInfo()
18 | err := mi.OpenFile(path)
19 | if err != nil {
20 | return err
21 | }
22 | defer mi.Close()
23 |
24 | m.Duration = mi.GetInt(mediainfo.MediaInfo_Stream_General, "Duration")
25 | m.Width = mi.GetInt(mediainfo.MediaInfo_Stream_Video, "Width")
26 | m.Height = mi.GetInt(mediainfo.MediaInfo_Stream_Video, "Height")
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/models/splash-type.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type SplashBaseType int
4 |
5 | const (
6 | Image SplashBaseType = iota
7 | Video
8 | )
9 |
10 | func (splash SplashBaseType) String() string {
11 | return [...]string{"Image", "Video"}[splash]
12 | }
13 |
14 | type (
15 | Splash struct {
16 | ID uint `gorm:"primaryKey;index"`
17 | FileCode string `gorm:"uniqueIndex;not null"`
18 | Status bool
19 | // requirements as json input
20 | BaseFile string `json:"base_file" binding:"required"`
21 | BaseAudio string `json:"base_audio" binding:"required"`
22 | SplashBase SplashBaseType
23 | Name string `json:"name" binding:"required"`
24 | MaxLength uint `json:"max_length" binding:"required"`
25 | BaseColor string `json:"base_color" binding:"required"`
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/transcoder/ffmpeg/progress.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | // Progress ...
4 | type Progress struct {
5 | FramesProcessed string
6 | CurrentTime string
7 | CurrentBitrate string
8 | Progress float64
9 | Speed string
10 | }
11 |
12 | // GetFramesProcessed ...
13 | func (p Progress) GetFramesProcessed() string {
14 | return p.FramesProcessed
15 | }
16 |
17 | // GetCurrentTime ...
18 | func (p Progress) GetCurrentTime() string {
19 | return p.CurrentTime
20 | }
21 |
22 | // GetCurrentBitrate ...
23 | func (p Progress) GetCurrentBitrate() string {
24 | return p.CurrentBitrate
25 | }
26 |
27 | // GetProgress ...
28 | func (p Progress) GetProgress() float64 {
29 | return p.Progress
30 | }
31 |
32 | // GetSpeed ...
33 | func (p Progress) GetSpeed() string {
34 | return p.Speed
35 | }
36 |
--------------------------------------------------------------------------------
/interfaces/file-manager/filemanager.go:
--------------------------------------------------------------------------------
1 | package filemanager
2 |
3 | import (
4 | "os"
5 | "sync"
6 |
7 | "github.com/cavaliercoder/grab"
8 | )
9 |
10 | type (
11 | Temperory struct {
12 | DirName string
13 | }
14 | FileManager interface {
15 | CreateTempDir() error
16 | DeleteTempDir() error
17 | }
18 | )
19 |
20 | func (t *Temperory) CreateTempDir() error {
21 | err := os.Mkdir("/tmp/"+t.DirName, 0777)
22 | return err
23 | }
24 |
25 | func (t *Temperory) DeleteTempDir() error {
26 | err := os.Remove("/tmp/" + t.DirName)
27 | return err
28 | }
29 |
30 | func DownloadFile(wg *sync.WaitGroup, url string, path string) (bool, string) {
31 | client := grab.NewClient()
32 | req, err := grab.NewRequest(path, url)
33 | resp := client.Do(req)
34 | defer wg.Done()
35 | return err == nil, resp.HTTPResponse.Status
36 | }
37 |
--------------------------------------------------------------------------------
/packages/database/postgres.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/amupxm/go-video-concat/config"
8 | "gorm.io/driver/postgres"
9 | "gorm.io/gorm"
10 | )
11 |
12 | type Postgres struct {
13 | DBCli *gorm.DB
14 | }
15 |
16 | var PostgresConnection = Postgres{}
17 |
18 | func (d *Postgres) ConnectDatabase(config *config.ConfigStruct) {
19 | dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=%v TimeZone=%v",
20 | config.Database.Postgres_host,
21 | config.Database.Postgres_username,
22 | config.Database.Postgres_password,
23 | config.Database.Postgres_databasename,
24 | config.Database.Postgres_port,
25 | "disable",
26 | "Asia/Tehran",
27 | )
28 |
29 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | d.DBCli = db
35 | }
36 |
--------------------------------------------------------------------------------
/packages/ffmpeg/executer.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 | )
7 |
8 | type Config struct {
9 | FfmpegBinPath string
10 | FfprobeBinPath string
11 | ProgressEnabled bool
12 | Verbose bool
13 | }
14 | type Progress struct {
15 | FramesProcessed string
16 | CurrentTime string
17 | CurrentBitrate string
18 | Progress float64
19 | Speed string
20 | }
21 |
22 | const (
23 | FfmpegBinPath = "/usr/bin/ffmpeg"
24 | FfprobeBinPath = "/usr/bin/ffprobe"
25 | ProgressEnabled = true
26 | Verbose = true
27 | )
28 |
29 | // Spawn chield proccess
30 | func Execute(args ...string) error {
31 | cmd := exec.Command(FfmpegBinPath, args...)
32 | if Verbose {
33 | cmd.Stderr = os.Stdout
34 | }
35 | err := cmd.Start()
36 | if err != nil {
37 | return err
38 | }
39 | err = cmd.Wait()
40 | if err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | // TODO : add transcoder for executable
47 |
--------------------------------------------------------------------------------
/models/api-types.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type (
6 | VideoRecipe struct {
7 | VideoName string `json:"video_name" binding:"required"`
8 | FrameCode string `json:"frame_code" binding:"required"`
9 | CallBackAddress string `json:"call_back_address" binding:"required"`
10 | SoundLevel uint `json:"sound_level" binding:"required"`
11 | Audio string `json:"audio_file" binding:"required" `
12 | Splash string `json:"splash_file" binding:"required" `
13 | ExternalAudio bool `json:"has_audio" binding:"required"`
14 | Chunks []RecipeChunks `json:"inputs"`
15 | Status bool
16 | Message string
17 | UUID string
18 | StartedAt time.Time
19 | EndedAt *time.Time
20 | }
21 | RecipeChunks struct {
22 | FinalFile string
23 | Type string `json:"type" binding:"required"`
24 | Url string `json:"path" binding:"required"`
25 | Start int `json:"start" binding:"required"`
26 | Name string
27 | End int `json:"end" binding:"required"`
28 | }
29 | )
30 |
--------------------------------------------------------------------------------
/wait-for-it/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2016 Giles Hall
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/packages/s3/minio.go:
--------------------------------------------------------------------------------
1 | package s3
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/amupxm/go-video-concat/config"
8 | "github.com/minio/minio-go/v7"
9 | "github.com/minio/minio-go/v7/pkg/credentials"
10 | )
11 |
12 | type ObjectStorageStruct struct {
13 | Client *minio.Client
14 | }
15 |
16 | var ObjectStorage = ObjectStorageStruct{}
17 |
18 | func (object *ObjectStorageStruct) Connect(cfg *config.ConfigStruct) {
19 |
20 | m, err := minio.New(cfg.Storage.Minio_host, &minio.Options{
21 | Creds: credentials.NewStaticV4(cfg.Storage.Minio_user, cfg.Storage.Minio_password, ""),
22 | Secure: false,
23 | })
24 | if err != nil {
25 | log.Fatalln(err)
26 | }
27 | ObjectStorage.Client = m
28 | }
29 |
30 | func InitBuckets(buckets []string) {
31 | for _, bucket := range buckets {
32 | s, err := ObjectStorage.Client.BucketExists(context.Background(), bucket)
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 | if !s {
37 | if err := ObjectStorage.Client.MakeBucket(context.Background(),
38 | bucket,
39 | minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: false}); err != nil {
40 | log.Fatal(err)
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/docker-compose.DEV.yml:
--------------------------------------------------------------------------------
1 | version : "3"
2 |
3 | services:
4 | redis :
5 | image : redis
6 | ports:
7 | - 6379:6379
8 | postgres:
9 | image: postgres:latest
10 | environment:
11 | POSTGRES_PASSWORD: 1202212022AaAa
12 | PGDATA : /var/lib/postgresql/data/pgdata
13 | ports:
14 | - 5432:5432
15 | volumes:
16 | - ./data:/var/lib/postgresql/data:Z
17 | nginx:
18 | image : nginx
19 | volumes:
20 | - ./sample-files:/usr/share/nginx/html:ro
21 | ports:
22 | - "8081:80"
23 | environment:
24 | - NGINX_PORT=80
25 | minio:
26 | image : minio/minio
27 | ports:
28 | - "9000:9000"
29 | volumes:
30 | - ./data/minio:/data:Z
31 | healthcheck:
32 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
33 | interval: 30s
34 | timeout: 20s
35 | retries: 3
36 | command: server /data
37 | environment:
38 | - MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE
39 | - MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
--------------------------------------------------------------------------------
/wait-for-it/test/container-runners.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Unit tests to run wait-for-it.py unit tests in several different docker images
4 |
5 | import unittest
6 | import os
7 | import docker
8 | from parameterized import parameterized
9 |
10 | client = docker.from_env()
11 | app_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..'))
12 | volumes = {app_path: {'bind': '/app', 'mode': 'ro'}}
13 |
14 | class TestContainers(unittest.TestCase):
15 | """
16 | Test multiple container types with the test cases in wait-for-it.py
17 | """
18 |
19 | @parameterized.expand([
20 | "python:3.5-buster",
21 | "python:3.5-stretch",
22 | "dougg/alpine-busybox:alpine-3.11.3_busybox-1.30.1",
23 | "dougg/alpine-busybox:alpine-3.11.3_busybox-1.31.1"
24 | ])
25 | def test_image(self, image):
26 | print(image)
27 | command="/app/test/wait-for-it.py"
28 | container = client.containers.run(image, command=command, volumes=volumes, detach=True)
29 | result = container.wait()
30 | logs = container.logs()
31 | container.remove()
32 | self.assertEqual(result["StatusCode"], 0)
33 |
34 | if __name__ == '__main__':
35 | unittest.main()
36 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/joho/godotenv"
8 | )
9 |
10 | type (
11 | ConfigStruct struct {
12 | Database DatabaseConfig
13 | Storage StorageConfig
14 | Redis RedisConfig
15 | }
16 | DatabaseConfig struct {
17 | Postgres_host string
18 | Postgres_username string
19 | Postgres_password string
20 | Postgres_databasename string
21 | Postgres_port string
22 | }
23 | StorageConfig struct {
24 | Minio_user string
25 | Minio_password string
26 | Minio_host string
27 | }
28 |
29 | RedisConfig struct {
30 | Host string
31 | Password string
32 | DB uint
33 | }
34 | )
35 |
36 | var config *ConfigStruct
37 |
38 | func (cfg *ConfigStruct) LoadConfigs() {
39 | err := godotenv.Load()
40 | if err != nil {
41 | log.Fatal("Error loading .env file")
42 | }
43 | cfg.Database.Postgres_databasename = os.Getenv("dbname")
44 | cfg.Database.Postgres_host = os.Getenv("dbhost")
45 | cfg.Database.Postgres_password = os.Getenv("dbpass")
46 | cfg.Database.Postgres_port = os.Getenv("dbport")
47 | cfg.Database.Postgres_username = os.Getenv("dbusername")
48 | cfg.Storage.Minio_host = os.Getenv("Minio_host")
49 | cfg.Storage.Minio_password = os.Getenv("Minio_password")
50 | cfg.Storage.Minio_user = os.Getenv("Minio_user")
51 |
52 | cfg.Redis.Host = os.Getenv("Redis_host")
53 | cfg.Redis.Password = os.Getenv("Redis_password")
54 | cfg.Redis.DB = 0
55 |
56 | config = cfg
57 | }
58 |
--------------------------------------------------------------------------------
/packages/cache/redis.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/amupxm/go-video-concat/config"
8 | "github.com/go-redis/redis/v8"
9 | )
10 |
11 | type CacheRedisContext struct {
12 | Cli *redis.Client
13 | }
14 |
15 | var ctx = context.Background()
16 | var CacheRedis = CacheRedisContext{}
17 |
18 | func Init(config *config.ConfigStruct) {
19 | db := redis.NewClient(&redis.Options{
20 | Addr: config.Redis.Host,
21 | Password: config.Redis.Password,
22 | DB: int(config.Redis.DB),
23 | })
24 | CacheRedis.Cli = db
25 | }
26 |
27 | func NewProccess(uuid string, sectionsCount int) bool {
28 | var r = &CacheRedis
29 | errors := make([]error, 4)
30 | errors[1] = r.Cli.Set(ctx, uuid+":status", "started", 0).Err()
31 | for _, e := range errors {
32 | if e != nil {
33 | return false
34 | }
35 | }
36 | return true
37 | }
38 |
39 | func UpdateStatus(uuid, message string, status bool) bool {
40 | var r = &CacheRedis
41 | errors := make([]error, 2)
42 | errors[1] = r.Cli.Set(ctx, uuid+":status", status, 0).Err()
43 | errors[0] = r.Cli.Set(ctx, uuid+":message", message, 0).Err()
44 |
45 | for _, e := range errors {
46 | if e == nil {
47 | return false
48 | }
49 | }
50 | return true
51 | }
52 |
53 | func GetStatus(code string) (bool, string) {
54 | var r = &CacheRedis
55 | log.Println(code + ":status")
56 | respStatus := r.Cli.Get(ctx, code+":status")
57 | respMessage := r.Cli.Get(ctx, code+":message")
58 | message := respMessage.Val()
59 | status, err := respStatus.Bool()
60 | if err == nil {
61 | return status, message
62 | }
63 | return false, "invalid code"
64 | }
65 |
--------------------------------------------------------------------------------
/transcoder/metadata.go:
--------------------------------------------------------------------------------
1 | package transcoder
2 |
3 | // Metadata ...
4 | type Metadata interface {
5 | GetFormat() Format
6 | GetStreams() []Streams
7 | }
8 |
9 | // Format ...
10 | type Format interface {
11 | GetFilename() string
12 | GetNbStreams() int
13 | GetNbPrograms() int
14 | GetFormatName() string
15 | GetFormatLongName() string
16 | GetDuration() string
17 | GetSize() string
18 | GetBitRate() string
19 | GetProbeScore() int
20 | GetTags() Tags
21 | }
22 |
23 | // Streams ...
24 | type Streams interface {
25 | GetIndex() int
26 | GetID() string
27 | GetCodecName() string
28 | GetCodecLongName() string
29 | GetProfile() string
30 | GetCodecType() string
31 | GetCodecTimeBase() string
32 | GetCodecTagString() string
33 | GetCodecTag() string
34 | GetWidth() int
35 | GetHeight() int
36 | GetCodedWidth() int
37 | GetCodedHeight() int
38 | GetHasBFrames() int
39 | GetSampleAspectRatio() string
40 | GetDisplayAspectRatio() string
41 | GetPixFmt() string
42 | GetLevel() int
43 | GetChromaLocation() string
44 | GetRefs() int
45 | GetQuarterSample() string
46 | GetDivxPacked() string
47 | GetRFrameRrate() string
48 | GetAvgFrameRate() string
49 | GetTimeBase() string
50 | GetDurationTs() int
51 | GetDuration() string
52 | GetDisposition() Disposition
53 | GetBitRate() string
54 | }
55 |
56 | // Tags ...
57 | type Tags interface {
58 | GetEncoder() string
59 | }
60 |
61 | // Disposition ...
62 | type Disposition interface {
63 | GetDefault() int
64 | GetDub() int
65 | GetOriginal() int
66 | GetComment() int
67 | GetLyrics() int
68 | GetKaraoke() int
69 | GetForced() int
70 | GetHearingImpaired() int
71 | GetVisualImpaired() int
72 | GetCleanEffects() int
73 | }
74 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/amupxm/go-video-concat
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/cavaliercoder/grab v2.0.0+incompatible
7 | github.com/floostack/transcoder v1.1.1
8 | github.com/gin-gonic/gin v1.7.1
9 | github.com/go-ini/ini v1.62.0 // indirect
10 | github.com/go-playground/validator/v10 v10.5.0 // indirect
11 | github.com/go-redis/redis/v8 v8.8.2
12 | github.com/gofrs/uuid v3.2.0+incompatible
13 | github.com/golang/protobuf v1.5.2 // indirect
14 | github.com/google/uuid v1.2.0 // indirect
15 | github.com/joho/godotenv v1.3.0
16 | github.com/json-iterator/go v1.1.11 // indirect
17 | github.com/klauspost/cpuid/v2 v2.0.6 // indirect
18 | github.com/lbryio/go_mediainfo v0.0.0-20200109212001-4c7318fd92ad
19 | github.com/leodido/go-urn v1.2.1 // indirect
20 | github.com/minio/md5-simd v1.1.2 // indirect
21 | github.com/minio/minio-go v6.0.14+incompatible // indirect
22 | github.com/minio/minio-go/v7 v7.0.10
23 | github.com/minio/sha256-simd v1.0.0 // indirect
24 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
25 | github.com/modern-go/reflect2 v1.0.1 // indirect
26 | github.com/rs/xid v1.3.0 // indirect
27 | github.com/ugorji/go v1.2.5 // indirect
28 | golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
29 | golang.org/x/net v0.0.0-20210508051633-16afe75a6701 // indirect
30 | golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 // indirect
31 | golang.org/x/text v0.3.6 // indirect
32 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
33 | gopkg.in/ini.v1 v1.62.0 // indirect
34 | gopkg.in/yaml.v2 v2.4.0 // indirect
35 | gorm.io/driver/postgres v1.1.0
36 | gorm.io/gorm v1.21.9
37 | honnef.co/go/tools v0.0.1-2019.2.3 // indirect
38 | )
39 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version : "3"
2 |
3 | services:
4 | app:
5 | build: .
6 | env_file: .env
7 | ports:
8 | - 8080:8080
9 | depends_on:
10 | - "redis"
11 | - "postgres"
12 | - "minio"
13 | environment:
14 | GIN_MODE : release
15 | networks:
16 | - inner
17 |
18 | redis :
19 | image : redis
20 | ports:
21 | - 6379:6379
22 | networks:
23 | - inner
24 | postgres:
25 | image: postgres:latest
26 | environment:
27 | POSTGRES_PASSWORD: 1202212022AaAa
28 | PGDATA : /var/lib/postgresql/data/pgdata
29 | ports:
30 | - 5432:5432
31 | volumes:
32 | - ./data:/var/lib/postgresql/data:Z
33 | networks:
34 | - inner
35 | nginx:
36 | image : nginx
37 | volumes:
38 | - ./sample-files:/usr/share/nginx/html:ro
39 | ports:
40 | - "8081:80"
41 | environment:
42 | - NGINX_PORT=80
43 | networks:
44 | - inner
45 | minio:
46 | image : minio/minio
47 | ports:
48 | - "9000:9000"
49 | volumes:
50 | - ./data/minio:/data:Z
51 | healthcheck:
52 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
53 | interval: 30s
54 | timeout: 20s
55 | retries: 3
56 | command: server /export
57 | environment:
58 | - MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE
59 | - MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
60 | networks:
61 | - inner
62 | networks:
63 | inner:
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/amupxm/go-video-concat/packages/cache"
5 | postgres "github.com/amupxm/go-video-concat/packages/database"
6 |
7 | ApiController "github.com/amupxm/go-video-concat/controller"
8 |
9 | s3 "github.com/amupxm/go-video-concat/packages/s3"
10 |
11 | "github.com/amupxm/go-video-concat/config"
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | var AppConfig = &config.ConfigStruct{}
16 |
17 | func main() {
18 | router := gin.Default()
19 | AppConfig.LoadConfigs()
20 | // ======= Database and Storage =========
21 | postgres.PostgresConnection.ConnectDatabase(AppConfig)
22 | postgres.AutoMigration()
23 | buckets := []string{"frame", "amupxm", "thumbnails", "splash", "upload", "splash-base", "splash-audio", "outputs"}
24 | s3.ObjectStorage.Connect(AppConfig)
25 | s3.InitBuckets(buckets)
26 | cache.Init(AppConfig)
27 | // =======================================
28 |
29 | // ============ Controllers ============
30 |
31 | // 1 - frame
32 | router.POST("/frame/upload", ApiController.Frame_Upload)
33 | router.POST("/frame", ApiController.Frame_Add)
34 | router.GET("/frame", ApiController.Frame_list)
35 | router.GET("/frame/:code/file", ApiController.Frame_File)
36 | router.GET("/frame/:code", ApiController.Frame_Single)
37 |
38 | // 2 - splash video (splash)
39 | router.POST("/splash/base", ApiController.Splash_Base)
40 | router.POST("/splash/audio", ApiController.Splash_Audio)
41 | router.POST("/splash", ApiController.Splash_Add)
42 | router.GET("/splash/:code", ApiController.Splash_file)
43 |
44 | // 3 - generator
45 | router.POST("/generator/upload", ApiController.Generator_Upload)
46 | router.POST("/generator", ApiController.Generator_Generate)
47 | router.GET("/generator/:code/file", ApiController.Generator_file)
48 | router.GET("/generator/:code", ApiController.Generator_Status)
49 |
50 | router.Run()
51 | }
52 |
--------------------------------------------------------------------------------
/interfaces/splash/splash.go:
--------------------------------------------------------------------------------
1 | package splash
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/amupxm/go-video-concat/models"
8 | "gorm.io/gorm"
9 |
10 | postgres "github.com/amupxm/go-video-concat/packages/database"
11 | "github.com/amupxm/go-video-concat/packages/s3"
12 | "github.com/minio/minio-go/v7"
13 | )
14 |
15 | type (
16 | Splash struct{ *models.Splash }
17 | SplashMessageChannel struct {
18 | Message string
19 | Status bool
20 | }
21 | SplashOperation interface {
22 | AddSplash() bool
23 | }
24 | )
25 |
26 | func (splash *Splash) AddSplash(uuid string) (bool, string) {
27 | var Database = &postgres.PostgresConnection
28 | var ObjectStorage = &s3.ObjectStorage
29 | if splash.BaseFile == "" || splash.BaseAudio == "" {
30 | return false, "object name should not be empty"
31 | }
32 | _, err := ObjectStorage.Client.StatObject(context.Background(), "splash-base", splash.BaseFile, minio.StatObjectOptions{})
33 | if err != nil {
34 | return false, "base file dont exits in storage"
35 | }
36 | _, err = ObjectStorage.Client.StatObject(context.Background(), "splash-audio", splash.BaseAudio, minio.StatObjectOptions{})
37 | if err != nil {
38 | return false, "audio file dont exits in storage"
39 | }
40 | splash.FileCode = uuid
41 | splash.Status = false
42 | result := Database.DBCli.Create(splash)
43 | return result.Error == nil, "done"
44 | }
45 |
46 | func (splash *Splash) FindSplash(code string) (Splash, bool) {
47 | var resultFrame Splash
48 | var Database = &postgres.PostgresConnection
49 | result := Database.DBCli.First(&resultFrame, "file_code = ?", code)
50 | return resultFrame, !errors.Is(result.Error, gorm.ErrRecordNotFound)
51 | }
52 |
53 | func (splash *Splash) GetFile(splashCode string) (*minio.Object, bool) {
54 | var tmp *minio.Object
55 | _, status := splash.FindSplash(splashCode)
56 | if !status {
57 | return tmp, status
58 | }
59 |
60 | var ObjectStorage = &s3.ObjectStorage
61 | reader, err := ObjectStorage.Client.GetObject(context.Background(), "splash", splashCode, minio.GetObjectOptions{})
62 | if err != nil {
63 | return tmp, false
64 | }
65 | return reader, true
66 | }
67 |
--------------------------------------------------------------------------------
/interfaces/frame/frame.go:
--------------------------------------------------------------------------------
1 | package frame
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "log"
7 |
8 | "github.com/amupxm/go-video-concat/models"
9 | postgres "github.com/amupxm/go-video-concat/packages/database"
10 | "github.com/amupxm/go-video-concat/packages/s3"
11 | "github.com/minio/minio-go/v7"
12 |
13 | "gorm.io/gorm"
14 | )
15 |
16 | type (
17 | Frame struct{ *models.Frame }
18 | FrameOperation interface {
19 | AddFrame() bool
20 | ListFrame() []Frame
21 | FindFrame(code string) (Frame, bool)
22 | GetFile(frameCode string) (Frame, bool)
23 | }
24 | )
25 |
26 | // add frame to database and s3.
27 | func (frame *Frame) AddFrame() bool {
28 | var Database = &postgres.PostgresConnection
29 | var ObjectStorage = &s3.ObjectStorage
30 | // check file code exists in storage
31 | _, err := ObjectStorage.Client.StatObject(context.Background(), "frame", frame.FileCode, minio.StatObjectOptions{})
32 | if err != nil {
33 | log.Fatal(err)
34 | return false
35 |
36 | }
37 | result := Database.DBCli.Create(frame)
38 | return result.Error == nil
39 | }
40 |
41 | // get all frames and return as array frames
42 | func (frame *Frame) ListFrame() []Frame {
43 | var Database = &postgres.PostgresConnection
44 | var respone []Frame
45 | Database.DBCli.Find(&respone)
46 | return respone
47 | }
48 |
49 | // use frame unique uuid to find frame and returns single frame
50 | func (frame *Frame) FindFrame(code string) (Frame, bool) {
51 | var resultFrame Frame
52 | var Database = &postgres.PostgresConnection
53 | result := Database.DBCli.First(&resultFrame, "file_code = ?", code)
54 | return resultFrame, !errors.Is(result.Error, gorm.ErrRecordNotFound)
55 | }
56 |
57 | // get frame file from s3 and returns s3 object( read io ).
58 | func (frame *Frame) GetFile(frameCode string) (*minio.Object, bool) {
59 | var tmp *minio.Object
60 | _, status := frame.FindFrame(frameCode)
61 | if !status {
62 | return tmp, status
63 | }
64 | var ObjectStorage = &s3.ObjectStorage
65 | reader, err := ObjectStorage.Client.GetObject(context.Background(), "frame", frameCode, minio.GetObjectOptions{})
66 | if err != nil {
67 | return tmp, false
68 | }
69 | return reader, true
70 | }
71 |
--------------------------------------------------------------------------------
/wait-for-it/README.md:
--------------------------------------------------------------------------------
1 | # wait-for-it
2 |
3 | `wait-for-it.sh` is a pure bash script that will wait on the availability of a
4 | host and TCP port. It is useful for synchronizing the spin-up of
5 | interdependent services, such as linked docker containers. Since it is a pure
6 | bash script, it does not have any external dependencies.
7 |
8 | ## Usage
9 |
10 | ```text
11 | wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
12 | -h HOST | --host=HOST Host or IP under test
13 | -p PORT | --port=PORT TCP port under test
14 | Alternatively, you specify the host and port as host:port
15 | -s | --strict Only execute subcommand if the test succeeds
16 | -q | --quiet Don't output any status messages
17 | -t TIMEOUT | --timeout=TIMEOUT
18 | Timeout in seconds, zero for no timeout
19 | -- COMMAND ARGS Execute command with args after the test finishes
20 | ```
21 |
22 | ## Examples
23 |
24 | For example, let's test to see if we can access port 80 on `www.google.com`,
25 | and if it is available, echo the message `google is up`.
26 |
27 | ```text
28 | $ ./wait-for-it.sh www.google.com:80 -- echo "google is up"
29 | wait-for-it.sh: waiting 15 seconds for www.google.com:80
30 | wait-for-it.sh: www.google.com:80 is available after 0 seconds
31 | google is up
32 | ```
33 |
34 | You can set your own timeout with the `-t` or `--timeout=` option. Setting
35 | the timeout value to 0 will disable the timeout:
36 |
37 | ```text
38 | $ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up"
39 | wait-for-it.sh: waiting for www.google.com:80 without a timeout
40 | wait-for-it.sh: www.google.com:80 is available after 0 seconds
41 | google is up
42 | ```
43 |
44 | The subcommand will be executed regardless if the service is up or not. If you
45 | wish to execute the subcommand only if the service is up, add the `--strict`
46 | argument. In this example, we will test port 81 on `www.google.com` which will
47 | fail:
48 |
49 | ```text
50 | $ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up"
51 | wait-for-it.sh: waiting 1 seconds for www.google.com:81
52 | wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81
53 | wait-for-it.sh: strict mode, refusing to execute subprocess
54 | ```
55 |
56 | If you don't want to execute a subcommand, leave off the `--` argument. This
57 | way, you can test the exit condition of `wait-for-it.sh` in your own scripts,
58 | and determine how to proceed:
59 |
60 | ```text
61 | $ ./wait-for-it.sh www.google.com:80
62 | wait-for-it.sh: waiting 15 seconds for www.google.com:80
63 | wait-for-it.sh: www.google.com:80 is available after 0 seconds
64 | $ echo $?
65 | 0
66 | $ ./wait-for-it.sh www.google.com:81
67 | wait-for-it.sh: waiting 15 seconds for www.google.com:81
68 | wait-for-it.sh: timeout occurred after waiting 15 seconds for www.google.com:81
69 | $ echo $?
70 | 124
71 | ```
72 |
73 | ## Community
74 |
75 | *Debian*: There is a [Debian package](https://tracker.debian.org/pkg/wait-for-it).
76 |
--------------------------------------------------------------------------------
/controller/frame.controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | AppContext "context"
5 | "fmt"
6 |
7 | "log"
8 | "net/http"
9 |
10 | frame "github.com/amupxm/go-video-concat/interfaces/frame"
11 | "github.com/amupxm/go-video-concat/packages/s3"
12 |
13 | "github.com/gin-gonic/gin"
14 | "github.com/gofrs/uuid"
15 | "github.com/minio/minio-go/v7"
16 | )
17 |
18 | // upload add
19 |
20 | func Frame_Upload(context *gin.Context) {
21 | file, err := context.FormFile("upload")
22 | // if no file contained
23 | if err != nil {
24 | context.JSON(http.StatusInternalServerError, gin.H{
25 | "ok": false,
26 | "message": "invalid file input",
27 | })
28 | return
29 | }
30 |
31 | uuid, _ := uuid.NewV4()
32 |
33 | fileIOReader, err := file.Open()
34 |
35 | if err != nil {
36 | context.JSON(http.StatusInternalServerError, gin.H{
37 | "ok": false,
38 | "message": "file error",
39 | })
40 | }
41 | var storage = &s3.ObjectStorage
42 |
43 | uploadInformation, err := storage.Client.PutObject(
44 | AppContext.Background(),
45 | "frame",
46 | uuid.String(),
47 | fileIOReader,
48 | file.Size,
49 | minio.PutObjectOptions{ContentType: file.Header.Get("Content-Type")})
50 | if err != nil {
51 | context.JSON(http.StatusInternalServerError, gin.H{
52 | "ok": false,
53 | "message": err,
54 | })
55 | }
56 | log.Print(uploadInformation.LastModified.Second())
57 | context.JSON(200, gin.H{
58 | "ok": true,
59 | "message": "file uploaded successfully",
60 | "file_name": uuid.String(),
61 | })
62 | }
63 |
64 | // add
65 | func Frame_Add(context *gin.Context) {
66 | var frame frame.Frame
67 | if err := context.ShouldBindJSON(&frame); err != nil {
68 | context.JSON(http.StatusNotAcceptable, gin.H{
69 | "ok": false,
70 | "message": "invalid json structure",
71 | })
72 | }
73 | status := frame.AddFrame()
74 |
75 | context.JSON(http.StatusAccepted, gin.H{
76 | "ok": status,
77 | "data": frame,
78 | })
79 | }
80 |
81 | // get all
82 |
83 | func Frame_list(context *gin.Context) {
84 | var frame frame.Frame
85 | frameList := frame.ListFrame()
86 | context.JSON(http.StatusAccepted, gin.H{
87 | "ok": true,
88 | "data": frameList,
89 | })
90 | }
91 |
92 | // TODO single view
93 | func Frame_Single(context *gin.Context) {
94 | frameCode := context.Param("code")
95 | var frame frame.Frame
96 | frameList, status := frame.FindFrame(frameCode)
97 | context.JSON(http.StatusAccepted, gin.H{
98 | "ok": status,
99 | "data": frameList,
100 | })
101 | }
102 |
103 | // TODO single file view
104 | func Frame_File(context *gin.Context) {
105 | frameCode := context.Param("code")
106 | var frame frame.Frame
107 | file, status := frame.GetFile(frameCode)
108 | stat, err := file.Stat()
109 | if err != nil || !status {
110 | context.JSON(http.StatusAccepted, gin.H{
111 | "ok": false,
112 | })
113 | }
114 | extraHeaders := map[string]string{}
115 | fmt.Print(stat)
116 | context.DataFromReader(http.StatusOK, stat.Size, stat.ContentType, file, extraHeaders)
117 | }
118 |
119 | // TODO remove
120 |
--------------------------------------------------------------------------------
/controller/generator-controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | AppContext "context"
5 | "fmt"
6 | "log"
7 | "net/http"
8 |
9 | "github.com/amupxm/go-video-concat/models"
10 | "github.com/amupxm/go-video-concat/packages/cache"
11 | "github.com/amupxm/go-video-concat/packages/ffmpeg"
12 | "github.com/amupxm/go-video-concat/packages/s3"
13 | "github.com/gin-gonic/gin"
14 | "github.com/gofrs/uuid"
15 | "github.com/minio/minio-go/v7"
16 | )
17 |
18 | func Generator_Upload(context *gin.Context) {
19 | file, err := context.FormFile("upload")
20 | // if no file contained
21 | if err != nil {
22 | context.JSON(http.StatusInternalServerError, gin.H{
23 | "ok": false,
24 | "message": "invalid file input",
25 | })
26 | return
27 | }
28 |
29 | uuid, _ := uuid.NewV4()
30 |
31 | fileIOReader, err := file.Open()
32 |
33 | if err != nil {
34 | context.JSON(http.StatusInternalServerError, gin.H{
35 | "ok": false,
36 | "message": "file error",
37 | })
38 | }
39 | var storage = &s3.ObjectStorage
40 |
41 | uploadInformation, err := storage.Client.PutObject(
42 | AppContext.Background(),
43 | "upload",
44 | uuid.String(),
45 | fileIOReader,
46 | file.Size,
47 | minio.PutObjectOptions{ContentType: file.Header.Get("Content-Type")})
48 | if err != nil {
49 | context.JSON(http.StatusInternalServerError, gin.H{
50 | "ok": false,
51 | "message": err,
52 | })
53 | }
54 | log.Print(uploadInformation.LastModified.Second())
55 | context.JSON(200, gin.H{
56 | "ok": true,
57 | "message": "file uploaded successfully",
58 | "file_name": uuid.String(),
59 | })
60 |
61 | }
62 |
63 | func Generator_Generate(context *gin.Context) {
64 | var recipe models.VideoRecipe
65 | if err := context.ShouldBindJSON(&recipe); err != nil {
66 | context.JSON(http.StatusNotAcceptable, gin.H{
67 | "ok": false,
68 | "message": "invalid structure",
69 | })
70 | fmt.Print(err)
71 | return
72 | }
73 | var f ffmpeg.FFmpeg_Generator
74 | f.Recipe = &recipe
75 | f.Error = &ffmpeg.FFmpeg_Message{}
76 | processId, _ := uuid.NewV4()
77 | f.UUID = processId.String()
78 | context.JSON(http.StatusAccepted, gin.H{
79 | "ok": true,
80 | "message": "please wait",
81 | "code": f.UUID,
82 | })
83 | go func() {
84 | ffmpeg.Generator(&f)
85 | }()
86 | }
87 |
88 | func Generator_Status(context *gin.Context) {
89 | code := context.Param("code")
90 | status, message := cache.GetStatus(code)
91 | context.JSON(200, gin.H{
92 | "status": status,
93 | "message": message,
94 | })
95 | }
96 |
97 | func Generator_file(context *gin.Context) {
98 | code := context.Param("code")
99 | var gen ffmpeg.FFmpeg_Generator
100 | file, _, status := gen.GetFromS3(code)
101 | if status != nil {
102 | log.Println(status)
103 | context.JSON(http.StatusAccepted, gin.H{
104 | "ok": false,
105 | })
106 | return
107 | }
108 | stat, err := file.Stat()
109 | if err != nil {
110 | context.JSON(http.StatusAccepted, gin.H{
111 | "ok": false,
112 | })
113 | return
114 | }
115 | defer file.Close()
116 | extraHeaders := map[string]string{}
117 | fmt.Print(stat)
118 | context.DataFromReader(http.StatusOK, stat.Size, stat.ContentType, file, extraHeaders)
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/packages/ffmpeg/core.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/amupxm/go-video-concat/interfaces/frame"
7 | "github.com/amupxm/go-video-concat/interfaces/splash"
8 | "github.com/amupxm/go-video-concat/models"
9 | "github.com/amupxm/go-video-concat/packages/cache"
10 | )
11 |
12 | type (
13 | FFmpeg_Audio struct {
14 | Format string
15 | Input string
16 | Output string
17 | Duration int
18 | }
19 | FFmpeg_Splash struct {
20 | Audio string
21 | Video string
22 | Output string
23 | }
24 | FFmpeg_Generator struct {
25 | Recipe *models.VideoRecipe
26 | Frame *frame.Frame
27 | Splash *splash.Splash
28 | Error *FFmpeg_Message
29 | UUID string
30 | Dir string
31 | }
32 | FFmpeg_Message struct {
33 | Status bool
34 | Message string
35 | UUID *string
36 | }
37 | FFmpegInterface interface {
38 | Generator(f *FFmpeg_Generator, responseChannel chan *FFmpeg_Message)
39 | GenerateSplash(splash *splash.Splash) *FFmpeg_Message
40 | img2vid(index int, wg *sync.WaitGroup)
41 | trimVid(index int, wg *sync.WaitGroup)
42 | CreateSplash(splash FFmpeg_Splash)
43 | GetSplashFile(wg *sync.WaitGroup)
44 | GetFRameFile(wg *sync.WaitGroup)
45 | TrimAudio(audio FFmpeg_Audio)
46 | Execute(args ...string) error
47 | MakeEvenNumber(i int) int
48 | ValidateSplash()
49 | GenerateChunks()
50 | ResponseError()
51 | DownloadFiles()
52 | FitSplash()
53 | Overlay()
54 | Concat()
55 | TmpDir()
56 | }
57 | )
58 |
59 | func Generator(f *FFmpeg_Generator) {
60 | // init redis status
61 | cache.NewProccess(f.UUID, len(f.Recipe.Chunks))
62 | f.Error.Status = false
63 | f.Error.Message = ""
64 |
65 | // validate frame
66 | cache.UpdateStatus(f.UUID, "validate frame", true)
67 | f.ValidateFrame()
68 | if f.Error.Status {
69 | f.ResponseError()
70 | return
71 | }
72 | // validate splash
73 | cache.UpdateStatus(f.UUID, "validate Splash", true)
74 | f.ValidateSplash()
75 | if f.Error.Status {
76 | f.ResponseError()
77 | return
78 | }
79 |
80 | // create tmp file
81 | cache.UpdateStatus(f.UUID, "init directory", true)
82 |
83 | f.TmpDir("init")
84 | if f.Error.Status {
85 | f.ResponseError()
86 | return
87 | }
88 |
89 | // download files
90 | cache.UpdateStatus(f.UUID, "download files", true)
91 | f.DownloadFiles()
92 | if f.Error.Status {
93 | f.ResponseError()
94 | return
95 | }
96 |
97 | // get splash and frame file
98 | cache.UpdateStatus(f.UUID, "connecting to s3", true)
99 | var fileControllerWaitGroup sync.WaitGroup
100 | fileControllerWaitGroup.Add(2)
101 | go f.GetSplashFile(&fileControllerWaitGroup)
102 | go f.GetFrameFile(&fileControllerWaitGroup)
103 | fileControllerWaitGroup.Wait()
104 | if f.Error.Status {
105 | f.ResponseError()
106 | return
107 | }
108 | // make splash in correct size
109 | cache.UpdateStatus(f.UUID, "generate splash", true)
110 | f.FitSplash()
111 | // trim video chunks in correct duration
112 | cache.UpdateStatus(f.UUID, "prepare", true)
113 | f.GenerateChunks()
114 | // connect chunks (+ sound overlay)
115 | cache.UpdateStatus(f.UUID, "concatting", true)
116 | f.Concat()
117 | //overLay
118 | cache.UpdateStatus(f.UUID, "overlaying", true)
119 | f.OverLay()
120 | cache.UpdateStatus(f.UUID, "done", true)
121 | f.WriteTos3()
122 | defer f.SendResponseCallback()
123 | }
124 |
--------------------------------------------------------------------------------
/controller/splash-controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | AppContext "context"
5 | "fmt"
6 | "log"
7 | "net/http"
8 |
9 | "github.com/amupxm/go-video-concat/interfaces/splash"
10 | "github.com/amupxm/go-video-concat/packages/ffmpeg"
11 | "github.com/amupxm/go-video-concat/packages/s3"
12 | "github.com/gin-gonic/gin"
13 | "github.com/gofrs/uuid"
14 | "github.com/minio/minio-go/v7"
15 | )
16 |
17 | func Splash_Audio(context *gin.Context) {
18 | file, err := context.FormFile("upload")
19 | // if no file contained
20 | if err != nil {
21 | context.JSON(http.StatusInternalServerError, gin.H{
22 | "ok": false,
23 | "message": "invalid file input",
24 | })
25 | return
26 | }
27 |
28 | uuid, _ := uuid.NewV4()
29 |
30 | fileIOReader, err := file.Open()
31 |
32 | if err != nil {
33 | context.JSON(http.StatusInternalServerError, gin.H{
34 | "ok": false,
35 | "message": "file error",
36 | })
37 | }
38 | var storage = &s3.ObjectStorage
39 |
40 | uploadInformation, err := storage.Client.PutObject(
41 | AppContext.Background(),
42 | "splash-audio",
43 | uuid.String(),
44 | fileIOReader,
45 | file.Size,
46 | minio.PutObjectOptions{ContentType: file.Header.Get("Content-Type")})
47 | if err != nil {
48 | context.JSON(http.StatusInternalServerError, gin.H{
49 | "ok": false,
50 | "message": err,
51 | })
52 | }
53 | log.Print(uploadInformation.LastModified.Second())
54 | context.JSON(200, gin.H{
55 | "ok": true,
56 | "message": "file uploaded successfully",
57 | "file_name": uuid.String(),
58 | })
59 | }
60 |
61 | func Splash_Base(context *gin.Context) {
62 | file, err := context.FormFile("upload")
63 | // if no file contained
64 | if err != nil {
65 | context.JSON(http.StatusInternalServerError, gin.H{
66 | "ok": false,
67 | "message": "invalid file input",
68 | })
69 | return
70 | }
71 |
72 | uuid, _ := uuid.NewV4()
73 |
74 | fileIOReader, err := file.Open()
75 |
76 | if err != nil {
77 | context.JSON(http.StatusInternalServerError, gin.H{
78 | "ok": false,
79 | "message": "file error",
80 | })
81 | }
82 | var storage = &s3.ObjectStorage
83 |
84 | uploadInformation, err := storage.Client.PutObject(
85 | AppContext.Background(),
86 | "splash-base",
87 | uuid.String(),
88 | fileIOReader,
89 | file.Size,
90 | minio.PutObjectOptions{ContentType: file.Header.Get("Content-Type")})
91 | if err != nil {
92 | context.JSON(http.StatusInternalServerError, gin.H{
93 | "ok": false,
94 | "message": err,
95 | })
96 | }
97 | log.Print(uploadInformation.LastModified.Second())
98 | context.JSON(200, gin.H{
99 | "ok": true,
100 | "message": "file uploaded successfully",
101 | "file_name": uuid.String(),
102 | })
103 | }
104 |
105 | func Splash_Add(context *gin.Context) {
106 | var Splash splash.Splash
107 |
108 | if err := context.ShouldBindJSON(&Splash); err != nil {
109 | context.JSON(http.StatusNotAcceptable, gin.H{
110 | "ok": false,
111 | "message": "invalid requires structure",
112 | })
113 | return
114 | }
115 |
116 | uuid, _ := uuid.NewV4()
117 |
118 | status, message := Splash.AddSplash(uuid.String())
119 | context.JSON(http.StatusAccepted, gin.H{
120 | "ok": status,
121 | "message": message,
122 | "code": uuid.String(),
123 | })
124 | // now should start proccessing
125 | res := ffmpeg.GenerateSplash(&Splash)
126 | fmt.Print(res)
127 |
128 | }
129 |
130 | func Splash_file(context *gin.Context) {
131 | splashCode := context.Param("code")
132 | var splash splash.Splash
133 | file, status := splash.GetFile(splashCode)
134 | stat, err := file.Stat()
135 | if err != nil || !status {
136 | context.JSON(http.StatusAccepted, gin.H{
137 | "ok": false,
138 | })
139 | }
140 | extraHeaders := map[string]string{}
141 | context.DataFromReader(http.StatusOK, stat.Size, stat.ContentType, file, extraHeaders)
142 | }
143 |
--------------------------------------------------------------------------------
/transcoder/ffmpeg/options.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // Options defines allowed FFmpeg arguments
9 | type Options struct {
10 | Aspect *string `flag:"-aspect"`
11 | Resolution *string `flag:"-s"`
12 | VideoBitRate *string `flag:"-b:v"`
13 | VideoBitRateTolerance *int `flag:"-bt"`
14 | VideoMaxBitRate *int `flag:"-maxrate"`
15 | VideoMinBitrate *int `flag:"-minrate"`
16 | VideoCodec *string `flag:"-c:v"`
17 | Vframes *int `flag:"-vframes"`
18 | FrameRate *int `flag:"-r"`
19 | AudioRate *int `flag:"-ar"`
20 | KeyframeInterval *int `flag:"-g"`
21 | AudioCodec *string `flag:"-c:a"`
22 | AudioBitrate *string `flag:"-ab"`
23 | AudioChannels *int `flag:"-ac"`
24 | AudioVariableBitrate *bool `flag:"-q:a"`
25 | BufferSize *int `flag:"-bufsize"`
26 | Threadset *bool `flag:"-threads"`
27 | Threads *int `flag:"-threads"`
28 | Preset *string `flag:"-preset"`
29 | Tune *string `flag:"-tune"`
30 | AudioProfile *string `flag:"-profile:a"`
31 | VideoProfile *string `flag:"-profile:v"`
32 | Target *string `flag:"-target"`
33 | Duration *string `flag:"-t"`
34 | Qscale *uint32 `flag:"-qscale"`
35 | Crf *uint32 `flag:"-crf"`
36 | Strict *int `flag:"-strict"`
37 | MuxDelay *string `flag:"-muxdelay"`
38 | SeekTime *string `flag:"-ss"`
39 | SeekUsingTimestamp *bool `flag:"-seek_timestamp"`
40 | MovFlags *string `flag:"-movflags"`
41 | HideBanner *bool `flag:"-hide_banner"`
42 | OutputFormat *string `flag:"-f"`
43 | CopyTs *bool `flag:"-copyts"`
44 | NativeFramerateInput *bool `flag:"-re"`
45 | InputInitialOffset *string `flag:"-itsoffset"`
46 | RtmpLive *string `flag:"-rtmp_live"`
47 | HlsPlaylistType *string `flag:"-hls_playlist_type"`
48 | HlsListSize *int `flag:"-hls_list_size"`
49 | HlsSegmentDuration *int `flag:"-hls_time"`
50 | HlsMasterPlaylistName *string `flag:"-master_pl_name"`
51 | HlsSegmentFilename *string `flag:"-hls_segment_filename"`
52 | HTTPMethod *string `flag:"-method"`
53 | HTTPKeepAlive *bool `flag:"-multiple_requests"`
54 | Hwaccel *string `flag:"-hwaccel"`
55 | StreamIds map[string]string `flag:"-streamid"`
56 | VideoFilter *string `flag:"-vf"`
57 | AudioFilter *string `flag:"-af"`
58 | SkipVideo *bool `flag:"-vn"`
59 | SkipAudio *bool `flag:"-an"`
60 | CompressionLevel *int `flag:"-compression_level"`
61 | MapMetadata *string `flag:"-map_metadata"`
62 | Metadata map[string]string `flag:"-metadata"`
63 | EncryptionKey *string `flag:"-hls_key_info_file"`
64 | Bframe *int `flag:"-bf"`
65 | PixFmt *string `flag:"-pix_fmt"`
66 | WhiteListProtocols []string `flag:"-protocol_whitelist"`
67 | Overwrite *bool `flag:"-y"`
68 | Shortest *bool `flag:"-shortest"`
69 | MapVideo *string `flag:"-map"`
70 | MapAudio *string `flag:"-map"`
71 | ExtraArgs map[string]interface{}
72 | }
73 |
74 | // GetStrArguments ...
75 | func (opts Options) GetStrArguments() []string {
76 | f := reflect.TypeOf(opts)
77 | v := reflect.ValueOf(opts)
78 |
79 | values := []string{}
80 |
81 | for i := 0; i < f.NumField(); i++ {
82 | flag := f.Field(i).Tag.Get("flag")
83 | value := v.Field(i).Interface()
84 |
85 | if !v.Field(i).IsNil() {
86 |
87 | if _, ok := value.(*bool); ok {
88 | values = append(values, flag)
89 | }
90 |
91 | if vs, ok := value.(*string); ok {
92 | values = append(values, flag, *vs)
93 | }
94 |
95 | if va, ok := value.([]string); ok {
96 |
97 | for i := 0; i < len(va); i++ {
98 | item := va[i]
99 | values = append(values, flag, item)
100 | }
101 | }
102 |
103 | if vm, ok := value.(map[string]interface{}); ok {
104 | for k, v := range vm {
105 | values = append(values, k, fmt.Sprintf("%v", v))
106 | }
107 | }
108 |
109 | if vi, ok := value.(*int); ok {
110 | values = append(values, flag, fmt.Sprintf("%d", *vi))
111 | }
112 |
113 | }
114 | }
115 |
116 | return values
117 | }
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [![LinkedIn][linkedin-shield]][linkedin-url]
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
simple Golang video concat api
14 |
15 |
16 | Extremely fast API for editing social media suitable contents.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ## Getting Started
29 |
30 | Just start app with docker compsoe.
31 | ### Prerequisites
32 |
33 | if you want to serve app outside of dockerd use script below :
34 | * debian based linux :
35 | ```sh
36 | apt-get update && apt-get install -y \
37 | ffmpeg \
38 | libmediainfo-dev \
39 | zlib* \
40 | gcc \
41 | go && rm -rf /var/lib/apt/lists/*
42 | go get . && go build -v
43 | ```
44 | * openSUSE :
45 | ```sh
46 | zypper in -y \
47 | libmediainfo-devel\
48 | zlib-devel\
49 | zlib*\
50 | ffmpeg\
51 | gcc \
52 | go && rm -rf /var/lib/apt/lists/*
53 | go get . && go build -v
54 | ```
55 | ### Usage
56 |
57 | #### 1. add frame :
58 | A frame is an image which your video will centered inside it.
59 | for example :
60 |
61 |
62 |
63 |
64 |
65 |
66 | First simply upload your frame to s3 :
67 | ```sh
68 | curl --location --request POST 'localhost:8080/frame/upload' \
69 | --form 'upload=@"cover.png"'
70 | ```
71 | Which will return an unique uuid code. Then send post requst to `localhost:8080/frame` like example :
72 |
73 | ``` json
74 | {
75 | "name" : "template name" ,
76 | "file_code" : "8a19d34e-ce11-4730-8853-9b2ec3a21ec4",
77 | "height": 303,
78 | "width": 540,
79 | "start_offset": 300
80 | }
81 | ```
82 | Start_offset is Y coordination of template.This request will return an uuid code.You can use below end-point to ger frames:
83 |
84 | ``` sh
85 | # get list of frames :
86 | curl --location --request GET 'localhost:8080/frame'
87 |
88 | # get single frame information (height , offests and ...) :
89 | curl --location --request GET 'localhost:8080/frame/:UUID'
90 |
91 | # get frame file
92 | curl --location --request GET 'localhost:8080/frame/:UUID/file'
93 | ```
94 |
95 |
96 | #### 2. Create splash
97 | A splash is a required short video (something like a logo) which shown in start of your video.You can merge any video with any voice ( or music).
98 |
99 | ``` sh
100 | # add base file ( video file )
101 | curl --location --request POST 'localhost:8080/splash/base' \
102 | --form 'upload=@"video.mp4"'
103 |
104 | # add voice (music file)
105 | curl --location --request POST 'localhost:8080/splash/audio' \
106 | --form 'upload=@"video.mp3"'
107 | ```
108 | Then merge them together :
109 |
110 | ``` json
111 | {
112 | "base_file": "c7d6d339-6668-42a8-b569-da7ab101e5a6",
113 | "base_audio": "db20ab4d-59e1-45fb-a9c5-0df2b4603d0e",
114 | "name": "splash for test",
115 | "max_length": 7 ,
116 | "base_color" : "#FF6600"
117 | }
118 | ```
119 |
120 | In this example max_length is duration of your splash file. It will generate empty file parts if the max_length be longer than video or audio length.The base_color will be a background for splash if you use transparent videos or when you are using splash file in different alignment frame size.
121 |
122 | You can check generated splash file using `localhost:8080/splash/:code` end point.
123 |
124 |
125 | #### 3. Concator:
126 | Concator is a process include merge / trim and overlaying media contents on frame.Like below example :
127 |
128 | ``` json
129 | {
130 | "video_name": "video name",
131 | "sound_level": 70,
132 | "call_back_address": "https://google.com",
133 | "splash_file": "8fe439c1-8f15-4950-8c52-48838acedbdd",
134 | "frame_code": "8a19d34e-ce11-4730-8853-9b2ec3a21ec4",
135 | "audio_file":"http://localhost:8081/voice.mp3",
136 | "has_audio" : true ,
137 | "inputs": [
138 | {
139 | "type": "image",
140 | "path": "http://localhost:8081/image.png",
141 | "start": 0,
142 | "end": 2
143 | },
144 | {
145 | "type": "video",
146 | "path": "http://localhost:8081/video.mp4",
147 | "start": 2,
148 | "end": 10
149 | }
150 | ]
151 | }
152 | ```
153 | This request will make a video with 17 seconds length (2'' image + 8'' video + 7'' splash [step 2]), Then overlay audio file with 70% sound level on it and place it inside of the frame. This could be a long process so it will call your call_back url , when it's done.
154 |
155 |
156 |
157 | You can use `localhost:8080/generator/:code` to check process percentage and current status.
158 |
159 |
160 |
161 | ## Contributing
162 |
163 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
164 |
165 | 1. Fork the Project
166 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
167 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
168 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
169 | 5. Open a Pull Request
170 |
171 |
172 |
173 |
174 | ## License
175 |
176 | Distributed under the MIT License. See `LICENSE` for more information.
177 |
178 |
179 |
180 |
181 | ## Contact
182 |
183 | Amir Hossein Mokaramifar - [@amupxm](https://twitter.com/amupxm) - amupxm@gmail.com
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
193 | [linkedin-url]: https://www.linkedin.com/in/mokaramifar/
194 | [product-screenshot]: images/screenshot.png
195 |
--------------------------------------------------------------------------------
/wait-for-it/wait-for-it.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Use this script to test if a given TCP host/port are available
3 |
4 | WAITFORIT_cmdname=${0##*/}
5 |
6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
7 |
8 | usage()
9 | {
10 | cat << USAGE >&2
11 | Usage:
12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
13 | -h HOST | --host=HOST Host or IP under test
14 | -p PORT | --port=PORT TCP port under test
15 | Alternatively, you specify the host and port as host:port
16 | -s | --strict Only execute subcommand if the test succeeds
17 | -q | --quiet Don't output any status messages
18 | -t TIMEOUT | --timeout=TIMEOUT
19 | Timeout in seconds, zero for no timeout
20 | -- COMMAND ARGS Execute command with args after the test finishes
21 | USAGE
22 | exit 1
23 | }
24 |
25 | wait_for()
26 | {
27 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
28 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
29 | else
30 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
31 | fi
32 | WAITFORIT_start_ts=$(date +%s)
33 | while :
34 | do
35 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
36 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT
37 | WAITFORIT_result=$?
38 | else
39 | (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
40 | WAITFORIT_result=$?
41 | fi
42 | if [[ $WAITFORIT_result -eq 0 ]]; then
43 | WAITFORIT_end_ts=$(date +%s)
44 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
45 | break
46 | fi
47 | sleep 1
48 | done
49 | return $WAITFORIT_result
50 | }
51 |
52 | wait_for_wrapper()
53 | {
54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
55 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then
56 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
57 | else
58 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
59 | fi
60 | WAITFORIT_PID=$!
61 | trap "kill -INT -$WAITFORIT_PID" INT
62 | wait $WAITFORIT_PID
63 | WAITFORIT_RESULT=$?
64 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then
65 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
66 | fi
67 | return $WAITFORIT_RESULT
68 | }
69 |
70 | # process arguments
71 | while [[ $# -gt 0 ]]
72 | do
73 | case "$1" in
74 | *:* )
75 | WAITFORIT_hostport=(${1//:/ })
76 | WAITFORIT_HOST=${WAITFORIT_hostport[0]}
77 | WAITFORIT_PORT=${WAITFORIT_hostport[1]}
78 | shift 1
79 | ;;
80 | --child)
81 | WAITFORIT_CHILD=1
82 | shift 1
83 | ;;
84 | -q | --quiet)
85 | WAITFORIT_QUIET=1
86 | shift 1
87 | ;;
88 | -s | --strict)
89 | WAITFORIT_STRICT=1
90 | shift 1
91 | ;;
92 | -h)
93 | WAITFORIT_HOST="$2"
94 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi
95 | shift 2
96 | ;;
97 | --host=*)
98 | WAITFORIT_HOST="${1#*=}"
99 | shift 1
100 | ;;
101 | -p)
102 | WAITFORIT_PORT="$2"
103 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi
104 | shift 2
105 | ;;
106 | --port=*)
107 | WAITFORIT_PORT="${1#*=}"
108 | shift 1
109 | ;;
110 | -t)
111 | WAITFORIT_TIMEOUT="$2"
112 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
113 | shift 2
114 | ;;
115 | --timeout=*)
116 | WAITFORIT_TIMEOUT="${1#*=}"
117 | shift 1
118 | ;;
119 | --)
120 | shift
121 | WAITFORIT_CLI=("$@")
122 | break
123 | ;;
124 | --help)
125 | usage
126 | ;;
127 | *)
128 | echoerr "Unknown argument: $1"
129 | usage
130 | ;;
131 | esac
132 | done
133 |
134 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
135 | echoerr "Error: you need to provide a host and port to test."
136 | usage
137 | fi
138 |
139 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
140 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
141 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
142 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
143 |
144 | # Check to see if timeout is from busybox?
145 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
146 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
147 |
148 | WAITFORIT_BUSYTIMEFLAG=""
149 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
150 | WAITFORIT_ISBUSY=1
151 | # Check if busybox timeout uses -t flag
152 | # (recent Alpine versions don't support -t anymore)
153 | if timeout &>/dev/stdout | grep -q -e '-t '; then
154 | WAITFORIT_BUSYTIMEFLAG="-t"
155 | fi
156 | else
157 | WAITFORIT_ISBUSY=0
158 | fi
159 |
160 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then
161 | wait_for
162 | WAITFORIT_RESULT=$?
163 | exit $WAITFORIT_RESULT
164 | else
165 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
166 | wait_for_wrapper
167 | WAITFORIT_RESULT=$?
168 | else
169 | wait_for
170 | WAITFORIT_RESULT=$?
171 | fi
172 | fi
173 |
174 | if [[ $WAITFORIT_CLI != "" ]]; then
175 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
176 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
177 | exit $WAITFORIT_RESULT
178 | fi
179 | exec "${WAITFORIT_CLI[@]}"
180 | else
181 | exit $WAITFORIT_RESULT
182 | fi
183 |
--------------------------------------------------------------------------------
/wait-for-it/test/wait-for-it.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import unittest
4 | import shlex
5 | from subprocess import Popen, PIPE
6 | import os
7 | import sys
8 | import socket
9 | import re
10 |
11 | MISSING_ARGS_TEXT = "Error: you need to provide a host and port to test."
12 | HELP_TEXT = "Usage:" # Start of help text
13 | DIVIDE_LINE = '-'*71 # Output line of dashes
14 |
15 |
16 | class TestWaitForIt(unittest.TestCase):
17 | """
18 | TestWaitForIt tests the wait-for-it.sh shell script.
19 | The wait-for-it.sh script is assumed to be in the parent directory to
20 | the test script.
21 | """
22 |
23 | def execute(self, cmd):
24 | """Executes a command and returns exit code, STDOUT, STDERR"""
25 | args = shlex.split(cmd)
26 | proc = Popen(args, stdout=PIPE, stderr=PIPE)
27 | out, err = proc.communicate()
28 | exitcode = proc.returncode
29 | return exitcode, out.decode('utf-8'), err.decode('utf-8')
30 |
31 | def open_local_port(self, timeout=5):
32 | s = socket.socket()
33 | s.bind(('', 0))
34 | s.listen(timeout)
35 | return s, s.getsockname()[1]
36 |
37 | def check_args(self, args, stdout_regex, stderr_regex, should_succeed):
38 | command = self.wait_script + " " + args
39 | exitcode, out, err = self.execute(command)
40 |
41 | # Check stderr
42 | msg = ("Failed check that STDERR:\n" +
43 | DIVIDE_LINE + "\n" + err + "\n" + DIVIDE_LINE +
44 | "\nmatches:\n" +
45 | DIVIDE_LINE + "\n" + stderr_regex + "\n" + DIVIDE_LINE)
46 | self.assertIsNotNone(re.match(stderr_regex, err, re.DOTALL), msg)
47 |
48 | # Check STDOUT
49 | msg = ("Failed check that STDOUT:\n" +
50 | DIVIDE_LINE + "\n" + out + "\n" + DIVIDE_LINE +
51 | "\nmatches:\n" +
52 | DIVIDE_LINE + "\n" + stdout_regex + "\n" + DIVIDE_LINE)
53 | self.assertIsNotNone(re.match(stdout_regex, out, re.DOTALL), msg)
54 |
55 | # Check exit code
56 | self.assertEqual(should_succeed, exitcode == 0)
57 |
58 | def setUp(self):
59 | script_path = os.path.dirname(sys.argv[0])
60 | parent_path = os.path.abspath(os.path.join(script_path, os.pardir))
61 | self.wait_script = os.path.join(parent_path, "wait-for-it.sh")
62 |
63 | def test_no_args(self):
64 | """
65 | Check that no aruments returns the missing args text and the
66 | correct return code
67 | """
68 | self.check_args(
69 | "",
70 | "^$",
71 | MISSING_ARGS_TEXT,
72 | False
73 | )
74 | # Return code should be 1 when called with no args
75 | exitcode, out, err = self.execute(self.wait_script)
76 | self.assertEqual(exitcode, 1)
77 |
78 | def test_help(self):
79 | """ Check that help text is printed with --help argument """
80 | self.check_args(
81 | "--help",
82 | "",
83 | HELP_TEXT,
84 | False
85 | )
86 |
87 | def test_no_port(self):
88 | """ Check with missing port argument """
89 | self.check_args(
90 | "--host=localhost",
91 | "",
92 | MISSING_ARGS_TEXT,
93 | False
94 | )
95 |
96 | def test_no_host(self):
97 | """ Check with missing hostname argument """
98 | self.check_args(
99 | "--port=80",
100 | "",
101 | MISSING_ARGS_TEXT,
102 | False
103 | )
104 |
105 | def test_host_port(self):
106 | """ Check that --host and --port args work correctly """
107 | soc, port = self.open_local_port()
108 | self.check_args(
109 | "--host=localhost --port={0} --timeout=1".format(port),
110 | "",
111 | "wait-for-it.sh: waiting 1 seconds for localhost:{0}".format(port),
112 | True
113 | )
114 | soc.close()
115 |
116 | def test_combined_host_port(self):
117 | """
118 | Tests that wait-for-it.sh returns correctly after establishing a
119 | connectionm using combined host and ports
120 | """
121 | soc, port = self.open_local_port()
122 | self.check_args(
123 | "localhost:{0} --timeout=1".format(port),
124 | "",
125 | "wait-for-it.sh: waiting 1 seconds for localhost:{0}".format(port),
126 | True
127 | )
128 | soc.close()
129 |
130 |
131 | def test_port_failure_with_timeout(self):
132 | """
133 | Note exit status of 124 is exected, passed from the timeout command
134 | """
135 | self.check_args(
136 | "localhost:8929 --timeout=1",
137 | "",
138 | ".*timeout occurred after waiting 1 seconds for localhost:8929",
139 | False
140 | )
141 |
142 | def test_command_execution(self):
143 | """
144 | Checks that a command executes correctly after a port test passes
145 | """
146 | soc, port = self.open_local_port()
147 | self.check_args(
148 | "localhost:{0} -- echo \"CMD OUTPUT\"".format(port),
149 | "CMD OUTPUT",
150 | ".*wait-for-it.sh: localhost:{0} is available after 0 seconds".format(port),
151 | True
152 | )
153 | soc.close()
154 |
155 | def test_failed_command_execution(self):
156 | """
157 | Check command failure. The command in question outputs STDERR and
158 | an exit code of 2
159 | """
160 | soc, port = self.open_local_port()
161 | self.check_args(
162 | "localhost:{0} -- ls not_real_file".format(port),
163 | "",
164 | ".*No such file or directory\n",
165 | False
166 | )
167 | soc.close()
168 |
169 | def test_command_after_connection_failure(self):
170 | """
171 | Test that a command still runs even if a connection times out
172 | and that the return code is correct for the comand being run
173 | """
174 | self.check_args(
175 | "localhost:8929 --timeout=1 -- echo \"CMD OUTPUT\"",
176 | "CMD OUTPUT",
177 | ".*timeout occurred after waiting 1 seconds for localhost:8929",
178 | True
179 | )
180 |
181 | if __name__ == '__main__':
182 | unittest.main()
183 |
--------------------------------------------------------------------------------
/packages/ffmpeg/tools.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "errors"
7 | "fmt"
8 | "log"
9 | "net/http"
10 | "sync"
11 |
12 | filemanager "github.com/amupxm/go-video-concat/interfaces/file-manager"
13 | "github.com/amupxm/go-video-concat/interfaces/frame"
14 | "github.com/amupxm/go-video-concat/interfaces/splash"
15 | "github.com/amupxm/go-video-concat/packages/cache"
16 |
17 | mediainfo "github.com/amupxm/go-video-concat/packages/mediaInfo"
18 | "github.com/amupxm/go-video-concat/packages/s3"
19 | "github.com/gofrs/uuid"
20 | "github.com/minio/minio-go/v7"
21 | )
22 |
23 | func (f *FFmpeg_Generator) GenerateChunks() {
24 | var wg sync.WaitGroup
25 | wg.Add(len(f.Recipe.Chunks))
26 | for chunkIndex := range f.Recipe.Chunks {
27 | switch f.Recipe.Chunks[chunkIndex].Type {
28 | case "image":
29 | go f.img2vid(chunkIndex, &wg)
30 | case "video":
31 | go f.trimVid(chunkIndex, &wg)
32 | }
33 | }
34 | wg.Wait()
35 | }
36 |
37 | // add color back ground as same size as frame requirements for Splash and place it in center.
38 | func (f *FFmpeg_Generator) FitSplash() {
39 | var splashMediaInfo mediainfo.MediaInfoStruct
40 | err := splashMediaInfo.GetVideoInfo(f.Dir + "splash-file")
41 | if err != nil {
42 | f.Error.Status = err == nil
43 | f.Error.Message = "mediaInfo splash error"
44 | return
45 | }
46 | splashCorrectionOperation := []string{
47 | "-i",
48 | f.Dir + "splash-file",
49 | "-f",
50 | "lavfi",
51 | "-i",
52 | fmt.Sprintf("color=red:s=%dx%d", MakeEvenNumber(f.Frame.Width), MakeEvenNumber(f.Frame.Height)),
53 | "-filter_complex",
54 | fmt.Sprintf("[1]scale=%d:-1[inner];[inner][0]overlay=%d:0:shortest=1[out]", MakeEvenNumber(f.Frame.Width), MakeEvenNumber((f.Frame.Width-int(splashMediaInfo.Width))/2)),
55 | "-map",
56 | "[out]:v",
57 | "-map",
58 | "0:a",
59 | f.Dir + "splash.final.mp4",
60 | }
61 | err = Execute(splashCorrectionOperation...)
62 | if err != nil {
63 | f.Error.Status = err == nil
64 | f.Error.Message = "mediaInfo splash error"
65 | }
66 | }
67 |
68 | // get splash file using splash from s3 and write to tmp dir
69 | func (f *FFmpeg_Generator) GetSplashFile(wg *sync.WaitGroup) {
70 | var objectStorage = &s3.ObjectStorage
71 | err := objectStorage.Client.FGetObject(
72 | context.Background(),
73 | "splash",
74 | f.Recipe.Splash,
75 | f.Dir+"splash-file",
76 | minio.GetObjectOptions{})
77 | if err != nil {
78 | f.Error.Status = err == nil
79 | f.Error.Message = "object splash error"
80 | }
81 | wg.Done()
82 | }
83 |
84 | // download all files (chunk + external audio id exists) and write to tmp dir.
85 | func (f *FFmpeg_Generator) DownloadFiles() {
86 | var wg sync.WaitGroup
87 |
88 | // add extra to wait group if has external audio (because it will download audio as external file from url)
89 | if f.Recipe.ExternalAudio {
90 | wg.Add(len(f.Recipe.Chunks) + 1)
91 |
92 | } else {
93 | wg.Add(len(f.Recipe.Chunks))
94 | }
95 |
96 | innerResponseChannel := make(chan *FFmpeg_Message)
97 |
98 | for chunkIndex := range f.Recipe.Chunks {
99 | go func(chunkIndex int) {
100 | // set a uuid as name for chunk (audio or video file)
101 | if newChunkName, err := uuid.NewV4(); err == nil {
102 | f.Recipe.Chunks[chunkIndex].Name = newChunkName.String()
103 | } else {
104 | innerResponseChannel <- &FFmpeg_Message{
105 | Status: false,
106 | Message: "internal error",
107 | }
108 | }
109 |
110 | status, statusCode := filemanager.DownloadFile(
111 | &wg,
112 | f.Recipe.Chunks[chunkIndex].Url,
113 | f.Dir+f.Recipe.Chunks[chunkIndex].Name)
114 | innerResponseChannel <- &FFmpeg_Message{
115 | Status: status,
116 | Message: "url responded withCode" + statusCode,
117 | }
118 | }(chunkIndex)
119 |
120 | }
121 | // download audio if exists
122 | if f.Recipe.ExternalAudio {
123 | go func() {
124 | audioName, _ := uuid.NewV4()
125 | filemanager.DownloadFile(
126 | &wg,
127 | f.Recipe.Audio,
128 | f.Dir+audioName.String())
129 | f.Recipe.Audio = audioName.String()
130 | }()
131 |
132 | }
133 | wg.Wait()
134 |
135 | isAllDownloaded := true
136 | count := 0
137 | for responseFromInnerChannel := range innerResponseChannel {
138 | isAllDownloaded = isAllDownloaded && responseFromInnerChannel.Status
139 | log.Println("Downloaded to : " + f.Dir + f.Recipe.Chunks[count].Name)
140 | count++
141 | if count == len(f.Recipe.Chunks) {
142 | close(innerResponseChannel)
143 | }
144 | }
145 |
146 | if !isAllDownloaded {
147 | f.Error.Status = false
148 | f.Error.Message = "error while downloading files"
149 | }
150 | }
151 |
152 | // get frame object from s3 and write to temp dir
153 | func (f *FFmpeg_Generator) GetFrameFile(wg *sync.WaitGroup) {
154 | var objectStorage = &s3.ObjectStorage
155 | err := objectStorage.Client.FGetObject(
156 | context.Background(),
157 | "frame",
158 | f.Frame.FileCode,
159 | f.Dir+f.Recipe.FrameCode,
160 | minio.GetObjectOptions{})
161 | if err != nil {
162 | f.Error.Status = false
163 | f.Error.Message = "s3 error"
164 | }
165 | wg.Done()
166 |
167 | }
168 |
169 | // send FFMPEG_MESSAGE to response channel. use to send errors to api
170 | func (f *FFmpeg_Generator) ResponseError() {
171 | cache.UpdateStatus(f.UUID, f.Error.Message, false)
172 | }
173 |
174 | // get Splash details from database and validate it.
175 | // set error on invalid frame.
176 | func (f *FFmpeg_Generator) ValidateSplash() {
177 | var splashFinder splash.Splash
178 | tmpSplash, status := splashFinder.FindSplash(f.Recipe.Splash)
179 | f.Error.Status = !status
180 | f.Error.Message = "splash file error"
181 | f.Splash = &tmpSplash
182 | }
183 |
184 | // get frame details from database and validate it.
185 | // set error on invalid frame.
186 | func (f *FFmpeg_Generator) ValidateFrame() {
187 | var frameFinder frame.Frame
188 | tmpFrame, status := frameFinder.FindFrame(f.Recipe.FrameCode)
189 | f.Error.Status = !status
190 | f.Error.Message = "template file error"
191 | f.Frame = &tmpFrame
192 | }
193 |
194 | // modify Temp directory
195 | // with operation (init)
196 | func (f *FFmpeg_Generator) TmpDir(operation string) {
197 | fm := filemanager.Temperory{DirName: f.UUID}
198 | if operation == "init" {
199 | f.Dir = "/tmp/" + f.UUID + "/"
200 | err := fm.CreateTempDir()
201 | if err != nil {
202 | f.Error.Status = false
203 | f.Error.Message = "file operation error"
204 | }
205 | }
206 | }
207 |
208 | func (f *FFmpeg_Generator) GetFromS3(code string) (*minio.Object, string, error) {
209 |
210 | var s3 = &s3.ObjectStorage
211 | var tmp *minio.Object
212 |
213 | reader, err := s3.Client.GetObject(context.Background(), "amupxm", code, minio.GetObjectOptions{})
214 | if err != nil {
215 | return tmp, "", err
216 | }
217 |
218 | if err != nil {
219 | return tmp, "", err
220 | }
221 | return reader, "", nil
222 | }
223 |
224 | //write results to minio
225 | func (f *FFmpeg_Generator) WriteTos3() error {
226 | var s3 = &s3.ObjectStorage
227 | _, err := s3.Client.FPutObject(
228 | context.Background(),
229 | "amupxm",
230 | f.UUID,
231 | f.Dir+"x.mp4",
232 | minio.PutObjectOptions{},
233 | )
234 | if err != nil {
235 | log.Println(111, err)
236 | return err
237 | }
238 | log.Println(111)
239 | return nil
240 | }
241 |
242 | // send callback status
243 | func (f *FFmpeg_Generator) SendResponseCallback() error {
244 | var callBack = []byte(`{"status":true , "url" : "url" }`)
245 | req, err := http.NewRequest(
246 | "POST",
247 | f.Recipe.CallBackAddress,
248 | bytes.NewBuffer(callBack),
249 | )
250 | if err != nil {
251 | return errors.New("invalid request generation")
252 | }
253 | req.Header.Set("Content-Type", "application/json")
254 | cli := &http.Client{}
255 | resp, err := cli.Do(req)
256 | if err != nil {
257 | return errors.New("call back failed ")
258 | }
259 | if resp.Status != "200 OK" {
260 | return errors.New("callback responded with not 200 code")
261 | }
262 | defer resp.Body.Close()
263 | return nil
264 | }
265 |
--------------------------------------------------------------------------------
/packages/ffmpeg/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 | "sync"
8 |
9 | filemanager "github.com/amupxm/go-video-concat/interfaces/file-manager"
10 | "github.com/amupxm/go-video-concat/interfaces/splash"
11 | "github.com/amupxm/go-video-concat/packages/cache"
12 | "github.com/amupxm/go-video-concat/packages/s3"
13 | "github.com/minio/minio-go/v7"
14 | )
15 |
16 | // create video from images with sertain duration ( end - start seconds )
17 | func (f *FFmpeg_Generator) img2vid(index int, wg *sync.WaitGroup) {
18 |
19 | img2vidArgs := []string{
20 | "-r",
21 | "30",
22 | "-framerate",
23 | fmt.Sprintf("1/%d", f.Recipe.Chunks[index].End-f.Recipe.Chunks[index].Start),
24 | "-i",
25 | f.Dir + f.Recipe.Chunks[index].Name,
26 | "-f", "lavfi", "-i", "anullsrc",
27 | "-filter_complex",
28 | fmt.Sprintf("[0:v]split=2[blur][vid];[blur]scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,boxblur=luma_radius=min(h\\,w)/20:luma_power=1:chroma_radius=min(cw\\,ch)/20:chroma_power=1[bg];[vid]scale=%d:%d:force_original_aspect_ratio=decrease[ov];[bg][ov]overlay=(W-w)/2:(H-h)/2[out]",
29 | f.Frame.Width, f.Frame.Height,
30 | f.Frame.Width, f.Frame.Height,
31 | f.Frame.Width, f.Frame.Height,
32 | ),
33 | "-vcodec", "libx264",
34 | "-pix_fmt", "yuv420p",
35 | "-map",
36 | "[out]:v", "-map", "1:a", "-shortest",
37 | fmt.Sprintf("%s/%d.final.mp4", f.Dir, index),
38 | }
39 | err := Execute(img2vidArgs...)
40 |
41 | if err != nil {
42 | f.Error.Status = false
43 | f.Error.Message = "img 2 vid failure"
44 | }
45 | wg.Done()
46 | }
47 |
48 | // cut video with sertain duration ( end - start seconds )
49 | func (f *FFmpeg_Generator) trimVid(index int, wg *sync.WaitGroup) {
50 | vidTrimArgs := []string{
51 | "-i",
52 | f.Dir + f.Recipe.Chunks[index].Name,
53 | "-ss",
54 | "0",
55 | "-t",
56 | fmt.Sprintf("%d", f.Recipe.Chunks[index].End-f.Recipe.Chunks[index].Start),
57 | "-async",
58 | "1",
59 | "-filter_complex",
60 | fmt.Sprintf("[0:v]split=2[blur][vid];[blur]scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,boxblur=luma_radius=min(h\\,w)/20:luma_power=1:chroma_radius=min(cw\\,ch)/20:chroma_power=1[bg];[vid]scale=%d:%d:force_original_aspect_ratio=decrease[ov];[bg][ov]overlay=(W-w)/2:(H-h)/2",
61 | f.Frame.Width, f.Frame.Height,
62 | f.Frame.Width, f.Frame.Height,
63 | f.Frame.Width, f.Frame.Height),
64 | "-c:a",
65 | "copy",
66 | "-avoid_negative_ts", "make_zero", "-fflags", "+genpts",
67 | fmt.Sprintf("%s/%d.final.mp4", f.Dir, index),
68 | }
69 | err := Execute(vidTrimArgs...)
70 | if err != nil {
71 | f.Error.Status = false
72 | f.Error.Message = "trim video failure"
73 | }
74 | wg.Done()
75 | }
76 |
77 | // connect chunk sections together to create video ( include splash )
78 | func (f *FFmpeg_Generator) Concat() bool {
79 | count := len(f.Recipe.Chunks)
80 | inputsStr := []string{}
81 |
82 | inputsStr = append(inputsStr, "-i", f.Dir+"splash.final.mp4")
83 | filterStr := "[0:v:0] [0:a:0] "
84 |
85 | for i := 0; i < count; i++ {
86 | inputsStr = append(inputsStr, "-i", fmt.Sprintf("%s%d.final.mp4", f.Dir, i))
87 | filterStr = filterStr + fmt.Sprintf("[%d:v:0] [%d:a:0] ", i+1, i+1)
88 | }
89 | concatArgs := fmt.Sprintf("concat=n=%d:v=1:a=1:unsafe=1 [v] [a]", count+1)
90 | mapArgs := []string{
91 | "-map", "[v]", "-map", "[a]",
92 | }
93 | args := []string{}
94 | args = append(args, inputsStr...)
95 | args = append(args, "-filter_complex")
96 | args = append(args, fmt.Sprintf("%s%s", filterStr, concatArgs))
97 | args = append(args, mapArgs...)
98 | args = append(args, f.Dir+"final.output.mp4")
99 |
100 | err := Execute(args...)
101 |
102 | return err == nil
103 | }
104 |
105 | // Overlay Concatted video and add audio file to it and set sound level
106 | func (f *FFmpeg_Generator) OverLay() {
107 | var args []string
108 | if f.Recipe.ExternalAudio {
109 | args = []string{"-loop", "1", "-i", f.Dir + f.Frame.FileCode, "-i", f.Dir + "final.output.mp4",
110 | "-i", f.Dir + f.Recipe.Audio,
111 | "-filter_complex",
112 | fmt.Sprintf("[1]scale=%d:%d[inner];[0][inner]overlay=0:%d:shortest=1[out];[1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=1[a1];[2:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.%d[a2];[a1][a2]amerge=inputs=2,pan=stereo:c0 trim audio ::
196 | _, err = storage.Client.StatObject(context.Background(), "splash-audio", splash.BaseAudio, minio.StatObjectOptions{})
197 | if err != nil {
198 | return &FFmpeg_Message{Message: "Object error", Status: false}
199 | }
200 | var audio = FFmpeg_Audio{Input: "/tmp/" + splash.FileCode + "/" + splash.BaseAudio, Output: "/tmp/" + splash.FileCode + "/" + "output.mp3", Duration: int(splash.MaxLength)}
201 | TrimAudio(audio)
202 |
203 | var video = FFmpeg_Splash{Audio: "/tmp/" + splash.FileCode + "/" + "output.mp3", Video: "/tmp/" + splash.FileCode + "/" + splash.BaseFile, Output: "/tmp/" + splash.FileCode + "/out.mp4"}
204 | CreateSplash(video)
205 |
206 | xx, err := storage.Client.FPutObject(context.Background(), "splash", splash.FileCode, video.Output, minio.PutObjectOptions{})
207 | fmt.Print("\n\n\n\n\n\n\n\n", xx)
208 | if err != nil {
209 | return &FFmpeg_Message{Message: "Object error", Status: false}
210 | }
211 | // ffmpeg.New().Input()
212 | return &FFmpeg_Message{Message: "done", Status: true}
213 |
214 | }
215 |
216 | /*
217 | return i+1 if i%2 == 0 || i
218 | */
219 | func MakeEvenNumber(i int) int {
220 | if i%2 == 0 {
221 | return i
222 | }
223 | return i + 1
224 | }
225 |
--------------------------------------------------------------------------------
/transcoder/ffmpeg/metadata.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import "github.com/amupxm/go-video-concat/transcoder"
4 |
5 | // Metadata ...
6 | type Metadata struct {
7 | Format Format `json:"format"`
8 | Streams []Streams `json:"streams"`
9 | }
10 |
11 | // Format ...
12 | type Format struct {
13 | Filename string
14 | NbStreams int `json:"nb_streams"`
15 | NbPrograms int `json:"nb_programs"`
16 | FormatName string `json:"format_name"`
17 | FormatLongName string `json:"format_long_name"`
18 | Duration string `json:"duration"`
19 | Size string `json:"size"`
20 | BitRate string `json:"bit_rate"`
21 | ProbeScore int `json:"probe_score"`
22 | Tags Tags `json:"tags"`
23 | }
24 |
25 | // Streams ...
26 | type Streams struct {
27 | Index int
28 | ID string `json:"id"`
29 | CodecName string `json:"codec_name"`
30 | CodecLongName string `json:"codec_long_name"`
31 | Profile string `json:"profile"`
32 | CodecType string `json:"codec_type"`
33 | CodecTimeBase string `json:"codec_time_base"`
34 | CodecTagString string `json:"codec_tag_string"`
35 | CodecTag string `json:"codec_tag"`
36 | Width int `json:"width"`
37 | Height int `json:"height"`
38 | CodedWidth int `json:"coded_width"`
39 | CodedHeight int `json:"coded_height"`
40 | HasBFrames int `json:"has_b_frames"`
41 | SampleAspectRatio string `json:"sample_aspect_ratio"`
42 | DisplayAspectRatio string `json:"display_aspect_ratio"`
43 | PixFmt string `json:"pix_fmt"`
44 | Level int `json:"level"`
45 | ChromaLocation string `json:"chroma_location"`
46 | Refs int `json:"refs"`
47 | QuarterSample string `json:"quarter_sample"`
48 | DivxPacked string `json:"divx_packed"`
49 | RFrameRrate string `json:"r_frame_rate"`
50 | AvgFrameRate string `json:"avg_frame_rate"`
51 | TimeBase string `json:"time_base"`
52 | DurationTs int `json:"duration_ts"`
53 | Duration string `json:"duration"`
54 | Disposition Disposition `json:"disposition"`
55 | BitRate string `json:"bit_rate"`
56 | }
57 |
58 | // Tags ...
59 | type Tags struct {
60 | Encoder string `json:"ENCODER"`
61 | }
62 |
63 | // Disposition ...
64 | type Disposition struct {
65 | Default int `json:"default"`
66 | Dub int `json:"dub"`
67 | Original int `json:"original"`
68 | Comment int `json:"comment"`
69 | Lyrics int `json:"lyrics"`
70 | Karaoke int `json:"karaoke"`
71 | Forced int `json:"forced"`
72 | HearingImpaired int `json:"hearing_impaired"`
73 | VisualImpaired int `json:"visual_impaired"`
74 | CleanEffects int `json:"clean_effects"`
75 | }
76 |
77 | // GetFormat ...
78 | func (m Metadata) GetFormat() transcoder.Format {
79 | return m.Format
80 | }
81 |
82 | // GetStreams ...
83 | func (m Metadata) GetStreams() (streams []transcoder.Streams) {
84 | for _, element := range m.Streams {
85 | streams = append(streams, element)
86 | }
87 | return streams
88 | }
89 |
90 | // GetFilename ...
91 | func (f Format) GetFilename() string {
92 | return f.Filename
93 | }
94 |
95 | // GetNbStreams ...
96 | func (f Format) GetNbStreams() int {
97 | return f.NbStreams
98 | }
99 |
100 | // GetNbPrograms ...
101 | func (f Format) GetNbPrograms() int {
102 | return f.NbPrograms
103 | }
104 |
105 | // GetFormatName ...
106 | func (f Format) GetFormatName() string {
107 | return f.FormatName
108 | }
109 |
110 | // GetFormatLongName ...
111 | func (f Format) GetFormatLongName() string {
112 | return f.FormatLongName
113 | }
114 |
115 | // GetDuration ...
116 | func (f Format) GetDuration() string {
117 | return f.Duration
118 | }
119 |
120 | // GetSize ...
121 | func (f Format) GetSize() string {
122 | return f.Size
123 | }
124 |
125 | // GetBitRate ...
126 | func (f Format) GetBitRate() string {
127 | return f.BitRate
128 | }
129 |
130 | // GetProbeScore ...
131 | func (f Format) GetProbeScore() int {
132 | return f.ProbeScore
133 | }
134 |
135 | // GetTags ...
136 | func (f Format) GetTags() transcoder.Tags {
137 | return f.Tags
138 | }
139 |
140 | // GetEncoder ...
141 | func (t Tags) GetEncoder() string {
142 | return t.Encoder
143 | }
144 |
145 | //GetIndex ...
146 | func (s Streams) GetIndex() int {
147 | return s.Index
148 | }
149 |
150 | //GetID ...
151 | func (s Streams) GetID() string {
152 | return s.ID
153 | }
154 |
155 | //GetCodecName ...
156 | func (s Streams) GetCodecName() string {
157 | return s.CodecName
158 | }
159 |
160 | //GetCodecLongName ...
161 | func (s Streams) GetCodecLongName() string {
162 | return s.CodecLongName
163 | }
164 |
165 | //GetProfile ...
166 | func (s Streams) GetProfile() string {
167 | return s.Profile
168 | }
169 |
170 | //GetCodecType ...
171 | func (s Streams) GetCodecType() string {
172 | return s.CodecType
173 | }
174 |
175 | //GetCodecTimeBase ...
176 | func (s Streams) GetCodecTimeBase() string {
177 | return s.CodecTimeBase
178 | }
179 |
180 | //GetCodecTagString ...
181 | func (s Streams) GetCodecTagString() string {
182 | return s.CodecTagString
183 | }
184 |
185 | //GetCodecTag ...
186 | func (s Streams) GetCodecTag() string {
187 | return s.CodecTag
188 | }
189 |
190 | //GetWidth ...
191 | func (s Streams) GetWidth() int {
192 | return s.Width
193 | }
194 |
195 | //GetHeight ...
196 | func (s Streams) GetHeight() int {
197 | return s.Height
198 | }
199 |
200 | //GetCodedWidth ...
201 | func (s Streams) GetCodedWidth() int {
202 | return s.CodedWidth
203 | }
204 |
205 | //GetCodedHeight ...
206 | func (s Streams) GetCodedHeight() int {
207 | return s.CodedHeight
208 | }
209 |
210 | //GetHasBFrames ...
211 | func (s Streams) GetHasBFrames() int {
212 | return s.HasBFrames
213 | }
214 |
215 | //GetSampleAspectRatio ...
216 | func (s Streams) GetSampleAspectRatio() string {
217 | return s.SampleAspectRatio
218 | }
219 |
220 | //GetDisplayAspectRatio ...
221 | func (s Streams) GetDisplayAspectRatio() string {
222 | return s.DisplayAspectRatio
223 | }
224 |
225 | //GetPixFmt ...
226 | func (s Streams) GetPixFmt() string {
227 | return s.PixFmt
228 | }
229 |
230 | //GetLevel ...
231 | func (s Streams) GetLevel() int {
232 | return s.Level
233 | }
234 |
235 | //GetChromaLocation ...
236 | func (s Streams) GetChromaLocation() string {
237 | return s.ChromaLocation
238 | }
239 |
240 | //GetRefs ...
241 | func (s Streams) GetRefs() int {
242 | return s.Refs
243 | }
244 |
245 | //GetQuarterSample ...
246 | func (s Streams) GetQuarterSample() string {
247 | return s.QuarterSample
248 | }
249 |
250 | //GetDivxPacked ...
251 | func (s Streams) GetDivxPacked() string {
252 | return s.DivxPacked
253 | }
254 |
255 | //GetRFrameRrate ...
256 | func (s Streams) GetRFrameRrate() string {
257 | return s.RFrameRrate
258 | }
259 |
260 | //GetAvgFrameRate ...
261 | func (s Streams) GetAvgFrameRate() string {
262 | return s.AvgFrameRate
263 | }
264 |
265 | //GetTimeBase ...
266 | func (s Streams) GetTimeBase() string {
267 | return s.TimeBase
268 | }
269 |
270 | //GetDurationTs ...
271 | func (s Streams) GetDurationTs() int {
272 | return s.DurationTs
273 | }
274 |
275 | //GetDuration ...
276 | func (s Streams) GetDuration() string {
277 | return s.Duration
278 | }
279 |
280 | //GetDisposition ...
281 | func (s Streams) GetDisposition() transcoder.Disposition {
282 | return s.Disposition
283 | }
284 |
285 | //GetBitRate ...
286 | func (s Streams) GetBitRate() string {
287 | return s.BitRate
288 | }
289 |
290 | //GetDefault ...
291 | func (d Disposition) GetDefault() int {
292 | return d.Default
293 | }
294 |
295 | //GetDub ...
296 | func (d Disposition) GetDub() int {
297 | return d.Dub
298 | }
299 |
300 | //GetOriginal ...
301 | func (d Disposition) GetOriginal() int {
302 | return d.Original
303 | }
304 |
305 | //GetComment ...
306 | func (d Disposition) GetComment() int {
307 | return d.Comment
308 | }
309 |
310 | //GetLyrics ...
311 | func (d Disposition) GetLyrics() int {
312 | return d.Lyrics
313 | }
314 |
315 | //GetKaraoke ...
316 | func (d Disposition) GetKaraoke() int {
317 | return d.Karaoke
318 | }
319 |
320 | //GetForced ...
321 | func (d Disposition) GetForced() int {
322 | return d.Forced
323 | }
324 |
325 | //GetHearingImpaired ...
326 | func (d Disposition) GetHearingImpaired() int {
327 | return d.HearingImpaired
328 | }
329 |
330 | //GetVisualImpaired ...
331 | func (d Disposition) GetVisualImpaired() int {
332 | return d.VisualImpaired
333 | }
334 |
335 | //GetCleanEffects ...
336 | func (d Disposition) GetCleanEffects() int {
337 | return d.CleanEffects
338 | }
339 |
--------------------------------------------------------------------------------
/transcoder/ffmpeg/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "os"
11 | "os/exec"
12 | "regexp"
13 | "strconv"
14 | "strings"
15 |
16 | "github.com/amupxm/go-video-concat/transcoder"
17 | "github.com/floostack/transcoder/utils"
18 | )
19 |
20 | // Transcoder ...
21 | type Transcoder struct {
22 | config *Config
23 | input string
24 | output []string
25 | options [][]string
26 | metadata transcoder.Metadata
27 | inputPipeReader *io.ReadCloser
28 | outputPipeReader *io.ReadCloser
29 | inputPipeWriter *io.WriteCloser
30 | outputPipeWriter *io.WriteCloser
31 | }
32 |
33 | // New ...
34 | func New(cfg *Config) transcoder.Transcoder {
35 | return &Transcoder{config: cfg}
36 | }
37 |
38 | // Start ...
39 | func (t *Transcoder) Start(opts transcoder.Options) (<-chan transcoder.Progress, error) {
40 |
41 | var stderrIn io.ReadCloser
42 |
43 | out := make(chan transcoder.Progress)
44 |
45 | defer t.closePipes()
46 |
47 | // Validates config
48 | if err := t.validate(); err != nil {
49 | return nil, err
50 | }
51 |
52 | // Get file metadata
53 | _, err := t.GetMetadata()
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | // Append input file and standard options
59 | args := append([]string{"-i", t.input}, opts.GetStrArguments()...)
60 | outputLength := len(t.output)
61 | optionsLength := len(t.options)
62 |
63 | if outputLength == 1 && optionsLength == 0 {
64 | // Just append the 1 output file we've got
65 | args = append(args, t.output[0])
66 | } else {
67 | for index, out := range t.output {
68 | // Get executable flags
69 | // If we are at the last output file but still have several options, append them all at once
70 | if index == outputLength-1 && outputLength < optionsLength {
71 | for i := index; i < len(t.options); i++ {
72 | args = append(args, t.options[i]...)
73 | }
74 | // Otherwise just append the current options
75 | } else {
76 | args = append(args, t.options[index]...)
77 | }
78 |
79 | // Append output flag
80 | args = append(args, out)
81 | }
82 | }
83 |
84 | // Initialize command
85 | cmd := exec.Command(t.config.FfmpegBinPath, args...)
86 |
87 | // If progress enabled, get stderr pipe and start progress process
88 | if t.config.ProgressEnabled && !t.config.Verbose {
89 | stderrIn, err = cmd.StderrPipe()
90 | if err != nil {
91 | return nil, fmt.Errorf("failed getting transcoding progress (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err)
92 | }
93 | }
94 |
95 | if t.config.Verbose {
96 | cmd.Stderr = os.Stdout
97 | }
98 |
99 | // Start process
100 | err = cmd.Start()
101 | if err != nil {
102 | return nil, fmt.Errorf("failed starting transcoding (%s) with args (%s) with error %s", t.config.FfmpegBinPath, args, err)
103 | }
104 |
105 | if t.config.ProgressEnabled && !t.config.Verbose {
106 | go func() {
107 | t.progress(stderrIn, out)
108 | }()
109 |
110 | go func() {
111 | defer close(out)
112 | err = cmd.Wait()
113 | }()
114 | } else {
115 | err = cmd.Wait()
116 | }
117 |
118 | return out, nil
119 | }
120 |
121 | // Input ...
122 | func (t *Transcoder) Input(arg string) transcoder.Transcoder {
123 | t.input = arg
124 | return t
125 | }
126 |
127 | // Output ...
128 | func (t *Transcoder) Output(arg string) transcoder.Transcoder {
129 | t.output = append(t.output, arg)
130 | return t
131 | }
132 |
133 | // InputPipe ...
134 | func (t *Transcoder) InputPipe(w *io.WriteCloser, r *io.ReadCloser) transcoder.Transcoder {
135 | if &t.input == nil {
136 | t.inputPipeWriter = w
137 | t.inputPipeReader = r
138 | }
139 | return t
140 | }
141 |
142 | // OutputPipe ...
143 | func (t *Transcoder) OutputPipe(w *io.WriteCloser, r *io.ReadCloser) transcoder.Transcoder {
144 | if &t.output == nil {
145 | t.outputPipeWriter = w
146 | t.outputPipeReader = r
147 | }
148 | return t
149 | }
150 |
151 | // WithOptions Sets the options object
152 | func (t *Transcoder) WithOptions(opts transcoder.Options) transcoder.Transcoder {
153 | t.options = [][]string{opts.GetStrArguments()}
154 | return t
155 | }
156 |
157 | // WithAdditionalOptions Appends an additional options object
158 | func (t *Transcoder) WithAdditionalOptions(opts transcoder.Options) transcoder.Transcoder {
159 | t.options = append(t.options, opts.GetStrArguments())
160 | return t
161 | }
162 |
163 | // validate ...
164 | func (t *Transcoder) validate() error {
165 | if t.config.FfmpegBinPath == "" {
166 | return errors.New("ffmpeg binary path not found")
167 | }
168 |
169 | if t.input == "" {
170 | return errors.New("missing input option")
171 | }
172 |
173 | outputLength := len(t.output)
174 |
175 | if outputLength == 0 {
176 | return errors.New("missing output option")
177 | }
178 |
179 | // length of output files being greater than length of options would produce an invalid ffmpeg command
180 | // unless there is only 1 output file, which obviously wouldn't be a problem
181 | if outputLength > len(t.options) && outputLength != 1 {
182 | return errors.New("number of options and output files does not match")
183 | }
184 |
185 | for index, output := range t.output {
186 | if output == "" {
187 | return fmt.Errorf("output at index %d is an empty string", index)
188 | }
189 | }
190 |
191 | return nil
192 | }
193 |
194 | // GetMetadata Returns metadata for the specified input file
195 | func (t *Transcoder) GetMetadata() (transcoder.Metadata, error) {
196 |
197 | if t.config.FfprobeBinPath != "" {
198 | var outb, errb bytes.Buffer
199 |
200 | input := t.input
201 |
202 | if t.inputPipeReader != nil {
203 | input = "pipe:"
204 | }
205 |
206 | args := []string{"-i", input, "-print_format", "json", "-show_format", "-show_streams", "-show_error"}
207 |
208 | cmd := exec.Command(t.config.FfprobeBinPath, args...)
209 | cmd.Stdout = &outb
210 | cmd.Stderr = &errb
211 |
212 | err := cmd.Run()
213 | if err != nil {
214 | return nil, fmt.Errorf("error executing (%s) with args (%s) | error: %s | message: %s %s", t.config.FfprobeBinPath, args, err, outb.String(), errb.String())
215 | }
216 |
217 | var metadata Metadata
218 |
219 | if err = json.Unmarshal([]byte(outb.String()), &metadata); err != nil {
220 | return nil, err
221 | }
222 |
223 | t.metadata = metadata
224 |
225 | return metadata, nil
226 | }
227 |
228 | return nil, errors.New("ffprobe binary not found")
229 | }
230 |
231 | // progress sends through given channel the transcoding status
232 | func (t *Transcoder) progress(stream io.ReadCloser, out chan transcoder.Progress) {
233 |
234 | defer stream.Close()
235 |
236 | split := func(data []byte, atEOF bool) (advance int, token []byte, spliterror error) {
237 | if atEOF && len(data) == 0 {
238 | return 0, nil, nil
239 | }
240 | if i := bytes.IndexByte(data, '\n'); i >= 0 {
241 | // We have a full newline-terminated line.
242 | return i + 1, data[0:i], nil
243 | }
244 | if i := bytes.IndexByte(data, '\r'); i >= 0 {
245 | // We have a cr terminated line
246 | return i + 1, data[0:i], nil
247 | }
248 | if atEOF {
249 | return len(data), data, nil
250 | }
251 |
252 | return 0, nil, nil
253 | }
254 |
255 | scanner := bufio.NewScanner(stream)
256 | scanner.Split(split)
257 |
258 | buf := make([]byte, 2)
259 | scanner.Buffer(buf, bufio.MaxScanTokenSize)
260 |
261 | for scanner.Scan() {
262 | Progress := new(Progress)
263 | line := scanner.Text()
264 |
265 | if strings.Contains(line, "frame=") && strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
266 | var re = regexp.MustCompile(`=\s+`)
267 | st := re.ReplaceAllString(line, `=`)
268 |
269 | f := strings.Fields(st)
270 |
271 | var framesProcessed string
272 | var currentTime string
273 | var currentBitrate string
274 | var currentSpeed string
275 |
276 | for j := 0; j < len(f); j++ {
277 | field := f[j]
278 | fieldSplit := strings.Split(field, "=")
279 |
280 | if len(fieldSplit) > 1 {
281 | fieldname := strings.Split(field, "=")[0]
282 | fieldvalue := strings.Split(field, "=")[1]
283 |
284 | if fieldname == "frame" {
285 | framesProcessed = fieldvalue
286 | }
287 |
288 | if fieldname == "time" {
289 | currentTime = fieldvalue
290 | }
291 |
292 | if fieldname == "bitrate" {
293 | currentBitrate = fieldvalue
294 | }
295 | if fieldname == "speed" {
296 | currentSpeed = fieldvalue
297 | }
298 | }
299 | }
300 |
301 | timesec := utils.DurToSec(currentTime)
302 | dursec, _ := strconv.ParseFloat(t.metadata.GetFormat().GetDuration(), 64)
303 |
304 | progress := (timesec * 100) / dursec
305 | Progress.Progress = progress
306 |
307 | Progress.CurrentBitrate = currentBitrate
308 | Progress.FramesProcessed = framesProcessed
309 | Progress.CurrentTime = currentTime
310 | Progress.Speed = currentSpeed
311 |
312 | out <- *Progress
313 | }
314 | }
315 | }
316 |
317 | // closePipes Closes pipes if opened
318 | func (t *Transcoder) closePipes() {
319 | if t.inputPipeReader != nil {
320 | ipr := *t.inputPipeReader
321 | ipr.Close()
322 | }
323 |
324 | if t.outputPipeWriter != nil {
325 | opr := *t.outputPipeWriter
326 | opr.Close()
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
6 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
7 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
8 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
9 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
10 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
11 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
12 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
13 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
14 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
15 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
16 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
17 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
18 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
19 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
20 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
21 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
22 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
23 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
24 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
27 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
28 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
29 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
30 | github.com/cavaliercoder/grab v1.0.0 h1:H6VQ1NiLO7AvXM6ZyaInnoZrRLeo2FoUTQEcXln4bvQ=
31 | github.com/cavaliercoder/grab v2.0.0+incompatible h1:wZHbBQx56+Yxjx2TCGDcenhh3cJn7cCLMfkEPmySTSE=
32 | github.com/cavaliercoder/grab v2.0.0+incompatible/go.mod h1:tTBkfNqSBfuMmMBFaO2phgyhdYhiZQ/+iXCZDzcDsMI=
33 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
34 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
35 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
36 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
37 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
38 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
39 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
40 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
41 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
42 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
43 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
44 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
45 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
46 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
47 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
48 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
49 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
50 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
51 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
52 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
53 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
54 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
55 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
56 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
57 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
58 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
59 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
60 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
61 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
62 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
63 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
64 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
65 | github.com/floostack/transcoder v1.1.1 h1:ApvdTZGt4r1N8dcT1PEl4jxX5OBHaOMY42kmCKNbYCE=
66 | github.com/floostack/transcoder v1.1.1/go.mod h1:lT+f8NEGaHP6AVeYLicas0EI0+TforD+DRuLziJg6bY=
67 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
68 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
69 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
70 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
71 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
72 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
73 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
74 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
75 | github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8=
76 | github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
77 | github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU=
78 | github.com/go-ini/ini v1.62.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
79 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
80 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
81 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
82 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
83 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
84 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
85 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
86 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
87 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
88 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
89 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
90 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
91 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
92 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
93 | github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
94 | github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
95 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
96 | github.com/go-redis/redis/v8 v8.8.2 h1:O/NcHqobw7SEptA0yA6up6spZVFtwE06SXM8rgLtsP8=
97 | github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
98 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
99 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
100 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
101 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
102 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
103 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
104 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
105 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
106 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
107 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
108 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
109 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
110 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
111 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
112 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
113 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
114 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
115 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
116 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
117 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
118 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
119 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
120 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
121 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
122 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
123 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
124 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
125 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
126 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
127 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
128 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
129 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
130 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
131 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
132 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
133 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
134 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
135 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
136 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
137 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
138 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
139 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
140 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
141 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
142 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
143 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
144 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
145 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
146 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
147 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
148 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
149 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
150 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
151 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
152 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
153 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
154 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
155 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
156 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
157 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
158 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
159 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
160 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
161 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
162 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
163 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
164 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
165 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
166 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
167 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
168 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
169 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
170 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
171 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
172 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
173 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
174 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
175 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
176 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
177 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
178 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
179 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
180 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
181 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
182 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
183 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
184 | github.com/jackc/pgconn v1.8.1 h1:ySBX7Q87vOMqKU2bbmKbUvtYhauDFclYbNDYIE1/h6s=
185 | github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
186 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
187 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
188 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
189 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
190 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
191 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
192 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
193 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
194 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
195 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
196 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
197 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
198 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
199 | github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8=
200 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
201 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
202 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
203 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
204 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
205 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
206 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
207 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
208 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
209 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
210 | github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs=
211 | github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
212 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
213 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
214 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
215 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
216 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
217 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
218 | github.com/jackc/pgx/v4 v4.11.0 h1:J86tSWd3Y7nKjwT/43xZBvpi04keQWx8gNC2YkdJhZI=
219 | github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
220 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
221 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
222 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
223 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
224 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
225 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
226 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
227 | github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
228 | github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
229 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
230 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
231 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
232 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
233 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
234 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
235 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
236 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
237 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
238 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
239 | github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
240 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
241 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
242 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
243 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
244 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
245 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
246 | github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
247 | github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
248 | github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
249 | github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
250 | github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
251 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
252 | github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
253 | github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
254 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
255 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
256 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
257 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
258 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
259 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
260 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
261 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
262 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
263 | github.com/lbryio/go_mediainfo v0.0.0-20200109212001-4c7318fd92ad h1:sfsYsbQXjA1uE51yFNtv4k79yMBzeGgAusK6KrLrcYs=
264 | github.com/lbryio/go_mediainfo v0.0.0-20200109212001-4c7318fd92ad/go.mod h1:xJtOuuGAo32jLOmrdBWRCURfahqz6OvK/hdSLhmYA38=
265 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
266 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
267 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
268 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
269 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
270 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
271 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
272 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
273 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
274 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
275 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
276 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
277 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
278 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
279 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
280 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
281 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
282 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
283 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
284 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
285 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
286 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
287 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
288 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
289 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
290 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
291 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
292 | github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
293 | github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
294 | github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
295 | github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
296 | github.com/minio/minio-go v1.0.0 h1:ooSujki+Z1PRGZsYffJw5jnF5eMBvzMVV86TLAlM0UM=
297 | github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
298 | github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
299 | github.com/minio/minio-go/v7 v7.0.10 h1:1oUKe4EOPUEhw2qnPQaPsJ0lmVTYLFu03SiItauXs94=
300 | github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
301 | github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
302 | github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
303 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
304 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
305 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
306 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
307 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
308 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
309 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
310 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
311 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
312 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
313 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
314 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
315 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
316 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
317 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
318 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
319 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
320 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
321 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
322 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
323 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
324 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
325 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
326 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
327 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
328 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
329 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
330 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
331 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
332 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
333 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
334 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
335 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
336 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
337 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
338 | github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
339 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
340 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
341 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
342 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
343 | github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
344 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
345 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
346 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
347 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
348 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
349 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
350 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
351 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
352 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
353 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
354 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
355 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
356 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
357 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
358 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
359 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
360 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
361 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
362 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
363 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
364 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
365 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
366 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
367 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
368 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
369 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
370 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
371 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
372 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
373 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
374 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
375 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
376 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
377 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
378 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
379 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
380 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
381 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
382 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
383 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
384 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
385 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
386 | github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
387 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
388 | github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
389 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
390 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
391 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
392 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
393 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
394 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
395 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
396 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
397 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
398 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
399 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
400 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
401 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
402 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
403 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
404 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
405 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
406 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
407 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
408 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
409 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
410 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
411 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
412 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
413 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
414 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
415 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
416 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
417 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
418 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
419 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
420 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
421 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
422 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
423 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
424 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
425 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
426 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
427 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
428 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
429 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
430 | github.com/ugorji/go v1.2.5 h1:NozRHfUeEta89taVkyfsDVSy2f7v89Frft4pjnWuGuc=
431 | github.com/ugorji/go v1.2.5/go.mod h1:gat2tIT8KJG8TVI8yv77nEO/KYT6dV7JE1gfUa8Xuls=
432 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
433 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
434 | github.com/ugorji/go/codec v1.2.5 h1:8WobZKAk18Msm2CothY2jnztY56YVY8kF1oQrj21iis=
435 | github.com/ugorji/go/codec v1.2.5/go.mod h1:QPxoTbPKSEAlAHPYt02++xp/en9B/wUdwFCz+hj5caA=
436 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
437 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
438 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
439 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
440 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
441 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
442 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
443 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
444 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
445 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
446 | go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
447 | go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
448 | go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
449 | go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
450 | go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q=
451 | go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
452 | go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
453 | go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
454 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
455 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
456 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
457 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
458 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
459 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
460 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
461 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
462 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
463 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
464 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
465 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
466 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
467 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
468 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
469 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
470 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
471 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
472 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
473 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
474 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
475 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
476 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
477 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
478 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
479 | golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 h1:QuAh/1Gwc0d+u9walMU1NqzhRemNegsv5esp2ALQIY4=
480 | golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
481 | golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
482 | golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
483 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
484 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
485 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
486 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
487 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
488 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
489 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
490 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
491 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
492 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
493 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
494 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
495 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
496 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
497 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
498 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
499 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
500 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
501 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
502 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
503 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
504 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
505 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
506 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
507 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
508 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
509 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
510 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
511 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
512 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
513 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
514 | golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA=
515 | golang.org/x/net v0.0.0-20210508051633-16afe75a6701/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
516 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
517 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
518 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
519 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
520 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
521 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
522 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
523 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
524 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
525 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
526 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
527 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
528 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
529 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
530 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
531 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
532 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
533 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
534 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
535 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
536 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
537 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
538 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
539 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
540 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
541 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
542 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
543 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
544 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
545 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
546 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
547 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
548 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
549 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
550 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
551 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
552 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
553 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
554 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
555 | golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
556 | golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
557 | golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo=
558 | golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
559 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
560 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
561 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
562 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
563 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
564 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
565 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
566 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
567 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
568 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
569 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
570 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
571 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
572 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
573 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
574 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
575 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
576 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
577 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
578 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
579 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
580 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
581 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
582 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
583 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
584 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
585 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
586 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
587 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
588 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
589 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
590 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
591 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
592 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
593 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
594 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
595 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
596 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
597 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
598 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
599 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
600 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
601 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
602 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
603 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
604 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
605 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
606 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
607 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
608 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
609 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
610 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
611 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
612 | google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
613 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
614 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
615 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
616 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
617 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
618 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
619 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
620 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
621 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
622 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
623 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
624 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
625 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
626 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
627 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
628 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
629 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
630 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
631 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
632 | gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
633 | gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
634 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
635 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
636 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
637 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
638 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
639 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
640 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
641 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
642 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
643 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
644 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
645 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
646 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
647 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
648 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
649 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
650 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
651 | gorm.io/driver/postgres v1.1.0 h1:afBljg7PtJ5lA6YUWluV2+xovIPhS+YiInuL3kUjrbk=
652 | gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw=
653 | gorm.io/gorm v1.21.9 h1:INieZtn4P2Pw6xPJ8MzT0G4WUOsHq3RhfuDF1M6GW0E=
654 | gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
655 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
656 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
657 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
658 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
659 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
660 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
661 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
662 |
--------------------------------------------------------------------------------