Names: split rar volumes; a file's size in brackets after its counter
Test and deploy / test (push) Successful in 43s
Test and deploy / edge (push) Successful in 1m45s
Test and deploy / deploy (push) Successful in 1m4s

- "rld-gtre.r13.002": a split volume after a rar volume is stripped like
  one after .rar (usenet-town's posts were 1,620 single files).
- "[26/32] - [4929] "…part24.rar"": the bracketed size after the counter
  goes (Kingdn's episodes, 2,850 single files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-12 10:52:35 +02:00
1 parent 3301216aa8
commit 358f5a8571
3 files changed
+25 -3

No files matched your search

+14 -2
View File
@@ -36,7 +36,7 @@ static SUFFIXES: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
(re(r"(?i)\.7z(\.[0-9]{3})?$"), ""),
(re(r"(?i)\.zip(\.[0-9]{3})?$"), ""),
// JS: (?<=\.(7z|…))\.\d{3}$ — no lookbehind here, so the extension is matched and put back.
(re(r"(?i)(\.(?:7z|zip|rar|tar|mkv|mp4|avi|m4v|ts|m2ts|iso|img|bin|vob))\.[0-9]{2,3}$"), "$1"),
(re(r"(?i)(\.(?:7z|zip|rar|tar|mkv|mp4|avi|m4v|ts|m2ts|iso|img|bin|vob|r[0-9]{2,3}))\.[0-9]{2,3}$"), "$1"),
(re(r"(?i)\.(nfo|sfv|nzb|srr|srs|txt|jpg|png|md5|sha1|srt|sub|idx|ass|ssa|m3u|m3u8|cue|log)$"), ""),
(re(r"(?i)\.tar$"), ""),
(re(r"(?i)\.(zst|gz|xz|bz2)$"), ""),
@@ -174,7 +174,8 @@ fn strip_suffix_rule(s: &[u8], rule: usize) -> Option<usize> {
// (?<=\.(7z|…))\.\d{2,3}$: only the volume number goes.
7 => {
let m = strip_volume_digits(s)?;
strip_ext(&s[..m], &SPLIT_EXTS).map(|_| m)
// Also after a rar volume ("rld-gtre.r13.002"): \.r\d{2,3} before it.
strip_ext(&s[..m], &SPLIT_EXTS).or_else(|| strip_suffix_rule(&s[..m], 3)).map(|_| m)
}
8 => strip_ext(s, &SIDE_EXTS),
9 => strip_ext(s, &[b"tar"]),
@@ -652,6 +653,8 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
// The name repeated elsewhere ("(NELET11.par2) [01/58] - "NELET11.par2"").
let without = if name.encode_utf16().count() >= 5 { without.replace(name, " ") } else { without };
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);
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);
@@ -731,6 +734,7 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
Release { key: hash(&["stem", poster, &base]), name: stem, source: "stem", file_total }
}
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}[-_ .]+(.+?)(?:_-_| - |-)"));
static VOLUME_NUMBER: LazyLock<Regex> = LazyLock::new(|| re(r"^(.*[^0-9. ])[. ]([0-9]{1,5})\.?$"));
@@ -1292,6 +1296,14 @@ mod image_set_tests {
assert_ne!(vlad("185", 77, "P8250001.JPG").key, vlad("234", 77, "P5300085.JPG").key);
}
#[test]
fn split_rar_volumes_and_sizes_after_the_counter() {
assert_eq!(super::file_stem("rld-gtre.r13.002"), "rld-gtre");
assert_eq!(super::file_stem_re("rld-gtre.r13.002"), "rld-gtre");
let k = |i: u32, size: u32, n: &str| release_identity("Kingdn", &format!(r#"Buffy The Vampire Slayer s02e04 [{i}/32] - [{size}] "{n}" yEnc"#), n);
assert_eq!(k(26, 4929, "Buffy s02e04 xvid-tns.part24.rar").key, k(27, 400, "Buffy s02e04 xvid-tns.vol00+01.PAR2").key);
}
#[test]
fn totals_padded_wider_than_the_index_are_no_counters() {
let who = |i: u32, n: &str| release_identity("p", &format!(r#"Very Best Of The Who [{i:02}/{:03}] - "{n}" yEnc"#, i * 10), n);
+4 -1
View File
@@ -33,7 +33,8 @@ const SUFFIXES = [
/\.7z(\.\d{3})?$/i,
/\.zip(\.\d{3})?$/i,
// Split volumes (x.mkv.001), not codec names (H.264, H.265).
/(?<=\.(7z|zip|rar|tar|mkv|mp4|avi|m4v|ts|m2ts|iso|img|bin|vob))\.\d{2,3}$/i,
// Also a split rar volume ("rld-gtre.r13.002": 1,620 single files of usenet-town's posts).
/(?<=\.(7z|zip|rar|tar|mkv|mp4|avi|m4v|ts|m2ts|iso|img|bin|vob|r\d{2,3}))\.\d{2,3}$/i,
/\.(nfo|sfv|nzb|srr|srs|txt|jpg|png|md5|sha1|srt|sub|idx|ass|ssa|m3u|m3u8|cue|log)$/i,
/\.tar$/i,
/\.(zst|gz|xz|bz2)$/i,
@@ -299,6 +300,8 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
.split(input.name.length >= 5 ? input.name : '\0')
.join(' ')
.replace(/"/g, ' ')
// A file's size in brackets after its counter ("[26/32] - [4929] "…part24.rar"": each file a release; Kingdn).
.replace(/(\[\d+\/\d+\])\s*-\s*\[\d+\]/g, '$1')
const remainder = subjectRemainder(named)
// Counted without the file name, as quoted names are: "shyla-shy-09-0195.jpg hardcore …" is no "9 of 195"
// (each of 866 images was a release of its own; LOG.md 2026-10-12 (5)).
+7
View File
@@ -276,6 +276,13 @@ test.group('release / par2 names', () => {
assert.notEqual(vlad('185', 77, 'P8250001.JPG').key, vlad('234', 77, 'P5300085.JPG').key)
})
test("split rar volumes; a file's size in brackets after its counter", ({ assert }) => {
assert.equal(fileStem('rld-gtre.r13.002'), 'rld-gtre')
const k = (i: number, size: number, n: string) =>
releaseIdentity({ poster: 'Kingdn', subject: `Buffy The Vampire Slayer s02e04 [${i}/32] - [${size}] "${n}" yEnc`, name: n, weak: false })!
assert.equal(k(26, 4929, 'Buffy s02e04 xvid-tns.part24.rar').key, k(27, 400, 'Buffy s02e04 xvid-tns.vol00+01.PAR2').key)
})
test('a total padded wider than its index is no counter', ({ assert }) => {
const who = (i: number, n: string) =>
releaseIdentity({ poster: 'p', subject: `Very Best Of The Who [${String(i).padStart(2, '0')}/${String(i * 10).padStart(3, '0')}] - "${n}" yEnc`, name: n, weak: false })!