Identity: per-file hex ids, dated stems, season packs under a bare counter
From the corpus sweep (695 ranges):
- A bracketed hex id per file ("[PRiVATE] Rosewood.S02E08… [1f1793f68d5321]
[newzNZB] [01/22]") goes from the subject: nEwZ[NZB]'s 32,101 single files.
- A stem that starts with a date is specific enough for the poster-tag rule
("09-30-2008_CTC.r00" under "#sterntuary - Coast To Coast Radio Show":
still one release of 25,188 files, the stem being too short to "describe").
- Episodes under a bare counter ("[01/36] "Pokemon - S02E01 - Pallet Party
Panic.mkv"") are one release per show, season and counter total.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cb6a4554f
commit
1866e38c25
3 files changed
+72
-12
No files matched your search
+42
-10
@@ -655,6 +655,8 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
let named = without.replace('"', " ");
|
||||
// A file's size in brackets after its counter ("[26/32] - [4929] "…part24.rar"").
|
||||
let named = replace(&SIZE_AFTER_COUNTER, &named, "$1", true);
|
||||
// A per-file hex id in brackets ("… [1f1793f68d5321] [newzNZB] [01/22]").
|
||||
let named = replace(&HEX_ID, &named, " ", true);
|
||||
let remainder = subject_remainder(&named);
|
||||
// Counted without the file name: "shyla-shy-09-0195.jpg hardcore …" is no "9 of 195".
|
||||
let total = counter_total(&named);
|
||||
@@ -678,20 +680,14 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
let letters = b.iter().take_while(|c| c.is_ascii_uppercase()).count();
|
||||
let code_digits = b[letters..].iter().take_while(|c| c.is_ascii_digit()).count();
|
||||
let code = (1..=5).contains(&letters) && (2..=6).contains(&code_digits) && matches!(b.get(letters + code_digits), Some(b'-' | b'_' | b' '));
|
||||
// Not a date: /^\d{1,2}[-._]\d{1,2}[-._](?:19|20)\d{2}(?!\d)/ ("09-30-2008_BAK_JRS").
|
||||
let date = {
|
||||
let run = |at: usize| b.get(at..).map_or(0, |s| s.iter().take_while(|c| c.is_ascii_digit()).count());
|
||||
let sep = |at: usize| matches!(b.get(at), Some(b'-' | b'.' | b'_'));
|
||||
let d1 = run(0);
|
||||
let d2 = if (1..=2).contains(&d1) && sep(d1) { run(d1 + 1) } else { 0 };
|
||||
let y = d1 + 1 + d2 + 1;
|
||||
(1..=2).contains(&d2) && sep(d1 + 1 + d2) && run(y) == 4 && matches!(&b[y..y + 2], b"19" | b"20")
|
||||
};
|
||||
let date = date_led(b);
|
||||
(index_at(0) || (code && index_at(letters + code_digits + 1))) && !date
|
||||
};
|
||||
// A hashed stem under text without a counter is the upload, the text a tag ("Seit 1753 Tagen - usenet4ever.info - …").
|
||||
let hashed = file_total.is_none() && test(&HASHED, &stem);
|
||||
let tag = hashed || (!image && !numbered && !test(&AUDIO, name) && describes(&stem) && !looks_random(&stem) && is_poster_tag(&remainder, &stem));
|
||||
// A dated stem says which upload it is, short as it is ("09-30-2008_CTC.r00").
|
||||
let dated = date_led(stem.as_bytes());
|
||||
let tag = hashed || (!image && !numbered && !test(&AUDIO, name) && (describes(&stem) || dated) && !looks_random(&stem) && is_poster_tag(&remainder, &stem));
|
||||
let prefix = if image { image_prefix(&stem) } else { String::new() };
|
||||
// An image set's subject may be one word, the model's name: /^[A-Za-z]{3,}$/.
|
||||
let one_word = image && remainder.len() >= 3 && remainder.bytes().all(|c| c.is_ascii_alphabetic());
|
||||
@@ -726,6 +722,16 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
return Release { key: hash(&["stem", poster, &tracks]), name: tracks, source: "stem", file_total };
|
||||
}
|
||||
}
|
||||
// A season's episodes under a bare counter ("Pokemon - S02E01 - Pallet Party Panic.mkv"): per show, season and total.
|
||||
if let Some(total) = post_total.filter(|&n| n != 0) {
|
||||
if test(&VIDEO, name) {
|
||||
if let Some(c) = EPISODE.captures(stem.as_bytes()) {
|
||||
let show = String::from_utf8(c[1].to_vec()).unwrap();
|
||||
let season = format!("{} S{:0>2}", js::trim(&show), std::str::from_utf8(&c[2]).unwrap());
|
||||
return Release { key: hash(&["stem", poster, &season.to_lowercase(), &total.to_string()]), name: season, source: "stem", file_total };
|
||||
}
|
||||
}
|
||||
}
|
||||
// A numbered volume as the extension, within the counter's total ("DN.VPL PCK.1508" of 2195): one upload.
|
||||
let base = match (VOLUME_NUMBER.captures(stem.as_bytes()), post_total.filter(|&n| n != 0)) {
|
||||
(Some(c), Some(total)) if number(&c[2]) <= total as f64 => String::from_utf8(c[1].to_vec()).unwrap(),
|
||||
@@ -739,6 +745,9 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
Release { key: hash(&["stem", poster, key_stem]), name: stem, source: "stem", file_total }
|
||||
}
|
||||
|
||||
static HEX_ID: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\[[0-9a-f]{8,}\]"));
|
||||
static VIDEO: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(mkv|mp4|avi|m4v|ts|wmv|mov)$"));
|
||||
static EPISODE: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)^(.+?)[ ._-]+S([0-9]{1,2})E[0-9]{1,3}"));
|
||||
static SIZE_AFTER_COUNTER: LazyLock<Regex> = LazyLock::new(|| re(r"(\[[0-9]+/[0-9]+\])SP*-SP*\[[0-9]+\]"));
|
||||
static HASHED: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)^[0-9a-f]{8,}(?:_[0-9a-f]{6,}){2,}(?:-[0-9a-z]+)?$"));
|
||||
static SCENE_TRACK: LazyLock<Regex> = LazyLock::new(|| re(r"^[0-9]{1,3}[-_ .]+(.+?)(?:_-_| - |-)"));
|
||||
@@ -969,6 +978,16 @@ fn name_like(b: &[u8]) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// /^\d{1,2}[-._]\d{1,2}[-._](?:19|20)\d{2}(?!\d)/: a stem that starts with a date ("09-30-2008_BAK_JRS").
|
||||
fn date_led(b: &[u8]) -> bool {
|
||||
let run = |at: usize| b.get(at..).map_or(0, |s| s.iter().take_while(|c| c.is_ascii_digit()).count());
|
||||
let sep = |at: usize| matches!(b.get(at), Some(b'-' | b'.' | b'_'));
|
||||
let d1 = run(0);
|
||||
let d2 = if (1..=2).contains(&d1) && sep(d1) { run(d1 + 1) } else { 0 };
|
||||
let y = d1 + 1 + d2 + 1;
|
||||
(1..=2).contains(&d2) && sep(d1 + 1 + d2) && run(y) == 4 && matches!(&b[y..y + 2], b"19" | b"20")
|
||||
}
|
||||
|
||||
/// A total zero-padded wider than its index ("[01/010]"): a posting tool's mistake, no counter (brokenTotal).
|
||||
fn broken_total(index: &[u8], total: &[u8]) -> bool {
|
||||
total.len() > 1 && total[0] == b'0' && total.len() > index.len()
|
||||
@@ -1301,6 +1320,19 @@ mod image_set_tests {
|
||||
assert_ne!(vlad("185", 77, "P8250001.JPG").key, vlad("234", 77, "P5300085.JPG").key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_ids_dated_stems_and_season_packs() {
|
||||
let nz = |id: &str, i: u32, n: &str| release_identity("nEwZ", &format!(r#"[PRiVATE] Rosewood.S02E08.SPANiSH.1080p.HDTV.x264-FCC [{id}] [newzNZB] [{i:02}/22] - "{n}" yEnc"#), n);
|
||||
assert_eq!(nz("1f1793f68d5321", 1, "[05cbdb538a]_x.nfo").key, nz("65e6464bc03cad", 2, "[e8a4e2c182]_x.r03").key);
|
||||
let show = |n: &str| release_identity("#sterntuary <aa@aa.com>", &format!(r#"#sterntuary - Coast To Coast Radio Show - "{n}" yEnc"#), n);
|
||||
assert_eq!(show("09-30-2008_CTC.nfo").key, show("09-30-2008_CTC.r00").key);
|
||||
assert_ne!(show("09-30-2008_CTC.r00").key, show("10-01-2008_CTC.r00").key);
|
||||
let ep = |i: u32, n: &str| release_identity("p", &format!(r#"[{i:02}/36] "{n}""#), n);
|
||||
assert_eq!(ep(1, "Pokemon - S02E01 - Pallet Party Panic.mkv").key, ep(2, "Pokemon - S02E02 - Gotta Catch Ya Later.mkv").key);
|
||||
assert_eq!(ep(1, "Pokemon - S02E01 - Pallet Party Panic.mkv").name, "Pokemon S02");
|
||||
assert_ne!(ep(1, "Pokemon - S02E01 - Pallet Party Panic.mkv").key, ep(1, "Pokemon - S03E01 - x.mkv").key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scene_index_files_go_with_the_release() {
|
||||
let p = "SavageCobra <x@y.nhc>";
|
||||
|
||||
Reference in new issue
Block a user