Skip to content

Commit

Permalink
Merge pull request #28 from mejgun/fork-and-templates
Browse files Browse the repository at this point in the history
2.0
  • Loading branch information
mejgun authored Sep 20, 2024
2 parents 2bde8ea + d9ca744 commit 0b61382
Show file tree
Hide file tree
Showing 39 changed files with 1,626 additions and 879 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
bin/
all.md5
ytproxy
log.txt
config.json
all.md5
config.jsonc
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## 2.0.0 - 2024-09-20
app refactored & reworked
### Added
- per site settings
- direct/no extractor (returning same url)
- json format logs
- disabling logs
- force https links to extractor options
- host setting in config
- stripping (bad) http(s) prefix in url
- os signals catching
- config reload (SIGHUP)
### Changed
- default config name from "config.json" to "config.jsonc"

## 1.6.0 - 2024-09-13
### Added
- ignoring comment strings in config file
Expand Down
97 changes: 3 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,11 @@
### What is this repository for? ###

This is part of another project: https://github.com/mesb1/xupnpd_youtube
This is yt-dlp based video restreamer, part of another project: https://github.com/mesb1/xupnpd_youtube

### Build ###

`cd src && go build`
`cd cmd && go build`

### Options ###

Run with `--help`

### Exit codes ###

- 0 - OK
- 1 - config read/parse error
- 2 - logger create error
- 3 - extractor create error
- 4 - streamer create error
- 5 - web server error
- 6 - links cache error

### Config explained ###
do not copypaste, comments are not allowed in this json.
use config.default.json instead.

