Initial Commit.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal 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
6
Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "file_grouper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
7
README.adoc
Normal file
7
README.adoc
Normal 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.
|
||||
0
dummy/a - 1/a - 1-landscape.png
Normal file
0
dummy/a - 1/a - 1-landscape.png
Normal file
0
dummy/a - 1/a - 1.mkv
Normal file
0
dummy/a - 1/a - 1.mkv
Normal file
0
dummy/a/a-landscape.jpg
Normal file
0
dummy/a/a-landscape.jpg
Normal file
0
dummy/a/a.mkv
Normal file
0
dummy/a/a.mkv
Normal file
0
dummy/a/a.nto
Normal file
0
dummy/a/a.nto
Normal file
0
dummy/a/a.png
Normal file
0
dummy/a/a.png
Normal file
0
dummy/b/b.mkv
Normal file
0
dummy/b/b.mkv
Normal file
0
dummy/c - 1/c - 1-landscape.png
Normal file
0
dummy/c - 1/c - 1-landscape.png
Normal file
0
dummy/c - 1/c - 1.mkv
Normal file
0
dummy/c - 1/c - 1.mkv
Normal file
20
pyproject.toml
Normal file
20
pyproject.toml
Normal 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
45
src/file_grouper.py
Normal 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
132
src/main.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user