Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sunfish-shogi committed Oct 25, 2021
0 parents commit b6002d2
Show file tree
Hide file tree
Showing 53 changed files with 5,483 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
jobs:
build_and_test:
docker:
- image: circleci/golang:1.16
steps:
- checkout
- run: go vet ./...
- run: go build
- run: go test -coverprofile=coverage.txt -covermode=count ./...
- run: go install github.com/mattn/goveralls@v0.0.9
- run: goveralls -coverprofile=coverage.txt -service=circle-ci
workflows:
version: 2
all:
jobs:
- build_and_test
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Binary
antares

# Test binary, built with `go test -c`
*.test

# Test coverage
cover.out
cover.html

# Dependency directories
vendor/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 AbemaTV

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.PHONY: build
build:
go build

.PHONY: vet
vet:
go vet

.PHONY: lint
lint:
golint ./...

.PHONY: test
test:
go test ./...

.PHONY: coverage
coverage:
go test -coverprofile=cover.out -covermode=count ./...
go tool cover -html=cover.out -o cover.html
if [ $(shell uname) == Darwin ]; then open cover.html; fi
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# antares

[![CircleCI](https://circleci.com/gh/abema/antares.svg?style=svg)](https://circleci.com/gh/abema/antares)
[![Coverage Status](https://coveralls.io/repos/github/abema/antares/badge.svg?t=BfVpc4)](https://coveralls.io/github/abema/antares)

Antares is monitoring system for HLS and MPEG-DASH.
This program is written by golang.

## Description

Antares monitors any HLS/MPEG-DASH streams and outputs inspection reports.
You can use prepared command or Go interfaces.

## Command

### Install

```sh
go install github.com/abema/antares
```

### Example

```sh
antares \
-hls.noEndlist \
-hls.playlistType omitted \
-export \
-export.meta \
-segment.maxBandwidth 500000 \
"http://localhost/index.m3u8"
```

### Help

```sh
antares -h
```

## Integrate with your Go application

### Monitor and Manager

You can use `core.Monitor` to monitor your live stream as follows:

```go
config := core.NewConfig("http://localhost/index.m3u8", core.StreamTypeHLS)
config.HLS.Inspectors = []core.HLSInspector{
hls.NewSpeedInspector(),
hls.NewVariantsSyncInspector(),
}
core.NewMonitor(config)
```

`manager.Manager` manages multiple monitors and provides batch update interface.

```go
manager := manager.NewManager(&manager.Config{})
for range time.Tick(time.Minute) {
configs := make(map[string]*core.Config)
for _, stream := range listMyCurrentStreams() {
config := core.NewConfig(stream.URL, stream.StreamType)
:
configs[stream.ID] = config
}
added, removed := manager.Batch(configs)
log.Println("added", added)
log.Println("removed:", removed)
}
```

### Inspectors

Inspector inspects manifest and segment files.
For example, `SpeedInspector` checks whether addition speed of segment is appropriate as compared to real time.
Some inspectors are implemented in `inspectors/hls` package and `inspectors/dash` package for each aims.
Implementing `hls.Inspector` or `dash.Inspector` interface, you can add your any inspectors to Monitor.

### Handlers and Adapters

You can set handlers to handle downloaded files, inspection reports, and etc.
And `adapters` package has some useful handlers.

```go
config.OnReport = core.MergeOnReportHandlers(
adapters.ReportLogger(&adapters.ReportLogConfig{JSON: true}, os.Stdout),
adapters.Alarm(&adapters.AlarmConfig{
OnAlarm : func(reports core.Reports) { /* start alarm */ },
OnRecover : func(reports core.Reports) { /* stop alarm */ },
Window : 10,
AlarmIfErrorGreaterThanEqual: 2,
RecoverIfErrorLessThanEqual : 0,
}),
func(reports core.Reports) { /* send metrics */ },
)
```

## Manifest format support

### HLS

- [x] Live
- [x] Event
- [x] On-demand
- [ ] Byte range
- [ ] LHLS
- [ ] Decryption
- [ ] I-frame-only playlists

### DASH

- [x] Live
- [x] Static
- [x] Timeline
- [ ] SegmentList
- [ ] Without Timeline/SegmentList
- [x] Multi-Period
- [x] Location
- [ ] Decryption

Identifiers for URL templates:

- [x] $$
- [x] $RepresentationID$
- [x] $Number$
- [x] $Bandwidth$
- [x] $Time$
- [ ] $SubNumber$
- [ ] IEEE 1003.1 Format Tag
## License
[MIT](https://github.com/abema/antares/blob/main/LICENSE)
119 changes: 119 additions & 0 deletions adapters/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package adapters

import (
"encoding/json"
"log"
"net/url"
"path"
"strings"

"github.com/abema/antares/core"
"github.com/abema/antares/internal/file"
)

func OnDownloadPathSuffixFilter(handler core.OnDownloadHandler, suffixes ...string) core.OnDownloadHandler {
return func(httpFile *core.File) {
u, err := url.Parse(httpFile.URL)
if err != nil {
log.Printf("ERROR: invalid URL: %s: %s", u, err)
return
}
for _, suffix := range suffixes {
if strings.HasSuffix(u.Path, suffix) {
handler(httpFile)
return
}
}
}
}

func LocalFileExporter(baseDir string, enableMeta bool) core.OnDownloadHandler {
e := &localFileExporter{
BaseDir: baseDir,
EnableMeta: enableMeta,
}
return func(httpFile *core.File) {
if err := e.onDownload(httpFile); err != nil {
log.Printf("ERROR: failed to export to file: %s: %s", httpFile.URL, err)
}
}
}

type localFileExporter struct {
BaseDir string
EnableMeta bool
}

func (e *localFileExporter) onDownload(httpFile *core.File) error {
p, err := e.resolvePath(httpFile)
if err != nil {
return err
}
if err := e.export(p, httpFile); err != nil {
return err
}
if e.EnableMeta {
if err := e.exportMeta(p+"-meta.json", httpFile); err != nil {
return err
}
}
return nil
}

func (e *localFileExporter) resolvePath(httpFile *core.File) (string, error) {
static := e.isStatic(httpFile)
u, err := url.Parse(httpFile.URL)
if err != nil {
return "", err
}
p := path.Join(e.BaseDir, u.Host, u.Path)
if !static {
ext := path.Ext(p)
p = p[:len(p)-len(ext)] + httpFile.RequestTimestamp.Format("-20060102-150405") + ext
}
return p, nil
}

func (e *localFileExporter) isStatic(httpFile *core.File) bool {
switch httpFile.ResponseHeader.Get("Content-Type") {
case "application/x-mpegURL", "application/dash+xml":
return false
case "video/mp4", "video/MP2T":
return true
}
u, err := url.Parse(httpFile.URL)
if err != nil {
return false
}
return strings.HasSuffix(u.Path, ".mp4") ||
strings.HasSuffix(u.Path, ".m4s") ||
strings.HasSuffix(u.Path, ".m4v") ||
strings.HasSuffix(u.Path, ".m4a") ||
strings.HasSuffix(u.Path, ".ts")
}

func (e *localFileExporter) export(path string, httpFile *core.File) error {
f, err := file.Create(path)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(httpFile.Body); err != nil {
return err
}
return nil
}

func (e *localFileExporter) exportMeta(path string, httpFile *core.File) error {
f, err := file.Create(path)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(httpFile.Meta); err != nil {
return err
}
return nil
}
Loading

0 comments on commit b6002d2

Please sign in to comment.