Initial commit: Tdarr plugin stack

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
This commit is contained in:
Tdarr Plugin Developer
2025-12-15 11:33:36 -08:00
commit aa71eb96d7
24 changed files with 6757 additions and 0 deletions

View File

@@ -0,0 +1,235 @@
# 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.