package tasks import ( "encoding/json" "os" "os/exec" "path/filepath" "strings" "backend/modules/medias" "github.com/google/uuid" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // @provider type DiscoverMedias struct { mediasSvc *medias.Service log *log.Entry } // Prepare func (d *DiscoverMedias) Prepare() error { d.log = log.WithField("module", "DiscoverMedias") return nil } func (d *DiscoverMedias) RunE(from, to string) error { d.log.Infof("Discover medias from: %s to: %s", from, to) mapFile := filepath.Join(to, "map.json") d.log.Infof("read in from: %s", mapFile) b, err := os.ReadFile(mapFile) if err != nil { return errors.Wrapf(err, "read file: %s", mapFile) } type mediaUuidMap struct { UUID string Name string } var store []mediaUuidMap err = json.Unmarshal(b, &store) if err != nil { return errors.Wrapf(err, "unmarshal json: %s", mapFile) } storeMap := make(map[string]string) for _, item := range store { storeMap[item.Name] = item.UUID } dirs, err := d.getSubDirs(from) if err != nil { return errors.Wrapf(err, "get sub dirs: %s", from) } for _, dir := range dirs { d.log.Infof("Discover medias in: %s", dir) if _, ok := storeMap[dir]; ok { d.log.Infof("Skip dir: %s", dir) continue } dirPath := filepath.Join(from, dir) uuid := uuid.New().String() to := filepath.Join(to, uuid) videos := d.globVideo(dirPath) if len(videos) > 0 { video := videos[0] if err := d.ffmpegVideoToM3U8(video, to); err != nil { return errors.Wrapf(err, "ffmpeg video to m3u8: %s", video) } } audios := d.globAudio(dirPath) if len(audios) > 0 { audio := audios[0] if err := d.ffmpegAudioToM3U8(audio, to); err != nil { return errors.Wrapf(err, "ffmpeg audio to m3u8: %s", audio) } } store = append(store, mediaUuidMap{ UUID: uuid, Name: dir, }) b, err := json.Marshal(store) if err != nil { return errors.Wrapf(err, "marshal json: %s", mapFile) } if err := os.WriteFile(mapFile, b, os.ModePerm); err != nil { return errors.Wrapf(err, "write file: %s", mapFile) } storeMap[dir] = uuid d.log.Infof("Store map: %s", storeMap) } return nil } func (d *DiscoverMedias) getSubDirs(root string) ([]string, error) { fd, err := os.Open(root) if err != nil { return nil, errors.Wrapf(err, "open root directory: %s", root) } defer fd.Close() entries, err := fd.Readdir(-1) if err != nil { return nil, errors.Wrapf(err, "read root directory: %s", root) } var paths []string for _, entry := range entries { if entry.IsDir() { paths = append(paths, entry.Name()) } } return paths, nil } func (d *DiscoverMedias) globVideo(root string) []string { files := []string{} exts := []string{"*.mp4", "*.MP4", "*.mov", "*.MOV"} for _, ext := range exts { fs, _ := filepath.Glob(filepath.Join(root, ext)) files = append(files, fs...) } return files } func (d *DiscoverMedias) globAudio(root string) []string { files := []string{} exts := []string{"*.mp3", "*.MP3", "*.wav", "*.WAV"} for _, ext := range exts { fs, _ := filepath.Glob(filepath.Join(root, ext)) files = append(files, fs...) } return files } // ensureDirectory ensure the directory exists func (d *DiscoverMedias) ensureDirectory(dir string) error { st, err := os.Stat(dir) if os.IsNotExist(err) { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return errors.Wrapf(err, "mkdir: %s", dir) } return nil } if !st.IsDir() { os.RemoveAll(dir) if err := os.MkdirAll(dir, os.ModePerm); err != nil { return errors.Wrapf(err, "mkdir: %s", dir) } } return nil } func (d *DiscoverMedias) ffmpegVideoToM3U8(input string, output string) error { output = filepath.Join(output, "video") if err := d.ensureDirectory(output); err != nil { return errors.Wrapf(err, "ensure directory: %s", output) } args := []string{ "-i", input, "-c:v", "libx264", "-c:a", "aac", "-strict", "-2", "-f", "hls", "-hls_time", "10", "-hls_list_size", "0", "-hls_segment_filename", filepath.Join(output, "%d.ts"), filepath.Join(output, "index.m3u8"), } log.Infof("cmd: ffmpeg %s", strings.Join(args, " ")) logs, err := exec.Command("ffmpeg", args...).Output() if err != nil { return errors.Wrapf(err, "ffmpeg video to m3u8: %s", input) } d.log.Infof("ffmpeg video to m3u8: %s", logs) return nil } func (d *DiscoverMedias) ffmpegAudioToM3U8(input string, output string) error { output = filepath.Join(output, "audio") if err := d.ensureDirectory(output); err != nil { return errors.Wrapf(err, "ensure directory: %s", output) } args := []string{ "-i", input, "-c:a", "aac", "-strict", "-2", "-f", "hls", "-hls_time", "10", "-hls_list_size", "0", "-hls_segment_filename", filepath.Join(output, "%d.ts"), filepath.Join(output, "index.m3u8"), } log.Infof("cmd: ffmpeg %s", strings.Join(args, " ")) logs, err := exec.Command("ffmpeg", args...).Output() if err != nil { return errors.Wrapf(err, "ffmpeg audio to m3u8: %s", input) } d.log.Infof("ffmpeg audio to m3u8: %s", logs) return nil }