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?)$"));
|
||||
|
||||
/// 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 {
|
||||
let b = stem.as_bytes();
|
||||
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,}$/.
|
||||
let one_word = image && remainder.len() >= 3 && remainder.bytes().all(|c| c.is_ascii_alphabetic());
|
||||
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 };
|
||||
}
|
||||
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)"));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn dates_are_no_counters() {
|
||||
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
|
||||
// of one upload (par2 and nfo included) share it (LOG.md 2026-10-09 (9)).
|
||||
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 {
|
||||
key: hash('subject', poster, remainder, total ?? prefix),
|
||||
key: hash('subject', poster, remainder, total ?? set),
|
||||
name: remainder,
|
||||
fileTotal,
|
||||
source: 'subject',
|
||||
|
||||
@@ -214,6 +214,14 @@ test.group('release / par2 names', () => {
|
||||
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 }) => {
|
||||
const { counterTotal } = await import('#lib/release/identity')
|
||||
assert.isUndefined(counterTotal('Avira AntiVir Rescue System 08-29-2011.rar'))
|
||||
|
||||
Reference in new issue
Block a user