Plugins: - misc_fixes v2.8: Pre-processing, container remux, stream conforming - stream_organizer v4.8: English priority, subtitle extraction, SRT conversion - combined_audio_standardizer v1.13: AAC/Opus encoding, downmix creation - av1_svt_converter v2.22: AV1 video encoding via SVT-AV1 Structure: - Local/ - Plugin .js files (mount in Tdarr) - agent_notes/ - Development documentation - Latest-Reports/ - Error logs for analysis
236 lines
6.3 KiB
Markdown
236 lines
6.3 KiB
Markdown
# Infinite Loop Scenario Analysis
|
||
|
||
**Date:** 2025-12-15
|
||
**Plugin Versions:** misc_fixes v2.7, stream_organizer v4.7, av1_converter v2.22, audio_standardizer v1.13
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
Analyzed all 4 plugins for potential infinite loop conditions. Found **1 confirmed risk**, **2 potential risks**, and **3 low/theoretical risks**.
|
||
|
||
| Risk Level | Plugin | Loop Scenario | Status |
|
||
|------------|--------|---------------|--------|
|
||
| <20> SAFE | misc_fixes | Container/reorder detection | **Already Fixed** |
|
||
| <20> SAFE | stream_organizer | Subtitle extraction edge case | Mitigated |
|
||
| <20> SAFE | audio_standardizer | Downmix creation detection | Safe |
|
||
| 🟢 SAFE | av1_converter | Force transcode disabled | Safe |
|
||
| 🟢 SAFE | stream_organizer | CC extraction | Safe |
|
||
|
||
---
|
||
|
||
## ✅ VERIFIED SAFE: misc_fixes Container/Reorder Detection
|
||
|
||
### Analysis
|
||
|
||
Upon code review, the reorder detection fix is **ALREADY IMPLEMENTED**:
|
||
|
||
```javascript
|
||
// Line 228-229 of misc_fixes.js
|
||
const firstStreamIsVideo = file.ffProbeData.streams[0]?.codec_type === 'video';
|
||
const needsReorder = inputs.ensure_video_first === 'true' && !firstStreamIsVideo;
|
||
```
|
||
|
||
**Protection Mechanism:**
|
||
- `needsReorder` is only `true` if video is NOT first
|
||
- After reordering, video IS first → `needsReorder = false`
|
||
- No infinite loop occurs
|
||
|
||
### Container Remux Logic
|
||
|
||
Also safe:
|
||
```javascript
|
||
if (currentContainer !== targetContainer) {
|
||
needsRemux = true;
|
||
}
|
||
```
|
||
- After remux to MKV, `currentContainer === 'mkv'`
|
||
- `targetContainer === 'mkv'`
|
||
- `needsRemux = false` on second pass
|
||
|
||
### Verified Behavior
|
||
|
||
1. First pass: File needs reorder → `processFile: true`, reorders
|
||
2. Second pass: Video already first → `needsReorder = false` → `processFile: false`
|
||
3. Loop terminates
|
||
|
||
**Status:** ✅ SAFE - No fix needed.
|
||
|
||
---
|
||
|
||
## 🟡 MEDIUM RISK: stream_organizer Subtitle Extraction
|
||
|
||
### The Problem
|
||
|
||
Subtitle extraction is protected by `needsSubtitleExtraction()` but has edge cases.
|
||
|
||
### Edge Case: Extraction Fails Silently
|
||
```javascript
|
||
if (needsSubtitleExtraction(subsFile, baseFile, fs)) {
|
||
extractCommand += ...
|
||
extractedFiles.add(subsFile);
|
||
}
|
||
```
|
||
**Problem:** If FFmpeg fails to create the file (returns success but file is corrupt), the plugin will:
|
||
1. See file doesn't exist (or is tiny)
|
||
2. Attempt extraction again
|
||
3. Loop
|
||
|
||
### Current Mitigation
|
||
```javascript
|
||
const attempts = extractionAttempts.get(attemptKey) || 0;
|
||
if (attempts >= MAX_EXTRACTION_ATTEMPTS) {
|
||
response.infoLog += `⚠️ Skipping - extraction failed ${MAX_EXTRACTION_ATTEMPTS} times.`;
|
||
continue;
|
||
}
|
||
```
|
||
**Status:** Protected by attempt counter (MAX = 3). **Mitigated.**
|
||
|
||
### Remaining Risk
|
||
- Counter is in-memory, resets on Tdarr restart
|
||
- If Tdarr restarts during processing, attempts reset to 0
|
||
|
||
---
|
||
|
||
## 🟡 MEDIUM RISK: audio_standardizer Downmix Detection
|
||
|
||
### The Problem
|
||
|
||
Plugin creates downmix tracks if they don't exist:
|
||
```javascript
|
||
if (inputs.create_downmix === 'true') {
|
||
const hasStereo = audioStreams.some(s => s.channels === 2);
|
||
if (!hasStereo) {
|
||
// Create 2ch downmix
|
||
}
|
||
}
|
||
```
|
||
|
||
### Potential Loop Scenario
|
||
1. File has 5.1 audio only
|
||
2. Plugin creates stereo downmix → `reQueueAfter: true`
|
||
3. On re-queue, file now has stereo
|
||
4. Should stop... but does it?
|
||
|
||
### Analysis
|
||
```javascript
|
||
if (needsTranscoding(stream, inputs, targetCodec)) {
|
||
needsTranscode = true;
|
||
}
|
||
```
|
||
|
||
**Question:** Does the NEW stereo track created in step 2 get detected as "already Opus"?
|
||
|
||
**Finding:** Likely safe because:
|
||
- New track is Opus (target codec)
|
||
- `needsTranscoding()` should return false
|
||
- `skip_if_compatible === 'true'` by default
|
||
|
||
**Recommendation:** Add explicit check:
|
||
```javascript
|
||
// Skip if we just created a downmix (Opus stereo exists)
|
||
const hasOpusStereo = audioStreams.some(s =>
|
||
s.channels === 2 && s.codec_name === 'opus'
|
||
);
|
||
if (hasOpusStereo && inputs.create_downmix === 'true') {
|
||
response.infoLog += 'ℹ️ Stereo downmix already exists (Opus). ';
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🟢 LOW RISK: av1_converter
|
||
|
||
### Analysis
|
||
|
||
The AV1 converter has **proper exit conditions**:
|
||
|
||
```javascript
|
||
// Already AV1 → Skip
|
||
if (isAV1 && sanitized.force_transcode !== 'enabled') {
|
||
response.processFile = false;
|
||
response.infoLog += 'File is already AV1 encoded and force_transcode is disabled. Skipping.\n';
|
||
return response;
|
||
}
|
||
```
|
||
|
||
**Status:** ✅ Safe. Clear skip when codec matches.
|
||
|
||
### Theoretical Risk
|
||
|
||
**Only if:** User enables `force_transcode = enabled`
|
||
- Then every run will transcode
|
||
- This is intentional (user wants to re-encode)
|
||
|
||
---
|
||
|
||
## 🟢 LOW RISK: CC Extraction Loop
|
||
|
||
### Analysis
|
||
|
||
CC extraction is protected by:
|
||
1. Lock file mechanism
|
||
2. File existence check using `originalLibraryFile.file`
|
||
3. Explicit skip when file exists
|
||
|
||
```javascript
|
||
if (ccExists) {
|
||
ccActuallyExtracted = false;
|
||
response.infoLog += `ℹ️ ${baseName}.cc.srt already exists. `;
|
||
}
|
||
```
|
||
|
||
**Status:** ✅ Safe.
|
||
|
||
---
|
||
|
||
## Recommendations
|
||
|
||
### No Immediate Fixes Needed
|
||
|
||
All plugins have proper loop termination conditions:
|
||
|
||
- **misc_fixes**: Already checks if video is first before reordering
|
||
- **stream_organizer**: Has extraction attempt counter (max 3)
|
||
- **audio_standardizer**: Detects existing codec (skip_if_compatible)
|
||
- **av1_converter**: Checks if already AV1 before processing
|
||
```
|
||
|
||
### Short-term (Priority 2)
|
||
|
||
**Add processing fingerprint to prevent duplicate runs:**
|
||
```javascript
|
||
// At start of plugin
|
||
const fingerprint = md5(JSON.stringify({
|
||
container: file.container,
|
||
streamOrder: file.ffProbeData.streams.map(s => s.codec_type),
|
||
imageCounts: // count of image streams
|
||
}));
|
||
|
||
// Store in file metadata or temp file
|
||
if (previousFingerprint === fingerprint) {
|
||
response.infoLog += '⚠️ File unchanged since last run, skipping.';
|
||
return response;
|
||
}
|
||
```
|
||
|
||
### Long-term (Priority 3)
|
||
|
||
**Add maximum run counter per plugin:**
|
||
- Tdarr maintains internal counter per file per plugin
|
||
- If counter > 3, flag file for manual review
|
||
- Prevents any unexpected loops
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
| Plugin | Loop Risk | Current Protection | Recommendation |
|
||
|--------|-----------|-------------------|----------------|
|
||
| misc_fixes | **HIGH** | None | Add order check |
|
||
| stream_organizer | LOW | Attempt counter | Already mitigated |
|
||
| audio_standardizer | LOW | Codec detection | Add explicit check |
|
||
| av1_converter | NONE | isAV1 check | None needed |
|
||
|
||
**Next Step:** Implement the misc_fixes reorder detection fix.
|