Image sets: sizes in brackets and one-word set names
Test and deploy / test (push) Successful in 45s
Test and deploy / edge (push) Successful in 1m51s
Test and deploy / deploy (push) Successful in 1m19s

Old picture groups split every image into a release (the reset's first
hour: 583k releases, 447k of one file; a.b.pictures.erotica.high-heels
262k): "Aletta Ocean - \"100628-035.jpg\" yEnc [347K]" kept its size in
the remainder, and "Corie - \"2877-041.jpg\" yEnc [290K]" named its set
with one word, which namesRelease (two words) refused, so the image's
own name keyed it. Sizes like [347K] (and a cut-off "336573 byt") are
removed; for images a one-word subject (3+ letters) names the set. On
100k high-heels articles: 4,509 releases -> 192. Both implementations;
tests; fixtures identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-11 21:25:18 +02:00
1 parent 47b445ffb0
commit b1213fbea0
3 files changed
+37 -6

No files matched your search

+24 -3
View File
@@ -304,7 +304,8 @@ static OF_COUNTER: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)[\[(]?\b(?:fileSP
static DASH_COUNTER: LazyLock<Regex> = LazyLock::new(|| re(r"\[([0-9]+)-([0-9]+)\]"));
static SLASH_COUNTER: LazyLock<Regex> = LazyLock::new(|| re(r"\b([0-9]+)/([0-9]+)\b"));
static ZERO_COUNTER: LazyLock<Regex> = LazyLock::new(|| re(r"\b(0[0-9]*)-([0-9]+)\b"));
static SIZES: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\b[0-9]+(?:[.,][0-9]+)?SP*(?:[kmgt]i?b|bytes?)\b"));
static SIZES: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\b[0-9]+(?:[.,][0-9]+)?SP*(?:[kmgt]i?b|bytes?|byt)\b"));
static BRACKET_SIZES: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\[SP*[0-9]+(?:[.,][0-9]+)?SP*[kmgt]SP*\]"));
static EMPTY_BRACKETS: LazyLock<Regex> = LazyLock::new(|| re(r" ?(?:\[ ?\]|\( ?\)) ?"));
fn number(digits: &[u8]) -> f64 {
@@ -326,7 +327,8 @@ fn remove_counters_and_sizes(s: &str) -> String {
let s = replace_counters(&DASH_COUNTER, &s);
let s = replace_counters(&SLASH_COUNTER, &s);
let s = replace_counters(&ZERO_COUNTER, &s);
replace(&SIZES, &s, " ", true)
let s = replace(&SIZES, &s, " ", true);
replace(&BRACKET_SIZES, &s, " ", true)
}
/// The end of subjectRemainder: what's left of brackets, double spaces, trim (after the separators became single spaces).
@@ -532,7 +534,9 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
};
let tag = !image && !numbered && !test(&AUDIO, name) && describes(&stem) && !looks_random(&stem) && is_poster_tag(&remainder, &stem);
let prefix = if image { image_prefix(&stem) } else { String::new() };
if !tag && names_release(&remainder) {
// 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());
return Release { key: hash(&["subject", poster, &remainder, &bound]), name: remainder, source: "subject", file_total };
}
@@ -1024,4 +1028,21 @@ mod numbered_tests {
assert_eq!(a.name, "ai engineer complete");
}
}
#[cfg(test)]
mod image_set_tests {
use super::{release_identity, subject_remainder, subject_remainder_re};
#[test]
fn sizes_in_brackets_and_one_word_sets() {
for s in [r#"Aletta Ocean - "100628-035.jpg" yEnc [347K]"#, r#"Corie - "2877-041.jpg" yEnc [290K]"#, "x - !!!blob2010-102katie009.jpg 336573 byt"] {
assert_eq!(subject_remainder(s), subject_remainder_re(s), "{s}");
}
assert_eq!(subject_remainder(r#"Aletta Ocean - "100628-035.jpg" yEnc [347K]"#), "Aletta Ocean");
let a = release_identity("p", r#"Corie - "2877-041.jpg" yEnc [290K]"#, "2877-041.jpg");
let b = release_identity("p", r#"Corie - "2877-042.jpg" yEnc [301K]"#, "2877-042.jpg");
assert_eq!(a.key, b.key);
assert_eq!(a.name, "Corie");
}
}
}
+5 -3
View File
@@ -74,8 +74,9 @@ export function subjectRemainder(subject: string): string {
.replace(/\[(\d+)-(\d+)\]/g, counter)
.replace(/\b(\d+)\/(\d+)\b/g, counter)
.replace(/\b(0\d*)-(\d+)\b/g, counter)
// File sizes ("76.88 MB", "335555 Bytes") differ per file.
.replace(/\b\d+(?:[.,]\d+)?\s*(?:[kmgt]i?b|bytes?)\b/gi, ' ')
// File sizes ("76.88 MB", "335555 Bytes", "336573 byt" cut off, "[347K]") differ per file.
.replace(/\b\d+(?:[.,]\d+)?\s*(?:[kmgt]i?b|bytes?|byt)\b/gi, ' ')
.replace(/\[\s*\d+(?:[.,]\d+)?\s*[kmgt]\s*\]/gi, ' ')
// File sizes follow "yEnc" at the end; other numbers (years, post ids) stay.
.replace(/\byEnc\b(\s+\d+)?\s*$/i, ' ')
.replace(/\byEnc\b/gi, ' ')
@@ -217,7 +218,8 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
// An image set without a counter: the image's name up to its last number, without numbers
// ("Kitchen Hottie 1 set233-76-lg" → "Kitchen Hottie set", "bm_jessica_blb_h087" → "bm jessica blb h").
const prefix = image ? stem.replace(/\d+\D*$/, '').replace(/\d+/g, ' ').replace(/[\s._-]+/g, ' ').trim() : ''
if (!tag && namesRelease(remainder)) {
// An image set's subject may be one word, the model's name ("Corie - "2877-041.jpg" yEnc [290K]").
if (!tag && (namesRelease(remainder) || (image && /^[A-Za-z]{3,}$/.test(remainder)))) {
// 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)
+8
View File
@@ -175,6 +175,14 @@ test.group('release / par2 names', () => {
assert.equal(par2ReleaseName(['Album.Name-2024-GRP.part01.rar', 'Album.Name-2024-GRP.part02.rar']), 'Album.Name-2024-GRP')
assert.equal(par2ReleaseName(['only.one.file.2024.mkv']), 'only.one.file.2024')
})
test('image sets: sizes in brackets and one-word set names', ({ assert }) => {
const id = (subject: string, name: string) => releaseIdentity({ poster: 'p', subject, name, weak: false })!
assert.equal(id('Aletta Ocean - "100628-035.jpg" yEnc [347K]', '100628-035.jpg').key, id('Aletta Ocean - "100628-036.jpg" yEnc [298K]', '100628-036.jpg').key)
const corie = id('Corie - "2877-041.jpg" yEnc [290K]', '2877-041.jpg')
assert.equal(corie.key, id('Corie - "2877-042.jpg" yEnc [301K]', '2877-042.jpg').key)
assert.equal(corie.name, 'Corie')
})
test('numbered items of a set stay one release under the set name (a course, not a poster tag)', ({ assert }) => {
const id = (subject: string, name: string) => releaseIdentity({ poster: 'Poster49118 <Poster49118@generic.mail>', subject, name, weak: false })!
const a = id('ai-engineer-complete [401/465] - "003 Defining Tools with JSON Schema.mp4" yEnc', '003 Defining Tools with JSON Schema.mp4')