Initial Commit.

This commit is contained in:
Cutieguwu
2025-02-19 08:35:49 -05:00
commit 3401f51e9e
16 changed files with 218 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@@ -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"

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "file_grouper"
version = "0.1.0"
edition = "2021"
[dependencies]

7
README.adoc Normal file
View File

@@ -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.

View File

0
dummy/a - 1/a - 1.mkv Normal file
View File

0
dummy/a/a-landscape.jpg Normal file
View File

0
dummy/a/a.mkv Normal file
View File

0
dummy/a/a.nto Normal file
View File

0
dummy/a/a.png Normal file
View File

0
dummy/b/b.mkv Normal file
View File

View File

0
dummy/c - 1/c - 1.mkv Normal file
View File

20
pyproject.toml Normal file
View File

@@ -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"
]

45
src/file_grouper.py Normal file
View File

@@ -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)}')

132
src/main.rs Normal file
View File

@@ -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<String> = match get_files(dir_base) {
Ok(files) => files,
Err(err) => panic!("{err}")
};
// Dirs in dir_base
let dirs: Vec<String> = 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<String> = {
let mut titles: Vec<String> = 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<String> = 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<String> {
Ok(String::from(env::current_dir()?.to_str().unwrap()))
}
fn get_files(path: &str) -> io::Result<Vec<String>> {
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<Vec<String>> {
let entries: fs::ReadDir = fs::read_dir(path)?;
let dir_entries: Vec<String> = 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<String>,
extension_pat: &str
) -> Vec<String> {
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(())
}
}