asses.One(path) seems to deport ass neato
parent
2e8c8d3d39
commit
1c7dafc78b
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -14,25 +14,25 @@ func One(ctx context.Context, p string) error {
|
||||||
if last, err := checkLast(ctx, p); err != nil {
|
if last, err := checkLast(ctx, p); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if last.T.IsZero() {
|
} else if last.T.IsZero() {
|
||||||
} else if cksum, err := cksum(ctx, p); err != nil {
|
} else if cksum, err := Cksum(p); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if cksum != last.Cksum {
|
} else if cksum != last.Cksum {
|
||||||
} else if time.Since(last.T) < 20+time.Duration(rand.Int()%10)*24*time.Hour {
|
} else if time.Since(last.T) < 20+time.Duration(rand.Int()%10)*24*time.Hour {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deportAsses(ctx, p); err != nil {
|
if err := deport(ctx, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cksum, err := cksum(ctx, p)
|
cksum, err := Cksum(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return checked(ctx, p, cksum)
|
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)
|
f, err := os.Open(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -43,7 +43,3 @@ func cksum(ctx context.Context, p string) (string, error) {
|
||||||
_, err = io.Copy(hasher, f)
|
_, err = io.Copy(hasher, f)
|
||||||
return base64.StdEncoding.EncodeToString(hasher.Sum(nil)), err
|
return base64.StdEncoding.EncodeToString(hasher.Sum(nil)), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func deportAsses(ctx context.Context, p string) error {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,22 @@ func TestOne(t *testing.T) {
|
||||||
p := path.Join(d, "f.mkv")
|
p := path.Join(d, "f.mkv")
|
||||||
os.WriteFile(p, b, os.ModePerm)
|
os.WriteFile(p, b, os.ModePerm)
|
||||||
|
|
||||||
|
cksum, _ := asses.Cksum(p)
|
||||||
|
|
||||||
if err := asses.One(ctx, p); err != nil {
|
if err := asses.One(ctx, p); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if err := asses.One(ctx, p); err != nil {
|
} else if err := asses.One(ctx, p); err != nil {
|
||||||
t.Fatal(err)
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue