mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
feat: transfer STRM tree subtitle links
This commit is contained in:
+24
-22
@@ -109,17 +109,18 @@ type generateSTRMReq struct {
|
||||
}
|
||||
|
||||
type generateSTRMTreeReq struct {
|
||||
Provider string `json:"provider"`
|
||||
TreeText string `json:"tree_text"`
|
||||
Paths []string `json:"paths"`
|
||||
SourceRoot string `json:"source_root"`
|
||||
OutputPrefix string `json:"output_prefix"`
|
||||
OutputDir string `json:"output_dir"`
|
||||
BaseURL string `json:"base_url"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Cleanup bool `json:"cleanup"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
BatchLimit int `json:"batch_limit"`
|
||||
Provider string `json:"provider"`
|
||||
TreeText string `json:"tree_text"`
|
||||
Paths []string `json:"paths"`
|
||||
SourceRoot string `json:"source_root"`
|
||||
OutputPrefix string `json:"output_prefix"`
|
||||
OutputDir string `json:"output_dir"`
|
||||
BaseURL string `json:"base_url"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Cleanup bool `json:"cleanup"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
BatchLimit int `json:"batch_limit"`
|
||||
TransferSubtitles bool `json:"transfer_subtitles"`
|
||||
}
|
||||
|
||||
func generateSTRMHandler(svc *service.Container) gin.HandlerFunc {
|
||||
@@ -182,17 +183,18 @@ func generateSTRMFromTreeHandler(svc *service.Container) gin.HandlerFunc {
|
||||
baseURL = strings.TrimRight(absoluteRequestURL(c, "/"), "/")
|
||||
}
|
||||
res, err := strmSvc.GenerateFromTree(c.Request.Context(), service.GenerateSTRMTreeOptions{
|
||||
Provider: req.Provider,
|
||||
TreeText: req.TreeText,
|
||||
Paths: req.Paths,
|
||||
SourceRoot: req.SourceRoot,
|
||||
OutputPrefix: req.OutputPrefix,
|
||||
OutputDir: req.OutputDir,
|
||||
BaseURL: baseURL,
|
||||
Overwrite: req.Overwrite,
|
||||
Cleanup: req.Cleanup,
|
||||
DryRun: req.DryRun,
|
||||
BatchLimit: req.BatchLimit,
|
||||
Provider: req.Provider,
|
||||
TreeText: req.TreeText,
|
||||
Paths: req.Paths,
|
||||
SourceRoot: req.SourceRoot,
|
||||
OutputPrefix: req.OutputPrefix,
|
||||
OutputDir: req.OutputDir,
|
||||
BaseURL: baseURL,
|
||||
Overwrite: req.Overwrite,
|
||||
Cleanup: req.Cleanup,
|
||||
DryRun: req.DryRun,
|
||||
BatchLimit: req.BatchLimit,
|
||||
TransferSubtitles: req.TransferSubtitles,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
|
||||
@@ -10,31 +10,49 @@ import (
|
||||
)
|
||||
|
||||
type GenerateSTRMTreeOptions struct {
|
||||
Provider string `json:"provider"`
|
||||
TreeText string `json:"tree_text,omitempty"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
SourceRoot string `json:"source_root,omitempty"`
|
||||
OutputPrefix string `json:"output_prefix,omitempty"`
|
||||
OutputDir string `json:"output_dir"`
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Cleanup bool `json:"cleanup"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
BatchLimit int `json:"batch_limit,omitempty"`
|
||||
Provider string `json:"provider"`
|
||||
TreeText string `json:"tree_text,omitempty"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
SourceRoot string `json:"source_root,omitempty"`
|
||||
OutputPrefix string `json:"output_prefix,omitempty"`
|
||||
OutputDir string `json:"output_dir"`
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Cleanup bool `json:"cleanup"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
BatchLimit int `json:"batch_limit,omitempty"`
|
||||
TransferSubtitles bool `json:"transfer_subtitles,omitempty"`
|
||||
}
|
||||
|
||||
type strmTreeSource struct {
|
||||
Provider string
|
||||
Path string
|
||||
RefPath string
|
||||
Kind string
|
||||
}
|
||||
|
||||
const (
|
||||
strmTreeSourceKindVideo = "video"
|
||||
strmTreeSourceKindSubtitle = "subtitle"
|
||||
)
|
||||
|
||||
type strmTreeSourceCollection struct {
|
||||
sources []strmTreeSource
|
||||
ignored []string
|
||||
ignoredCount int
|
||||
}
|
||||
|
||||
type strmTreeSourceCollector struct {
|
||||
fallbackProvider string
|
||||
transferSubtitles bool
|
||||
sources []strmTreeSource
|
||||
subtitles []strmTreeSource
|
||||
ignored []string
|
||||
ignoredCount int
|
||||
seen map[string]struct{}
|
||||
seenIgnored map[string]struct{}
|
||||
}
|
||||
|
||||
func (s *STRMService) GenerateFromTree(ctx context.Context, opts GenerateSTRMTreeOptions) (*GenerateSTRMResult, error) {
|
||||
provider := normalizeSTRMTreeProvider(opts.Provider)
|
||||
if provider == "" {
|
||||
@@ -97,6 +115,9 @@ func strmTreeBatchLimitReached(result *GenerateSTRMResult, limit int) bool {
|
||||
func generateTreeSTRMItem(outputDir string, source strmTreeSource, opts GenerateSTRMTreeOptions) GenerateSTRMItem {
|
||||
relSource := strmTreeRelativeSource(source.Path, opts.SourceRoot)
|
||||
relPath, err := strmTreeOutputRelativePath(relSource)
|
||||
if source.Kind == strmTreeSourceKindSubtitle {
|
||||
relPath, err = strmTreeOutputSubtitleLinkRelativePath(relSource)
|
||||
}
|
||||
item := GenerateSTRMItem{Title: strings.TrimSuffix(path.Base(source.Path), path.Ext(source.Path))}
|
||||
if err != nil {
|
||||
item.Action = "error"
|
||||
@@ -141,49 +162,100 @@ func generateTreeSTRMItem(outputDir string, source strmTreeSource, opts Generate
|
||||
}
|
||||
|
||||
func collectSTRMTreeSources(opts GenerateSTRMTreeOptions) strmTreeSourceCollection {
|
||||
fallbackProvider := normalizeSTRMTreeProvider(opts.Provider)
|
||||
out := make([]strmTreeSource, 0, len(opts.Paths))
|
||||
seen := map[string]struct{}{}
|
||||
ignored := make([]string, 0)
|
||||
ignoredCount := 0
|
||||
seenIgnored := map[string]struct{}{}
|
||||
addIgnored := func(value string) {
|
||||
if ignoredPath, ok := strmTreeIgnoredFileLikeSource(value); ok {
|
||||
key := strings.ToLower(ignoredPath)
|
||||
if _, exists := seenIgnored[key]; exists {
|
||||
return
|
||||
}
|
||||
seenIgnored[key] = struct{}{}
|
||||
ignoredCount++
|
||||
if len(ignored) < strmTreeIgnoredItemSampleLimit {
|
||||
ignored = append(ignored, ignoredPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
add := func(value string) {
|
||||
source := normalizeSTRMTreeSourceWithProvider(value, fallbackProvider)
|
||||
if source.Provider == "" || source.Path == "" || !strmTreeSourceIsVideo(source.Path) {
|
||||
addIgnored(value)
|
||||
return
|
||||
}
|
||||
key := strings.ToLower(source.Provider) + "\x00" + strings.ToLower(source.Path) + "\x00" + strings.ToLower(source.cloudRefPath())
|
||||
if _, ok := seen[key]; ok {
|
||||
return
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
out = append(out, source)
|
||||
}
|
||||
collector := newSTRMTreeSourceCollector(opts)
|
||||
for _, value := range opts.Paths {
|
||||
add(value)
|
||||
collector.add(value)
|
||||
}
|
||||
treeSources, treeIgnored := parseSTRMTreeTextWithIgnored(opts.TreeText)
|
||||
for _, value := range treeSources {
|
||||
add(value)
|
||||
collector.add(value)
|
||||
}
|
||||
for _, value := range treeIgnored {
|
||||
addIgnored(value)
|
||||
collector.addIgnoredOrSubtitle(value)
|
||||
}
|
||||
return strmTreeSourceCollection{sources: out, ignored: ignored, ignoredCount: ignoredCount}
|
||||
collector.finalizeSubtitles()
|
||||
return collector.collection()
|
||||
}
|
||||
|
||||
func newSTRMTreeSourceCollector(opts GenerateSTRMTreeOptions) *strmTreeSourceCollector {
|
||||
return &strmTreeSourceCollector{
|
||||
fallbackProvider: normalizeSTRMTreeProvider(opts.Provider),
|
||||
transferSubtitles: opts.TransferSubtitles,
|
||||
sources: make([]strmTreeSource, 0, len(opts.Paths)),
|
||||
subtitles: make([]strmTreeSource, 0),
|
||||
ignored: make([]string, 0),
|
||||
seen: map[string]struct{}{},
|
||||
seenIgnored: map[string]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) add(value string) {
|
||||
source := normalizeSTRMTreeSourceWithProvider(value, c.fallbackProvider)
|
||||
if source.Provider == "" || source.Path == "" || !strmTreeSourceIsVideo(source.Path) {
|
||||
c.addIgnoredOrSubtitle(value)
|
||||
return
|
||||
}
|
||||
source.Kind = strmTreeSourceKindVideo
|
||||
c.addSource(source)
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) addIgnoredOrSubtitle(value string) {
|
||||
if c.transferSubtitles && c.addSubtitleCandidate(value) {
|
||||
return
|
||||
}
|
||||
c.addIgnored(value)
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) addSource(source strmTreeSource) {
|
||||
if source.Kind == "" {
|
||||
source.Kind = strmTreeSourceKindVideo
|
||||
}
|
||||
key := strings.ToLower(source.Provider) + "\x00" + strings.ToLower(source.Kind) + "\x00" + strings.ToLower(source.Path) + "\x00" + strings.ToLower(source.cloudRefPath())
|
||||
if _, ok := c.seen[key]; ok {
|
||||
return
|
||||
}
|
||||
c.seen[key] = struct{}{}
|
||||
c.sources = append(c.sources, source)
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) addSubtitleCandidate(value string) bool {
|
||||
source := normalizeSTRMTreeSubtitleSourceWithProvider(value, c.fallbackProvider)
|
||||
if source.Provider == "" || source.Path == "" {
|
||||
return false
|
||||
}
|
||||
c.subtitles = append(c.subtitles, source)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) addIgnored(value string) {
|
||||
if ignoredPath, ok := strmTreeIgnoredFileLikeSource(value); ok {
|
||||
key := strings.ToLower(ignoredPath)
|
||||
if _, exists := c.seenIgnored[key]; exists {
|
||||
return
|
||||
}
|
||||
c.seenIgnored[key] = struct{}{}
|
||||
c.ignoredCount++
|
||||
if len(c.ignored) < strmTreeIgnoredItemSampleLimit {
|
||||
c.ignored = append(c.ignored, ignoredPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) finalizeSubtitles() {
|
||||
if !c.transferSubtitles {
|
||||
return
|
||||
}
|
||||
for _, source := range c.subtitles {
|
||||
if strmTreeSubtitleMatchesVideo(source, c.sources) {
|
||||
c.addSource(source)
|
||||
continue
|
||||
}
|
||||
c.addIgnored(source.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *strmTreeSourceCollector) collection() strmTreeSourceCollection {
|
||||
return strmTreeSourceCollection{sources: c.sources, ignored: c.ignored, ignoredCount: c.ignoredCount}
|
||||
}
|
||||
|
||||
func (s strmTreeSource) cloudRefPath() string {
|
||||
|
||||
@@ -121,6 +121,51 @@ func TestGenerateSTRMFromTreeReportsIgnoredFileLikeRows(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSTRMFromTreeTransfersMatchingSubtitleLinks(t *testing.T) {
|
||||
outDir := filepath.Join(t.TempDir(), "strm")
|
||||
svc := NewSTRMService(zap.NewNop(), nil, nil)
|
||||
|
||||
res, err := svc.GenerateFromTree(t.Context(), GenerateSTRMTreeOptions{
|
||||
Provider: "openlist",
|
||||
Paths: []string{
|
||||
"/Movies/A.mkv",
|
||||
"/Movies/A.zh.srt",
|
||||
"/Movies/Orphan.srt",
|
||||
},
|
||||
TreeText: strings.Join([]string{
|
||||
"电视剧",
|
||||
"└── Show.Name.2026",
|
||||
" ├── Show.S01E01.mp4",
|
||||
" ├── Show.S01E01.ass",
|
||||
" └── Show.S01E02.srt",
|
||||
}, "\n"),
|
||||
OutputDir: outDir,
|
||||
BaseURL: "https://media.example.com",
|
||||
TransferSubtitles: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Generated != 4 || res.Ignored != 2 || len(res.Errors) != 0 {
|
||||
t.Fatalf("result = %#v, want two video links, two matching subtitle links, and two orphan subtitles ignored", res)
|
||||
}
|
||||
movieSubtitle := readSTRM(t, filepath.Join(outDir, "Movies", "A.zh.srt.strm"))
|
||||
if !strings.HasPrefix(movieSubtitle, "https://media.example.com/api/cloud/play/openlist?") ||
|
||||
!strings.Contains(movieSubtitle, "ref=%2FMovies%2FA.zh.srt") {
|
||||
t.Fatalf("subtitle link = %q, want cloud play URL for subtitle source", movieSubtitle)
|
||||
}
|
||||
showSubtitle := readSTRM(t, filepath.Join(outDir, "电视剧", "Show.Name.2026", "Show.S01E01.ass.strm"))
|
||||
if !strings.Contains(showSubtitle, "ref=%2F%E7%94%B5%E8%A7%86%E5%89%A7%2FShow.Name.2026%2FShow.S01E01.ass") {
|
||||
t.Fatalf("tree subtitle link = %q, want full tree subtitle ref", showSubtitle)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(outDir, "Movies", "Orphan.srt.strm")); !os.IsNotExist(err) {
|
||||
t.Fatalf("orphan subtitle should not generate a link, stat err=%v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(outDir, "电视剧", "Show.Name.2026", "Show.S01E02.srt.strm")); !os.IsNotExist(err) {
|
||||
t.Fatalf("subtitle without matching episode should not generate a link, stat err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSTRMFromTreeLimitsIgnoredItemSamples(t *testing.T) {
|
||||
outDir := filepath.Join(t.TempDir(), "strm")
|
||||
paths := make([]string, 0, 25)
|
||||
|
||||
@@ -31,6 +31,15 @@ var strmTreeKnownNonVideoExtensions = map[string]struct{}{
|
||||
".webp": {},
|
||||
}
|
||||
|
||||
var strmTreeSubtitleExtensions = map[string]struct{}{
|
||||
".ass": {},
|
||||
".idx": {},
|
||||
".srt": {},
|
||||
".ssa": {},
|
||||
".sub": {},
|
||||
".vtt": {},
|
||||
}
|
||||
|
||||
func normalizeSTRMTreeProvider(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "115", "115pan", "pan115", "cloud115":
|
||||
@@ -109,6 +118,30 @@ func normalizeSTRMTreeSourceWithProvider(raw, fallbackProvider string) strmTreeS
|
||||
return strmTreeSource{Provider: provider, Path: source}
|
||||
}
|
||||
|
||||
func normalizeSTRMTreeSubtitleSourceWithProvider(raw, fallbackProvider string) strmTreeSource {
|
||||
provider := normalizeSTRMTreeProvider(fallbackProvider)
|
||||
value := strings.TrimSpace(strings.Trim(raw, `"'`))
|
||||
if value == "" {
|
||||
return strmTreeSource{}
|
||||
}
|
||||
if info, ok := ParseCloudLibraryMount(value); ok {
|
||||
source, sourceOK := strmTreeSubtitleFileLikeSource(info.DisplayDir)
|
||||
if !sourceOK {
|
||||
return strmTreeSource{}
|
||||
}
|
||||
ref, refOK := strmTreeSubtitleFileLikeSource(info.ScanDir)
|
||||
if !refOK {
|
||||
ref = source
|
||||
}
|
||||
return strmTreeSource{Provider: normalizeSTRMTreeProvider(info.Provider), Path: source, RefPath: ref, Kind: strmTreeSourceKindSubtitle}
|
||||
}
|
||||
source, ok := strmTreeSubtitleFileLikeSource(value)
|
||||
if !ok {
|
||||
return strmTreeSource{}
|
||||
}
|
||||
return strmTreeSource{Provider: provider, Path: source, Kind: strmTreeSourceKindSubtitle}
|
||||
}
|
||||
|
||||
func strmTreeCloudPlaySourceFromURL(parsed *url.URL) (string, string) {
|
||||
if parsed == nil {
|
||||
return "", ""
|
||||
@@ -241,6 +274,18 @@ func strmTreeIgnoredFileLikeSource(raw string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
func strmTreeSubtitleFileLikeSource(raw string) (string, bool) {
|
||||
source, ok := strmTreeIgnoredFileLikeSource(raw)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
ext := strings.ToLower(path.Ext(source))
|
||||
if _, ok := strmTreeSubtitleExtensions[ext]; !ok {
|
||||
return "", false
|
||||
}
|
||||
return source, true
|
||||
}
|
||||
|
||||
func cleanSTRMTreePathDecorations(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
|
||||
@@ -29,6 +29,14 @@ func strmTreeCloudRef(source, sourceRoot string) string {
|
||||
}
|
||||
|
||||
func strmTreeOutputRelativePath(source string) (string, error) {
|
||||
return strmTreeOutputRelativePathWithLinkExtension(source, videoExtensions, ".strm", false)
|
||||
}
|
||||
|
||||
func strmTreeOutputSubtitleLinkRelativePath(source string) (string, error) {
|
||||
return strmTreeOutputRelativePathWithLinkExtension(source, strmTreeSubtitleExtensions, ".strm", true)
|
||||
}
|
||||
|
||||
func strmTreeOutputRelativePathWithLinkExtension(source string, allowedExtensions map[string]struct{}, linkExtension string, appendLinkExtension bool) (string, error) {
|
||||
parts := strings.Split(strings.Trim(strings.ReplaceAll(source, "\\", "/"), "/"), "/")
|
||||
if len(parts) == 0 {
|
||||
return "", errors.New("empty source path")
|
||||
@@ -41,10 +49,16 @@ func strmTreeOutputRelativePath(source string) (string, error) {
|
||||
}
|
||||
if i == len(parts)-1 {
|
||||
ext := strings.ToLower(path.Ext(part))
|
||||
if _, ok := videoExtensions[ext]; !ok {
|
||||
if _, ok := allowedExtensions[ext]; !ok {
|
||||
return "", fmt.Errorf("unsupported media extension %q", ext)
|
||||
}
|
||||
part = strings.TrimSuffix(part, path.Ext(part)) + ".strm"
|
||||
if linkExtension != "" {
|
||||
if appendLinkExtension {
|
||||
part += linkExtension
|
||||
} else {
|
||||
part = strings.TrimSuffix(part, path.Ext(part)) + linkExtension
|
||||
}
|
||||
}
|
||||
}
|
||||
safe := sanitizeFilename(part)
|
||||
if safe == "" {
|
||||
@@ -55,6 +69,37 @@ func strmTreeOutputRelativePath(source string) (string, error) {
|
||||
return filepath.Join(out...), nil
|
||||
}
|
||||
|
||||
func strmTreeSubtitleMatchesVideo(subtitle strmTreeSource, videos []strmTreeSource) bool {
|
||||
subDir, subBase := strmTreeDirAndBase(subtitle.Path)
|
||||
if subBase == "" {
|
||||
return false
|
||||
}
|
||||
for _, video := range videos {
|
||||
if video.Kind != "" && video.Kind != strmTreeSourceKindVideo {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(subtitle.Provider, video.Provider) {
|
||||
continue
|
||||
}
|
||||
videoDir, videoBase := strmTreeDirAndBase(video.Path)
|
||||
if !strings.EqualFold(subDir, videoDir) || videoBase == "" {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(subBase, videoBase) || strings.HasPrefix(strings.ToLower(subBase), strings.ToLower(videoBase)+".") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func strmTreeDirAndBase(source string) (string, string) {
|
||||
source = normalizeSTRMTreeSource(source)
|
||||
dir := path.Dir(source)
|
||||
name := path.Base(source)
|
||||
base := strings.TrimSuffix(name, path.Ext(name))
|
||||
return strings.ToLower(strings.Trim(dir, "/")), strings.ToLower(base)
|
||||
}
|
||||
|
||||
func strmTreeOutputPrefixPath(prefix string) (string, error) {
|
||||
parts := strings.Split(strings.Trim(strings.ReplaceAll(prefix, "\\", "/"), "/"), "/")
|
||||
out := make([]string, 0, len(parts))
|
||||
|
||||
@@ -46,6 +46,7 @@ export type GenerateSTRMTreeInput = {
|
||||
cleanup?: boolean
|
||||
dry_run?: boolean
|
||||
batch_limit?: number
|
||||
transfer_subtitles?: boolean
|
||||
}
|
||||
|
||||
export const strmAPI = {
|
||||
|
||||
@@ -52,7 +52,9 @@ export function StrmTreeGenerateSection({
|
||||
setProvider,
|
||||
setSourceRoot,
|
||||
setTreeText,
|
||||
setTransferSubtitles,
|
||||
sourceRoot,
|
||||
transferSubtitles,
|
||||
treeText,
|
||||
}: StrmTreeGenerateSectionProps) {
|
||||
const batchLimitEnabled = Number.parseInt(batchLimit, 10) > 0
|
||||
@@ -179,6 +181,10 @@ export function StrmTreeGenerateSection({
|
||||
<input type="checkbox" checked={overwrite} onChange={(e) => setOverwrite(e.target.checked)} />
|
||||
覆盖已存在
|
||||
</label>
|
||||
<label className="flex min-h-10 items-center gap-2 rounded-2xl border border-gray-200 bg-white/70 px-3 py-2 text-sm text-ink-50 md:col-span-4">
|
||||
<input type="checkbox" checked={transferSubtitles} onChange={(e) => setTransferSubtitles(e.target.checked)} />
|
||||
转移同名字幕链接
|
||||
</label>
|
||||
<label className="flex min-h-10 items-center gap-2 rounded-2xl border border-gray-200 bg-white/70 px-3 py-2 text-sm text-ink-50 md:col-span-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -17,6 +17,7 @@ export function useStrmTreeGenerateForm() {
|
||||
const [pathsText, setPathsText] = useState('')
|
||||
const [overwrite, setOverwrite] = useState(false)
|
||||
const [cleanup, setCleanup] = useState(false)
|
||||
const [transferSubtitles, setTransferSubtitles] = useState(true)
|
||||
const [runningMode, setRunningMode] = useState<'generate' | 'preview' | null>(null)
|
||||
const [result, setResult] = useState<GenerateSTRMResult | null>(null)
|
||||
|
||||
@@ -69,6 +70,7 @@ export function useStrmTreeGenerateForm() {
|
||||
cleanup: parsedBatchLimit > 0 ? false : cleanup,
|
||||
dry_run: dryRun,
|
||||
batch_limit: parsedBatchLimit || undefined,
|
||||
transfer_subtitles: transferSubtitles,
|
||||
})
|
||||
setResult(next)
|
||||
if (dryRun) {
|
||||
@@ -117,7 +119,9 @@ export function useStrmTreeGenerateForm() {
|
||||
setProvider,
|
||||
setSourceRoot,
|
||||
setTreeText,
|
||||
setTransferSubtitles,
|
||||
sourceRoot,
|
||||
transferSubtitles,
|
||||
treeText,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user