Suggestion: rename photos in batches in a more specific way.

Suggestion: rename photos in batches in a more specific way.

Hello, my name is Anderson.
This text was originally written
in Portuguese and translated to English
using an AI, as English is not my native language.
I hope it is understandable.

I want to contribute to DigiKam by suggesting a feature to rename photos based on sorting
algorithms like InsertionSort, QuickSort, MergeSort, etc.
We don’t always want to sort photos by date, resolution, or size. Sometimes we want to sort by
subjective values that only make sense personally.
For this reason, I created my own Python code for this task, as I often need to put photos in a
specific, personalized order.
I hope you can review my Python code (the only language I know) and, if you find it useful,
consider adapting it to DigiKam.
I hope I am not breaking any rules and that this can help someone.
Thank you very much!

Below is my Python code. Comments were added by AI to help understanding.

python code

Code Commented

# Sorting by pictures
import os  # Importing the os module for interacting with the operating system
from PIL import Image  # Importing the Image class from the PIL library for image processing
import tkinter as tk  # Importing tkinter for creating GUI applications
from tkinter import filedialog  # Importing filedialog for file selection dialogs
from PIL import ImageTk  # Importing ImageTk for displaying images in tkinter

def buscar_imagens(diretorio):
  imagens = []  # Initializing an empty list to store image paths
  for arquivo in os.listdir(diretorio):  # Iterating through files in the specified directory
    if arquivo.lower().endswith(('.jpg', '.jpeg', '.png')):  # Checking if the file is an image
      imagens.append(os.path.join(diretorio, arquivo))  # Adding the image path to the list
  return imagens  # Returning the list of image paths

def mostrar_imagens_comparacao(img1_path, img2_path):
  janela = tk.Tk()  # Creating a new tkinter window
  janela.title("Comparar Imagens")  # Setting the window title
  img1 = Image.open(img1_path)  # Opening the first image
  img2 = Image.open(img2_path)  # Opening the second image
  img1 = img1.resize((600, 600))  # Resizing the first image to 600x600 pixels
  img2 = img2.resize((600, 600))  # Resizing the second image to 600x600 pixels
  tk_img1 = ImageTk.PhotoImage(img1)  # Converting the first image to a format suitable for tkinter
  tk_img2 = ImageTk.PhotoImage(img2)  # Converting the second image to a format suitable for tkinter
  lbl1 = tk.Label(janela, image=tk_img1)  # Creating a label for the first image
  lbl1.grid(row=0, column=0, padx=10, pady=10)  # Placing the first image label in the grid
  lbl2 = tk.Label(janela, image=tk_img2)  # Creating a label for the second image
  lbl2.grid(row=0, column=1, padx=10, pady=10)  # Placing the second image label in the grid
  lbl1.image = tk_img1  # Keeping a reference to the first image to prevent garbage collection
  lbl2.image = tk_img2  # Keeping a reference to the second image to prevent garbage collection
  resultado = [0]  # Initializing a list to store the user's choice
  def escolher_1():  # Function to handle the selection of the first image
    resultado[0] = 1  # Setting the result to indicate the first image was chosen
    janela.destroy()  # Closing the window
  def escolher_2():  # Function to handle the selection of the second image
    resultado[0] = 2  # Setting the result to indicate the second image was chosen
    janela.destroy()  # Closing the window
  btn1 = tk.Button(janela, text="Escolher Imagem 1", command=escolher_1)  # Button for selecting the first image
  btn1.grid(row=1, column=0, pady=10)  # Placing the button in the grid
  btn2 = tk.Button(janela, text="Escolher Imagem 2", command=escolher_2)  # Button for selecting the second image
  btn2.grid(row=1, column=1, pady=10)  # Placing the button in the grid
  janela.mainloop()  # Starting the tkinter event loop
  return resultado[0]  # Returning the user's choice

# --- Rotate Merge Sort adapted for interactive comparisons ---
def rotate(arr, start, mid, end):
  def rev(a, l, r):  # Helper function to reverse a portion of the array
    i, j = l, r - 1  # Initializing pointers for the reversal
    while i < j:  # While the pointers do not cross
      a[i], a[j] = a[j], a[i]  # Swapping elements
      i += 1  # Moving the left pointer to the right
      j -= 1  # Moving the right pointer to the left
  rev(arr, start, mid)  # Reversing the first half
  rev(arr, mid, end)  # Reversing the second half
  rev(arr, start, end)  # Reversing the entire segment

