From 3401f51e9eccecd44bde315c12bd5f3bde50c491 Mon Sep 17 00:00:00 2001 From: Cutieguwu Date: Wed, 19 Feb 2025 08:35:49 -0500 Subject: [PATCH] Initial Commit. --- .gitignore | 1 + Cargo.lock | 7 ++ Cargo.toml | 6 ++ README.adoc | 7 ++ dummy/a - 1/a - 1-landscape.png | 0 dummy/a - 1/a - 1.mkv | 0 dummy/a/a-landscape.jpg | 0 dummy/a/a.mkv | 0 dummy/a/a.nto | 0 dummy/a/a.png | 0 dummy/b/b.mkv | 0 dummy/c - 1/c - 1-landscape.png | 0 dummy/c - 1/c - 1.mkv | 0 pyproject.toml | 20 +++++ src/file_grouper.py | 45 +++++++++++ src/main.rs | 132 ++++++++++++++++++++++++++++++++ 16 files changed, 218 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.adoc create mode 100644 dummy/a - 1/a - 1-landscape.png create mode 100644 dummy/a - 1/a - 1.mkv create mode 100644 dummy/a/a-landscape.jpg create mode 100644 dummy/a/a.mkv create mode 100644 dummy/a/a.nto create mode 100644 dummy/a/a.png create mode 100644 dummy/b/b.mkv create mode 100644 dummy/c - 1/c - 1-landscape.png create mode 100644 dummy/c - 1/c - 1.mkv create mode 100644 pyproject.toml create mode 100644 src/file_grouper.py create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e488cf9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "file_grouper" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5c26b05 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "file_grouper" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..8f6e206 --- /dev/null +++ b/README.adoc @@ -0,0 +1,7 @@ +:source-highlighter: highlight.js +:highlightjs-languages: python, rust +:toc: auto + += *file_grouper* + +I messed up making my media server, and needed a simple way to group a mess of files with common names together. \ No newline at end of file diff --git a/dummy/a - 1/a - 1-landscape.png b/dummy/a - 1/a - 1-landscape.png new file mode 100644 index 0000000..e69de29 diff --git a/dummy/a - 1/a - 1.mkv b/dummy/a - 1/a - 1.mkv new file mode 100644 index 0000000..e69de29 diff --git a/dummy/a/a-landscape.jpg b/dummy/a/a-landscape.jpg new file mode 100644 index 0000000..e69de29 diff --git a/dummy/a/a.mkv b/dummy/a/a.mkv new file mode 100644 index 0000000..e69de29 diff --git a/dummy/a/a.nto b/dummy/a/a.nto new file mode 100644 index 0000000..e69de29 diff --git a/dummy/a/a.png b/dummy/a/a.png new file mode 100644 index 0000000..e69de29 diff --git a/dummy/b/b.mkv b/dummy/b/b.mkv new file mode 100644 index 0000000..e69de29 diff --git a/dummy/c - 1/c - 1-landscape.png b/dummy/c - 1/c - 1-landscape.png new file mode 100644 index 0000000..e69de29 diff --git a/dummy/c - 1/c - 1.mkv b/dummy/c - 1/c - 1.mkv new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0d71d2d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "file_grouper" +description = "Simple script that groups files into folders according to common names." +authors = [ + {name = "Cutieguwu"} +] + +version = "0.0.1" +requires-python = ">=3.11" +dependencies = [ + "icecream>=2.1" +] + +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: POSIX :: Linux", + "Natural Language :: English" +] \ No newline at end of file diff --git a/src/file_grouper.py b/src/file_grouper.py new file mode 100644 index 0000000..2fe7793 --- /dev/null +++ b/src/file_grouper.py @@ -0,0 +1,45 @@ +#!~/.pyenv/versions/3.11.6/bin/python +# +# Copyright (c) 2024 Cutieguwu | Olivia Brooks +# +# -*- coding: utf-8 -*- +# @Title: file_grouper +# @Author: Cutieguwu | Olivia Brooks +# @Description: Simple Math practise helper. +# +# @Script: file_grouper.py +# @Date Created: 22 Jan, 2025 +# @Last Modified: 22 Jan, 2025 +# @Last Modified by: Cutieguwu | Olivia Brooks +# -------------------------------------------- + +from os import path, makedirs +from pathlib import Path + + +BASE_PATH = Path(f'{path.dirname(__file__)}/../dummy') + +title_groups: list[str] = [] + +# Use *.mkv to determine groups. +for file in BASE_PATH.glob('*.mkv'): + title_groups.append(path.basename(file.with_suffix(''))) + +title_groups = sorted(title_groups, key=lambda x: (len(x), x)) +title_groups.reverse() + +for title in title_groups: + group_path = f'{BASE_PATH.name}/{title}' + + # Create a dir if dir doesn't exist. + # Use if statement instead of handling OSError. + if not path.exists(group_path): + print(f'$BASE_PATH/{title}/ does not exist. Creating path...') + makedirs(group_path) + + for file in BASE_PATH.glob(f'{title}*'): + # Find file entries, ignore dir. + if path.isfile(file): + # Move file to grouping dir. + print(f'Moving "{path.basename(file)}" -> $BASE_PATH/{title}/') + Path(file).rename(f'{group_path}/{path.basename(file)}') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2603eb8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,132 @@ +use std::{env, fs, io, path}; + +fn main() { + // Should really use std::env for this, but I'm lazy and will recompile. + let dir_base: &String = &String::from("/mnt/Metacrisis2"); + + // Files in dir_base + let mut files: Vec = match get_files(dir_base) { + Ok(files) => files, + Err(err) => panic!("{err}") + }; + + // Dirs in dir_base + let dirs: Vec = match get_dirs(dir_base) { + Ok(dirs) => dirs, + Err(err) => panic!("{err}") + }; + + // Get all common titles and sort them longest to shortest. + // This is to prevent issues with long titles containing short titles. + let title_groups: Vec = { + let mut titles: Vec = get_titles(files.to_owned(), ".mkv"); + + // Sort, producing inverted pattern of a <= b + titles.sort_by(|a, b| b.chars().count().cmp(&a.chars().count())); + + titles + }; + + for group in title_groups { + let dir_target: &String = &format!("{}/{}", dir_base, group); + + // If target dir does not exist, make it. + if dirs.iter().find(|dir| **dir == group).is_none() { + match fs::create_dir(dir_target) { + Ok(_) => (), + Err(err) => panic!("{err}") + } + } + + let mut files_moved: Vec = vec![]; + + for file_name in &files { + if file_name.contains(group.as_str()) { + let from: String = format!("{}/{}", dir_base, file_name); + let to: String = format!("{}/{}", dir_target, file_name); + + if let Err(err) = move_file(from.as_str(), to.as_str()) { + panic!("{err}") + } + + files_moved.push(file_name.to_owned()); + } + } + + // Forget files that have already been moved + for file in files_moved { + let index: usize = files.iter() + .position(|f| *f == file) + .unwrap(); + + files.remove(index); + } + } + +} + +#[allow(dead_code)] +fn get_path() -> io::Result { + Ok(String::from(env::current_dir()?.to_str().unwrap())) +} + +fn get_files(path: &str) -> io::Result> { + let entries: fs::ReadDir = fs::read_dir(path)?; + + Ok(entries.filter_map(|entry| { + let path: path::PathBuf = entry.ok()?.path(); + + if path.is_file() { + path.file_name()? + .to_str() + .map(|s| s.to_owned()) + } else { + None + } + }) + .collect()) +} + +fn get_dirs(path: &str) -> io::Result> { + let entries: fs::ReadDir = fs::read_dir(path)?; + + let dir_entries: Vec = entries + .filter_map(|entry| { + let path: path::PathBuf = entry.ok()?.path(); + if path.is_dir() { + path.file_name()? + .to_str() + .map(|s| s.to_owned()) + } else { + None + } + }) + .collect(); + + Ok(dir_entries) +} + +fn get_titles( + files: Vec, + extension_pat: &str +) -> Vec { + files.iter().filter_map(|file| { + if file.ends_with(extension_pat) { + Some(file.strip_suffix(".mkv")?.to_string()) + } else { + None + } + }) + .collect() +} + +fn move_file(from: &str, to: &str) -> io::Result<()> { + if let Err(err) = fs::rename(from, to) { + match fs::copy(from, to) { + Ok(_) => fs::remove_file(from), + Err(_) => Err(err) + } + } else { + Ok(()) + } +} \ No newline at end of file