Skip to content

Commit

Permalink
channels checks
Browse files Browse the repository at this point in the history
  • Loading branch information
ubaldus committed Jul 1, 2024
1 parent 8771589 commit 75b262b
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 4 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
tv
tv.exe
tv.json
tibula.db

dist/

!cmd/tv
2 changes: 2 additions & 0 deletions cmd/tv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main

import (
"os"
"tv/internal/core"
"tv/internal/sys"
"tv/internal/web"

Expand Down Expand Up @@ -41,6 +42,7 @@ func main() {
log.Fatal("Cannot create media folder", err)
}
}
go core.Start()
if err := tibulaWeb.Start(); err != nil {
log.Fatal("Cannot start the web service: ", err)
}
Expand Down
25 changes: 25 additions & 0 deletions internal/av/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright by Ubaldo Porcheddu <ubaldo@eja.it>

package av

import (
"os/exec"

"github.com/eja/tibula/log"
)

const tag = "[FF]"

func FFmpeg(args []string) error {
baseArgs := []string{"-y", "-nostdin", "-hide_banner"}
cmd := exec.Command("ffmpeg", append(baseArgs, args...)...)
log.Trace(tag, "ffmpeg", args)
return cmd.Run()
}

func FFprobe(args []string) ([]byte, error) {
baseArgs := []string{"-y", "-nostdin", "-hide_banner", "-v", "error"}
cmd := exec.Command("ffprobe", append(baseArgs, args...)...)
log.Trace(tag, "ffprobe", args)
return cmd.Output()
}
109 changes: 109 additions & 0 deletions internal/core/channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright by Ubaldo Porcheddu <ubaldo@eja.it>

package core

import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"time"

"tv/internal/av"
"tv/internal/sys"

tibula "github.com/eja/tibula/db"
"github.com/eja/tibula/log"
)

const channelsBatch = 100

func checkChannels() (err error) {
db := tibula.Session()
if err = db.Open(sys.Options.DbType, sys.Options.DbName, sys.Options.DbUser, sys.Options.DbPass, sys.Options.DbHost, sys.Options.DbPort); err != nil {
return
}
for {
timeLastCheck := time.Now().Add(-time.Duration(sys.Options.CheckInterval) * time.Second).Format("2006-01-02 15:04:05")
timeLastWorking := time.Now().Add(-30 * 24 * time.Hour).Format("2006-01-02 15:04:05")
rows, err := db.Rows(`SELECT * FROM tvChannels WHERE
(checkLast < ? OR checkLast IS NULL OR checkLast = "") AND
(
(checkLastWorking > ?) OR
(ejaLog > ? AND (checkLastWorking IS NULL OR checkLastWorking = ""))
) AND
power > 0 AND
name != ""
ORDER BY power DESC, checkLast ASC
LIMIT ?
`, timeLastCheck, timeLastWorking, timeLastWorking, channelsBatch)
if err != nil {
return err
}
for _, row := range rows {
var videoWidth int64
var videoHeight int64
var videoSize string
status := 0
ABR := 0
framePath := filepath.Join(sys.Options.MediaPath, row["name"]+".png")

cors, subtitles, err := checkPlaylist(row["sourceUrl"])
if err != nil {
log.Warn(tag, "playlist check error", row["name"], err)
} else {
status += 1
log.Trace(tag, "playlist ok", row["label"], cors, subtitles)
if strings.HasPrefix(strings.ToLower(row["sourceUrl"]), "https") {
status += 100
}
probeData, err := av.FFprobe([]string{"-timeout", "10000000", "-print_format", "json", "-show_format", "-show_streams", row["sourceUrl"]})
if err != nil {
log.Warn(tag, "probe check error", row["name"], err)
} else {
status += 10
var probeJson map[string]interface{}
err = json.Unmarshal(probeData, &probeJson)
if err != nil {
log.Warn(tag, "error unmarshalling ffprobe json", err)
} else {
av.FFmpeg([]string{"-timeout", "10000000", "-i", row["sourceUrl"], "-vframes", "1", framePath})
if streams, ok := probeJson["streams"].([]interface{}); !ok {
log.Warn(tag, "json streams not found")
} else {
for _, streamMap := range streams {
if stream, ok := streamMap.(map[string]interface{}); ok {
if stream["codec_type"] == "video" {
w := sys.Number(stream["width"])
h := sys.Number(stream["height"])
if w > videoWidth {
videoWidth = w
}
if h > videoHeight {
videoHeight = h
}
ABR++
log.Trace(stream["width"], stream["height"])
}
}
}
if videoWidth > 0 && videoHeight > 0 {
videoSize = fmt.Sprintf("%dx%d", videoWidth, videoHeight)
}
}
}
}
}
db.Run("UPDATE tvChannels SET status=?,checkLast=?,size=?,subtitle=?,abr=? WHERE name=?", status, db.Now(), videoSize, subtitles, ABR, row["name"])
log.Trace(tag, "status:", row["country"], row["label"], status, videoSize)
if status > 0 {
db.Run("UPDATE tvChannels SET checkLastWorking=? WHERE name=?", db.Now(), row["name"])
}
}

log.Trace(tag, "sleep")
time.Sleep(10 * time.Second)
}

return
}
22 changes: 22 additions & 0 deletions internal/core/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright by Ubaldo Porcheddu <ubaldo@eja.it>

