/target/ | |||||
fakeplex/ | fakeplex/ | ||||
*~ | |||||
\#*\# |
[[package]] | |||||
name = "booty-core" | |||||
version = "0.1.0" | |||||
[[package]] | |||||
name = "booty-id-filebot" | |||||
version = "0.1.0" | |||||
dependencies = [ | |||||
"booty-core 0.1.0", | |||||
] | |||||
[[package]] | |||||
name = "bootyd" | |||||
version = "0.1.0" | |||||
dependencies = [ | |||||
"booty-core 0.1.0", | |||||
"booty-id-filebot 0.1.0", | |||||
] | |||||
[workspace] | |||||
members = [ | |||||
"bootyd", | |||||
"core", | |||||
"identity/filebot" | |||||
] |
# Bootybot | # Bootybot | ||||
This is a script for doing things with Plex that make it easier to do other | |||||
things. I'm not going to say what things those are, but we all know. | |||||
The new verion of Bootybot is a daemon for managing all of your tasks on the | |||||
high seas. | |||||
## Dependencies | |||||
**Note:** This is highly experimental and very early in development. | |||||
* Filebot | |||||
## Components | |||||
* `unrar` | |||||
### Server | |||||
* Python 3 | |||||
The `bootyd` program runs as a system service or in a container and should have | |||||
access to all of the files you expect it to be running on. You can talk to it | |||||
over a unix socket. | |||||
## Configuration | |||||
### CLI | |||||
The default config location is `~/.config/bootybot.conf`. Below is an example. | |||||
The `bootyctl` program is how you primarily interact with the running `bootyd` | |||||
server to trigger content ingests and perform other administration tasks. | |||||
```json | |||||
{ | |||||
"outdir": "/mnt/xvol1/plexdata", | |||||
"extractdir": "/tmp/bootybot_extract", | |||||
"simpleaction": "copy", | |||||
"overrides": [ | |||||
{ | |||||
"name": "The Simpsons", | |||||
"pattern": "The\\.Simpsons\\..*" | |||||
} | |||||
] | |||||
} | |||||
``` | |||||
### Identity Engines | |||||
The `outdir` property has the `{plex}` formatter from Filebot appended to it, | |||||
so you should not have to worry about handling different media classes (TV, | |||||
Movies, etc.). | |||||
These are things for actually identifying content. | |||||
The `extractdir` is where we extract data from RAR archives into. It usually | |||||
ends up being emptied after we finish processing, as we move data out of here | |||||
after extraction. | |||||
* `filebot` - Traditional and more versatile, but not libre | |||||
The `simpleaction` property is passed to Filebot when processing simple, | |||||
non-archived media files. I use `copy` because of how I want to handle dealing | |||||
with data *after* it's been loaded into Plex, but you might want to use `move`, | |||||
`hardlink`, or `keeplink` depending on what you're situation is like. | |||||
The `overrides` section is used to enforce that TV shows have their names | |||||
properly auto-detected, as occasionally Filebot trips up and misses it. If none | |||||
of the entries match the file then we just hope that Filebot figures it out on | |||||
its own. Note that the regexes must match the *entire* filename. So it's a | |||||
good idea to put a `.*` at the end to make sure it matches every file format | |||||
and from any "distributor". | |||||
## Usage | |||||
Once you've configured it, you can just run `booty.py` in the directory of the | |||||
"media" you're trying to prepare. | |||||
You can set the `BOOTYCFG` envvar to override the config location. | |||||
* `parley` - Bootybot's native identification engine; faster, libre, but highly experimental |
[package] | |||||
name = "bootyd" | |||||
version = "0.1.0" | |||||
authors = ["treyzania <treyzania@gmail.com>"] | |||||
[[bin]] | |||||
name = "bootyd" | |||||
path = "daemon.rs" | |||||
[[bin]] | |||||
name = "bootyctl" | |||||
path = "cli.rs" | |||||
[dependencies] | |||||
booty-core = { path = "../core" } | |||||
booty-id-filebot = { path = "../identity/filebot" } |
fn main() { | |||||
println!("also not implemented yet"); | |||||
} |
extern crate booty_core; | |||||
extern crate booty_id_filebot; | |||||
fn main() { | |||||
println!("not yet implemented"); | |||||
} |
[package] | |||||
name = "booty-core" | |||||
version = "0.1.0" | |||||
authors = ["treyzania <treyzania@gmail.com>"] | |||||
[lib] | |||||
name = "booty_core" | |||||
path = "lib.rs" | |||||
[dependencies] |
/// Identifier for a particular movie or episode. Doesn't handle different | |||||
/// releases of movies or anything like that. | |||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)] | |||||
pub enum MediaId { | |||||
Movie { | |||||
/// Name of the movie. | |||||
name: String, | |||||
/// Year released, to disambiguate. | |||||
year: String | |||||
}, | |||||
/// Episode of some TV show or anime. | |||||
Episode { | |||||
/// Name of the show. | |||||
name: String, | |||||
/// Some shows aren't released as seasons, often anime. | |||||
season: Option<u32>, | |||||
/// Episode with the season, or overall of no seasons. | |||||
episode: u32, | |||||
/// What kind of TV is it? | |||||
cat: TvCategory | |||||
} | |||||
} | |||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] | |||||
pub enum TvCategory { | |||||
/// Regular TV, typically western but mostly everything else. | |||||
Tv, | |||||
/// Anime, which often is categorized seperately. | |||||
Anime | |||||
} | |||||
/// Hint to provide to matching engine about content identity. | |||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)] | |||||
pub enum IdHint { | |||||
/// Name of thing, like "Inception", or "Game of Thrones". | |||||
Name(String), | |||||
/// Year released, usually for movies, like "2010". | |||||
Year(String), | |||||
/// Season of a show, like "S06". | |||||
Season(String), | |||||
/// Episode number, usually passed *with* a season, like "E11". | |||||
Episode(String) | |||||
} | |||||
/// Some kind of error that can happen when trying to identify a match. | |||||
#[derive(Debug)] | |||||
enum MatchError { | |||||
/// No matches found. | |||||
NoMatches, | |||||
/// Muliple matches for the file, should handle accordingly. | |||||
Multiple(Vec<MediaId>), | |||||
/// Error with the network. | |||||
NetworkError, | |||||
/// File permissions error. | |||||
PermissionsError, | |||||
/// In case they're using the wrong version of filebot. | |||||
NonlibreError, | |||||
} | |||||
// TODO Decide what we want to be able to do with an engine. | |||||
trait MatchinEngine { | |||||
fn identify(&self, path: &Path) -> Result<MediaId, MatchError>; | |||||
} |
#[cfg(test)] | |||||
mod tests { | |||||
#[test] | |||||
fn it_works() { | |||||
assert_eq!(2 + 2, 4); | |||||
} | |||||
} |
[package] | |||||
name = "booty-id-filebot" | |||||
version = "0.1.0" | |||||
authors = ["treyzania <treyzania@gmail.com>"] | |||||
[lib] | |||||
name = "booty_id_filebot" | |||||
path = "lib.rs" | |||||
[dependencies] | |||||
booty-core = { path = "../../core" } |
extern crate booty_core; | |||||
#[cfg(test)] | |||||
mod tests { | |||||
#[test] | |||||
fn it_works() { | |||||
assert_eq!(2 + 2, 4); | |||||
} | |||||
} |
[package] | |||||
name = "booty-id-parley" | |||||
version = "0.1.0" | |||||
authors = ["treyzania <treyzania@gmail.com>"] | |||||
[lib] | |||||
name = "booty_id_parley" | |||||
path = "lib.rs" | |||||
[dependencies] | |||||
booty-core = { path = "../../core" } |
extern crate booty_core; | |||||
#[cfg(test)] | |||||
mod tests { | |||||
#[test] | |||||
fn it_works() { | |||||
assert_eq!(2 + 2, 4); | |||||
} | |||||
} |
ssh_opts= | |||||
ssh_prop=localhost | |||||
remote_rx_dir=rx | |||||
remote_mv_dir=rename | |||||
#!/bin/bash | |||||
MAIN_CONFIG="~/.config/bootybot/plunder.conf" | |||||
if [ -f localconfig ]; then | |||||
source localconfig-plunder | |||||
elif [ -f "$MAIN_CONFIG" ]; then | |||||
source $MAIN_CONFIG | |||||
else | |||||
echo 'error: no valid config found' | |||||
exit 1 | |||||
fi | |||||
# This is from the Deluge Execute plugin. | |||||
tid=$1 | |||||
tname=$2 | |||||
tpath=$3 | |||||
######## | |||||
troot="$tpath/$tid" | |||||
rxpath="$remote_rx_dir/$tid" | |||||
mvpath="$remote_mv_dir/$tid" | |||||
function remote_exec () { | |||||
ssh $ssh_opts $ssh_prop $@ | |||||
} | |||||
# Setup. | |||||
remote_exec mkdir -p $remote_rx_dir | |||||
remote_exec mkdir -p $remote_mv_dir | |||||
# Actual transfer. | |||||
remote_exec mkdir "$txpath" | |||||
scp -r $troot $ssh_prop:"$remote_rx_dir" | |||||
# Cleanup. This should be very fast. | |||||
remote_exec mv "$rxpath" "$mvpath" | |||||
# TODO Automaticially invoke (& fork) the rename script? | |||||