diff --git a/src/asses/deport.go b/src/asses/deport.go new file mode 100644 index 0000000..72c0514 --- /dev/null +++ b/src/asses/deport.go @@ -0,0 +1,162 @@ +package asses + +import ( + "bytes" + "context" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "slices" + "strings" +) + +func deport(ctx context.Context, p string) error { + assStreams, err := assStreams(ctx, p) + if err != nil { + return err + } + + assStreamIDs := make([]string, len(assStreams)) + for i, stream := range assStreams { + assStreamIDs[i] = stream.id + assF := path.Join( + path.Dir(p), + fmt.Sprintf( + ".%s.%s.%s.ass", + path.Base(p), + stream.id, + stream.title, + ), + ) + if err := ffmpeg(ctx, "-i", p, "-map", stream.id, assF); err != nil { + return fmt.Errorf("failed to pull %s from %s: %w", stream.id, p, err) + } + } + + asses, err := filepath.Glob(path.Join( + path.Dir(p), + fmt.Sprintf(".%s.*.ass", path.Base(p)), + )) + if err != nil { + return err + } + for _, ass := range asses { + srt := fmt.Sprintf("%s.srt", strings.TrimSuffix(ass, ".ass")) + if _, err := os.Stat(srt); err == nil { + continue + } + + if err := ffmpeg(ctx, "-i", ass, srt); err != nil { + return err + } + } + + srts, err := filepath.Glob(path.Join( + path.Dir(p), + fmt.Sprintf(".%s.*.srt", path.Base(p)), + )) + if err != nil { + return err + } + slices.SortFunc(srts, func(a, b string) int { + // if skip a { return 1 } + // if skip b { return -1 } + // return -1 * (wc(a) - wc(b)) + return strings.Compare(a, b) + }) + for i := range srts { + if i == 0 { + base := path.Base(p) + withoutExt := strings.TrimSuffix(base, path.Ext(base)) + + srt := path.Join(path.Dir(p), fmt.Sprintf("%s.srt", withoutExt)) + if err := os.Rename(srts[i], srt); err != nil { + return err + } + + p2 := path.Join(path.Dir(p), fmt.Sprintf("%s.subless.mkv", withoutExt)) + args := []string{ + "-i", p, + "-map", "0", + } + for _, assStreamID := range assStreamIDs { + args = append(args, "-map", "-"+assStreamID) + } + args = append(args, + "-c", "copy", + p2, + ) + if err := ffmpeg(ctx, args...); err != nil { + return err + } else if err := os.Rename(p2, p); err != nil { + return err + } + } else { + os.Remove(srts[i]) + } + } + + return nil +} + +type stream struct { + id string + title string +} + +func assStreams(ctx context.Context, p string) ([]stream, error) { + output, err := ffprobe(ctx, "-i", p) + if err != nil { + return nil, err + } + + result := []stream{} + for _, line := range strings.Split(output, "\n") { + fields := strings.Fields(line) + if len(fields) < 3 { + continue + } else if fields[0] != "Stream" { + continue + } else if !strings.Contains(fields[1], "(") { + continue + } else if fields[2] != "Subtitle:" { + continue + } else if fields[3] != "ass" { + continue + } + field1 := fields[1] + id := strings.Trim(strings.Split(field1, "(")[0], "#") + title := strings.Trim(strings.Split(field1, "(")[1], "):") + result = append(result, stream{ + id: id, + title: title, + }) + } + return result, nil +} + +func ffprobe(ctx context.Context, args ...string) (string, error) { + return execc(ctx, "ffprobe", args...) +} + +func ffmpeg(ctx context.Context, args ...string) error { + std, err := execc(ctx, "ffmpeg", args...) + if err != nil { + return fmt.Errorf("(%w) %s", err, std) + } + return nil +} + +func execc(ctx context.Context, bin string, args ...string) (string, error) { + stdout := bytes.NewBuffer(nil) + + cmd := exec.CommandContext(ctx, bin, args...) + cmd.Stdin = nil + cmd.Stderr = stdout + cmd.Stdout = stdout + + err := cmd.Run() + return string(stdout.Bytes()), err +} diff --git a/src/asses/one.go b/src/asses/one.go index 8e29190..d6fea30 100644 --- a/src/asses/one.go +++ b/src/asses/one.go @@ -14,25 +14,25 @@ func One(ctx context.Context, p string) error { if last, err := checkLast(ctx, p); err != nil { return err } else if last.T.IsZero() { - } else if cksum, err := cksum(ctx, p); err != nil { + } else if cksum, err := Cksum(p); err != nil { return err } else if cksum != last.Cksum { } else if time.Since(last.T) < 20+time.Duration(rand.Int()%10)*24*time.Hour { return nil } - if err := deportAsses(ctx, p); err != nil { + if err := deport(ctx, p); err != nil { return err } - cksum, err := cksum(ctx, p) + cksum, err := Cksum(p) if err != nil { return err } return checked(ctx, p, cksum) } -func cksum(ctx context.Context, p string) (string, error) { +func Cksum(p string) (string, error) { f, err := os.Open(p) if err != nil { return "", err @@ -43,7 +43,3 @@ func cksum(ctx context.Context, p string) (string, error) { _, err = io.Copy(hasher, f) return base64.StdEncoding.EncodeToString(hasher.Sum(nil)), err } - -func deportAsses(ctx context.Context, p string) error { - return io.EOF -} diff --git a/src/asses/one_test.go b/src/asses/one_test.go index a90dae5..ca5f175 100644 --- a/src/asses/one_test.go +++ b/src/asses/one_test.go @@ -17,9 +17,22 @@ func TestOne(t *testing.T) { p := path.Join(d, "f.mkv") os.WriteFile(p, b, os.ModePerm) + cksum, _ := asses.Cksum(p) + if err := asses.One(ctx, p); err != nil { t.Fatal(err) } else if err := asses.One(ctx, p); err != nil { t.Fatal(err) } + + if _, err := os.Stat(p); err != nil { + t.Fatalf("lost original mkv: %v", err) + } else if _, err := os.Stat(path.Join(d, "f.srt")); err != nil { + t.Fatalf("no new srt: %v", err) + } + + newCksum, _ := asses.Cksum(p) + if cksum == newCksum { + t.Fatalf("cksum unchanged") + } }