package core

import (
"os"

"tv/internal/sys"
)

const tag = "[tv] [core]"

func Start() (err error) {
if _, err = os.Stat(sys.Options.MediaPath); err != nil {
err = os.MkdirAll(sys.Options.MediaPath, os.ModePerm)
if err != nil {
return err
}
}

return checkChannels()
}
52 changes: 52 additions & 0 deletions internal/core/playlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright by Ubaldo Porcheddu <ubaldo@eja.it>

package core

import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)

func checkPlaylist(url string) (cors bool, subtitles bool, err error) {
client := &http.Client{
Timeout: 10 * time.Second,
}

resp, err := client.Get(url)
if err != nil {
return false, false, fmt.Errorf("error getting the playlist: %v", err)
}
defer resp.Body.Close()

allowOrigin := resp.Header.Get("Access-Control-Allow-Origin")
if allowOrigin == "*" {
cors = true
} else {
cors = false
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, false, fmt.Errorf("error reading response body: %v", err)
}

lines := strings.Split(string(body), "\n")
for number, line := range lines {
if number == 0 && !strings.HasPrefix(strings.ToLower(lines[0]), "#extm3u") {
return false, false, fmt.Errorf("not a playlist file")
}
if strings.HasPrefix(strings.ToLower(line), "#ext-x-media:type=subtitles") {
subtitles = true
break
}
if strings.HasPrefix(strings.ToLower(line), "#ext-x-media:type=closed-captions") {
subtitles = true
break
}
}

return
}
2 changes: 1 addition & 1 deletion internal/sys/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var Options typeConfigSys

func Configure() error {
flag.StringVar(&Options.MediaPath, "media-path", "/opt/eja/tv/media", "Media folder path")
flag.StringVar(&Options.TmpPath, "tmp-path", "/tmp/", "Temporary folder path")
flag.IntVar(&Options.CheckInterval, "check-interval", 3600, "Channels check interval")

if err := sys.Configure(); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions internal/sys/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

type typeConfigSys struct {
sys.TypeConfig
MediaPath string `json:"media_path,omitempty"`
TmpPath string `json:"tmp_path,omitempty"`
MediaPath string `json:"media_path,omitempty"`
CheckInterval int `json:"check_interval,omitempty"`
}

var String = sys.String
Expand Down
5 changes: 4 additions & 1 deletion internal/sys/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ func Wizard() error {
}

Options.MediaPath = sys.WizardPrompt("Media folder path")
Options.TmpPath = sys.WizardPrompt("Temporary folder path")
checkInterval := sys.WizardPrompt("Channels check interval")
if checkInterval != "" {
Options.CheckInterval = int(Number(checkInterval))
}

tibula.Assets = dbAssets

Expand Down

0 comments on commit 75b262b

Please sign in to comment.