{
  "slug": "music-file-renamer",
  "name": "gizza-ai/music-file-renamer",
  "version": "0.1.0",
  "title": "Music File Renamer — gizza.ai",
  "description": "Build a safe rename and folder-move plan for music files from pasted tag dumps, templates, and filesystem rules.",
  "tags": [
    "music",
    "audio",
    "rename",
    "tags",
    "id3",
    "library",
    "files",
    "developer"
  ],
  "category": "audio",
  "urls": {
    "page": "https://gizza.ai/tools/music-file-renamer/",
    "markdown": "https://gizza.ai/tools/music-file-renamer/index.md",
    "descriptor": "https://gizza.ai/tools/music-file-renamer/tool.json",
    "deep_link_example": "https://gizza.ai/tools/music-file-renamer/?tracks=file%2Cartist%2Calbum%2Ctrack%2Ctitle%0Atrack01.mp3%2CTame%20Impala%2CCurrents%2C1%2CLet%20It%20Happen%0Atrack02.mp3%2CTame%20Impala%2CCurrents%2C2%2CNangs&input_format=auto&pattern=%7Bartist%7D%2F%7Balbum%7D%2F%7Btrack%7D%20%7Btitle%7D&base_dir=%2FMusic%2FLibrary&track_padding=2&on_missing=unknown&unknown_text=Unknown&charset=windows&replace_char=_&space_style=keep&case_style=keep&max_component=100&keep_extension=true&format=table"
  },
  "cli": "gizza tool music-file-renamer \"file,artist,album,track,title\ntrack01.mp3,Tame Impala,Currents,1,Let It Happen\ntrack02.mp3,Tame Impala,Currents,2,Nangs\"",
  "tool": {
    "description": "Turn a music tag dump into a deterministic current-path -> new-path rename and move plan. Reads CSV/TSV, a JSON array of tag objects (ffprobe/music-metadata shapes included) or key=value blocks (ffprobe -show_format, exiftool), then builds each target from a {token} path template over the tags: artist, albumartist, album, title, track, disc, year, genre, composer, comment, ext and any other field in the dump, with {a|b} fallback chains, zero-padded track numbers, year normalisation, a destination root, per-filesystem character sanitising (windows/unix/ascii accent folding), space and case styles, a per-component length cap and case-insensitive collision detection. Output as a table, a plain list, CSV, JSON, or a runnable mkdir -p + mv -n shell script. Preview only: it computes names and never touches, uploads or moves any file. Max 5000 records per run.",
    "parameters": {
      "additionalProperties": false,
      "properties": {
        "base_dir": {
          "default": "",
          "description": "Optional destination root prefixed to every target path, e.g. /srv/music or D:/Library. Empty (default) leaves the plan relative to wherever the current paths are.",
          "type": "string"
        },
        "case_style": {
          "default": "keep",
          "description": "Letter case applied to every generated path component: keep the tag's own case (default), lower, upper, or title case.",
          "enum": [
            "keep",
            "lower",
            "upper",
            "title"
          ],
          "type": "string"
        },
        "charset": {
          "default": "windows",
          "description": "Which characters the destination filesystem tolerates. windows (default, also safe on macOS/Linux) strips < > : \" | ? * and control characters, trims trailing dots/spaces and defuses reserved names like CON; unix only forbids the separator and NUL; ascii applies the windows rules and folds accents so the result is plain 7-bit ASCII (Sigur Rós → Sigur Ros).",
          "enum": [
            "windows",
            "unix",
            "ascii"
          ],
          "type": "string"
        },
        "format": {
          "default": "table",
          "description": "Shape of the plan: table (default) is a summary line plus aligned 'current -> new' rows with [unchanged]/[collision] flags, list is bare 'current -> new' lines, csv is current_path,new_path,status, json is a structured plan with counts, and sh emits a reviewable /bin/sh script of mkdir -p plus mv -n commands you can run yourself.",
          "enum": [
            "table",
            "list",
            "csv",
            "json",
            "sh"
          ],
          "type": "string"
        },
        "input_format": {
          "default": "auto",
          "description": "How to read the tracks text. auto (default) sniffs JSON vs delimited vs key=value and picks the delimiter; force csv, tsv, json or keyvalue when the sniffer guesses wrong.",
          "enum": [
            "auto",
            "csv",
            "tsv",
            "json",
            "keyvalue"
          ],
          "type": "string"
        },
        "keep_extension": {
          "default": true,
          "description": "When true (default) the current file's extension is appended to the last path component unless the pattern already ends with it. Turn it off to control the extension yourself with an explicit {ext} token.",
          "type": "boolean"
        },
        "max_component": {
          "default": 100,
          "description": "Maximum characters per folder or file name, 8-255 (default 100). Longer components are truncated; the file extension is re-appended after the cut so it is never lost.",
          "maximum": 255,
          "minimum": 8,
          "type": "integer"
        },
        "on_missing": {
          "default": "unknown",
          "description": "What to do with a file whose dump lacks a tag the pattern needs: unknown (default) substitutes unknown_text and still renames it, skip leaves it out of the plan and lists it under Skipped, keep_original plans no move for it.",
          "enum": [
            "unknown",
            "skip",
            "keep_original"
          ],
          "type": "string"
        },
        "pattern": {
          "default": "{artist}/{album}/{track} {title}",
          "description": "Target path template. {token} inserts a tag; / (or \\) starts a new folder, so the plan is a move, not just a rename. Canonical tokens: artist, albumartist, album, title, track, disc, year, genre, composer, comment, ext — plus any other column/field in your dump by its own name (e.g. {bitrate}, {isrc}) and the current-file tokens {filename}, {stem}, {dir}. Write {a|b|c} for a fallback chain: {albumartist|artist} keeps compilations in one folder. Default: {artist}/{album}/{track} {title}.",
          "type": "string"
        },
        "replace_char": {
          "default": "_",
          "description": "Text substituted for each character that is illegal on the chosen charset (default _). Set it to an empty string to delete illegal characters instead of replacing them.",
          "type": "string"
        },
        "space_style": {
          "default": "keep",
          "description": "What happens to spaces in generated names: keep them (default), or convert every space to an underscore or a hyphen.",
          "enum": [
            "keep",
            "underscore",
            "hyphen"
          ],
          "type": "string"
        },
        "track_padding": {
          "default": 2,
          "description": "Digits to zero-pad {track} to, 0-6 (default 2, so 3 and '3/12' both render as 03). Use 0 to leave the number unpadded.",
          "maximum": 6,
          "minimum": 0,
          "type": "integer"
        },
        "tracks": {
          "description": "The tag dump, one record per file. Accepts CSV/TSV (or ;/| delimited) with a header row, a JSON array of tag objects (ffprobe -print_format json and music-metadata shapes included), or key=value / key: value blocks separated by blank lines (ffprobe -show_format, exiftool). Every record needs its current file name in a file/filename/path/SourceFile field, plus whatever tags it has (artist, albumartist, album, title, track, disc, year, genre, composer, comment, ...). Max 5000 records per run. Example: 'file,artist,album,track,title' then 'track01.mp3,Tame Impala,Currents,1,Let It Happen'.",
          "type": "string"
        },
        "unknown_text": {
          "default": "Unknown",
          "description": "Filler written in place of a missing tag when on_missing=unknown, and used when a whole path component sanitises away to nothing. Default: Unknown.",
          "type": "string"
        }
      },
      "required": [
        "tracks"
      ],
      "type": "object"
    }
  }
}