Kyoo/transcoder/src/extract.go
2025-11-04 09:48:21 +01:00

153 lines
3.6 KiB
Go

package src
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/zoriya/kyoo/transcoder/src/storage"
"github.com/zoriya/kyoo/transcoder/src/utils"
)
const ExtractVersion = 1
func (s *MetadataService) ExtractSubs(ctx context.Context, info *MediaInfo) (any, error) {
get_running, set := s.extractLock.Start(info.Sha)
if get_running != nil {
return get_running()
}
err := s.extractSubs(ctx, info)
if err != nil {
log.Printf("Couldn't extract subs: %v", err)
return set(nil, err)
}
_, err = s.database.Exec(`update gocoder.info set ver_extract = $2 where sha = $1`, info.Sha, ExtractVersion)
return set(nil, err)
}
func (s *MetadataService) GetAttachment(ctx context.Context, sha string, name string) (io.ReadCloser, error) {
_, err := s.extractLock.WaitFor(sha)
if err != nil {
return nil, err
}
itemPath := fmt.Sprintf("%s/%s/%s", sha, "att", name)
item, err := s.storage.GetItem(ctx, itemPath)
if err != nil {
return nil, fmt.Errorf("failed to get attachment with path %q: %w", itemPath, err)
}
return item, nil
}
func (s *MetadataService) GetSubtitle(
ctx context.Context,
path string,
sha string,
name string,
) (io.ReadCloser, error) {
// name is either `{index}.{extension}` or `ext-{filename}.{extension}`
if strings.HasPrefix(name, "ext") {
// external subtitle already available on the fs
return os.Open(path)
}
_, err := s.extractLock.WaitFor(sha)
if err != nil {
return nil, err
}
itemPath := fmt.Sprintf("%s/sub/%s", sha, name)
item, err := s.storage.GetItem(ctx, itemPath)
if err != nil {
return nil, fmt.Errorf("failed to get subtitle with path %q: %w", itemPath, err)
}
return item, nil
}
func (s *MetadataService) extractSubs(ctx context.Context, info *MediaInfo) (err error) {
defer utils.PrintExecTime("extraction of %s", info.Path)()
// If there are no supported, embedded subtitles, there is nothing to extract.
hasSupportedSubtitle := false
for _, sub := range info.Subtitles {
if !sub.IsExternal && sub.Extension != nil {
hasSupportedSubtitle = true
break
}
}
if !hasSupportedSubtitle {
return nil
}
workdir := filepath.Join(os.TempDir(), info.Sha)
if err := os.MkdirAll(workdir, 0660); err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
attDir := filepath.Join(workdir, "att")
if err := os.MkdirAll(attDir, 0660); err != nil {
return fmt.Errorf("failed to create attachment directory: %w", err)
}
subDir := filepath.Join(workdir, "sub")
if err := os.MkdirAll(subDir, 0660); err != nil {
return fmt.Errorf("failed to create subtitles directory: %w", err)
}
defer utils.CleanupWithErr(
&err, func() error {
return os.RemoveAll(workdir)
},
"failed to remove attachment working directory",
)
// Dump the attachments and subtitles
cmd := exec.Command(
"ffmpeg",
"-dump_attachment:t", "",
// override old attachments
"-y",
"-i", info.Path,
)
cmd.Dir = attDir
for _, sub := range info.Subtitles {
if ext := sub.Extension; ext != nil {
if sub.IsExternal {
// skip extraction of external subtitles
continue
}
cmd.Args = append(
cmd.Args,
"-map", fmt.Sprintf("0:s:%d", *sub.Index),
"-c:s", "copy",
fmt.Sprintf("%s/%d.%s", subDir, *sub.Index, *ext),
)
}
}
log.Printf("Starting extraction with the command: %s", cmd)
cmd.Stdout = nil
if err := cmd.Run(); err != nil {
fmt.Println("Error starting ffmpeg extract:", err)
return err
}
err = storage.SaveFilesToBackend(ctx, s.storage, workdir, info.Sha)
if err != nil {
return fmt.Errorf("failed while saving files to backend: %w", err)
}
log.Printf("Extraction finished for %s", info.Path)
return nil
}