Skip to content

Commit

Permalink
支持TVBox和DIYP的txt播放列表格式
Browse files Browse the repository at this point in the history
  • Loading branch information
snowie2000 committed Sep 2, 2024
1 parent f3811db commit b43000a
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 27 deletions.
74 changes: 50 additions & 24 deletions handler/live.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handler

import (
"bytes"
"compress/gzip"
"context"
"io"
"log"
Expand Down Expand Up @@ -42,6 +43,26 @@ func M3UHandler(c *gin.Context) {
c.Data(http.StatusOK, "application/vnd.apple.mpegurl", []byte(content))
}

func TXTHandler(c *gin.Context) {
disableProtection := os.Getenv("LIVETV_FREEACCESS") == "1"
// verify token against the unique token of the requested channel
if !disableProtection {
token := c.Query("token")
if token != global.GetSecretToken() { // invalid token
c.String(http.StatusForbidden, "Forbidden")
return
}
}

content, err := service.TXTGenerate()
if err != nil {
log.Println(err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
c.Data(http.StatusOK, "text/plain; charset=UTF-8", []byte(content))
}

func LivePreHandler(c *gin.Context) {
channelNumber := util.String2Uint(c.Query("c"))
if channelNumber == 0 {
Expand Down Expand Up @@ -145,8 +166,18 @@ func LiveHandler(c *gin.Context) {
if handleNonHTTPProtocol(liveInfo.LiveUrl, c) {
return
}
// the GetM3U8Content will handle health-check, reparse, url decoration etc. and returns the final result and the final url used
bodyString, finalUrl, err := service.GetM3U8Content(channelInfo.URL, liveInfo.LiveUrl, channelInfo.ProxyUrl, channelInfo.Parser)

var (
bodyString string
finalUrl string
)
if forger, ok := parser.(plugin.Forger); ok {
// if supported, use forged m3u8 playlist
finalUrl, bodyString, err = forger.ForgeM3U8(liveInfo)
} else {
// the GetM3U8Content will handle health-check, reparse, url decoration etc. and returns the final result and the final url used
bodyString, finalUrl, err = service.GetM3U8Content(channelInfo.URL, liveInfo.LiveUrl, channelInfo.ProxyUrl, channelInfo.Parser)
}
if bodyString == "" {
log.Println(err)
c.AbortWithStatus(http.StatusInternalServerError)
Expand Down Expand Up @@ -191,26 +222,15 @@ func M3U8ProxyHandler(c *gin.Context) {
c.AbortWithStatus(http.StatusNotFound)
return
}
rurl, err := url.Parse(remoteURL)
_, err = url.Parse(remoteURL)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
}

client := http.Client{Timeout: global.HttpClientTimeout}
req := c.Request.Clone(context.Background())
req.RequestURI = ""
req.Host = ""
req.URL = rurl
// remove x-forward-* headers
badKeys := make([]string, 0)
for key, _ := range req.Header {
if strings.HasPrefix(key, "X-") {
badKeys = append(badKeys, key)
}
}
for _, key := range badKeys {
req.Header.Del(key)
}
req, _ := http.NewRequest(http.MethodGet, remoteURL, nil)
req.Header.Set("User-Agent", plugin.DefaultUserAgent)
//req.Header.Set("Accept-Encoding", "gzip")
// added possible custom headers
queries := c.Request.URL.Query()
for key, value := range queries {
Expand All @@ -224,16 +244,22 @@ func M3U8ProxyHandler(c *gin.Context) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
for key, values := range resp.Header {
for _, value := range values {
if !strings.EqualFold(key, "Content-Length") && !strings.EqualFold(key, "Content-Encoding") {
c.Writer.Header().Add(key, value)
}
defer resp.Body.Close()
// deal with gzip
var reader io.ReadCloser
if resp.Header.Get("Content-Encoding") == "gzip" {
// If gzipped, create a new gzip reader
reader, err = gzip.NewReader(resp.Body)
if err != nil {
panic(err)
}
defer reader.Close()
} else {
// Otherwise, use the response body directly
reader = resp.Body
}
defer resp.Body.Close()
buffer := &bytes.Buffer{}
io.Copy(buffer, resp.Body)
io.Copy(buffer, reader)
// make prefixURL from ourselves
prefixUrl, _ := global.GetConfig("base_url")
newList := service.M3U8Process(remoteURL, buffer.String(), prefixUrl, global.GetLiveToken(), true, nil)
Expand Down
2 changes: 1 addition & 1 deletion handler/web/p__channels.async.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion handler/web/p__channels.chunk.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func main() {
binding := os.Getenv("LIVETV_LISTEN")
if binding == "" {
binding = *listen
os.Setenv("LIVETV_LISTEN", binding)
}
rand.Seed(time.Now().UnixNano())
log.SetFlags(log.LstdFlags | log.Lshortfile)
Expand Down
2 changes: 1 addition & 1 deletion plugin/ytdlp-oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var m3u8Template string = `
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_mp4a.40.2_48000",DEFAULT=YES,AUTOSELECT=YES,URI="{AUDIO}"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_mp4a.40.2_48000",NAME="a48000_zho",DEFAULT=YES,AUTOSELECT=YES,URI="{AUDIO}"
#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=2152910,AUDIO="audio_mp4a.40.2_48000"
{VIDEO}
`
Expand Down
1 change: 1 addition & 0 deletions route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
func Register(r *gin.Engine) {
r.OPTIONS("/", handler.CORSHandler)
r.GET("/lives.m3u", handler.M3UHandler)
r.GET("/lives.txt", handler.TXTHandler)
r.GET("/live.m3u8", handler.LiveHandler)
r.HEAD("/live.m3u8", handler.LivePreHandler)
r.GET("/live.ts", handler.TsProxyHandler)
Expand Down
74 changes: 74 additions & 0 deletions service/txt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package service

import (
"fmt"
"log"
"strings"

"github.com/snowie2000/livetv/global"
)

type genre struct {
name string
channels map[string][]string
list []string
}

func (g *genre) addChannel(chName string, url string) {
chName = strings.Replace(chName, ",", "_", -1)
if group, ok := g.channels[chName]; ok {
group = append(group, url)
g.channels[chName] = group
} else {
g.channels[chName] = []string{url}
g.list = append(g.list, chName)
}
}

func (g *genre) String() string {
channels := []string{g.name + ",#genre#"}
for _, group := range g.list {
for _, url := range g.channels[group] {
channels = append(channels, group+","+url)
}
}
return strings.Join(channels, "\n")
}

func TXTGenerate() (string, error) {
baseUrl, err := global.GetConfig("base_url")
if err != nil {
log.Println(err)
return "", err
}
channels, err := GetAllChannel()
if err != nil {
log.Println(err)
return "", err
}
genres := make(map[string]*genre)
var genreList []string
for _, v := range channels {
category := "LiveTV"
if v.Category != "" {
category = v.Category
}
if g, ok := genres[category]; ok {
g.addChannel(v.Name, fmt.Sprintf("%s/live.m3u8?token=%s&c=%d", baseUrl, v.Token, v.ID))
} else {
g = &genre{
name: category,
channels: make(map[string][]string),
}
genreList = append(genreList, category)
g.addChannel(v.Name, fmt.Sprintf("%s/live.m3u8?token=%s&c=%d", baseUrl, v.Token, v.ID))
genres[category] = g
}
}
var txt strings.Builder
for _, category := range genreList {
txt.WriteString(genres[category].String())
txt.WriteString("\n\n")
}
return txt.String(), nil
}

0 comments on commit b43000a

Please sign in to comment.