Structured for download project and processing project. Responsibility split

This commit is contained in:
kacarmichael 2025-07-20 00:43:30 -05:00
parent f5da1941f6
commit 93b62618a1
12 changed files with 133 additions and 35 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
data/
scratch.txt

View File

@ -2,7 +2,9 @@
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/data" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

51
CLAUDE.md Normal file
View File

@ -0,0 +1,51 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Go-based M3U8 downloader that parses HLS (HTTP Live Streaming) playlists to extract video and audio stream metadata. The end goal of this project is to have a listening REST API take in m3u8 urls, parse them, and eventually send to a conversion service.
## Architecture
The project follows a clean separation of concerns:
- **main.go**: Entry point that demonstrates usage of the media package
- **media/**: Core package containing M3U8 parsing logic
- **types.go**: Contains the main parsing logic and data structures (`StreamSet`, `VideoURL`, `AudioURL`)
- **utils.go**: Utility functions for parsing attributes and resolution calculations
The `GetStreamMetadata()` function is the main entry point that:
1. Fetches the M3U8 master playlist via HTTP
2. Parses the content line by line
3. Extracts video streams (`#EXT-X-STREAM-INF`) and audio streams (`#EXT-X-MEDIA`)
4. Returns a `StreamSet` containing all parsed metadata
## Common Development Commands
```bash
# Build the project
go build -o m3u8-downloader
# Run the project
go run main.go
# Run with module support
go mod tidy
# Test the project (when tests are added)
go test ./...
# Format code
go fmt ./...
```
## Key Data Structures
- `StreamSet`: Root structure containing playlist URL and all streams
- `VideoURL`: Represents video stream with bandwidth, codecs, resolution, frame rate
- `AudioURL`: Represents audio stream with media type, group ID, name, and selection flags
## Error Handling
The current implementation uses `panic()` for error handling. When extending functionality, consider implementing proper error handling with returned error values following Go conventions.

35
cmd/downloader/main.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"fmt"
downloader2 "m3u8-downloader/pkg/downloader"
)
func main() {
masterUrl := "https://d17cyqyz9yhmep.cloudfront.net/streams/234945/playlist_1752291107574_1752292056713.m3u8"
stream, err := downloader2.ParseMasterPlaylist(masterUrl)
if err != nil {
panic(err)
}
audio, video, err := stream.FetchSegmentPlaylists()
if err != nil {
panic(err)
}
videoPlaylist := downloader2.ParseMediaPlaylist(video)
audioPlaylist := downloader2.ParseMediaPlaylist(audio)
for _, segment := range videoPlaylist.Segments {
fmt.Println(segment.URL)
}
for _, segment := range audioPlaylist.Segments {
err := downloader2.DownloadTSFile(stream.BuildSegmentURL(segment.URL), downloader2.OutputDirPath)
if err != nil {
return
}
}
}

1
cmd/proc/main.go Normal file
View File

@ -0,0 +1 @@
package proc

27
main.go
View File

@ -1,27 +0,0 @@
package main
import (
"fmt"
"m3u8-downloader/media"
)
func main() {
masterUrl := "https://d17cyqyz9yhmep.cloudfront.net/streams/234945/playlist_1752291107574_1752292056713.m3u8"
stream, err := media.ParseMasterPlaylist(masterUrl)
if err != nil {
panic(err)
}
audio, video, err := stream.FetchSegmentPlaylists()
if err != nil {
panic(err)
}
videoPlaylist := media.ParseMediaPlaylist(video)
audioPlaylist := media.ParseMediaPlaylist(audio)
fmt.Println(videoPlaylist)
fmt.Println(audioPlaylist)
}

View File

@ -1,4 +1,4 @@
package media
package downloader
import (
"context"
@ -6,6 +6,9 @@ import (
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"time"
)
@ -86,6 +89,31 @@ func FetchPlaylistContent(url string) (string, error) {
return string(data), nil
}
func DownloadTSFile(url string, outputDir string) error {
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
fileName := path.Base(url)
filePath := filepath.Join(outputDir, fileName)
data, err := defaultClient.Get(url)
if err != nil {
return err
}
out, err := os.Create(filePath)
if err != nil {
return err
}
defer out.Close()
_, err = out.Write(data)
if err != nil {
return err
}
return nil
}
func FetchPlaylistContentWithContext(ctx context.Context, url string) (string, error) {
data, err := defaultClient.GetWithContext(ctx, url)
if err != nil {

View File

@ -1,4 +1,4 @@
package media
package downloader
import "time"
@ -14,4 +14,5 @@ const (
ClientDefaultTimeout = 30 * time.Second
HTTPUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0"
PlaylistTypeVOD = "VOD"
OutputDirPath = "./data"
)

View File

@ -1,4 +1,4 @@
package media
package downloader
import (
"strconv"

View File

@ -1,4 +1,4 @@
package media
package downloader
import (
"fmt"

View File

@ -1,7 +1,8 @@
package media
package downloader
import (
"errors"
"fmt"
"strconv"
"strings"
)
@ -44,6 +45,10 @@ func (s *StreamSet) FetchSegmentPlaylists() (videoPlaylist, audioPlaylist string
return videoContent, audioContent, nil
}
func (s *StreamSet) BuildSegmentURL(filename string) string {
return fmt.Sprintf("%s%s", HTTPSPrefix, s.Metadata.Domain+"/streams/"+s.Metadata.StreamID+"/a/5000/"+filename)
}
func ParseMediaPlaylist(content string) *MediaPlaylist {
lines := strings.Split(content, "\n")
var segments []Segment

View File

@ -1,4 +1,4 @@
package media
package downloader
import (
"errors"
@ -54,7 +54,7 @@ type AudioStream struct {
func NewAudioStream(mediaInfo string) (*AudioStream, error) {
if !strings.HasPrefix(mediaInfo, ExtXMedia) {
return nil, errors.New("invalid media info line")
return nil, errors.New("invalid downloader info line")
}
attributes := parseMediaAttributes(mediaInfo)