Skip to content

Commit

Permalink
Merge pull request #75 from Zibbp/hls
Browse files Browse the repository at this point in the history
feat(archive): HLS support
  • Loading branch information
Zibbp authored Jan 6, 2023
2 parents 3f0b230 + 0e8803e commit e2803f0
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 15 deletions.
56 changes: 43 additions & 13 deletions internal/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,17 @@ func (s *Service) TaskVideoConvert(ch *ent.Channel, v *ent.Vod, q *ent.Queue, co
return
}

// Check if video should be saved as hls
if viper.GetBool("archive.save_as_hls") {
err = exec.ConvertToHLS(v)
if err != nil {
log.Error().Err(err).Msg("error converting video to hls")
q.Update().SetTaskVideoConvert(utils.Failed).SaveX(context.Background())
s.TaskError(ch, v, q, "video_convert")
return
}
}

q.Update().SetTaskVideoConvert(utils.Success).SaveX(context.Background())

// Always invoke task video move if video convert was successful
Expand All @@ -710,24 +721,43 @@ func (s *Service) TaskVideoMove(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont
log.Debug().Msgf("starting task video move for vod %s", v.ID)
q.Update().SetTaskVideoMove(utils.Running).SaveX(context.Background())

sourcePath := fmt.Sprintf("/tmp/%s_%s-video-convert.mp4", v.ExtID, v.ID)
destPath := fmt.Sprintf("/vods/%s/%s_%s/%s-video.mp4", ch.Name, v.ExtID, v.ID, v.ExtID)
// Check if video is saved as HLS
if viper.GetBool("archive.save_as_hls") {
// Move HLS folder to vod folder
sourcePath := fmt.Sprintf("/tmp/%s_%s-video_hls0", v.ExtID, v.ID)
destPath := fmt.Sprintf("/vods/%s/%s_%s/%s-video_hls", ch.Name, v.ExtID, v.ID, v.ExtID)
err := utils.MoveFolder(sourcePath, destPath)
if err != nil {
log.Error().Err(err).Msg("error moving video hls directory")
q.Update().SetTaskVideoMove(utils.Failed).SaveX(context.Background())
s.TaskError(ch, v, q, "video_move")
return
}
// Update video path to hls path
v.Update().SetVideoPath(fmt.Sprintf("/vods/%s/%s_%s/%s-video_hls/%s-video.m3u8", ch.Name, v.ExtID, v.ID, v.ExtID, v.ExtID)).SaveX(context.Background())
} else {
sourcePath := fmt.Sprintf("/tmp/%s_%s-video-convert.mp4", v.ExtID, v.ID)
destPath := fmt.Sprintf("/vods/%s/%s_%s/%s-video.mp4", ch.Name, v.ExtID, v.ID, v.ExtID)

err := utils.MoveFile(sourcePath, destPath)
if err != nil {
log.Error().Err(err).Msg("error moving video")
q.Update().SetTaskVideoMove(utils.Failed).SaveX(context.Background())
s.TaskError(ch, v, q, "video_move")
return
err := utils.MoveFile(sourcePath, destPath)
if err != nil {
log.Error().Err(err).Msg("error moving video")
q.Update().SetTaskVideoMove(utils.Failed).SaveX(context.Background())
s.TaskError(ch, v, q, "video_move")
return
}
}

// Clean up files
// Delete source file
err = utils.DeleteFile(fmt.Sprintf("/tmp/%s_%s-video.mp4", v.ExtID, v.ID))
err := utils.DeleteFile(fmt.Sprintf("/tmp/%s_%s-video.mp4", v.ExtID, v.ID))
if err != nil {
log.Error().Err(err).Msg("error deleting video")
q.Update().SetTaskVideoMove(utils.Failed).SaveX(context.Background())
s.TaskError(ch, v, q, "video_move")
return
log.Info().Err(err).Msgf("error deleting source file for vod %s", v.ID)
}
// Delete converted file
err = utils.DeleteFile(fmt.Sprintf("/tmp/%s_%s-video-convert.mp4", v.ExtID, v.ID))
if err != nil {
log.Info().Err(err).Msgf("error deleting converted file for vod %s", v.ID)
}

q.Update().SetTaskVideoMove(utils.Success).SaveX(context.Background())
Expand Down
16 changes: 16 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type Conf struct {
ChatRender string `json:"chat_render"`
StreamlinkLive string `json:"streamlink_live"`
} `json:"parameters"`
Archive struct {
SaveAsHls bool `json:"save_as_hls"`
} `json:"archive"`
Notifications Notification `json:"notifications"`
}

Expand Down Expand Up @@ -72,6 +75,7 @@ func NewConfig() {
viper.SetDefault("parameters.video_convert", "-c:v copy -c:a copy")
viper.SetDefault("parameters.chat_render", "-h 1440 -w 340 --framerate 30 --font Inter --font-size 13")
viper.SetDefault("parameters.streamlink_live", "--force-progress,--force,--twitch-low-latency,--twitch-disable-hosting")
viper.SetDefault("archive.save_as_hls", false)
// Notifications
viper.SetDefault("notifications.video_success_webhook_url", "")
viper.SetDefault("notifications.video_success_template", "✅ Video Archived: {{vod_title}} by {{channel_display_name}}.")
Expand Down Expand Up @@ -108,6 +112,13 @@ func (s *Service) GetConfig(c echo.Context) (*Conf, error) {
return &Conf{
RegistrationEnabled: viper.GetBool("registration_enabled"),
DBSeeded: viper.GetBool("db_seeded"),
Archive: struct {
SaveAsHls bool `json:"save_as_hls"`
}(struct {
SaveAsHls bool
}{
SaveAsHls: viper.GetBool("archive.save_as_hls"),
}),
Parameters: struct {
VideoConvert string `json:"video_convert"`
ChatRender string `json:"chat_render"`
Expand All @@ -129,6 +140,7 @@ func (s *Service) UpdateConfig(c echo.Context, cDto *Conf) error {
viper.Set("parameters.video_convert", cDto.Parameters.VideoConvert)
viper.Set("parameters.chat_render", cDto.Parameters.ChatRender)
viper.Set("parameters.streamlink_live", cDto.Parameters.StreamlinkLive)
viper.Set("archive.save_as_hls", cDto.Archive.SaveAsHls)
err := viper.WriteConfig()
if err != nil {
return fmt.Errorf("error writing config file: %w", err)
Expand Down Expand Up @@ -211,6 +223,10 @@ func refreshConfig(configPath string) {
log.Error().Err(err).Msg("error unsetting config value")
}
}
// Archive
if !viper.IsSet("archive.save_as_hls") {
viper.Set("archive.save_as_hls", false)
}

}

Expand Down
29 changes: 29 additions & 0 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,35 @@ func ConvertTwitchVodVideo(v *ent.Vod) error {
return nil
}

func ConvertToHLS(v *ent.Vod) error {
// Delete original video file to save space
log.Debug().Msgf("deleting original video file for %s to save space", v.ExtID)
if err := os.Remove(fmt.Sprintf("/tmp/%s_%s-video.mp4", v.ExtID, v.ID)); err != nil {
log.Error().Err(err).Msg("error deleting original video file")
return err
}

cmd := osExec.Command("ffmpeg", "-y", "-hide_banner", "-i", fmt.Sprintf("/tmp/%s_%s-video-convert.mp4", v.ExtID, v.ID), "-c", "copy", "-start_number", "0", "-hls_time", "10", "-hls_list_size", "0", "-hls_segment_filename", fmt.Sprintf("/tmp/%s_%s-video_hls%s/%s_segment%s.ts", v.ExtID, v.ID, "%v", v.ExtID, "%d"), "-f", "hls", fmt.Sprintf("/tmp/%s_%s-video_hls%s/%s-video.m3u8", v.ExtID, v.ID, "%v", v.ExtID))

videoConverLogFile, err := os.Open(fmt.Sprintf("/logs/%s_%s-video-convert.log", v.ExtID, v.ID))
if err != nil {
log.Error().Err(err).Msg("error opening video convert logfile")
return err
}
defer videoConverLogFile.Close()
cmd.Stdout = videoConverLogFile
cmd.Stderr = videoConverLogFile

if err := cmd.Run(); err != nil {
log.Error().Err(err).Msg("error running ffmpeg for vod video convert - hls")
return err
}

log.Debug().Msgf("finished vod video convert - hls for %s", v.ExtID)
return nil

}

func DownloadTwitchLiveVideo(v *ent.Vod, ch *ent.Channel) error {
// Fetch config params
liveStreamlinkParams := viper.GetString("parameters.streamlink_live")
Expand Down
6 changes: 6 additions & 0 deletions internal/transport/http/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type UpdateConfigRequest struct {
ChatRender string `json:"chat_render" validate:"required"`
StreamlinkLive string `json:"streamlink_live"`
} `json:"parameters"`
Archive struct {
SaveAsHls bool `json:"save_as_hls"`
} `json:"archive"`
}

type UpdateNotificationRequest struct {
Expand Down Expand Up @@ -56,6 +59,9 @@ func (h *Handler) UpdateConfig(c echo.Context) error {
}
cDto := config.Conf{
RegistrationEnabled: conf.RegistrationEnabled,
Archive: struct {
SaveAsHls bool `json:"save_as_hls"`
}(conf.Archive),
Parameters: struct {
VideoConvert string `json:"video_convert"`
ChatRender string `json:"chat_render"`
Expand Down
65 changes: 63 additions & 2 deletions internal/utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package utils
import (
"encoding/json"
"fmt"
"github.com/rs/zerolog/log"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"

"github.com/rs/zerolog/log"
)

// CreateFolder - creates folder if it doesn't exist
Expand Down Expand Up @@ -84,11 +86,70 @@ func MoveFile(sourcePath, destPath string) error {
// Copy was successful - delete source file
err = os.Remove(sourcePath)
if err != nil {
return fmt.Errorf("error removing file: %v", err)
log.Info().Msgf("error deleting source file: %v", err)
}
return nil
}

func MoveFolder(src string, dst string) error {
// Check if the source path exists
if _, err := os.Stat(src); os.IsNotExist(err) {
return err
}

// Create the destination directory
err := os.MkdirAll(dst, os.ModePerm)
if err != nil {
return err
}

// Walk through the source directory and copy each file and directory
// to the destination
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// Compute the relative path of the file/directory
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}

// Create the destination path
dstPath := filepath.Join(dst, relPath)

// If the source path is a directory, create it in the destination
if info.IsDir() {
return os.MkdirAll(dstPath, os.ModePerm)
}

// Otherwise, it's a file. Open the file for reading
srcFile, err := os.Open(path)
if err != nil {
return err
}
defer srcFile.Close()

// Open the destination file for writing
dstFile, err := os.Create(dstPath)
if err != nil {
return err
}
defer dstFile.Close()

// Copy the contents of the file
_, err = io.Copy(dstFile, srcFile)
return err
})
if err != nil {
return err
}

// Finally, remove the source directory
return os.RemoveAll(src)
}

func DeleteFile(path string) error {
log.Debug().Msgf("deleting file: %s", path)
err := os.Remove(path)
Expand Down

0 comments on commit e2803f0

Please sign in to comment.