mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
feat: report ignored STRM tree sidecars
This commit is contained in:
@@ -24,6 +24,8 @@ type GenerateSTRMOptions struct {
|
|||||||
SkipSettingsSave bool `json:"-"`
|
SkipSettingsSave bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const strmTreeIgnoredItemSampleLimit = 20
|
||||||
|
|
||||||
type GenerateSTRMResult struct {
|
type GenerateSTRMResult struct {
|
||||||
LibraryID string `json:"library_id"`
|
LibraryID string `json:"library_id"`
|
||||||
OutputDir string `json:"output_dir"`
|
OutputDir string `json:"output_dir"`
|
||||||
@@ -34,8 +36,10 @@ type GenerateSTRMResult struct {
|
|||||||
Total int `json:"total,omitempty"`
|
Total int `json:"total,omitempty"`
|
||||||
Remaining int `json:"remaining,omitempty"`
|
Remaining int `json:"remaining,omitempty"`
|
||||||
BatchLimited bool `json:"batch_limited,omitempty"`
|
BatchLimited bool `json:"batch_limited,omitempty"`
|
||||||
|
Ignored int `json:"ignored,omitempty"`
|
||||||
Previewed int `json:"previewed,omitempty"`
|
Previewed int `json:"previewed,omitempty"`
|
||||||
Errors []string `json:"errors,omitempty"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
|
IgnoredItems []string `json:"ignored_items,omitempty"`
|
||||||
Items []GenerateSTRMItem `json:"items,omitempty"`
|
Items []GenerateSTRMItem `json:"items,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +296,13 @@ func (r *GenerateSTRMResult) merge(part *GenerateSTRMResult) {
|
|||||||
r.Skipped += part.Skipped
|
r.Skipped += part.Skipped
|
||||||
r.Cleaned += part.Cleaned
|
r.Cleaned += part.Cleaned
|
||||||
r.Previewed += part.Previewed
|
r.Previewed += part.Previewed
|
||||||
|
r.Ignored += part.Ignored
|
||||||
r.Errors = append(r.Errors, part.Errors...)
|
r.Errors = append(r.Errors, part.Errors...)
|
||||||
|
for _, item := range part.IgnoredItems {
|
||||||
|
if len(r.IgnoredItems) >= strmTreeIgnoredItemSampleLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.IgnoredItems = append(r.IgnoredItems, item)
|
||||||
|
}
|
||||||
r.Items = append(r.Items, part.Items...)
|
r.Items = append(r.Items, part.Items...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ type strmTreeSource struct {
|
|||||||
RefPath string
|
RefPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type strmTreeSourceCollection struct {
|
||||||
|
sources []strmTreeSource
|
||||||
|
ignored []string
|
||||||
|
ignoredCount int
|
||||||
|
}
|
||||||
|
|
||||||
func (s *STRMService) GenerateFromTree(ctx context.Context, opts GenerateSTRMTreeOptions) (*GenerateSTRMResult, error) {
|
func (s *STRMService) GenerateFromTree(ctx context.Context, opts GenerateSTRMTreeOptions) (*GenerateSTRMResult, error) {
|
||||||
provider := normalizeSTRMTreeProvider(opts.Provider)
|
provider := normalizeSTRMTreeProvider(opts.Provider)
|
||||||
if provider == "" {
|
if provider == "" {
|
||||||
@@ -44,8 +50,11 @@ func (s *STRMService) GenerateFromTree(ctx context.Context, opts GenerateSTRMTre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result := &GenerateSTRMResult{LibraryID: provider, OutputDir: outputDir}
|
result := &GenerateSTRMResult{LibraryID: provider, OutputDir: outputDir}
|
||||||
sources := collectSTRMTreeSources(opts)
|
collection := collectSTRMTreeSources(opts)
|
||||||
|
sources := collection.sources
|
||||||
result.Total = len(sources)
|
result.Total = len(sources)
|
||||||
|
result.Ignored = collection.ignoredCount
|
||||||
|
result.IgnoredItems = collection.ignored
|
||||||
expectedFiles := make(map[string]struct{})
|
expectedFiles := make(map[string]struct{})
|
||||||
for i, source := range sources {
|
for i, source := range sources {
|
||||||
select {
|
select {
|
||||||
@@ -131,13 +140,30 @@ func generateTreeSTRMItem(outputDir string, source strmTreeSource, opts Generate
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectSTRMTreeSources(opts GenerateSTRMTreeOptions) []strmTreeSource {
|
func collectSTRMTreeSources(opts GenerateSTRMTreeOptions) strmTreeSourceCollection {
|
||||||
fallbackProvider := normalizeSTRMTreeProvider(opts.Provider)
|
fallbackProvider := normalizeSTRMTreeProvider(opts.Provider)
|
||||||
out := make([]strmTreeSource, 0, len(opts.Paths))
|
out := make([]strmTreeSource, 0, len(opts.Paths))
|
||||||
seen := map[string]struct{}{}
|
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) {
|
add := func(value string) {
|
||||||
source := normalizeSTRMTreeSourceWithProvider(value, fallbackProvider)
|
source := normalizeSTRMTreeSourceWithProvider(value, fallbackProvider)
|
||||||
if source.Provider == "" || source.Path == "" || !strmTreeSourceIsVideo(source.Path) {
|
if source.Provider == "" || source.Path == "" || !strmTreeSourceIsVideo(source.Path) {
|
||||||
|
addIgnored(value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
key := strings.ToLower(source.Provider) + "\x00" + strings.ToLower(source.Path) + "\x00" + strings.ToLower(source.cloudRefPath())
|
key := strings.ToLower(source.Provider) + "\x00" + strings.ToLower(source.Path) + "\x00" + strings.ToLower(source.cloudRefPath())
|
||||||
@@ -150,10 +176,14 @@ func collectSTRMTreeSources(opts GenerateSTRMTreeOptions) []strmTreeSource {
|
|||||||
for _, value := range opts.Paths {
|
for _, value := range opts.Paths {
|
||||||
add(value)
|
add(value)
|
||||||
}
|
}
|
||||||
for _, value := range parseSTRMTreeText(opts.TreeText) {
|
treeSources, treeIgnored := parseSTRMTreeTextWithIgnored(opts.TreeText)
|
||||||
|
for _, value := range treeSources {
|
||||||
add(value)
|
add(value)
|
||||||
}
|
}
|
||||||
return out
|
for _, value := range treeIgnored {
|
||||||
|
addIgnored(value)
|
||||||
|
}
|
||||||
|
return strmTreeSourceCollection{sources: out, ignored: ignored, ignoredCount: ignoredCount}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s strmTreeSource) cloudRefPath() string {
|
func (s strmTreeSource) cloudRefPath() string {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -82,6 +83,65 @@ func TestGenerateSTRMFromTreeSupportsCommonVideoExtensions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateSTRMFromTreeReportsIgnoredFileLikeRows(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/poster.jpg",
|
||||||
|
"/Movies/fanart.jpg (cover image)",
|
||||||
|
"/Movies/Disc.Image.2026.iso",
|
||||||
|
"/Movies/Existing.strm",
|
||||||
|
},
|
||||||
|
TreeText: strings.Join([]string{
|
||||||
|
"电视剧",
|
||||||
|
"└── Show.Name.2026",
|
||||||
|
" ├── Show.S01E01.mp4",
|
||||||
|
" └── Show.S01E01.nfo",
|
||||||
|
" └── Show.S01E01.srt 72 KB",
|
||||||
|
}, "\n"),
|
||||||
|
OutputDir: outDir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Generated != 2 || res.Ignored != 6 || len(res.IgnoredItems) != 6 {
|
||||||
|
t.Fatalf("result = %#v, want two generated videos and six ignored sidecars", res)
|
||||||
|
}
|
||||||
|
for _, item := range res.IgnoredItems {
|
||||||
|
if item == "Show.Name.2026" || item == "电视剧/Show.Name.2026" {
|
||||||
|
t.Fatalf("directory-like dotted title should not be reported as ignored: %#v", res.IgnoredItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.Contains(strings.Join(res.IgnoredItems, "\n"), "Show.S01E01.nfo") {
|
||||||
|
t.Fatalf("directory-like dotted title should not be reported as ignored: %#v", res.IgnoredItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSTRMFromTreeLimitsIgnoredItemSamples(t *testing.T) {
|
||||||
|
outDir := filepath.Join(t.TempDir(), "strm")
|
||||||
|
paths := make([]string, 0, 25)
|
||||||
|
for i := 0; i < 25; i++ {
|
||||||
|
paths = append(paths, filepath.ToSlash(filepath.Join("/Movies", "sidecar-"+strconv.Itoa(i)+".nfo")))
|
||||||
|
}
|
||||||
|
svc := NewSTRMService(zap.NewNop(), nil, nil)
|
||||||
|
|
||||||
|
res, err := svc.GenerateFromTree(t.Context(), GenerateSTRMTreeOptions{
|
||||||
|
Provider: "openlist",
|
||||||
|
Paths: paths,
|
||||||
|
OutputDir: outDir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Ignored != 25 || len(res.IgnoredItems) != strmTreeIgnoredItemSampleLimit {
|
||||||
|
t.Fatalf("ignored = %d samples = %d, want 25/%d", res.Ignored, len(res.IgnoredItems), strmTreeIgnoredItemSampleLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateSTRMFromTreeDryRunDoesNotWriteOrCleanup(t *testing.T) {
|
func TestGenerateSTRMFromTreeDryRunDoesNotWriteOrCleanup(t *testing.T) {
|
||||||
outDir := filepath.Join(t.TempDir(), "strm")
|
outDir := filepath.Join(t.TempDir(), "strm")
|
||||||
stale := filepath.Join(outDir, "Movies", "Old.Movie.strm")
|
stale := filepath.Join(outDir, "Movies", "Old.Movie.strm")
|
||||||
|
|||||||
@@ -11,6 +11,26 @@ var strmTreeListPrefixRE = regexp.MustCompile(`^\s*(?:[-*+•·]\s+|\d{1,4}[\.)
|
|||||||
|
|
||||||
var strmTreeURLPathQueryKeys = []string{"path", "ref", "file", "filepath", "filename", "name"}
|
var strmTreeURLPathQueryKeys = []string{"path", "ref", "file", "filepath", "filename", "name"}
|
||||||
|
|
||||||
|
var strmTreeKnownNonVideoExtensions = map[string]struct{}{
|
||||||
|
".ass": {},
|
||||||
|
".bmp": {},
|
||||||
|
".gif": {},
|
||||||
|
".idx": {},
|
||||||
|
".iso": {},
|
||||||
|
".jpeg": {},
|
||||||
|
".jpg": {},
|
||||||
|
".nfo": {},
|
||||||
|
".png": {},
|
||||||
|
".srt": {},
|
||||||
|
".ssa": {},
|
||||||
|
".strm": {},
|
||||||
|
".sub": {},
|
||||||
|
".txt": {},
|
||||||
|
".url": {},
|
||||||
|
".vtt": {},
|
||||||
|
".webp": {},
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeSTRMTreeProvider(raw string) string {
|
func normalizeSTRMTreeProvider(raw string) string {
|
||||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||||
case "115", "115pan", "pan115", "cloud115":
|
case "115", "115pan", "pan115", "cloud115":
|
||||||
@@ -199,6 +219,28 @@ func strmTreeSourceIsVideo(source string) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strmTreeIgnoredFileLikeSource(raw string) (string, bool) {
|
||||||
|
source := normalizeSTRMTreeSource(raw)
|
||||||
|
if source == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
source = trimSTRMTreeKnownFileMetadata(cleanSTRMTreeItemName(source))
|
||||||
|
ext := strings.ToLower(path.Ext(source))
|
||||||
|
if ext == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if ext == ".strm" {
|
||||||
|
return source, true
|
||||||
|
}
|
||||||
|
if _, ok := videoExtensions[ext]; ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if _, ok := strmTreeKnownNonVideoExtensions[ext]; ok {
|
||||||
|
return source, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func cleanSTRMTreePathDecorations(value string) string {
|
func cleanSTRMTreePathDecorations(value string) string {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return ""
|
return ""
|
||||||
@@ -243,24 +285,34 @@ func cleanSTRMTreeItemName(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trimSTRMTreeMediaMetadata(value string) string {
|
func trimSTRMTreeMediaMetadata(value string) string {
|
||||||
|
return trimSTRMTreeMetadataByExtensions(value, videoExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimSTRMTreeKnownFileMetadata(value string) string {
|
||||||
|
return trimSTRMTreeMetadataByExtensions(value, videoExtensions, strmTreeKnownNonVideoExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimSTRMTreeMetadataByExtensions(value string, extensionSets ...map[string]struct{}) string {
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
lower := strings.ToLower(value)
|
lower := strings.ToLower(value)
|
||||||
bestEnd := -1
|
bestEnd := -1
|
||||||
for ext := range videoExtensions {
|
for _, extensions := range extensionSets {
|
||||||
from := 0
|
for ext := range extensions {
|
||||||
for {
|
from := 0
|
||||||
idx := strings.Index(lower[from:], ext)
|
for {
|
||||||
if idx < 0 {
|
idx := strings.Index(lower[from:], ext)
|
||||||
break
|
if idx < 0 {
|
||||||
}
|
break
|
||||||
idx += from
|
}
|
||||||
end := idx + len(ext)
|
idx += from
|
||||||
if strmTreeMediaExtBoundary(value, end) && end > bestEnd {
|
end := idx + len(ext)
|
||||||
bestEnd = end
|
if strmTreeMediaExtBoundary(value, end) && end > bestEnd {
|
||||||
}
|
bestEnd = end
|
||||||
from = idx + 1
|
}
|
||||||
if from >= len(lower) {
|
from = idx + 1
|
||||||
break
|
if from >= len(lower) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func parseSTRMTreeText(raw string) []string {
|
func parseSTRMTreeText(raw string) []string {
|
||||||
|
out, _ := parseSTRMTreeTextWithIgnored(raw)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSTRMTreeTextWithIgnored(raw string) ([]string, []string) {
|
||||||
var out []string
|
var out []string
|
||||||
|
var ignored []string
|
||||||
stack := make([]string, 0, 8)
|
stack := make([]string, 0, 8)
|
||||||
plainIndents := make([]int, 0, 8)
|
plainIndents := make([]int, 0, 8)
|
||||||
rootOffset := 0
|
rootOffset := 0
|
||||||
@@ -23,6 +29,10 @@ func parseSTRMTreeText(raw string) []string {
|
|||||||
out = append(out, strmTreeJoinedSource(stack, item))
|
out = append(out, strmTreeJoinedSource(stack, item))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strmTreeItemIsIgnoredFileSource(item) {
|
||||||
|
ignored = append(ignored, strmTreeJoinedSource(stack, item))
|
||||||
|
continue
|
||||||
|
}
|
||||||
stack = append(stack, item)
|
stack = append(stack, item)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -30,7 +40,11 @@ func parseSTRMTreeText(raw string) []string {
|
|||||||
plainIndents = plainIndents[:0]
|
plainIndents = plainIndents[:0]
|
||||||
level += rootOffset
|
level += rootOffset
|
||||||
stack = stack[:min(level, len(stack))]
|
stack = stack[:min(level, len(stack))]
|
||||||
out = append(out, path.Join(append(append([]string{}, stack...), item)...))
|
if strmTreeItemIsVideoSource(item) {
|
||||||
|
out = append(out, path.Join(append(append([]string{}, stack...), item)...))
|
||||||
|
} else if strmTreeItemIsIgnoredFileSource(item) {
|
||||||
|
ignored = append(ignored, path.Join(append(append([]string{}, stack...), item)...))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item := cleanSTRMTreeItemName(line)
|
item := cleanSTRMTreeItemName(line)
|
||||||
@@ -44,20 +58,32 @@ func parseSTRMTreeText(raw string) []string {
|
|||||||
out = append(out, strmTreeJoinedSource(stack, item))
|
out = append(out, strmTreeJoinedSource(stack, item))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strmTreeItemIsIgnoredFileSource(item) {
|
||||||
|
ignored = append(ignored, strmTreeJoinedSource(stack, item))
|
||||||
|
continue
|
||||||
|
}
|
||||||
stack = append(stack, item)
|
stack = append(stack, item)
|
||||||
plainIndents = append(plainIndents, indent)
|
plainIndents = append(plainIndents, indent)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strmTreeItemIsVideoSource(item) || strings.ContainsAny(item, `/\`) {
|
if strmTreeItemIsVideoSource(item) || strings.ContainsAny(item, `/\`) {
|
||||||
plainIndents = plainIndents[:0]
|
plainIndents = plainIndents[:0]
|
||||||
out = append(out, item)
|
if strmTreeItemIsVideoSource(item) {
|
||||||
|
out = append(out, item)
|
||||||
|
} else if strmTreeItemIsIgnoredFileSource(item) {
|
||||||
|
ignored = append(ignored, item)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strmTreeItemIsIgnoredFileSource(item) {
|
||||||
|
ignored = append(ignored, item)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stack = []string{item}
|
stack = []string{item}
|
||||||
plainIndents = plainIndents[:0]
|
plainIndents = plainIndents[:0]
|
||||||
rootOffset = 1
|
rootOffset = 1
|
||||||
}
|
}
|
||||||
return out
|
return out, ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSTRMTreeLine(line string) (string, int, bool) {
|
func parseSTRMTreeLine(line string) (string, int, bool) {
|
||||||
@@ -97,7 +123,7 @@ func parseSTRMTreeContinuationLine(line string, plainTreeActive bool) (string, i
|
|||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
item := cleanSTRMTreeItemName(line[prefixLen:])
|
item := cleanSTRMTreeItemName(line[prefixLen:])
|
||||||
if item == "" || !strmTreeItemIsVideoSource(item) {
|
if item == "" || (!strmTreeItemIsVideoSource(item) && !strmTreeItemIsIgnoredFileSource(item)) {
|
||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
return item, strmTreeIndentLevel(line[:prefixLen]), true
|
return item, strmTreeIndentLevel(line[:prefixLen]), true
|
||||||
@@ -152,6 +178,11 @@ func strmTreeItemIsVideoSource(item string) bool {
|
|||||||
return source.Path != "" && strmTreeSourceIsVideo(source.Path)
|
return source.Path != "" && strmTreeSourceIsVideo(source.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strmTreeItemIsIgnoredFileSource(item string) bool {
|
||||||
|
_, ok := strmTreeIgnoredFileLikeSource(item)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func strmTreeJoinedSource(stack []string, item string) string {
|
func strmTreeJoinedSource(stack []string, item string) string {
|
||||||
if strmTreeItemIsAbsoluteSource(item) {
|
if strmTreeItemIsAbsoluteSource(item) {
|
||||||
return item
|
return item
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ export type GenerateSTRMResult = {
|
|||||||
total?: number
|
total?: number
|
||||||
remaining?: number
|
remaining?: number
|
||||||
batch_limited?: boolean
|
batch_limited?: boolean
|
||||||
|
ignored?: number
|
||||||
previewed?: number
|
previewed?: number
|
||||||
errors?: string[]
|
errors?: string[]
|
||||||
|
ignored_items?: string[]
|
||||||
items?: Array<{
|
items?: Array<{
|
||||||
media_id: string
|
media_id: string
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@@ -273,11 +273,17 @@ export function StrmGenerateResultPanel({ result }: { result: GenerateSTRMResult
|
|||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
{result.previewed ? `预检 ${result.previewed} · ` : ''}
|
{result.previewed ? `预检 ${result.previewed} · ` : ''}
|
||||||
{result.total ? `共 ${result.total} · ` : ''}
|
{result.total ? `共 ${result.total} · ` : ''}
|
||||||
新增 {result.generated} · 更新 {result.updated} · 跳过 {result.skipped} · 清理 {result.cleaned || 0}
|
新增 {result.generated} · 更新 {result.updated} · 跳过 {result.skipped}
|
||||||
|
{result.ignored ? ` · 忽略 ${result.ignored}` : ''} · 清理 {result.cleaned || 0}
|
||||||
</div>
|
</div>
|
||||||
{result.batch_limited && (
|
{result.batch_limited && (
|
||||||
<div className="mt-1 text-amber-600">本批已达到上限,剩余 {result.remaining || 0} 个可继续运行。</div>
|
<div className="mt-1 text-amber-600">本批已达到上限,剩余 {result.remaining || 0} 个可继续运行。</div>
|
||||||
)}
|
)}
|
||||||
|
{result.ignored_items && result.ignored_items.length > 0 && (
|
||||||
|
<div className="mt-1 text-amber-600">
|
||||||
|
已忽略 {result.ignored || result.ignored_items.length} 个非视频/sidecar:{result.ignored_items.slice(0, 3).join(';')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{result.errors && result.errors.length > 0 && (
|
{result.errors && result.errors.length > 0 && (
|
||||||
<div className="mt-2 text-red-500">
|
<div className="mt-2 text-red-500">
|
||||||
失败 {result.errors.length} 条:{result.errors.slice(0, 3).join(';')}
|
失败 {result.errors.length} 条:{result.errors.slice(0, 3).join(';')}
|
||||||
|
|||||||
Reference in New Issue
Block a user