loadtup-dl/main.go

176 lines
3.7 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
)
type config struct {
youtubeDl string
mkvmerge string
overrideOutput string
deleteTemporaries bool
}
func performDownload(ctx context.Context, cfg *config, targetUrl string) error {
//
var content []byte
var err error
if targetUrl == "-" {
// Read HTML page from stdin
content, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
} else {
// Download HTML page from URL
resp, err := http.Get(targetUrl)
if err != nil {
return err
}
content, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
_ = resp.Body.Close() // swallow error
}
ltc, err := NewLoadTupContent(content)
if err != nil {
return err
}
err = ltc.Validate()
if err != nil {
return err
}
// Create temporary directory
tmpdir, err := ioutil.TempDir("", "loadtup-dl-")
if err != nil {
return err
}
if cfg.deleteTemporaries {
defer os.RemoveAll(tmpdir)
}
// Download the video
ytdl := exec.CommandContext(ctx, cfg.youtubeDl, `-f`, `bestvideo+bestaudio`, "https://youtu.be/"+ltc.VideoID, `--merge-output-format`, `mkv`, "-o", filepath.Join(tmpdir, "downloaded"))
ytdl.Stdout = os.Stdout
ytdl.Stderr = os.Stderr
err = ytdl.Run()
if err != nil {
return err
}
// Determine video's total length
// Create the subtitle file (clamped to total length)
fh, err := os.OpenFile(filepath.Join(tmpdir, "subtitles.srt"), os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
err = ltc.WriteSRT(fh)
fh.Close()
if err != nil {
return err
}
// Mux the subtitles into the file
mkvm := exec.CommandContext(ctx, cfg.mkvmerge, `-o`, filepath.Join(tmpdir, "muxed.mkv"), filepath.Join(tmpdir, "downloaded.mkv"), filepath.Join(tmpdir, "subtitles.srt"))
mkvm.Stdout = os.Stdout
mkvm.Stderr = os.Stderr
err = mkvm.Run()
if err != nil {
return err
}
// Determine final filename
outputFile := cfg.overrideOutput
if outputFile == "" {
// Generate the CRC32 and put it into the filename
hw := NewCRCwriter(crc32.IEEE, ioutil.Discard)
fhm, err := os.OpenFile(filepath.Join(tmpdir, "muxed.mkv"), os.O_RDONLY, 0400)
if err != nil {
return err
}
_, err = io.Copy(hw, fhm)
fhm.Close()
if err != nil {
return err
}
outputFile = fmt.Sprintf(`[Loadtup] %s [%08X].mkv`, ltc.Title, hw.Sum())
}
err = os.Rename(filepath.Join(tmpdir, "muxed.mkv"), outputFile)
if err != nil {
return err
}
// Done
return nil
}
func usage() {
fmt.Fprintln(os.Stderr, `Usage: loadtup-dl [options] [--] URL|- [URL...]
Supported URLs take the form 'https://loadtup.com/abcdefghijk'. Use a hyphen to
read equivalent loadtup.com HTML content from stdin.
Options:
--youtube-dl PATH Override path to youtube-dl
--mkvmerge PATH Override path to mkvmerge
--output PATH Override output filename
(only valid for a single URL)
--delete-temporary=false Preserve temporary files
`)
os.Exit(1)
}
func main() {
ctx := context.Background()
cfg := config{}
flag.StringVar(&cfg.youtubeDl, "youtube-dl", "youtube-dl", "")
flag.StringVar(&cfg.mkvmerge, "mkvmerge", "mkvmerge", "")
flag.StringVar(&cfg.overrideOutput, "output", "", "")
flag.BoolVar(&cfg.deleteTemporaries, "delete-temporary", true, "")
flag.Usage = usage
flag.Parse()
if len(flag.Args()) == 0 {
usage() // n.b. calls os.Exit(1)
}
if len(flag.Args()) > 1 && cfg.overrideOutput != "" {
fmt.Fprintln(os.Stderr, "Can't use --output when supplying multiple URLs")
os.Exit(1)
}
for _, targetUrl := range flag.Args() {
err := performDownload(ctx, &cfg, targetUrl)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}
}