fixes
This commit is contained in:
parent
ac144a36a8
commit
cc2befe093
@ -48,6 +48,20 @@ func normUser(s string) string {
|
|||||||
return strings.ToLower(strings.TrimSpace(s))
|
return strings.ToLower(strings.TrimSpace(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chaturbateShowFromSnapshot(showByUser map[string]string, user string) string {
|
||||||
|
user = normUser(user)
|
||||||
|
if user == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
show, exists := showByUser[user]
|
||||||
|
if !exists {
|
||||||
|
return "offline"
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizePendingShowServer(show)
|
||||||
|
}
|
||||||
|
|
||||||
func watchedChaturbateUserKey(m WatchedModelLite) string {
|
func watchedChaturbateUserKey(m WatchedModelLite) string {
|
||||||
key := normUser(m.ModelKey)
|
key := normUser(m.ModelKey)
|
||||||
if key != "" {
|
if key != "" {
|
||||||
@ -275,17 +289,12 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
// normaler Retry für API-public
|
// normaler Retry für API-public
|
||||||
const retryCooldown = 25 * time.Second
|
const retryCooldown = 25 * time.Second
|
||||||
|
|
||||||
// aggressiver vermeiden für "nicht in API, aber watched" Blind-Try
|
|
||||||
const blindRetryCooldown = 2 * time.Minute
|
|
||||||
|
|
||||||
// wie lange wir warten, ob eine echte Datei entsteht
|
// wie lange wir warten, ob eine echte Datei entsteht
|
||||||
const outputProbeMax = 20 * time.Second
|
const outputProbeMax = 20 * time.Second
|
||||||
|
|
||||||
queue := make([]autoStartItem, 0, 64)
|
queue := make([]autoStartItem, 0, 64)
|
||||||
queued := map[string]bool{}
|
queued := map[string]bool{}
|
||||||
lastTry := map[string]time.Time{}
|
lastTry := map[string]time.Time{}
|
||||||
lastBlindTry := map[string]time.Time{}
|
|
||||||
probeCursor := 0
|
|
||||||
|
|
||||||
scanTicker := time.NewTicker(scanInterval)
|
scanTicker := time.NewTicker(scanInterval)
|
||||||
startTicker := time.NewTicker(startInterval)
|
startTicker := time.NewTicker(startInterval)
|
||||||
@ -417,7 +426,6 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
|
|
||||||
// laufende Jobs sammeln
|
// laufende Jobs sammeln
|
||||||
runningByUser := map[string]*RecordJob{}
|
runningByUser := map[string]*RecordJob{}
|
||||||
hiddenProbeUser := ""
|
|
||||||
|
|
||||||
jobsMu.RLock()
|
jobsMu.RLock()
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
@ -437,10 +445,6 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runningByUser[u] = j
|
runningByUser[u] = j
|
||||||
|
|
||||||
if j.Hidden && hiddenProbeUser == "" {
|
|
||||||
hiddenProbeUser = u
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
jobsMu.RUnlock()
|
jobsMu.RUnlock()
|
||||||
|
|
||||||
@ -493,6 +497,11 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
nextQueued := map[string]bool{}
|
nextQueued := map[string]bool{}
|
||||||
|
|
||||||
for _, it := range queue {
|
for _, it := range queue {
|
||||||
|
// alte Blind-Probe-Einträge aus früheren Versionen verwerfen
|
||||||
|
if it.blind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if runningByUser[it.userKey] != nil {
|
if runningByUser[it.userKey] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -534,22 +543,9 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[it.userKey])
|
show := chaturbateShowFromSnapshot(showByUser, it.userKey)
|
||||||
|
if show != "public" {
|
||||||
if it.blind {
|
continue
|
||||||
// Offline-/Unknown-Blind-Probes nur für manuell hinzugefügte Models.
|
|
||||||
// Watched/Resume dürfen bei offline nicht in "Wartend" bleiben.
|
|
||||||
if it.source != "manual" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if show != "offline" && show != "unknown" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if show != "public" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextQueue = append(nextQueue, it)
|
nextQueue = append(nextQueue, it)
|
||||||
@ -559,16 +555,6 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
queue = nextQueue
|
queue = nextQueue
|
||||||
queued = nextQueued
|
queued = nextQueued
|
||||||
|
|
||||||
blindQueued := false
|
|
||||||
|
|
||||||
for _, it := range queue {
|
|
||||||
if it.blind {
|
|
||||||
blindQueued = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offlineCandidates := make([]autoStartItem, 0, len(watchedOrder))
|
|
||||||
nextWatchedPending := make([]PendingAutoStartItem, 0, len(watchedOrder))
|
nextWatchedPending := make([]PendingAutoStartItem, 0, len(watchedOrder))
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
@ -585,7 +571,7 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[user])
|
show := chaturbateShowFromSnapshot(showByUser, user)
|
||||||
if show != "private" && show != "hidden" && show != "away" {
|
if show != "private" && show != "hidden" && show != "away" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -633,7 +619,7 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
u = fmt.Sprintf("https://chaturbate.com/%s/", user)
|
u = fmt.Sprintf("https://chaturbate.com/%s/", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[user])
|
show := chaturbateShowFromSnapshot(showByUser, user)
|
||||||
img := strings.TrimSpace(imageByUser[user])
|
img := strings.TrimSpace(imageByUser[user])
|
||||||
|
|
||||||
switch show {
|
switch show {
|
||||||
@ -671,6 +657,7 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
pendingAutoStartMu.Unlock()
|
pendingAutoStartMu.Unlock()
|
||||||
|
|
||||||
case "offline":
|
case "offline":
|
||||||
|
// Wenn ein Resume-Model wirklich offline ist, nicht mehr in Pending anzeigen.
|
||||||
pendingAutoStartMu.Lock()
|
pendingAutoStartMu.Lock()
|
||||||
_ = removeResumePendingAutoStartItemForProvider("chaturbate", user)
|
_ = removeResumePendingAutoStartItemForProvider("chaturbate", user)
|
||||||
pendingAutoStartMu.Unlock()
|
pendingAutoStartMu.Unlock()
|
||||||
@ -701,7 +688,7 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[user])
|
show := chaturbateShowFromSnapshot(showByUser, user)
|
||||||
img := strings.TrimSpace(imageByUser[user])
|
img := strings.TrimSpace(imageByUser[user])
|
||||||
|
|
||||||
switch show {
|
switch show {
|
||||||
@ -769,7 +756,7 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[user])
|
show := chaturbateShowFromSnapshot(showByUser, user)
|
||||||
|
|
||||||
switch show {
|
switch show {
|
||||||
case "public":
|
case "public":
|
||||||
@ -796,57 +783,19 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
queued[user] = true
|
queued[user] = true
|
||||||
|
|
||||||
case "private", "hidden", "away":
|
case "private", "hidden", "away":
|
||||||
// manual pending bleibt einfach stehen und wartet auf public
|
// Manual Pending bleibt wartend, solange das Model nicht offline ist.
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "offline":
|
||||||
|
// Offline gehört nicht in Pending Autostart.
|
||||||
|
pendingAutoStartMu.Lock()
|
||||||
|
_ = removeManualPendingAutoStartItemForProvider("chaturbate", user)
|
||||||
|
pendingAutoStartMu.Unlock()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if autostartPaused {
|
// unknown nicht als offline/wartend speichern.
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
if runningByUser[user] != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if queued[user] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if t, ok := lastBlindTry[user]; ok && now.Sub(t) < blindRetryCooldown {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
offlineCandidates = append(offlineCandidates, autoStartItem{
|
|
||||||
userKey: user,
|
|
||||||
url: u,
|
|
||||||
blind: true,
|
|
||||||
source: "manual",
|
|
||||||
ownerUserKey: itm.OwnerUserKey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nur EIN Offline-Kandidat gleichzeitig in die Queue.
|
|
||||||
// Bei pausiertem Autostart werden oben keine normalen Offline-Kandidaten erzeugt.
|
|
||||||
if !blindQueued && !hasHiddenProbeRunningForProvider("chaturbate") && len(offlineCandidates) > 0 {
|
|
||||||
n := len(offlineCandidates)
|
|
||||||
start := 0
|
|
||||||
if probeCursor > 0 {
|
|
||||||
start = probeCursor % n
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
idx := (start + i) % n
|
|
||||||
it := offlineCandidates[idx]
|
|
||||||
|
|
||||||
if queued[it.userKey] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if runningByUser[it.userKey] != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
queue = append(queue, it)
|
|
||||||
queued[it.userKey] = true
|
|
||||||
probeCursor = (idx + 1) % n
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,13 +851,19 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
queue = append(queue[:startIdx], queue[startIdx+1:]...)
|
queue = append(queue[:startIdx], queue[startIdx+1:]...)
|
||||||
delete(queued, it.userKey)
|
delete(queued, it.userKey)
|
||||||
|
|
||||||
show := normalizePendingShowServer(showByUser[it.userKey])
|
// Blind-Probes sind deaktiviert. Alte Queue-Einträge aus früheren Versionen
|
||||||
|
// werden hier nur noch verworfen.
|
||||||
|
if it.blind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
show := chaturbateShowFromSnapshot(showByUser, it.userKey)
|
||||||
isPublic := show == "public"
|
isPublic := show == "public"
|
||||||
|
|
||||||
// Normale queued Starts dürfen NICHT als Blind-Probe gestartet werden.
|
// Normale queued Starts dürfen NICHT als Blind-Probe gestartet werden.
|
||||||
// Wenn ein resume/watched/manual Eintrag inzwischen nicht mehr public ist,
|
// Wenn ein resume/watched/manual Eintrag inzwischen nicht mehr public ist,
|
||||||
// bleibt er wartend bzw. wird im nächsten Scan korrekt neu bewertet.
|
// bleibt er wartend bzw. wird im nächsten Scan korrekt neu bewertet.
|
||||||
if !it.blind && !isPublic {
|
if !isPublic {
|
||||||
if it.source == "resume" {
|
if it.source == "resume" {
|
||||||
pendingAutoStartMu.Lock()
|
pendingAutoStartMu.Lock()
|
||||||
_ = saveResumePendingAutoStartItemForProvider("chaturbate", PendingAutoStartItem{
|
_ = saveResumePendingAutoStartItemForProvider("chaturbate", PendingAutoStartItem{
|
||||||
@ -924,131 +879,64 @@ func startChaturbateAutoStartWorker(store *ModelStore) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nicht-public nur einzeln nacheinander prüfen.
|
|
||||||
if it.blind && hasHiddenProbeRunningForProvider("chaturbate") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isJobRunningForURL(it.url) {
|
if isJobRunningForURL(it.url) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !it.blind {
|
lastTry[it.userKey] = time.Now()
|
||||||
lastTry[it.userKey] = time.Now()
|
|
||||||
|
|
||||||
job, err := startRecordingInternal(RecordRequest{
|
|
||||||
URL: it.url,
|
|
||||||
Cookie: lastCookieHdr,
|
|
||||||
IgnoreConcurrentLimit: it.force,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if verboseLogs() {
|
|
||||||
logChaturbateAutoStartError("❌ [autostart] start failed:", it.url, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
job, err := startRecordingInternal(RecordRequest{
|
||||||
|
URL: it.url,
|
||||||
|
Cookie: lastCookieHdr,
|
||||||
|
IgnoreConcurrentLimit: it.force,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
if verboseLogs() {
|
if verboseLogs() {
|
||||||
if it.source == "resume" {
|
logChaturbateAutoStartError("❌ [autostart] start failed:", it.url, err)
|
||||||
appLogln("▶️ [autostart] resumed:", it.url)
|
|
||||||
} else {
|
|
||||||
appLogln("▶️ [autostart] started:", it.url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if job != nil {
|
if verboseLogs() {
|
||||||
if it.source == "resume" {
|
if it.source == "resume" {
|
||||||
|
appLogln("▶️ [autostart] resumed:", it.url)
|
||||||
|
} else {
|
||||||
|
appLogln("▶️ [autostart] started:", it.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if job != nil {
|
||||||
|
if it.source == "resume" {
|
||||||
|
pendingAutoStartMu.Lock()
|
||||||
|
_ = removeResumePendingAutoStartItemForProvider("chaturbate", it.userKey)
|
||||||
|
pendingAutoStartMu.Unlock()
|
||||||
|
|
||||||
|
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, nil, func() {
|
||||||
pendingAutoStartMu.Lock()
|
pendingAutoStartMu.Lock()
|
||||||
_ = removeResumePendingAutoStartItemForProvider("chaturbate", it.userKey)
|
_ = saveResumePendingAutoStartItemForProvider("chaturbate", PendingAutoStartItem{
|
||||||
pendingAutoStartMu.Unlock()
|
ModelKey: it.userKey,
|
||||||
|
URL: it.url,
|
||||||
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, nil, func() {
|
Mode: "wait_public",
|
||||||
pendingAutoStartMu.Lock()
|
CurrentShow: "unknown",
|
||||||
_ = saveResumePendingAutoStartItemForProvider("chaturbate", PendingAutoStartItem{
|
Source: "resume",
|
||||||
ModelKey: it.userKey,
|
|
||||||
URL: it.url,
|
|
||||||
Mode: "wait_public",
|
|
||||||
CurrentShow: "unknown",
|
|
||||||
Source: "resume",
|
|
||||||
})
|
|
||||||
pendingAutoStartMu.Unlock()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.source == "manual" {
|
|
||||||
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, func() {
|
|
||||||
pendingAutoStartMu.Lock()
|
|
||||||
_ = removeManualPendingAutoStartItemForProvider("chaturbate", it.userKey)
|
|
||||||
pendingAutoStartMu.Unlock()
|
|
||||||
}, nil)
|
|
||||||
} else {
|
|
||||||
pendingAutoStartMu.Lock()
|
|
||||||
_ = removeWatchedPendingAutoStartItemForProvider("chaturbate", it.userKey)
|
|
||||||
pendingAutoStartMu.Unlock()
|
pendingAutoStartMu.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, nil, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if paused && !it.force {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lastBlindTry[it.userKey] = time.Now()
|
|
||||||
|
|
||||||
job, err := startRecordingInternal(RecordRequest{
|
|
||||||
URL: it.url,
|
|
||||||
Cookie: lastCookieHdr,
|
|
||||||
Hidden: true,
|
|
||||||
IgnoreConcurrentLimit: it.force,
|
|
||||||
})
|
|
||||||
if err != nil || job == nil {
|
|
||||||
if verboseLogs() {
|
|
||||||
if err != nil {
|
|
||||||
logChaturbateAutoStartError("❌ [autostart] blind start failed:", it.url, err)
|
|
||||||
} else {
|
|
||||||
appLogln("❌ [autostart] blind start failed:", it.url, "job is nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if verboseLogs() {
|
|
||||||
appLogln("▶️ [autostart] blind try started:", it.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.source == "manual" {
|
if it.source == "manual" {
|
||||||
go chaturbateAbortIfNoOutput(
|
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, func() {
|
||||||
job.ID,
|
pendingAutoStartMu.Lock()
|
||||||
outputProbeMax,
|
_ = removeManualPendingAutoStartItemForProvider("chaturbate", it.userKey)
|
||||||
func() {
|
pendingAutoStartMu.Unlock()
|
||||||
// Download läuft wirklich: manuellen Wartend-Eintrag entfernen.
|
}, nil)
|
||||||
pendingAutoStartMu.Lock()
|
|
||||||
_ = removeManualPendingAutoStartItemForProvider("chaturbate", it.userKey)
|
|
||||||
pendingAutoStartMu.Unlock()
|
|
||||||
},
|
|
||||||
func() {
|
|
||||||
// Blind-Probe hat keine Datei erzeugt:
|
|
||||||
// Jetzt erst als "Wartend" speichern.
|
|
||||||
owner := strings.TrimSpace(it.ownerUserKey)
|
|
||||||
if owner == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingAutoStartMu.Lock()
|
|
||||||
_ = saveManualPendingAutoStartItemForUser(owner, PendingAutoStartItem{
|
|
||||||
ModelKey: it.userKey,
|
|
||||||
URL: it.url,
|
|
||||||
Mode: "wait_public",
|
|
||||||
CurrentShow: "offline",
|
|
||||||
Source: "manual",
|
|
||||||
})
|
|
||||||
pendingAutoStartMu.Unlock()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
|
pendingAutoStartMu.Lock()
|
||||||
|
_ = removeWatchedPendingAutoStartItemForProvider("chaturbate", it.userKey)
|
||||||
|
pendingAutoStartMu.Unlock()
|
||||||
|
|
||||||
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, nil, nil)
|
go chaturbateAbortIfNoOutput(job.ID, outputProbeMax, nil, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1689,6 +1689,7 @@ func reencodeTSToMP4(ctx context.Context, tsPath, mp4Path string) error {
|
|||||||
|
|
||||||
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||||
"-c:v", "libx264",
|
"-c:v", "libx264",
|
||||||
|
"-threads", "2",
|
||||||
"-preset", "veryfast",
|
"-preset", "veryfast",
|
||||||
"-crf", "20",
|
"-crf", "20",
|
||||||
"-pix_fmt", "yuv420p",
|
"-pix_fmt", "yuv420p",
|
||||||
|
|||||||
@ -167,6 +167,40 @@ func normalizePendingShowServer(v string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitizePendingAutoStartItems(items []PendingAutoStartItem) []PendingAutoStartItem {
|
||||||
|
out := make([]PendingAutoStartItem, 0, len(items))
|
||||||
|
seen := make(map[string]struct{}, len(items))
|
||||||
|
|
||||||
|
for _, it := range items {
|
||||||
|
it.ModelKey = strings.ToLower(strings.TrimSpace(it.ModelKey))
|
||||||
|
it.URL = strings.TrimSpace(it.URL)
|
||||||
|
it.Mode = normalizePendingModeServer(it.Mode)
|
||||||
|
it.CurrentShow = normalizePendingShowServer(it.CurrentShow)
|
||||||
|
it.ImageURL = strings.TrimSpace(it.ImageURL)
|
||||||
|
it.Source = normalizePendingSourceServer(it.Source)
|
||||||
|
|
||||||
|
if it.ModelKey == "" || it.URL == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wichtig:
|
||||||
|
// Offline-Models gehören nicht in Pending Autostart.
|
||||||
|
if it.CurrentShow == "offline" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dedupeKey := it.Source + "|" + pendingProviderFromURL(it.URL) + "|" + it.ModelKey
|
||||||
|
if _, exists := seen[dedupeKey]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[dedupeKey] = struct{}{}
|
||||||
|
|
||||||
|
out = append(out, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func safeUserKeyForFile(v string) string {
|
func safeUserKeyForFile(v string) string {
|
||||||
re := regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
re := regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
||||||
return re.ReplaceAllString(v, "_")
|
return re.ReplaceAllString(v, "_")
|
||||||
@ -388,12 +422,13 @@ func loadPendingAutoStartItems(userKey string) ([]PendingAutoStartItem, error) {
|
|||||||
return []PendingAutoStartItem{}, nil
|
return []PendingAutoStartItem{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Items == nil {
|
if len(f.Items) == 0 {
|
||||||
f.Items = []PendingAutoStartItem{}
|
return []PendingAutoStartItem{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// noch einmal sauber normalisieren
|
|
||||||
out := make([]PendingAutoStartItem, 0, len(f.Items))
|
out := make([]PendingAutoStartItem, 0, len(f.Items))
|
||||||
|
seen := make(map[string]struct{}, len(f.Items))
|
||||||
|
|
||||||
for _, it := range f.Items {
|
for _, it := range f.Items {
|
||||||
key := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
key := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
||||||
u := strings.TrimSpace(it.URL)
|
u := strings.TrimSpace(it.URL)
|
||||||
@ -401,14 +436,33 @@ func loadPendingAutoStartItems(userKey string) ([]PendingAutoStartItem, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show := normalizePendingShowServer(it.CurrentShow)
|
||||||
|
|
||||||
|
// Offline-Models gehören nicht in Pending Autostart.
|
||||||
|
// Alte persistierte Offline-Einträge werden dadurch beim Lesen ignoriert.
|
||||||
|
if show == "offline" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
source := normalizePendingSourceServer(it.Source)
|
||||||
|
mode := normalizePendingModeServer(it.Mode)
|
||||||
|
imageURL := strings.TrimSpace(it.ImageURL)
|
||||||
|
provider := pendingProviderFromURL(u)
|
||||||
|
|
||||||
|
dedupeKey := source + "|" + provider + "|" + key
|
||||||
|
if _, exists := seen[dedupeKey]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[dedupeKey] = struct{}{}
|
||||||
|
|
||||||
out = append(out, PendingAutoStartItem{
|
out = append(out, PendingAutoStartItem{
|
||||||
ModelKey: key,
|
ModelKey: key,
|
||||||
URL: u,
|
URL: u,
|
||||||
Mode: normalizePendingModeServer(it.Mode),
|
Mode: mode,
|
||||||
NextProbeAtMs: it.NextProbeAtMs,
|
NextProbeAtMs: it.NextProbeAtMs,
|
||||||
CurrentShow: normalizePendingShowServer(it.CurrentShow),
|
CurrentShow: show,
|
||||||
ImageURL: strings.TrimSpace(it.ImageURL),
|
ImageURL: imageURL,
|
||||||
Source: normalizePendingSourceServer(it.Source),
|
Source: source,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,7 +614,7 @@ func savePendingAutoStartItems(userKey string, items []PendingAutoStartItem) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
payload := pendingAutoStartFile{
|
payload := pendingAutoStartFile{
|
||||||
Items: items,
|
Items: sanitizePendingAutoStartItems(items),
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := json.MarshalIndent(payload, "", " ")
|
raw, err := json.MarshalIndent(payload, "", " ")
|
||||||
|
|||||||
@ -390,8 +390,8 @@ func (pq *PostWorkQueue) StatusForKey(key string) PostWorkKeyStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// global (oder in deinem app struct halten)
|
// global (oder in deinem app struct halten)
|
||||||
var postWorkQ = NewPostWorkQueue(512, 1) // schneller kritischer Pfad
|
var postWorkQ = NewPostWorkQueue(1024, 1) // schneller kritischer Pfad
|
||||||
var enrichQ = NewPostWorkQueue(512, 1) // langsame Hintergrundaufgaben
|
var enrichQ = NewPostWorkQueue(128, 1) // langsame Hintergrundaufgaben
|
||||||
|
|
||||||
// --- Status Refresher (ehemals postwork_refresh.go) ---
|
// --- Status Refresher (ehemals postwork_refresh.go) ---
|
||||||
|
|
||||||
@ -622,3 +622,27 @@ func startPostWorkStatusRefresher() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pq *PostWorkQueue) CancelRunning() int {
|
||||||
|
pq.mu.Lock()
|
||||||
|
|
||||||
|
cancels := make([]context.CancelFunc, 0, len(pq.cancelByKey))
|
||||||
|
|
||||||
|
for key, cancel := range pq.cancelByKey {
|
||||||
|
if cancel == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, running := pq.runningKeys[key]; running {
|
||||||
|
cancels = append(cancels, cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pq.mu.Unlock()
|
||||||
|
|
||||||
|
for _, cancel := range cancels {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(cancels)
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
@ -597,6 +598,105 @@ func buildPostworkTempMP4Path(tsPath string) (string, error) {
|
|||||||
return uniqueDestPath(tmpDir, base)
|
return uniqueDestPath(tmpDir, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveRepairSourceTSBestEffort(tsPath string, finalMP4Path string) string {
|
||||||
|
tsPath = strings.TrimSpace(tsPath)
|
||||||
|
if tsPath == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := requireNonEmptyRegularFile(tsPath, "repair source ts"); err != nil {
|
||||||
|
appLogln("⚠️ repair source ts not usable:", err)
|
||||||
|
return tsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
s := getSettings()
|
||||||
|
|
||||||
|
doneAbs, err := resolvePathRelativeToApp(s.DoneDir)
|
||||||
|
if err != nil || strings.TrimSpace(doneAbs) == "" {
|
||||||
|
doneAbs = filepath.Dir(strings.TrimSpace(finalMP4Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
doneAbs = strings.TrimSpace(doneAbs)
|
||||||
|
if doneAbs == "" {
|
||||||
|
return tsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
repairDir := filepath.Join(doneAbs, ".repair_sources")
|
||||||
|
if err := os.MkdirAll(repairDir, 0o755); err != nil {
|
||||||
|
appLogln("⚠️ repair source dir create failed:", err)
|
||||||
|
return tsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
dst, err := uniqueDestPath(repairDir, filepath.Base(tsPath))
|
||||||
|
if err != nil {
|
||||||
|
appLogln("⚠️ repair source unique path failed:", err)
|
||||||
|
return tsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := renameWithRetryAggressive(tsPath, dst); err != nil {
|
||||||
|
appLogln("⚠️ repair source move failed:", err)
|
||||||
|
return tsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
purgeDurationCacheForPath(tsPath)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func markVideoNeedsRepairBestEffort(videoPath string, sourceTSPath string, reason string, detail string) {
|
||||||
|
videoPath = strings.TrimSpace(videoPath)
|
||||||
|
if videoPath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assetID := strings.TrimSpace(assetIDFromVideoPath(videoPath))
|
||||||
|
if assetID == "" {
|
||||||
|
assetID = canonicalAssetIDFromName(filepath.Base(videoPath))
|
||||||
|
}
|
||||||
|
if assetID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
marker := map[string]any{
|
||||||
|
"needsRepair": true,
|
||||||
|
"reason": strings.TrimSpace(reason),
|
||||||
|
"detail": strings.TrimSpace(detail),
|
||||||
|
"videoPath": videoPath,
|
||||||
|
"sourceTSPath": strings.TrimSpace(sourceTSPath),
|
||||||
|
"createdAt": time.Now().UTC().Format(time.RFC3339Nano),
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(marker, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
appLogln("⚠️ repair marker marshal failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repairPath := ""
|
||||||
|
|
||||||
|
if _, _, _, _, metaPath, err := assetPathsForID(assetID); err == nil {
|
||||||
|
metaPath = strings.TrimSpace(metaPath)
|
||||||
|
if metaPath != "" {
|
||||||
|
dir := filepath.Dir(metaPath)
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err == nil {
|
||||||
|
repairPath = filepath.Join(dir, "repair.json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if repairPath == "" {
|
||||||
|
stem := strings.TrimSuffix(filepath.Base(videoPath), filepath.Ext(videoPath))
|
||||||
|
repairPath = filepath.Join(filepath.Dir(videoPath), stem+".repair.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(repairPath, b, 0o644); err != nil {
|
||||||
|
appLogln("⚠️ repair marker write failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appLogln("⚠️ video marked for deferred repair:", filepath.Base(videoPath), "marker=", repairPath)
|
||||||
|
}
|
||||||
|
|
||||||
func runPrimaryPostworkPipeline(
|
func runPrimaryPostworkPipeline(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
job *RecordJob,
|
job *RecordJob,
|
||||||
@ -611,6 +711,11 @@ func runPrimaryPostworkPipeline(
|
|||||||
cleanupTSAfterMove := ""
|
cleanupTSAfterMove := ""
|
||||||
removeTempOutOnMoveFailure := false
|
removeTempOutOnMoveFailure := false
|
||||||
|
|
||||||
|
needsRepair := false
|
||||||
|
repairReason := ""
|
||||||
|
repairDetail := ""
|
||||||
|
repairSourceTS := ""
|
||||||
|
|
||||||
// 1) TS -> MP4 zuerst
|
// 1) TS -> MP4 zuerst
|
||||||
if strings.EqualFold(filepath.Ext(out), ".ts") {
|
if strings.EqualFold(filepath.Ext(out), ".ts") {
|
||||||
tsPath := out
|
tsPath := out
|
||||||
@ -669,28 +774,26 @@ func runPrimaryPostworkPipeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := validateVideoDecodesNearEnd(ctx, mp4Path); err != nil {
|
if err := validateVideoDecodesNearEnd(ctx, mp4Path); err != nil {
|
||||||
appLogln("⚠️ remux mp4 decode validation failed, fallback to reencode:", err)
|
// Wichtig:
|
||||||
|
// Nicht mehr inline reencoden. Reencode ist CPU-teuer und blockiert die postWorkQ.
|
||||||
|
// Das remuxte MP4 bleibt erhalten und wird als "needs repair" markiert.
|
||||||
|
appLogln("⚠️ remux mp4 decode validation failed; keeping remux result and deferring repair:", err)
|
||||||
|
|
||||||
_ = removeWithRetry(mp4Path)
|
needsRepair = true
|
||||||
|
repairReason = "decode_near_end_failed"
|
||||||
if rerr := reencodeTSToMP4(ctx, tsPath, mp4Path); rerr != nil {
|
repairDetail = err.Error()
|
||||||
_ = removeWithRetry(mp4Path)
|
repairSourceTS = tsPath
|
||||||
return tsPath, appErrorf("remux invalid (%v), reencode fallback failed: %w", err, rerr)
|
}
|
||||||
}
|
|
||||||
|
// TS erst NACH erfolgreichem Move löschen.
|
||||||
if err2 := requireNonEmptyRegularFile(mp4Path, "reencode result"); err2 != nil {
|
// Bei Repair-Verdacht behalten wir die TS als Reparaturquelle.
|
||||||
_ = removeWithRetry(mp4Path)
|
if !needsRepair {
|
||||||
return tsPath, err2
|
cleanupTSAfterMove = tsPath
|
||||||
}
|
} else {
|
||||||
|
cleanupTSAfterMove = ""
|
||||||
if err2 := validateVideoDecodesNearEnd(ctx, mp4Path); err2 != nil {
|
repairSourceTS = tsPath
|
||||||
_ = removeWithRetry(mp4Path)
|
|
||||||
return tsPath, appErrorf("reencoded mp4 still invalid: %w", err2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TS erst NACH erfolgreichem Move löschen
|
|
||||||
cleanupTSAfterMove = tsPath
|
|
||||||
removeTempOutOnMoveFailure = true
|
removeTempOutOnMoveFailure = true
|
||||||
out = mp4Path
|
out = mp4Path
|
||||||
}
|
}
|
||||||
@ -729,6 +832,15 @@ func runPrimaryPostworkPipeline(
|
|||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if needsRepair {
|
||||||
|
keptSource := strings.TrimSpace(repairSourceTS)
|
||||||
|
if keptSource != "" {
|
||||||
|
keptSource = moveRepairSourceTSBestEffort(keptSource, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
markVideoNeedsRepairBestEffort(out, keptSource, repairReason, repairDetail)
|
||||||
|
}
|
||||||
|
|
||||||
if cleanupTSAfterMove != "" {
|
if cleanupTSAfterMove != "" {
|
||||||
if err := removeWithRetry(cleanupTSAfterMove); err != nil && !os.IsNotExist(err) {
|
if err := removeWithRetry(cleanupTSAfterMove); err != nil && !os.IsNotExist(err) {
|
||||||
appLogln("⚠️ ts cleanup after successful move:", err)
|
appLogln("⚠️ ts cleanup after successful move:", err)
|
||||||
@ -1134,6 +1246,10 @@ func enqueuePostworkOrFail(job *RecordJob, out string, postTarget JobStatus) {
|
|||||||
postFile := filepath.Base(out)
|
postFile := filepath.Base(out)
|
||||||
postAssetID := assetIDFromVideoPath(out)
|
postAssetID := assetIDFromVideoPath(out)
|
||||||
|
|
||||||
|
_, _, maxParallel := postWorkQ.Stats()
|
||||||
|
sortBucket, sortName := postWorkSortForVideoPath(out)
|
||||||
|
addedAt := time.Now()
|
||||||
|
|
||||||
jobsMu.Lock()
|
jobsMu.Lock()
|
||||||
job.Phase = "postwork"
|
job.Phase = "postwork"
|
||||||
job.PostWorkKey = postKey
|
job.PostWorkKey = postKey
|
||||||
@ -1142,18 +1258,21 @@ func enqueuePostworkOrFail(job *RecordJob, out string, postTarget JobStatus) {
|
|||||||
Position: 0,
|
Position: 0,
|
||||||
Waiting: 0,
|
Waiting: 0,
|
||||||
Running: 0,
|
Running: 0,
|
||||||
MaxParallel: 1,
|
MaxParallel: maxParallel,
|
||||||
}
|
}
|
||||||
jobsMu.Unlock()
|
jobsMu.Unlock()
|
||||||
|
|
||||||
okQueued := postWorkQ.Enqueue(PostWorkTask{
|
okQueued := postWorkQ.Enqueue(PostWorkTask{
|
||||||
Key: postKey,
|
Key: postKey,
|
||||||
Path: out,
|
Path: out,
|
||||||
Added: time.Now(),
|
Added: addedAt,
|
||||||
|
SortBucket: sortBucket,
|
||||||
|
SortName: sortName,
|
||||||
Run: func(ctx context.Context) error {
|
Run: func(ctx context.Context) error {
|
||||||
return runQueuedPostwork(ctx, job, out, postTarget, postKey)
|
return runQueuedPostwork(ctx, job, out, postTarget, postKey)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if okQueued {
|
if okQueued {
|
||||||
publishQueuedPostworkState(job, postKey, postFile, postAssetID)
|
publishQueuedPostworkState(job, postKey, postFile, postAssetID)
|
||||||
return
|
return
|
||||||
@ -1367,6 +1486,25 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitBeforeHeavyStep := func(queue string, phase string) error {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
publishDeferredPhase(fileName, assetID, queue, phase, "missing", "", nil)
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := waitUntilEnrichMayRun(ctx); err != nil {
|
||||||
|
publishDeferredPhase(fileName, assetID, queue, phase, "missing", "", nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
publishDeferredPhase(fileName, assetID, queue, phase, "missing", "", nil)
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
truth := assetsTruthForVideo(outPath)
|
truth := assetsTruthForVideo(outPath)
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@ -1374,6 +1512,10 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
if !truth.MetaReady {
|
if !truth.MetaReady {
|
||||||
|
if err := waitBeforeHeavyStep("postwork", "meta"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
publishDeferredPhase(fileName, assetID, "postwork", "meta", "running", "Meta", nil)
|
publishDeferredPhase(fileName, assetID, "postwork", "meta", "running", "Meta", nil)
|
||||||
|
|
||||||
okMeta, err := ensureMetaForVideoCtx(ctx, outPath, sourceURL)
|
okMeta, err := ensureMetaForVideoCtx(ctx, outPath, sourceURL)
|
||||||
@ -1393,6 +1535,10 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truth.ThumbReady {
|
if !truth.ThumbReady {
|
||||||
|
if err := waitBeforeHeavyStep("postwork", "thumb"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
publishDeferredPhase(fileName, assetID, "postwork", "thumb", "running", "Vorschaubild", nil)
|
publishDeferredPhase(fileName, assetID, "postwork", "thumb", "running", "Vorschaubild", nil)
|
||||||
|
|
||||||
_, err := ensurePrimaryAssetsForVideoWithProgressCtx(ctx, outPath, sourceURL, nil)
|
_, err := ensurePrimaryAssetsForVideoWithProgressCtx(ctx, outPath, sourceURL, nil)
|
||||||
@ -1412,6 +1558,10 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truth.TeaserReady {
|
if !truth.TeaserReady {
|
||||||
|
if err := waitBeforeHeavyStep("postwork", "teaser"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
publishDeferredPhase(fileName, assetID, "postwork", "teaser", "running", "Teaser", nil)
|
publishDeferredPhase(fileName, assetID, "postwork", "teaser", "running", "Teaser", nil)
|
||||||
|
|
||||||
_, err := ensureTeaserForVideoCtx(ctx, outPath, sourceURL)
|
_, err := ensureTeaserForVideoCtx(ctx, outPath, sourceURL)
|
||||||
@ -1437,6 +1587,10 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
if !truth.SpritesReady {
|
if !truth.SpritesReady {
|
||||||
|
if err := waitBeforeHeavyStep("postwork", "sprites"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
publishDeferredPhase(fileName, assetID, "postwork", "sprites", "running", "Sprites", nil)
|
publishDeferredPhase(fileName, assetID, "postwork", "sprites", "running", "Sprites", nil)
|
||||||
|
|
||||||
_, err := ensureSpritesForVideoCtx(ctx, outPath, sourceURL)
|
_, err := ensureSpritesForVideoCtx(ctx, outPath, sourceURL)
|
||||||
@ -1452,6 +1606,7 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
firstErr = err
|
firstErr = err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Best effort. Wenn währenddessen postWork reinkommt, beendet ctx diesen Check.
|
||||||
if _, checkErr := runTailBlackoutCheck(ctx, outPath); checkErr != nil {
|
if _, checkErr := runTailBlackoutCheck(ctx, outPath); checkErr != nil {
|
||||||
appLogln("⚠️ sprite tail blackout check failed:", outPath, checkErr)
|
appLogln("⚠️ sprite tail blackout check failed:", outPath, checkErr)
|
||||||
}
|
}
|
||||||
@ -1477,6 +1632,10 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st
|
|||||||
analyzeReady := hasAIResultsForAllOutputGoals(outPath, requiredGoals)
|
analyzeReady := hasAIResultsForAllOutputGoals(outPath, requiredGoals)
|
||||||
|
|
||||||
if !analyzeReady {
|
if !analyzeReady {
|
||||||
|
if err := waitBeforeHeavyStep("enrich", "analyze"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
publishDeferredPhase(fileName, assetID, "enrich", "analyze", "running", "Analysen", nil)
|
publishDeferredPhase(fileName, assetID, "enrich", "analyze", "running", "Analysen", nil)
|
||||||
|
|
||||||
_, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, outPath, sourceURL)
|
_, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, outPath, sourceURL)
|
||||||
|
|||||||
@ -677,7 +677,7 @@ func main() {
|
|||||||
|
|
||||||
fixKeepRootFilesIntoModelSubdirs()
|
fixKeepRootFilesIntoModelSubdirs()
|
||||||
|
|
||||||
postWorkQ.StartWorkers(1)
|
postWorkQ.StartWorkers(2)
|
||||||
enrichQ.StartWorkers(1)
|
enrichQ.StartWorkers(1)
|
||||||
startPostWorkStatusRefresher()
|
startPostWorkStatusRefresher()
|
||||||
startEnrichStatusRefresher()
|
startEnrichStatusRefresher()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user