Skip to content

Commit

Permalink
Merge pull request #161 from sonroyaalmerol/category-paths
Browse files Browse the repository at this point in the history
Dynamic stream base path based on source URL
  • Loading branch information
sonroyaalmerol committed Sep 16, 2024
2 parents 89c86b1 + 7bdc20f commit a76d559
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Access the generated M3U playlist at `http://<server ip>:8080/playlist.m3u`.
|-----------------------------|----------------------------------------------------------|---------------|------------------------------------------------|
| PUID | Set UID of user running the container. | 1000 | Any valid UID |
| PGID | Set GID of user running the container. | 1000 | Any valid GID |
| TZ | Set timezone | Etc/UTC | [TZ Identifiers](https://nodatime.org/TimeZones) |
| M3U_URL_1, M3U_URL_2, M3U_URL_X | Set M3U URLs as environment variables. | N/A | Any valid M3U URLs |
| M3U_MAX_CONCURRENCY_1, M3U_MAX_CONCURRENCY_2, M3U_MAX_CONCURRENCY_X | Set max concurrency. | 1 | Any integer |
| MAX_RETRIES | Set max number of retries (loop) across all M3Us while streaming. 0 to never stop retrying (beware of throttling from provider). | 5 | Any integer greater than or equal 0 |
Expand All @@ -114,7 +115,6 @@ Access the generated M3U playlist at `http://<server ip>:8080/playlist.m3u`.
| EXCLUDE_TITLE_1, EXCLUDE_TITLE_2, EXCLUDE_TITLE_X | Set channels to exclude based on title | N/A | Go regexp |
| TITLE_SUBSTR_FILTER | Sets a regex pattern used to exclude substrings from channel titles | none | Go regexp |
| BASE_URL | Sets the base URL for the stream URls in the M3U file to be generated. | http/s://<request_hostname> (e.g. <http://192.168.1.10:8080>) | Any string that follows the URL format |
| TZ | Set timezone | Etc/UTC | [TZ Identifiers](https://nodatime.org/TimeZones) |
| SYNC_CRON | Set cron schedule expression of the background updates. | 0 0 * * * | Any valid cron expression |
| SYNC_ON_BOOT | Set if an initial background syncing will be executed on boot | true | true/false |
| CACHE_ON_SYNC | Set if an initial background cache building will be executed after sync. Requires BASE_URL to be set. | false | true/false |
Expand Down
37 changes: 32 additions & 5 deletions m3u/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,39 @@ func getFileExtensionFromUrl(rawUrl string) (string, error) {
return path.Ext(u.Path), nil
}

func GenerateStreamURL(baseUrl string, slug string, sampleUrl string) string {
ext, err := getFileExtensionFromUrl(sampleUrl)
func getSubPathFromUrl(rawUrl string) (string, error) {
parsedURL, err := url.Parse(rawUrl)
if err != nil {
return fmt.Sprintf("%s/%s\n", baseUrl, slug)
return "", err
}

pathSegments := strings.Split(parsedURL.Path, "/")

if len(pathSegments) <= 1 {
return "stream", nil
}

basePath := strings.Join(pathSegments[1:len(pathSegments)-1], "/")
return basePath, nil
}

func GenerateStreamURL(baseUrl string, stream database.StreamInfo) string {
var subPath string
var err error
for _, srcUrl := range stream.URLs {
subPath, err = getSubPathFromUrl(srcUrl)
if err != nil {
continue
}

ext, err := getFileExtensionFromUrl(srcUrl)
if err != nil {
return fmt.Sprintf("%s/proxy/%s/%s\n", baseUrl, subPath, stream.Slug)
}

return fmt.Sprintf("%s/proxy/%s/%s%s\n", baseUrl, subPath, stream.Slug, ext)
}
return fmt.Sprintf("%s/%s%s\n", baseUrl, slug, ext)
return fmt.Sprintf("%s/proxy/stream/%s\n", baseUrl, stream.Slug)
}

func GenerateAndCacheM3UContent(db *database.Instance, r *http.Request) string {
Expand Down Expand Up @@ -109,7 +136,7 @@ func GenerateAndCacheM3UContent(db *database.Instance, r *http.Request) string {
extInfTags = append(extInfTags, fmt.Sprintf("group-title=\"%s\"", stream.Group))

content.WriteString(fmt.Sprintf("%s,%s\n", strings.Join(extInfTags, " "), stream.Title))
content.WriteString(GenerateStreamURL(baseUrl, stream.Slug, stream.URLs[0]))
content.WriteString(GenerateStreamURL(baseUrl, stream))
}

if debug {
Expand Down
2 changes: 1 addition & 1 deletion m3u/m3u_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestGenerateM3UContent(t *testing.T) {
// Check the generated M3U content
expectedContent := fmt.Sprintf(`#EXTM3U
#EXTINF:-1 tvg-id="1" tvg-logo="http://example.com/logo.png" tvg-name="TestStream" group-title="TestGroup",TestStream
%s`, GenerateStreamURL("http:///stream", "test-stream", stream.URLs[0]))
%s`, GenerateStreamURL("http://", stream))
if rr.Body.String() != expectedContent {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expectedContent)
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ func main() {
http.HandleFunc("/playlist.m3u", func(w http.ResponseWriter, r *http.Request) {
m3u.Handler(w, r)
})
http.HandleFunc("/stream/", func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc("/proxy/", func(w http.ResponseWriter, r *http.Request) {
proxy.Handler(w, r)
})

// Start the server
utils.SafeLogln("Server is running on port 8080...")
utils.SafeLogln("Playlist Endpoint is running (`/playlist.m3u`)")
utils.SafeLogln("Stream Endpoint is running (`/stream/{streamID}.{fileExt}`)")
utils.SafeLogln("Stream Endpoint is running (`/proxy/{originalBasePath}/{streamID}.{fileExt}`)")
err = http.ListenAndServe(":8080", nil)
if err != nil {
utils.SafeLogFatalf("HTTP server error: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestStreamHandler(t *testing.T) {

for _, stream := range streams {
log.Printf("Stream (%s): %v", stream.Title, stream)
genStreamUrl := strings.TrimSpace(m3u.GenerateStreamURL("", stream.Slug, stream.URLs[0]))
genStreamUrl := strings.TrimSpace(m3u.GenerateStreamURL("", stream))

req := httptest.NewRequest("GET", genStreamUrl, nil)
w := httptest.NewRecorder()
Expand Down
3 changes: 2 additions & 1 deletion proxy/stream_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"os"
"path"
"slices"
"sort"
"strconv"
Expand Down Expand Up @@ -286,7 +287,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {

utils.SafeLogf("Received request from %s for URL: %s\n", r.RemoteAddr, r.URL.Path)

streamUrl := strings.Split(strings.TrimPrefix(r.URL.Path, "/stream/"), ".")[0]
streamUrl := strings.Split(path.Base(r.URL.Path), ".")[0]
if streamUrl == "" {
utils.SafeLogf("Invalid m3uID for request from %s: %s\n", r.RemoteAddr, r.URL.Path)
http.NotFound(w, r)
Expand Down
18 changes: 16 additions & 2 deletions utils/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ func GetEnv(env string) string {
}
}

var m3uIndexes []int
var m3uIndexesInitialized bool

func GetM3UIndexes() []int {
m3uIndexes := []int{}
if m3uIndexesInitialized {
return m3uIndexes
}
m3uIndexes = []int{}
for _, env := range os.Environ() {
pair := strings.SplitN(env, "=", 2)
if strings.HasPrefix(pair[0], "M3U_URL_") {
Expand All @@ -34,11 +40,18 @@ func GetM3UIndexes() []int {
m3uIndexes = append(m3uIndexes, index-1)
}
}
m3uIndexesInitialized = true
return m3uIndexes
}

var filters []string
var filtersInitialized bool

func GetFilters(baseEnv string) []string {
filters := []string{}
if filtersInitialized {
return filters
}
filters = []string{}
for _, env := range os.Environ() {
pair := strings.SplitN(env, "=", 2)
if strings.HasPrefix(pair[0], baseEnv) {
Expand All @@ -50,5 +63,6 @@ func GetFilters(baseEnv string) []string {
filters = append(filters, pair[1])
}
}
filtersInitialized = true
return filters
}
6 changes: 3 additions & 3 deletions utils/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ func CustomHttpRequest(method string, url string) (*http.Response, error) {

func DetermineBaseURL(r *http.Request) string {
if customBase, ok := os.LookupEnv("BASE_URL"); ok {
return fmt.Sprintf("%s/stream", strings.TrimSuffix(customBase, "/"))
return strings.TrimSuffix(customBase, "/")
}

if r != nil {
if r.TLS == nil {
return fmt.Sprintf("http://%s/stream", r.Host)
return fmt.Sprintf("http://%s", r.Host)
} else {
return fmt.Sprintf("https://%s/stream", r.Host)
return fmt.Sprintf("https://%s", r.Host)
}
}

Expand Down

0 comments on commit a76d559

Please sign in to comment.