```jsonc
{
// web server listen port
"port": 8080,
// restreamer config
"streamer": {
// show errors in headers (insecure)
"error-headers": false,
// do not strictly check video headers
"ignore-missing-headers": false,
// do not check video server certificate (insecure)
"ignore-ssl-errors": false,
// video file that will be shown on errors
"error-video": "corrupted.mp4",
// audio file that will be played on errors
// dwnlded here youtu.be/_b8KPiT1PxI (suggest your options)
"error-audio": "failed.m4a",
// how to set streamer's user-agent
// request - set from user's request (old default)
// extractor - set from extractor on app start (default)
// config - set from config
"set-user-agent": "extractor",
// custom user agent used if "set-user-agent" set to "config"
"user-agent": "Mozilla",
// proxy for restreamer
// empty - no proxy
// "env" - read proxy from environment variables (e.g. HTTP_PROXY="http://127.0.0.1:3128")
// proxy url - e.g. "socks5://127.0.0.1:9999"
"proxy": "env",
// min TLS version: "TLS 1.3", "TLS 1.2", etc.
"min-tls-version": "TLS 1.2 "
},
// media extractor config
"extractor": {
// file path
"path": "yt-dlp",
// arguments for extractor
// args separator is ",,", not space
// {{.HEIGHT}} will be replaced with requested height (360/480/720)
// {{.URL}} will be replace with requested url
// also you can use {{.FORMAT}} - requested format (now - only mp4 or m4a)
"mp4": "-f,,(mp4)[height<={{.HEIGHT}}],,-g,,{{.URL}}",
// same for m4a
"m4a": "-f,,(m4a),,-g,,{{.URL}}",
// args for getting user-agent
"get-user-agent": "--dump-user-agent",
// custom options list to extractor, like proxy, etc.
// same rules as mp4/m4a
// HEIGHT/URL/.. templates also can be used
"custom-options": [
"--option1,,value1",
"--option2",
"value2",
"--option3",
"very long value 3"
]
},
// logger config
"log": {
// log level
// debug/info/warning/error/nothing
"level": "info",
// log destination
// stdout/file/both
"output": "stdout",
// filename if writing to file
"filename": "log.txt"
},
// links cache config
"cache": {
// links expire time
// time units are "s", "m", "h", e.g. "1h10m10s", "10h", "1s"
// "0s" will disable cache
"expire-time": "3h"
}
}
Run with `--help`
10 changes: 5 additions & 5 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import (
)

const (
binDir = "bin"
filePrefix = "yt-proxy"
goBin = "go"
myos = "linux"
myarch = "amd64"
binDir = "bin"
goBin = "go"
myos = "linux"
myarch = "amd64"
)

func main() {
total := len(knownArch) * len(knownOS)
filePrefix := os.Args[1]
for os_ := range knownOS {
for arch := range knownArch {
file := fmt.Sprintf("%s-%s-%s", filePrefix, os_, arch)
Expand Down
17 changes: 9 additions & 8 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
#!/bin/bash

set -e

BinDir="bin"
Md5File="all.md5"
FilePrefix="yt-proxy"

date >${Md5File}
mkdir -p ${BinDir} || exit
rm ${BinDir}/${FilePrefix}* -rf || exit
cd src || exit
go run ../build.go || exit
cd ../$BinDir || exit
for i in $(ls -1 *); do
md5sum $i >>../${Md5File} || exit
done
mkdir -p ${BinDir}
rm ${BinDir}/${FilePrefix}* -rf
cd cmd
go run ../build.go ${FilePrefix}
cd ../$BinDir
md5sum -b ${FilePrefix}* >${Md5File}
cd ..
7 changes: 7 additions & 0 deletions cmd/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ytproxy

go 1.22.6

replace lib => ../lib

require lib v0.0.0-00010101000000-000000000000
185 changes: 185 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package main

import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"

app "lib/app"
cache "lib/cache"
config "lib/config"
extractor "lib/extractor"
logger "lib/logger"
streamer "lib/streamer"
)

const appVersion = "2.0.0"

type flagsT struct {
version bool
config string
}

const (
NoError = iota
SomeError
)

func parseCLIFlags() flagsT {
var f flagsT
flag.BoolVar(&f.version, "version", false, "prints current yt-proxy version")
flag.StringVar(&f.config, "config", "config.jsonc", "config file path")
flag.Parse()
return f
}

func main() {
flags := parseCLIFlags()
if flags.version {
os.Stdout.WriteString(fmt.Sprintf("%s\n", appVersion))
os.Exit(NoError)
}
startApp(flags.config)
}

func startApp(conf_file string) {
conf, def, opts, log, err := readConfig(conf_file)
if err != nil {
os.Stderr.WriteString(fmt.Sprintf("Config read error: %s\n", err))
os.Exit(SomeError)
}
app := app.New(
logger.NewLayer(log, "App"),
def, opts)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.LogInfo("Bad request", r.RemoteAddr, r.RequestURI)
log.LogDebug("Bad request", r)
http.NotFound(w, r)
})
http.HandleFunc("/play/", func(w http.ResponseWriter, r *http.Request) {
log.LogInfo("Play request", r.RemoteAddr, r.RequestURI)
log.LogDebug("User request", r)
app.Run(w, r)
log.LogInfo("Player disconnected", r.RemoteAddr)
})
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.Host, conf.PortInt),
}
go signalsCatcher(conf_file, log, app, s)

log.LogInfo("Starting web server", "host", conf.Host, "port", conf.PortInt)
if err = s.ListenAndServe(); err == http.ErrServerClosed {
log.LogInfo("HTTP server closed")
os.Exit(NoError)
} else {
log.LogError("HTTP server", err)
log.Close()
os.Exit(SomeError)
}
}

func readConfig(conf_file string) (config.ConfigT, app.Option, []app.Option,
logger.T, error) {
conf, err := config.Read(conf_file)
if err != nil {
return config.ConfigT{}, app.Option{}, nil, nil, err
}

log, err := logger.New(conf.Log)
if err != nil {
return config.ConfigT{}, app.Option{}, nil, nil, err
}

defapp, err := getNewApp(log, config.SubConfigT{
ConfigT: config.ConfigT{
Streamer: conf.Streamer,
Extractor: conf.Extractor,
Cache: conf.Cache,
},
Name: "default",
})
if err != nil {
return config.ConfigT{}, app.Option{}, nil, nil, err
}

opts := make([]app.Option, 0)
for _, v := range conf.SubConfig {
opt, err := getNewApp(log, v)
if err != nil {
return config.ConfigT{}, app.Option{}, nil, nil, err
}
opts = append(opts, opt)
}
return conf, defapp, opts, log, nil
}

func signalsCatcher(conf_file string, log logger.T, app *app.AppLogic, s *http.Server) {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
for {
switch <-sigint {
case syscall.SIGHUP:
log.LogWarning("Config reloading")
_, def, opts, lognew, err := readConfig(conf_file)
if err != nil {
log.LogError("Config reload error", err)
} else {
app.ReloadConfig(logger.NewLayer(lognew, "App"), def, opts)
log = lognew
}
case syscall.SIGINT:
fallthrough
case syscall.SIGTERM:
log.LogWarning("Exiting")
app.Shutdown()
if err := s.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout:
log.LogError("HTTP server Shutdown", err)
}
return
}
}
}

func getNewApp(log logger.T, v config.SubConfigT) (app.Option, error) {
texts := [3]string{
"Extractor",
"Cache",
"Streamer",
}

newname := func(name string) string {
return fmt.Sprintf("[%s] %s", v.Name, name)
}
nameerr := func(name string, err error) error {
return fmt.Errorf("%s: %s", newname(name), err)
}
xtr, err := extractor.New(v.Extractor,
logger.NewLayer(log, newname(texts[0])))
if err != nil {
return app.Option{}, nameerr(texts[0], err)
}
cch, err := cache.New(v.Cache,
logger.NewLayer(log, newname(texts[1])))
if err != nil {
return app.Option{}, nameerr(texts[1], err)
}
strm, err := streamer.New(v.Streamer,
logger.NewLayer(log, newname(texts[2])), xtr)
if err != nil {
return app.Option{}, nameerr(texts[2], err)
}
return app.Option{
Name: v.Name,
Sites: v.Sites,
X: xtr,
S: strm,
C: cch,
L: logger.NewLayer(log, fmt.Sprintf("[%s] app", v.Name)),
}, nil
}
Loading

0 comments on commit 0b61382

Please sign in to comment.