def binary_search_left_imagens(arr, start, end, value_path):
  """Finds the first position in [start, end) where arr[pos] >= value using visual comparisons.
  Returns the index pos such that arr[pos] is the first image the user considers >= value.
  """
  lo, hi = start, end  # Initializing low and high pointers for binary search
  while lo < hi:  # While the search space is valid
    m = (lo + hi) // 2  # Finding the middle index
    resposta = mostrar_imagens_comparacao(arr[m], value_path)  # Comparing the middle image with the value
    # If the user prefers arr[m] (resposta==1), we consider arr[m] <= value_path (arr[m] 'comes before' or equal)
    # We need to decide direction: treated here as arr[m] < value => lo = m+1
    if resposta == 1:  # If the user prefers the middle image
      lo = m + 1  # Search to the right
    else:  # If the user prefers the value path
      hi = m  # Search to the left
  return lo  # Returning the position found

def inplace_merge_imagens(arr, start, mid, end):
  if start >= mid or mid >= end:  # If the indices are invalid, return
    return
  if (mid - start) + (end - mid) <= 16:  # If the segment is small enough, use a simple insertion sort
    i = start  # Pointer for the left segment
    j = mid  # Pointer for the right segment
    while i < j and j < end:  # While both pointers are valid
      resposta = mostrar_imagens_comparacao(arr[i], arr[j])  # Comparing the images
      if resposta == 1:  # If the user prefers the left image
        i += 1  # Move the left pointer
      else:  # If the user prefers the right image
        val = arr[j]  # Storing the value to be inserted
        k = j  # Pointer for shifting elements
        while k > i:  # Shifting elements to the right
          arr[k] = arr[k-1]  # Moving elements
          k -= 1  # Moving the pointer
        arr[i] = val  # Inserting the value
        i += 1  # Moving the left pointer
        j += 1  # Moving the right pointer
    return  # Returning after merging

  left_mid = (start + mid) // 2  # Finding the middle of the left segment
  # Finding the position in the right segment for arr[left_mid]
  right_pos = binary_search_left_imagens(arr, mid, end, arr[left_mid])  # Performing binary search

  rotate(arr, left_mid, mid, right_pos)  # Rotating the segments
  new_mid = left_mid + (right_pos - mid)  # Calculating the new middle index

  inplace_merge_imagens(arr, start, left_mid, new_mid)  # Merging the left segment
  inplace_merge_imagens(arr, new_mid, right_pos, end)  # Merging the right segment

def rotate_mergesort_imagens(arr, start=0, end=None):
  if end is None:  # If end is not specified, set it to the length of the array
    end = len(arr)
  if end - start <= 1:  # If the segment is too small, return
    return
  mid = (start + end) // 2  # Finding the middle index
  rotate_mergesort_imagens(arr, start, mid)  # Recursively sorting the left segment
  rotate_mergesort_imagens(arr, mid, end)  # Recursively sorting the right segment
  inplace_merge_imagens(arr, start, mid, end)  # Merging the two sorted segments
  return arr  # Returning the sorted array
# --- end Rotate Merge Sort ---

# Part of Tim
def insertion_sort_run(arr, left, right):
  for i in range(left + 1, right + 1):  # Iterating through the segment
    chave = arr[i]  # Storing the current value
    j = i - 1  # Pointer for the sorted segment
    while j >= left:  # While the pointer is valid
      res = mostrar_imagens_comparacao(arr[j], chave)  # Comparing the images
      if res == 1:  # If the user prefers the current value
        break  # Break the loop
      else:  # If the user prefers the image at j
        arr[j + 1] = arr[j]  # Shifting the element to the right
        j -= 1  # Moving the pointer
    arr[j + 1] = chave  # Inserting the current value in the correct position

