feat(media): Implement directory listing
Work on tokenizing file names for TMDB API calls in future.
This commit is contained in:
parent
2b47cf5321
commit
cb687fa808
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -94,6 +94,12 @@ version = "3.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
|
@ -103,6 +109,17 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfb"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fnv",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -206,6 +223,12 @@ version = "1.0.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
|
@ -244,6 +267,15 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||
dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inquire"
|
||||
version = "0.6.2"
|
||||
|
@ -367,6 +399,7 @@ name = "plex-media-ingest"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"infer",
|
||||
"inquire",
|
||||
"log",
|
||||
"serde",
|
||||
|
@ -583,6 +616,12 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
infer = "0.15.0"
|
||||
inquire = "0.6.2"
|
||||
log = "0.4.20"
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, fs::{self, DirEntry}, error::Error};
|
||||
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
use crate::media::handle_media;
|
||||
|
||||
fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||
/*fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| entry.depth() == 0 || !s.starts_with("."))
|
||||
.map(|s| entry.depth() == 0 || (!s.starts_with(".") && !s.starts_with("@"))) // todo!: Allow ignored chars to be configured, here, @ is QNAP special folders
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn list_files(path: PathBuf) -> Vec<PathBuf>{
|
||||
pub fn walk_path(path: PathBuf) -> Vec<PathBuf> {
|
||||
let mut entries: Vec<PathBuf> = vec![];
|
||||
WalkDir::new(path)
|
||||
.into_iter()
|
||||
|
@ -18,4 +18,52 @@ pub fn list_files(path: PathBuf) -> Vec<PathBuf>{
|
|||
.filter_map(|v| v.ok())
|
||||
.for_each(|x| entries.push(x.into_path()));
|
||||
entries
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn search_path(path: PathBuf) -> Result<(), Box<dyn Error>> {
|
||||
let entries = fs::read_dir(path)?;
|
||||
let mut files: Vec<DirEntry> = Vec::new();
|
||||
let mut dirs: Vec<DirEntry> = Vec::new();
|
||||
|
||||
// Put all files and folders in corresponding vectors
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
dirs.push(entry);
|
||||
} else if file_type.is_file() {
|
||||
files.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dirs.len() == 0 {
|
||||
// No folders present, assuming there are only distinct media files
|
||||
for file in files {
|
||||
handle_media(file);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
Look at current directory:
|
||||
Only directories, no media files ->
|
||||
Media must be in subfolders, look at name of folder (in case media file has cryptic name) and traverse into it, look at media files
|
||||
|
||||
Media files present, folders as well ->
|
||||
Treat media as media to add, traverse into subfolders and look for eventual extra content
|
||||
|
||||
Media file(s), but no folders present ->
|
||||
Treat media as media to add
|
||||
*/
|
||||
|
||||
/*
|
||||
Use folder/file name as name to look up on tmdb (replace . with ' ' till first occurence of non alphanumeric symbol ([]())) (For shows, look for SxxEyy or similar tokens, if single file assume movie by default)
|
||||
If there is a token with only 4 digits (and maybe parantheses), assume this is a year, add it to tmdb search, retry search without 'year' if result is empty
|
||||
Show user file with title(s) found on tmdb, make user select one
|
||||
Remember selection and look in current folder for extra content (deleted scenes, trailers, featurettes) -> show user what files were found and have them select which files they want
|
||||
For each selection show file name plus try to match to extras category, show user selection of which kind of extra it is, then allow them to enter a arbitary name (prefill from file if possible)
|
||||
*/
|
10
src/main.rs
10
src/main.rs
|
@ -1,5 +1,6 @@
|
|||
mod config;
|
||||
mod directory;
|
||||
mod media;
|
||||
|
||||
use log::*;
|
||||
use clap::Parser;
|
||||
|
@ -62,10 +63,13 @@ fn main() {
|
|||
args.path.unwrap()
|
||||
};
|
||||
|
||||
let files = directory::list_files(search_path);
|
||||
//let files = directory::walk_path(search_path);
|
||||
directory::search_path(search_path).unwrap();
|
||||
|
||||
for file in files {
|
||||
/*for file in files.clone() {
|
||||
info!("Found: {}", file.to_str().unwrap());
|
||||
}
|
||||
}*/
|
||||
|
||||
//search_media(files).unwrap();
|
||||
|
||||
}
|
109
src/media.rs
Normal file
109
src/media.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use std::{path::PathBuf, error::Error, io::Read, fs::{File, DirEntry}, cmp};
|
||||
use infer;
|
||||
use log::{info, warn, error, trace, debug};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MediaName {
|
||||
name: String,
|
||||
year: String
|
||||
}
|
||||
|
||||
fn get_file_header(path: PathBuf) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let f = File::open(path)?;
|
||||
|
||||
let limit = f
|
||||
.metadata()
|
||||
.map(|m| cmp::min(m.len(), 8192) as usize + 1)
|
||||
.unwrap_or(0);
|
||||
let mut bytes = Vec::with_capacity(limit);
|
||||
f.take(8192).read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn token_year_likely(t: &&str) -> bool {
|
||||
if t.len() == 6 &&
|
||||
(t.starts_with('[') && t.ends_with(']')) ||
|
||||
(t.starts_with('(') && t.ends_with(')')) ||
|
||||
(t.starts_with('{') && t.ends_with('}'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn token_valid(t: &&str) -> bool {
|
||||
if
|
||||
t.eq_ignore_ascii_case("dvd") ||
|
||||
t.eq_ignore_ascii_case("bluray") ||
|
||||
t.eq_ignore_ascii_case("webrip") ||
|
||||
t.eq_ignore_ascii_case("web") ||
|
||||
t.eq_ignore_ascii_case("uhd") ||
|
||||
t.eq_ignore_ascii_case("hd") ||
|
||||
t.eq_ignore_ascii_case("tv") ||
|
||||
t.eq_ignore_ascii_case("tvrip") ||
|
||||
t.eq_ignore_ascii_case("1080p") ||
|
||||
t.eq_ignore_ascii_case("1080i") ||
|
||||
t.eq_ignore_ascii_case("2160p") ||
|
||||
(t.len() != 6 && t.starts_with('[') && t.ends_with(']')) ||
|
||||
(t.len() != 6 && t.starts_with('(') && t.ends_with(')')) ||
|
||||
(t.len() != 6 && t.starts_with('{') && t.ends_with('}'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn find_media_name(file_name: String) -> MediaName {
|
||||
let mut tokens: Vec<&str> = file_name.split(&['-', ' ', ':', '@', '.'][..]).filter(|t| token_valid(t)).collect();
|
||||
trace!("Tokens are: {:#?}", tokens);
|
||||
|
||||
// Remove last token (file ext)
|
||||
_ = tokens.pop();
|
||||
|
||||
let mut year = String::new();
|
||||
let mut name = String::new();
|
||||
|
||||
for token in tokens {
|
||||
if token_year_likely(&token) {
|
||||
year = token.strip_prefix(['(', '[', '{']).unwrap().strip_suffix([')', ']', '}']).unwrap().to_string();
|
||||
} else if token.len() != 0 {
|
||||
name.push_str(token);
|
||||
name.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove last added space
|
||||
name.pop();
|
||||
|
||||
let media_name = MediaName { name: name, year: year };
|
||||
debug!("Name is now: {:#?}", media_name);
|
||||
media_name
|
||||
}
|
||||
|
||||
fn video_file_handler(entry: DirEntry) {
|
||||
let path = entry.path();
|
||||
info!("Found video file: {:#?}", path);
|
||||
|
||||
let file_name = path.file_name().unwrap_or_default();
|
||||
trace!("File name is: {:#?}", file_name);
|
||||
|
||||
let name = find_media_name(file_name.to_str().unwrap_or_default().to_string());
|
||||
todo!("Do TMDB API calls");
|
||||
}
|
||||
|
||||
pub fn handle_media(entry: DirEntry) {
|
||||
if entry.file_type().is_ok_and(|t| t.is_dir()) {
|
||||
warn!("Directory passed to handle_media, {:#?} will be skipped", entry);
|
||||
return
|
||||
}
|
||||
|
||||
match get_file_header(entry.path()) {
|
||||
Ok(header) => {
|
||||
// Handle video files
|
||||
if infer::is_video(&header) {
|
||||
video_file_handler(entry);
|
||||
}
|
||||
},
|
||||
Err(error) => error!("Can not get file header for {:#?}, Error: {:#?}", entry, error),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue