{"record":{"id":"82d13d8c2bafcdf7","repo":"affaan-m/ECC","slug":"draft-name-must-be-a-plain-file-name","errorCode":null,"errorMessage":"Draft name must be a plain file name","messagePattern":"Draft name must be a plain file name","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/media/capcut.py","lineNumber":61,"sourceCode":"\ndef export_draft(\n    files,\n    drafts,\n    name,\n    width=1920,\n    height=1080,\n    fps=30,\n    overwrite=False,\n    cc=None,\n    probe=duration_of,\n):\n    geometry(width, height, fps)\n    if overwrite:\n        raise ValueError(\"CapCut draft replacement is unsafe; choose a new name\")\n    if not re.fullmatch(r\"[A-Za-z0-9][A-Za-z0-9_. -]{0,99}\", name) or name.endswith(\n        \".\"\n    ):\n        raise ValueError(\"Draft name must be a plain file name\")\n    if not files:\n        raise ValueError(\"At least one video is required\")\n    prepared = []\n    for filename in files:\n        path = Path(filename).resolve(strict=True)\n        duration = probe(path)\n        if not math.isfinite(duration) or duration <= 0:\n            raise ValueError(f\"Invalid media duration: {path.name}\")\n        prepared.append((str(path), duration))\n    if cc is None:\n        import pycapcut as cc\n    folder = cc.DraftFolder(str(drafts))\n    script = folder.create_draft(name, width, height, fps=fps, allow_replace=False)\n    script.add_track(cc.TrackType.video)\n    elapsed = 0\n    for filename, duration in prepared:\n        script.add_segment(\n            cc.VideoSegment(filename, cc.trange(f\"{elapsed:.6f}s\", f\"{duration:.6f}s\"))","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/media/capcut.py#L43-L79","documentation":"export_draft validates the draft name against ^[A-Za-z0-9][A-Za-z0-9_. -]{0,99}$ and rejects names ending with a dot. This keeps the name a single safe path component that works as a CapCut draft folder name on all platforms. Anything with path separators, leading special characters, control characters, or trailing dots is rejected.","triggerScenarios":"Passing names containing '/' or '\\\\', leading dots/spaces/hyphens, names over 100 characters, names ending in '.', or empty/unicode names to export_draft.","commonSituations":"Deriving the draft name from a source filename including its directory; Windows-style paths leaking into the name; a user typing 'My Edit.' with a trailing period; names auto-generated with timestamps using ':' characters.","solutions":["Use only letters, digits, underscores, dots, spaces, and hyphens, starting with an alphanumeric character","Strip or replace path components: name = Path(raw).stem or re.sub(r'[^A-Za-z0-9_. -]', '_', raw)","Remove trailing dots: name = name.rstrip('.')","Keep the name at or under 100 characters"],"exampleFix":"// before\nname = str(Path(video).with_suffix(''))  # may contain '/'\n\n// after\nname = Path(video).stem.replace(':', '-')[:100].rstrip('.') or 'draft'","handlingStrategy":"validation","validationCode":"import re\nDRAFT_NAME_RE = re.compile(r'[A-Za-z0-9][A-Za-z0-9_. -]{0,99}')\ndef draft_name_ok(name):\n    return bool(name) and bool(DRAFT_NAME_RE.fullmatch(name)) and not name.endswith('.')","typeGuard":"def is_safe_draft_name(name):\n    return isinstance(name, str) and bool(re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_. -]{0,99}', name)) and not name.endswith('.')","tryCatchPattern":"try:\n    export_draft(files, name=name)\nexcept ValueError as e:\n    if 'plain file name' in str(e):\n        export_draft(files, name=sanitize(name))","preventionTips":["Sanitize user input: strip path components and invalid characters before naming drafts","Normalize names: re.sub(r'[^A-Za-z0-9_. -]', '_', raw).lstrip(string.punctuation)","Strip trailing dots and cap length at 100 characters","Test name sanitization with paths from both POSIX and Windows sources"],"tags":["python","capcut","naming","validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}