# Part of Timsort
def merge_timsort(arr, left, mid, right):
  left_run = arr[left:mid + 1]  # Creating a left run
  right_run = arr[mid + 1:right + 1]  # Creating a right run
  i = j = 0  # Initializing pointers for both runs
  k = left  # Pointer for the main array
  while i < len(left_run) and j < len(right_run):  # While both runs have elements
    res = mostrar_imagens_comparacao(left_run[i], right_run[j])  # Comparing the images
    if res == 1:  # If the user prefers the left run
      arr[k] = left_run[i]  # Adding the left run element to the main array
      i += 1  # Moving the left pointer
    else:  # If the user prefers the right run
      arr[k] = right_run[j]  # Adding the right run element to the main array
      j += 1  # Moving the right pointer
    k += 1  # Moving the main array pointer
  while i < len(left_run):  # If there are remaining elements in the left run
    arr[k] = left_run[i]  # Adding the remaining elements to the main array
    i += 1  # Moving the left pointer
    k += 1  # Moving the main array pointer
  while j < len(right_run):  # If there are remaining elements in the right run
    arr[k] = right_run[j]  # Adding the remaining elements to the main array
    j += 1  # Moving the right pointer
    k += 1  # Moving the main array pointer

# Tim Sort - Main
def timsort_adaptativo(arr):
  n = len(arr)  # Getting the length of the array
  min_run = 4  # Setting the minimum run size
  for start in range(0, n, min_run):  # Iterating through the array in chunks
    end = min(start + min_run - 1, n - 1)  # Defining the end of the current run
    insertion_sort_run(arr, start, end)  # Sorting the current run
  size = min_run  # Initializing the size for merging
  while size < n:  # While the size is less than the array length
    for left in range(0, n, 2 * size):  # Iterating through the array in pairs of runs
      mid = min(n - 1, left + size - 1)  # Finding the middle index
      right = min((left + 2 * size - 1), (n - 1))  # Finding the right index
      if mid < right:  # If the runs are valid
        merge_timsort(arr, left, mid, right)  # Merging the runs
    size *= 2  # Doubling the size for the next iteration
  return arr  # Returning the sorted array

# Binary Insertion Sort
def binary_insertion_sort_imagens(arr):
  size = len(arr)  # Getting the length of the array
  for i in range(1, size):  # Iterating through the array
    chave = arr[i]  # Storing the current value
    left = 0  # Initializing the left pointer
    right = i  # Initializing the right pointer
    while left < right:  # While the pointers are valid
      meio = (left + right) // 2  # Finding the middle index
      res = mostrar_imagens_comparacao(arr[meio], chave)  # Comparing the images
      if res == 1:  # If the user prefers the middle image
        left = meio + 1  # Searching to the right
      else:  # If the user prefers the current value
        right = meio  # Searching to the left
    arr[left+1:i+1] = arr[left:i]  # Shifting elements to the right
    arr[left] = chave  # Inserting the current value in the correct position
  return arr  # Returning the sorted array

# Part of Merge Sort
def merge(left, right):
  resultado = []  # Initializing an empty list for the merged result
  i = j = 0  # Initializing pointers for both lists
  while i < len(left) and j < len(right):  # While both lists have elements
    resposta = mostrar_imagens_comparacao(left[i], right[j])  # Comparing the images
    if resposta == 1:  # If the user prefers the left image
      resultado.append(left[i])  # Adding the left image to the result
      i += 1  # Moving the left pointer
    else:  # If the user prefers the right image
      resultado.append(right[j])  # Adding the right image to the result
      j += 1  # Moving the right pointer
  resultado.extend(left[i:])  # Adding any remaining elements from the left list
  resultado.extend(right[j:])  # Adding any remaining elements from the right list
  return resultado  # Returning the merged result

# Mergesort - main
def merge_sort_imagens(imagens):
  if len(imagens) <= 1:  # If the list has one or no elements, return it
    return imagens
  meio = len(imagens) // 2  # Finding the middle index
  left = imagens[:meio]  # Splitting the list into left half
  right = imagens[meio:]  # Splitting the list into right half
  left = merge_sort_imagens(left)  # Recursively sorting the left half
  right = merge_sort_imagens(right)  # Recursively sorting the right half
  return merge(left, right)  # Merging the sorted halves

# Quick Sort
def quick_sort_imagens(imagens):
  size = len(imagens)  # Getting the length of the list
  if size <= 1:  # If the list has one or no elements, return it
    return imagens
  else:
    less = []  # List for elements less than the pivot
    meio = []  # List for elements equal to the pivot
    more = []  # List for elements greater than the pivot
    num = size * 6 // 10  # Choosing a pivot index (60% of the list)
    pivo = imagens[num]  # Selecting the pivot element
    for cada in imagens:  # Iterating through the list
      if cada == pivo:  # If the element is equal to the pivot
        meio.append(cada)  # Adding it to the middle list
      else:
        resposta = mostrar_imagens_comparacao(cada, pivo)  # Comparing the element with the pivot
        if resposta == 1:  # If the user prefers the current element
          less.append(cada)  # Adding it to the less list
        else:
          more.append(cada)  # Adding it to the more list
    return quick_sort_imagens(less) + meio + quick_sort_imagens(more)  # Returning the sorted list

