Under a constant subject: an upload's random volume stem, and an image's set head
- "[ TOWN ] [ www town ag ] [ TV ] … "Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar"", "….vol4+4.par2": the subject and the total were the key, so every upload of a total was one release (2,338 files for 20; also partner-of-ssl-news, brothers-of-usenet). A random stem (no dots or spaces) on a volume file joins the subject key; a scene name doesn't, so its nfo stays. - "Molly Set [i/50] "mo103x003.jpg"": every set of 50 was one. An image's name up to its first number, when another follows, joins the key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9f255e6ac0
commit
f1c5af5f36
3 files changed
+53
-3
No files matched your search
+27
-2
@@ -813,8 +813,21 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
let one_word = image && remainder.len() >= 3 && remainder.bytes().all(|c| c.is_ascii_alphabetic());
|
||||
if !tag && (names_release(&remainder) || one_word) {
|
||||
// Without a counter an image's set: its name up to its last number, digits kept; none without numbers.
|
||||
let bound = total.clone().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 };
|
||||
let set = if image { image_set(&stem) } else { String::new() };
|
||||
let bound = total.clone().unwrap_or_else(|| set.clone());
|
||||
let mut parts: Vec<&str> = vec!["subject", poster, &remainder, &bound];
|
||||
// An image's set as well under a counter ("Molly Set" [i/50]: "mo103x003.jpg"): its name up to its first number,
|
||||
// when another follows (/^(\D*\d+)\D+\d+/).
|
||||
let head = if image && total.is_some() { SET_HEAD.captures(stem.as_bytes()).map(|c| String::from_utf8(c[1].to_vec()).unwrap()) } else { None };
|
||||
if let Some(h) = head.as_deref() {
|
||||
parts.push(h);
|
||||
}
|
||||
// An upload's random stem on its volumes ("Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar" under "[ TOWN ] … [ TV ]").
|
||||
let stripped: String = stem.chars().filter(|c| !matches!(c, '-' | '_')).collect();
|
||||
if !image && test(&RANDOM_VOLUME, name) && stem.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-') && looks_random(&stripped) {
|
||||
parts.push(&stem);
|
||||
}
|
||||
return Release { key: hash(&parts), name: remainder, source: "subject", file_total };
|
||||
}
|
||||
if let Some(total) = post_total.filter(|&n| n != 0 && !tag) {
|
||||
if is_post_token(&remainder) {
|
||||
@@ -918,6 +931,10 @@ static SCENE_TRACK: LazyLock<Regex> = LazyLock::new(|| re(r"^[0-9]{1,3}[-_ .]+(.
|
||||
static SCENE_RELEASE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
re(r"(?i)^(.+?)(?:_-_| - |-).*-(?:web|cd[a-z]?|vinyl|vls|sat|cable|fm|dab|single|ep|lp|promo|bootleg)-.*(?:19|20)[0-9]{2}(?:-[A-Za-z0-9_]+)?$")
|
||||
});
|
||||
/// RANDOM_VOLUME (lib/release/identity.ts): a file of a multi-volume upload.
|
||||
static RANDOM_VOLUME: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(?:part[0-9]+\.rar|vol[0-9]+[+-][0-9]+\.par2|par2|r[0-9]{2,3}|7z\.[0-9]{3}|zip\.[0-9]{3}|[0-9]{3})$"));
|
||||
/// An image's set head (lib/release/identity.ts): its name up to its first number, when another follows.
|
||||
static SET_HEAD: LazyLock<Regex> = LazyLock::new(|| re(r"^([^0-9]*[0-9]+)[^0-9]+[0-9]+"));
|
||||
static VOLUME_NUMBER: LazyLock<Regex> = LazyLock::new(|| re(r"^(.*[^0-9. ])[. ]([0-9]{1,5})\.?$"));
|
||||
|
||||
static GENERIC: LazyLock<Regex> = LazyLock::new(|| {
|
||||
@@ -1567,6 +1584,14 @@ mod image_set_tests {
|
||||
lop("022", "457", "r86_022 - Gwen Guthrie - Ain't Nothing Going on but the Rent.mp3").key,
|
||||
lop("022", "333", "r98_333 - Jellie - Roller Coaster.mp3").key
|
||||
);
|
||||
let town = |i: &str, n: &str| super::release_identity("Kim <Jong-Un@town.ag>", &format!(r#"[ TOWN ] [ www.town.ag ] [ TV ] [{i}/20] - "{n}" yEnc"#), n);
|
||||
assert_eq!(town("01", "Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar").key, town("18", "Z3mMTMPsoT4ikE_Di3fKnNF21.vol4+4.par2").key);
|
||||
assert_ne!(town("01", "Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar").key, town("01", "i3DpbaTUJT4L6V48fEBdbVV46.part01.rar").key);
|
||||
let show = |i: &str, n: &str| super::release_identity("p", &format!(r#"Some.Show.S01E01 [{i}/20] - "{n}" yEnc"#), n);
|
||||
assert_eq!(show("01", "The.Big.Bang.Theory.S01E01.part01.rar").key, show("20", "The.Big.Bang.Theory.S01E01.nfo").key);
|
||||
let molly = |i: &str, n: &str| super::release_identity("p", &format!(r#"Molly Set [{i}/50] - "{n}" yEnc"#), n);
|
||||
assert_eq!(molly("01", "mo103x003.jpg").key, molly("02", "mo103x004.jpg").key);
|
||||
assert_ne!(molly("01", "mo103x003.jpg").key, molly("01", "mop120x032.jpg").key);
|
||||
let mdg = |post: &str, i: &str, n: &str| super::release_identity("p", &format!(r#"{post} - ({i}/90) "{n}" - 16,14 MB - yEnc"#), n);
|
||||
assert_eq!(mdg("MDG005b", "01", "IMG_2307.JPG").key, mdg("MDG005b", "02", "IMG_2308.jpg").key);
|
||||
assert_ne!(mdg("MDG005b", "01", "IMG_2307.JPG").key, mdg("MDG006a", "01", "IMG_4410.JPG").key);
|
||||
|
||||
+12
-1
@@ -150,6 +150,8 @@ const HASHED = /^(?:[0-9a-f]{8,}(?:_[0-9a-f]{6,}){2,}|[0-9a-f]{24,})(?:-[0-9a-z]
|
||||
const SCENE_TRACK = /^\d{1,3}[-_ .]+(.+?)(?:_-_| - |-)/
|
||||
/** A scene music release's name: the artist, then its source (web, cd, vinyl, …) and the year, last the group. */
|
||||
const SCENE_RELEASE = /^(.+?)(?:_-_| - |-).*-(?:web|cd[a-z]?|vinyl|vls|sat|cable|fm|dab|single|ep|lp|promo|bootleg)-.*(?:19|20)\d{2}(?:-[A-Za-z0-9_]+)?$/i
|
||||
/** A file of a multi-volume upload: a rar part or volume, a par2 (index or volume), a numbered split. */
|
||||
const RANDOM_VOLUME = /\.(?:part\d+\.rar|vol\d+[+-]\d+\.par2|par2|r\d{2,3}|7z\.\d{3}|zip\.\d{3}|\d{3})$/i
|
||||
const VOLUME_NUMBER = /^(.*[^\d. ])[. ](\d{1,5})\.?$/
|
||||
|
||||
/** Image files: a set's images are named one by one ("IMG_3086.jpg"); its subject names the set. */
|
||||
@@ -405,8 +407,17 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
|
||||
// → "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 ? imageSet(stem) : ''
|
||||
const parts: (string | number)[] = ['subject', poster, remainder, total ?? set]
|
||||
// An image's set as well under a counter ("Molly Set" [i/50]: "mo103x003.jpg", "mop120x032.jpg": every set of 50 one release):
|
||||
// its name up to its first number, when another follows ("mo103", "destiny-001"; not "IMG_2307", one number an image).
|
||||
const head = image && total !== undefined ? /^(\D*\d+)\D+\d+/.exec(stem) : null
|
||||
if (head) parts.push(head[1])
|
||||
// An upload's random stem on its volumes ("[ TOWN ] [ www town ag ] [ TV ] … "Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar"",
|
||||
// "….vol4+4.par2"): under a constant tag every upload of a total was one release (2,338 files for 20; LOG.md
|
||||
// 2026-10-14 (51)). No dots or spaces: a scene name ("The.Big.Bang.Theory.S01E01") isn't random.
|
||||
if (!image && RANDOM_VOLUME.test(input.name) && /^[A-Za-z0-9_-]+$/.test(stem) && looksRandom(stem.replace(/[-_]/g, ''))) parts.push(stem)
|
||||
return {
|
||||
key: hash('subject', poster, remainder, total ?? set),
|
||||
key: hash(...parts),
|
||||
name: remainder,
|
||||
fileTotal,
|
||||
source: 'subject',
|
||||
|
||||
@@ -443,6 +443,20 @@ test.group('release / par2 names', () => {
|
||||
assert.notEqual(r('022', '457', "r86_022 - Gwen Guthrie - Ain't Nothing Going on but the Rent.mp3").key, r('022', '333', "r98_333 - Jellie - Roller Coaster.mp3").key)
|
||||
})
|
||||
|
||||
test("under a constant tag an upload's random stem, and an image's set, bound the subject", ({ assert }) => {
|
||||
const id = (subject: string, name: string) => releaseIdentity({ poster: 'Kim <Jong-Un@town.ag>', subject, name, weak: false })!
|
||||
const town = (i: string, n: string) => id(`[ TOWN ] [ www.town.ag ] [ TV ] [${i}/20] - "${n}" yEnc`, n)
|
||||
assert.equal(town('01', 'Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar').key, town('18', 'Z3mMTMPsoT4ikE_Di3fKnNF21.vol4+4.par2').key)
|
||||
assert.equal(town('01', 'Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar').name, town('01', 'i3DpbaTUJT4L6V48fEBdbVV46.part01.rar').name)
|
||||
assert.notEqual(town('01', 'Z3mMTMPsoT4ikE_Di3fKnNF21.part01.rar').key, town('01', 'i3DpbaTUJT4L6V48fEBdbVV46.part01.rar').key)
|
||||
// A scene name keeps the subject's key, so its nfo stays with it.
|
||||
const show = (i: string, n: string) => id(`Some.Show.S01E01 [${i}/20] - "${n}" yEnc`, n)
|
||||
assert.equal(show('01', 'The.Big.Bang.Theory.S01E01.part01.rar').key, show('20', 'The.Big.Bang.Theory.S01E01.nfo').key)
|
||||
const molly = (i: string, n: string) => id(`Molly Set [${i}/50] - "${n}" yEnc`, n)
|
||||
assert.equal(molly('01', 'mo103x003.jpg').key, molly('02', 'mo103x004.jpg').key)
|
||||
assert.notEqual(molly('01', 'mo103x003.jpg').key, molly('01', 'mop120x032.jpg').key)
|
||||
})
|
||||
|
||||
test('an image without words around it is keyed by its set', ({ assert }) => {
|
||||
const id = (name: string, counter: string) => releaseIdentity({ poster: 'p', subject: `##[ "${name}" (${counter})`, name, weak: false })!
|
||||
assert.equal(id('diana-040-001.jpg', '01/68').key, id('diana-040-002.jpg', '02/68').key)
|
||||
|
||||
Reference in new issue
Block a user