music-library/main.go

125 lines
3.2 KiB
Go
Raw Normal View History

2025-01-15 12:48:20 +01:00
package main
import (
"context"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"git.tordarus.net/tordarus/channel"
"git.tordarus.net/tordarus/envvars"
"github.com/fsnotify/fsnotify"
)
var ( // flags
2025-01-15 15:22:52 +01:00
FlagSourceDir = envvars.String("SOURCE_PATH", "/video")
FlagTargetDir = envvars.String("TARGET_PATH", "/audio")
FlagCacheFile = envvars.String("CACHE_FILE", "/hashes.bin")
2025-01-15 21:06:45 +01:00
FlagExcludeFiles = envvars.StringSlice("EXCLUDE_FILES", ":", []string{".songs"})
2025-01-15 15:22:52 +01:00
FlagUpdateCacheFileInterval = envvars.Duration("UPDATE_CACHE_FILE_INTERVAL", 10*time.Second)
2025-01-15 12:48:20 +01:00
)
func main() {
if _, err := exec.LookPath("ffmpeg"); err != nil {
fmt.Println("ffmpeg not found in PATH")
os.Exit(1)
}
FlagSourceDir = AbsPath(FlagSourceDir)
FlagTargetDir = AbsPath(FlagTargetDir)
FlagCacheFile = AbsPath(FlagCacheFile)
2025-01-15 21:06:45 +01:00
for i := 0; i < len(FlagExcludeFiles); i++ {
FlagExcludeFiles[i] = filepath.Join(FlagSourceDir, FlagExcludeFiles[i])
}
2025-01-15 12:48:20 +01:00
InitCache()
ops := []fsnotify.Op{
fsnotify.Create,
fsnotify.Remove,
fsnotify.Write,
fsnotify.Rename,
}
files, err := WatchDirectory(context.Background(), FlagSourceDir, ops...)
if err != nil {
panic(err)
}
groupedFiles := channel.GroupByTime(files, time.Second, func(events map[string]struct{}, event string) map[string]struct{} {
if events == nil {
events = map[string]struct{}{}
}
events[event] = struct{}{}
return events
})
filesByGroup := channel.FlatMap(groupedFiles, func(key string, _ struct{}) string { return key })
filteredFiles := channel.Filter(filesByGroup, Or(Not(Exists), And(SymlinkFilesOnly, VideosOnly)))
for file := range filteredFiles {
if err := TranscodeFile(file); err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("could not transcode file '%s': %w", file, err))
}
}
}
func TranscodeFile(file string) error {
2025-01-15 15:01:28 +01:00
relPath := strings.TrimPrefix(file[:len(file)-len(filepath.Ext(file))], FlagSourceDir)
newPath := filepath.Join(FlagTargetDir, relPath+".mp3")
2025-01-15 12:48:20 +01:00
newDir := filepath.Dir(newPath)
fmt.Printf("check '%s'\n", relPath)
// file was deleted in source directory.
// delete corresponding file in target directory as well
if _, err := os.Stat(file); errors.Is(err, fs.ErrNotExist) {
fmt.Printf("remove '%s'\n", relPath)
return os.Remove(newPath)
}
oldFileHash, err := HashFile(file)
if err != nil {
return err
}
if err := os.MkdirAll(newDir, 0755); err != nil {
return fmt.Errorf("creation of playlist failed: %w", err)
}
// file does exist in target directory.
// compare hashsums to determine if file should be re-transcoded
if _, err := os.Stat(newPath); !errors.Is(err, fs.ErrNotExist) {
if expectedFileHash, exists := HashCache.GetHas(oldFileHash); exists {
currentFileHash, err := HashFile(newPath)
if err != nil {
return err
}
if currentFileHash == expectedFileHash {
fmt.Printf("skip '%s'. hashes match\n", relPath)
return nil
}
}
}
fmt.Printf("convert '%s'\n", relPath)
if err := ConvertToAudio(context.Background(), file, newPath); err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("encoding of video '%s' failed: %w", file, err))
}
newFileHash, err := HashFile(newPath)
if err != nil {
return err
}
HashCache.Put(oldFileHash, newFileHash)
return nil
}