{"record":{"id":"a5c2ef67c030df2e","repo":"SubtitleEdit/subtitleedit","slug":"no-vob-files-supplied","errorCode":null,"errorMessage":"No VOB files supplied.","messagePattern":"No VOB files supplied\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/seconv/Core/VobSubExtractor.cs","lineNumber":38,"sourceCode":"/// </summary>\ninternal static class VobSubExtractor\n{\n    /// <summary>One output produced by a successful extraction.</summary>\n    public sealed record StreamOutput(string Path, int StreamId, int Written);\n\n    /// <summary>\n    /// Parse <paramref name=\"vobFiles\"/> (treated as one logical title) and write\n    /// one .sub + .idx pair per discovered subpicture stream. <paramref name=\"subOutputPath\"/>\n    /// must already end in <c>.sub</c>; when there's more than one stream, the\n    /// stream index is inserted before the extension (<c>movie.sub</c> →\n    /// <c>movie.0.sub</c>, <c>movie.1.sub</c>, …) and the matching .idx is\n    /// written alongside each one.\n    /// </summary>\n    public static IReadOnlyList<StreamOutput> Extract(IReadOnlyList<string> vobFiles, string subOutputPath, bool isPal)\n    {\n        if (vobFiles.Count == 0)\n        {\n            throw new InvalidOperationException(\"No VOB files supplied.\");\n        }\n\n        if (!subOutputPath.EndsWith(\".sub\", StringComparison.OrdinalIgnoreCase))\n        {\n            // Guard: VobSubWriter derives the .idx path with a literal\n            // Substring(0, Length - 3) + \"idx\" — pass anything other than \".sub\"\n            // here and the .idx ends up at the wrong path (e.g. movie → \"vie\"+\"idx\"\n            // = \"vieidx\"). Caller normalises, but assert just in case.\n            throw new ArgumentException(\"subOutputPath must end in '.sub'\", nameof(subOutputPath));\n        }\n\n        // Parse every VOB into per-stream merged packs. DVDs assign continuous PTS\n        // across VOB chunks of the same title, so packs from later VOBs naturally\n        // land after earlier ones in playback time.\n        var allPacks = new List<VobSubMergedPack>();\n        foreach (var vob in vobFiles)\n        {\n            var parser = new VobSubParser(isPal);","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/seconv/Core/VobSubExtractor.cs#L20-L56","documentation":"Thrown by VobSubExtractor.Extract when the vobFiles list is empty. The extractor needs at least one .VOB to parse, so an empty input is a caller bug (the CLI should not reach here with no files).","triggerScenarios":"VobSubExtractor.Extract is called with an IReadOnlyList<string> of length 0.","commonSituations":"A glob pattern that matched no VOB files was passed through to the extractor; the input list was built but filtered down to nothing; programmatic caller forgot to validate.","solutions":["Confirm the .VOB files exist and the glob/path list is non-empty before calling Extract.","Pass explicit VTS_xx_1.VOB (and later) files — the subtitle-bearing chunks, not VTS_xx_0.VOB.","Add a guard in the caller to report a clearer 'no input files' message."],"exampleFix":"// before\nVobSubExtractor.Extract(Array.Empty<string>(), outPath, isPal);\n// after\nif (vobFiles.Count == 0) throw new ArgumentException(\"Supply at least one .VOB file.\");\nVobSubExtractor.Extract(vobFiles, outPath, isPal);","handlingStrategy":"validation","validationCode":"if (vobFiles is null || vobFiles.Count == 0)\n    throw new ArgumentException(\"Supply at least one .VOB file.\", nameof(vobFiles));","typeGuard":"static bool HasVobs(IReadOnlyList<string> f) => f is { Count: > 0 };","tryCatchPattern":"null","preventionTips":["Validate the file list at the CLI boundary before calling Extract.","Log how many files matched the input glob.","Treat zero matches as a hard error, not a no-op."],"tags":["seconv","vobsub","dvd","argument-validation","empty-input"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}