Image sets by their numbered name, digits kept
Without a file counter a subject-named image release was bounded by the
image's name with all numbers removed:
- STIRILING SILVER's sets stp250x…stp465x became one 44k-image release
(all "stp x"); picture threads of many sets (keira_s04, keira_s05,
perla_s01, apneatic-7, -8 …) one release each.
- Free-text names without numbers ("JimAndJoey Kindly Husband-to-Be.JPG")
made each image a set: one thread was 1,177 releases.
The bound is now the name up to its last number with its digits
("stp250x", "keira_s05_l keira_s05_", "apneatic-7-"), and nothing for a
name without numbers. models fixture: 172 -> 754 releases, by set. Both
implementations; tests; fixtures identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0577b16e5e
commit
c4f48a6d4c
3 files changed
+36
-2
No files matched your search
+23
-1
@@ -414,6 +414,18 @@ static AUDIO: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(mp3|flac|m4a|m4b|og
|
|||||||
static DOCUMENT: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(pdf|epub|mobi|azw3?|djvu|cbr|cbz|cb7|chm|lit|docx?)$"));
|
static DOCUMENT: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(pdf|epub|mobi|azw3?|djvu|cbr|cbz|cb7|chm|lit|docx?)$"));
|
||||||
|
|
||||||
/// An image set's name without a counter: up to the last number, without numbers.
|
/// An image set's name without a counter: up to the last number, without numbers.
|
||||||
|
/// `/\d/.test(stem) ? stem.replace(/\d+\D*$/, '') : ''`: an image's set, digits kept.
|
||||||
|
fn image_set(stem: &str) -> String {
|
||||||
|
let b = stem.as_bytes();
|
||||||
|
match b.iter().rposition(u8::is_ascii_digit) {
|
||||||
|
Some(last) => {
|
||||||
|
let start = b[..=last].iter().rposition(|c| !c.is_ascii_digit()).map_or(0, |i| i + 1);
|
||||||
|
stem[..start].to_string()
|
||||||
|
}
|
||||||
|
None => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn image_prefix(stem: &str) -> String {
|
fn image_prefix(stem: &str) -> String {
|
||||||
let b = stem.as_bytes();
|
let b = stem.as_bytes();
|
||||||
let cut = match b.iter().rposition(u8::is_ascii_digit) {
|
let cut = match b.iter().rposition(u8::is_ascii_digit) {
|
||||||
@@ -599,7 +611,8 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
|||||||
// An image set's subject may be one word, the model's name: /^[A-Za-z]{3,}$/.
|
// 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());
|
let one_word = image && remainder.len() >= 3 && remainder.bytes().all(|c| c.is_ascii_alphabetic());
|
||||||
if !tag && (names_release(&remainder) || one_word) {
|
if !tag && (names_release(&remainder) || one_word) {
|
||||||
let bound = counter_total(subject).unwrap_or_else(|| prefix.clone());
|
// Without a counter an image's set: its name up to its last number, digits kept; none without numbers.
|
||||||
|
let bound = counter_total(subject).unwrap_or_else(|| if image { image_set(&stem) } else { String::new() });
|
||||||
return Release { key: hash(&["subject", poster, &remainder, &bound]), name: remainder, source: "subject", file_total };
|
return Release { key: hash(&["subject", poster, &remainder, &bound]), name: remainder, source: "subject", file_total };
|
||||||
}
|
}
|
||||||
if let Some(total) = post_total.filter(|&n| n != 0 && !tag) {
|
if let Some(total) = post_total.filter(|&n| n != 0 && !tag) {
|
||||||
@@ -1117,6 +1130,15 @@ mod image_set_tests {
|
|||||||
assert!(!random_poster("w.merklein@freenet.de (muffin)"));
|
assert!(!random_poster("w.merklein@freenet.de (muffin)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn image_sets_by_their_numbered_name() {
|
||||||
|
let silver = |n: &str| release_identity("p", &format!(r#"The excellent STIRILING SILVER - Always popular - "{n}" yEnc 320794 bytes"#), n);
|
||||||
|
assert_eq!(silver("stp250x001.jpg").key, silver("stp250x002.jpg").key);
|
||||||
|
assert_ne!(silver("stp250x001.jpg").key, silver("stp305x001.jpg").key);
|
||||||
|
let mint = |n: &str| release_identity("Cert", &format!(r#"I'm not a breath mint, but I am not all there either. [{n}" yEnc]"#), n);
|
||||||
|
assert_eq!(mint("JimAndJoey Kindly Husband-to-Be.JPG").key, mint("Oops OnlyOneMore.JPG").key);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dates_are_no_counters() {
|
fn dates_are_no_counters() {
|
||||||
use super::counter_total;
|
use super::counter_total;
|
||||||
|
|||||||
@@ -254,8 +254,12 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
|
|||||||
// Bounded by the counter's total, so the uploads a poster names alike are apart while the files
|
// Bounded by the counter's total, so the uploads a poster names alike are apart while the files
|
||||||
// of one upload (par2 and nfo included) share it (LOG.md 2026-10-09 (9)).
|
// of one upload (par2 and nfo included) share it (LOG.md 2026-10-09 (9)).
|
||||||
const total = counterTotal(input.subject)
|
const total = counterTotal(input.subject)
|
||||||
|
// Without a counter an image's set bounds it: its name up to its last number, digits kept ("stp250x001.jpg"
|
||||||
|
// → "stp250x": STIRILING SILVER's 300 sets were one release of 44k images); a name without numbers names no
|
||||||
|
// set ("Kindly Husband-to-Be.JPG": a thread of 1,177 releases), so the subject alone (LOG.md 2026-10-12 (2)).
|
||||||
|
const set = image && /\d/.test(stem) ? stem.replace(/\d+\D*$/, '') : ''
|
||||||
return {
|
return {
|
||||||
key: hash('subject', poster, remainder, total ?? prefix),
|
key: hash('subject', poster, remainder, total ?? set),
|
||||||
name: remainder,
|
name: remainder,
|
||||||
fileTotal,
|
fileTotal,
|
||||||
source: 'subject',
|
source: 'subject',
|
||||||
|
|||||||
@@ -214,6 +214,14 @@ test.group('release / par2 names', () => {
|
|||||||
assert.isFalse(randomPoster('w.merklein@freenet.de (muffin)'))
|
assert.isFalse(randomPoster('w.merklein@freenet.de (muffin)'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('image sets by their numbered name, digits kept; names without numbers name no set', ({ assert }) => {
|
||||||
|
const silver = (n: string) => releaseIdentity({ poster: 'p', subject: `The excellent STIRILING SILVER - Always popular - "${n}" yEnc 320794 bytes`, name: n, weak: false })!
|
||||||
|
assert.equal(silver('stp250x001.jpg').key, silver('stp250x002.jpg').key)
|
||||||
|
assert.notEqual(silver('stp250x001.jpg').key, silver('stp305x001.jpg').key)
|
||||||
|
const mint = (n: string) => releaseIdentity({ poster: 'Cert', subject: `I'm not a breath mint, but I am not all there either. [${n}" yEnc]`, name: n, weak: false })!
|
||||||
|
assert.equal(mint('JimAndJoey Kindly Husband-to-Be.JPG').key, mint('Oops OnlyOneMore.JPG').key)
|
||||||
|
})
|
||||||
|
|
||||||
test('dates are no file counters', async ({ assert }) => {
|
test('dates are no file counters', async ({ assert }) => {
|
||||||
const { counterTotal } = await import('#lib/release/identity')
|
const { counterTotal } = await import('#lib/release/identity')
|
||||||
assert.isUndefined(counterTotal('Avira AntiVir Rescue System 08-29-2011.rar'))
|
assert.isUndefined(counterTotal('Avira AntiVir Rescue System 08-29-2011.rar'))
|
||||||
|
|||||||
Reference in new issue
Block a user