Batch rename of files

I have a lot of 4k scenes and they take a lot of space. Since I’m totally fine with 1080p I want to downscale the said 4k scenes. To achieve that I thought I would batch rename all 4k scene files by adding “to_downscale” in the filename or something like that to make them easy to find and then downscale them.

But I have not found a way to achieve that. I saw that there are 3 plugins to rename files and I tested one of them but, afaik, none of them do batch renaming.

So is there a way to do batch rename of files? Or is there any other method of acheiving what I want?

Thanks!

1 Like
  1. Create a new tag in Stash to keep track of the scenes you want to downscale.
  2. Run the script below, it’ll output all the file paths of the scenes you tagged above.

Once you’ve got the list of file paths you can do whatever you want with it, e.g. use it as input into a process that’ll rename the files for you or even loop through each line and run ffmpeg.

cat << EOF | sqlite3 /path/to/stash.db
SELECT
  folders.path || "/" || files.basename AS path
FROM
  scenes_tags st
  LEFT JOIN tags ON st.tag_id = tags.id
  LEFT JOIN scenes_files sf ON st.scene_id = sf.scene_id
  LEFT JOIN files ON sf.file_id = files.id
  LEFT JOIN folders ON files.parent_folder_id = folders.id
WHERE
  tags.name = "to_downscale";
EOF
1 Like

Stash doesn’t rename by design.

But there are several plugins that do allow batch processing:

Make sure to configure them properly before using.

1 Like

PPP, I took your lead and wrote a python script to move the files that have a certain tag.
The script takes 3 parameters:

  1. the Stash SQLite db
  2. the tag
  3. the destination folder

import sqlite3
import argparse
import os
import shutil

def move_files_from_db(db_path, tag, destination_folder):
    # Ensure destination folder exists
    os.makedirs(destination_folder, exist_ok=True)

    # Connect to the SQLite database
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    # SQL query using parameterized input for tag
    query = """
    SELECT
      folders.path || '/' || files.basename AS path
    FROM
      scenes_tags st
      LEFT JOIN tags ON st.tag_id = tags.id
      LEFT JOIN scenes_files sf ON st.scene_id = sf.scene_id
      LEFT JOIN files ON sf.file_id = files.id
      LEFT JOIN folders ON files.parent_folder_id = folders.id
    WHERE
      tags.name = ?
    """

    try:
        cursor.execute(query, (tag,))
        rows = cursor.fetchall()

        if not rows:
            print(f"No files found for tag '{tag}'.")
            return

        moved_files = 0

        for row in rows:
            original_path = row[0]

            # Replace leading '/data' with '/Volumes/Tank/Stash'
            if original_path.startswith('/data'):
                file_path = original_path.replace('/data', '/Volumes/Tank/Stash', 1)
            else:
                print(f"⚠️ Path does not start with '/data': {original_path}")
                continue

            if not os.path.isfile(file_path):
                print(f"⚠️ File not found: {file_path}")
                continue

            dest_path = os.path.join(destination_folder, os.path.basename(file_path))

            if os.path.exists(dest_path):
                print(f"⚠️ Destination file already exists: {dest_path}")
                continue

            try:
                shutil.move(file_path, dest_path)
                print(f"✅ Moved: {file_path} → {dest_path}")
                moved_files += 1
            except Exception as e:
                print(f"❌ Error moving {file_path}: {e}")

        print(f"\n✅ Done. {moved_files} file(s) moved.")

    finally:
        conn.close()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Move files from database paths to a destination folder.")
    parser.add_argument("database", help="Path to the SQLite .db file")
    parser.add_argument("tag", help="Tag name to filter files")
    parser.add_argument("destination", help="Destination folder for moved files")

    args = parser.parse_args()

    move_files_from_db(args.database, args.tag, args.destination)

Beware that the script has a hardcoded location relative to my setup.
If anyone wants to turn this into a plugin, feel free to do it.

2 Likes

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.