# End of sorting blocks

def processamento(imagens, algoritmo):
  if algoritmo == 1:  # If the user chose Merge Sort
    return merge_sort_imagens(imagens.copy())  # Sorting and returning the images
  elif algoritmo == 2:  # If the user chose Quick Sort
    return quick_sort_imagens(imagens.copy())  # Sorting and returning the images
  elif algoritmo == 3:  # If the user chose Binary Insertion Sort
    return binary_insertion_sort_imagens(imagens.copy())  # Sorting and returning the images
  elif algoritmo == 4:  # If the user chose Adaptive Tim Sort
    return timsort_adaptativo(imagens.copy())  # Sorting and returning the images
  elif algoritmo == 5:  # If the user chose Rotate Merge Sort
    return rotate_mergesort_imagens(imagens.copy())  # Sorting and returning the images

# Functions for output and user interaction remain
def saida_renomeado(imagens_ordenadas):
  for i, img_path in enumerate(imagens_ordenadas):  # Iterating through the sorted images
    dir_atual = os.path.dirname(img_path)  # Getting the directory of the image
    nome_arquivo_original = os.path.basename(img_path)  # Getting the original file name
    nome_novo = f"{i+1:03d}_{nome_arquivo_original}"  # Creating a new file name with a prefix
    novo_caminho = os.path.join(dir_atual, nome_novo)  # Constructing the new file path
    if not os.path.exists(novo_caminho):  # If the new file does not exist
      os.rename(img_path, novo_caminho)  # Renaming the file
    else:  # If the new file already exists
      print(f"Arquivo já existe: {novo_caminho}. Renomeando com sufixo extra.")  # Informing the user
      novo_caminho = os.path.join(dir_atual, f"{i+1:03d}_dup_{nome_arquivo_original}")  # Creating a new name with a suffix
      os.rename(img_path, novo_caminho)  # Renaming the file

def pergunta_algoritmo():
  while True:  # Loop until a valid input is received
    texto = "Algoritmo de ordenação:\n"  # Prompting the user for sorting algorithm
    # texto += "1. Merge Sort\n2. Quick Sort\n3. Binary Insertion Sort\n4. Timsort Adaptativo\n"
    texto += "1: Merge Sort.\n"  # Adding Merge Sort option
    texto += "2: Quick Sort.\n"  # Adding Quick Sort option
    texto += "3: Binary Insertion Sort.\n"  # Adding Binary Insertion Sort option
    texto += "4: Tim Sort.\n"  # Adding Tim Sort option
    texto += "5: Rotate Merge Sort.\n"  # Adding Rotate Merge Sort option
    print(texto)  # Displaying the options
    res = input("Digite: ")  # Getting user input
    try:
      res = int(res)  # Converting input to integer
      if res in [1, 2, 3, 4, 5]:  # Checking if the input is valid
        break  # Breaking the loop if valid
      else:
        print("Digite 1 até 5: ")  # Prompting for valid input
    except ValueError:  # Handling non-integer input
      print("Digite um número válido")  # Prompting for valid input
  return res  # Returning the selected algorithm

def entrada():
  diretorio = filedialog.askdirectory(title="Selecione a pasta com as imagens")  # Prompting user to select a directory
  if not diretorio:  # If no directory is selected
    return None, None  # Returning None values
  imagens = buscar_imagens(diretorio)  # Fetching images from the selected directory
  print(f"Encontradas {len(imagens)} imagens")  # Informing the user of the number of images found
  return imagens, diretorio  # Returning the list of images and the directory

if __name__ == "__main__":  # Checking if the script is run directly
  op = pergunta_algoritmo()  # Asking the user for the sorting algorithm
  imagens, diretorio = entrada()  # Getting the images and directory
  if not imagens:  # If no images were found
    print("Nenhuma imagem encontrada ou operação cancelada")  # Informing the user
    exit()  # Exiting the program
  imagens_ordenadas = processamento(imagens, op)  # Processing the images with the selected algorithm
  saida_renomeado(imagens_ordenadas)  # Renaming the sorted images