Identity: hashed stems under a tag, numbered volumes, scene tracks
From the top-100 groups:
- A hashed stem under text without a counter is the upload ("Seit 1753
Tagen - usenet4ever.info - "055631a1_b6c820bc_…-u4e.part01.rar""): the
day's uploads were one release of 3,747 files.
- A numbered volume as the extension, within the [i/M] total ("(????)
[0659/2195] - "DN.VPL PCK.1508"", "TKY.ASN PCK.691."): keyed without it.
- Scene tracks under a bare counter ("[06/14] - "02-repiet_-_dont_think_
twice_(extended_mix).mp3""): the artist's album bounded by the total.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3fc38edda8
commit
d43603a43f
3 files changed
+69
-4
No files matched your search
+36
-2
@@ -674,7 +674,9 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
let code = (1..=5).contains(&letters) && (2..=6).contains(&code_digits) && matches!(b.get(letters + code_digits), Some(b'-' | b'_' | b' '));
|
||||
index_at(0) || (code && index_at(letters + code_digits + 1))
|
||||
};
|
||||
let tag = !image && !numbered && !test(&AUDIO, name) && describes(&stem) && !looks_random(&stem) && is_poster_tag(&remainder, &stem);
|
||||
// A hashed stem under text without a counter is the upload, the text a tag ("Seit 1753 Tagen - usenet4ever.info - …").
|
||||
let hashed = file_total.is_none() && test(&HASHED, &stem);
|
||||
let tag = hashed || (!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() };
|
||||
// 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());
|
||||
@@ -697,14 +699,30 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
}
|
||||
// An audiobook's or album's tracks without a name around them: by their name without its numbers.
|
||||
if test(&AUDIO, name) {
|
||||
// Scene tracks under a bare counter ("02-repiet_-_dont_think_twice_(extended_mix).mp3"): the artist's album.
|
||||
if let Some(total) = post_total.filter(|&n| n != 0) {
|
||||
if let Some(c) = SCENE_TRACK.captures(stem.as_bytes()) {
|
||||
let artist = String::from_utf8(c[1].to_vec()).unwrap();
|
||||
return Release { key: hash(&["stem", poster, &artist, &total.to_string()]), name: artist, source: "stem", file_total };
|
||||
}
|
||||
}
|
||||
let tracks = image_prefix(&stem);
|
||||
if !tracks.is_empty() {
|
||||
return Release { key: hash(&["stem", poster, &tracks]), name: tracks, source: "stem", file_total };
|
||||
}
|
||||
}
|
||||
Release { key: hash(&["stem", poster, &stem]), name: stem, source: "stem", file_total }
|
||||
// A numbered volume as the extension, within the counter's total ("DN.VPL PCK.1508" of 2195): one upload.
|
||||
let base = match (VOLUME_NUMBER.captures(stem.as_bytes()), post_total.filter(|&n| n != 0)) {
|
||||
(Some(c), Some(total)) if number(&c[2]) <= total as f64 => String::from_utf8(c[1].to_vec()).unwrap(),
|
||||
_ => stem.clone(),
|
||||
};
|
||||
Release { key: hash(&["stem", poster, &base]), name: stem, source: "stem", file_total }
|
||||
}
|
||||
|
||||
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})\.?$"));
|
||||
|
||||
static GENERIC: LazyLock<Regex> = LazyLock::new(|| {
|
||||
re(r"(?i)^(video_ts|audio_ts|bdmv|certificate|hvdvd_ts|samples?|subs?|subtitles?|proof|cover|covers|extras?|featurettes?|(cd|dvd|disc|disk)[ ._-]?[0-9]{1,2})$")
|
||||
});
|
||||
@@ -1254,6 +1272,22 @@ mod image_set_tests {
|
||||
assert_ne!(vlad("185", 77, "P8250001.JPG").key, vlad("234", 77, "P5300085.JPG").key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_stems_volume_numbers_and_scene_tracks() {
|
||||
let u4e = |n: &str| release_identity("p", &format!(r#"Seit 1753 Tagen - usenet4ever.info - "{n}" yEnc"#), n);
|
||||
assert_eq!(u4e("055631a1_b6c820bc_a856086c_85112a5-u4e.part01.rar").key, u4e("055631a1_b6c820bc_a856086c_85112a5-u4e.par2").key);
|
||||
assert_ne!(u4e("055631a1_b6c820bc_a856086c_85112a5-u4e.part01.rar").key, u4e("4cc7c4c6_d3c2fecf_0bff51d6_d10df86-u4e.part01.rar").key);
|
||||
let pck = |i: &str, n: &str| release_identity("p", &format!(r#"(????) [{i}/2195] - "{n}" yEnc"#), n);
|
||||
assert_eq!(pck("0659", "DN.VPL PCK.1508").key, pck("0660", "DN.VPL PCK.1509").key);
|
||||
assert_eq!(pck("0659", "DN.VPL PCK.1508").name, "DN.VPL PCK.1508");
|
||||
let movie = |i: &str, n: &str| release_identity("p", &format!(r#"[{i}/30] - "{n}" yEnc"#), n);
|
||||
assert_ne!(movie("01", "Movie.2008").key, movie("02", "Movie.2009").key);
|
||||
let track = |i: &str, n: &str| release_identity("p", &format!(r#"[{i}/14] - "{n}" yEnc"#), n);
|
||||
assert_eq!(track("06", "02-repiet_-_dont_think_twice_(extended_mix).mp3").key, track("07", "03-repiet_-_other_song.mp3").key);
|
||||
assert_eq!(track("06", "02-repiet_-_dont_think_twice_(extended_mix).mp3").name, "repiet");
|
||||
assert_eq!(track("01", "01-2bm_mq-real_rap_(intro).mp3").name, "2bm_mq");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn images_without_words_by_their_set() {
|
||||
let id = |n: &str, c: &str| release_identity("p", &format!(r#"##[ "{n}" ({c})"#), n);
|
||||
|
||||
+17
-2
@@ -127,6 +127,10 @@ function hash(...parts: (string | number | undefined)[]) {
|
||||
return digest('sha1', parts.map((p) => p ?? '').join('\n'))
|
||||
}
|
||||
|
||||
const HASHED = /^[0-9a-f]{8,}(?:_[0-9a-f]{6,}){2,}(?:-[0-9a-z]+)?$/i
|
||||
const SCENE_TRACK = /^\d{1,3}[-_ .]+(.+?)(?:_-_| - |-)/
|
||||
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. */
|
||||
const IMAGE = /\.(jpe?g|png|gif|bmp|webp|tiff?)$/i
|
||||
|
||||
@@ -300,7 +304,10 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
|
||||
// lessons were releases of their own; LOG.md 2026-10-11 (7)). Uploads hours apart are apart anyway.
|
||||
// Also after a disc code ("SC8367-12 - Wynette, Tammy - Singing My Song", karaoke discs).
|
||||
const numbered = /^(?:[A-Z]{1,5}\d{2,6}[-_ ])?\d{1,3}[ ._-]/.test(stem)
|
||||
const tag = !image && !numbered && !AUDIO.test(input.name) && describes(stem) && !looksRandom(stem) && isPosterTag(remainder, stem)
|
||||
// A hashed stem under text without a counter is the upload, the text a tag ("Seit 1753 Tagen - usenet4ever.info -
|
||||
// "055631a1_b6c820bc_a856086c_85112a5-u4e.part01.rar"": a day's uploads were one release; LOG.md 2026-10-12 (11)).
|
||||
const hashed = fileTotal === undefined && HASHED.test(stem)
|
||||
const tag = hashed || (!image && !numbered && !AUDIO.test(input.name) && describes(stem) && !looksRandom(stem) && isPosterTag(remainder, stem))
|
||||
// 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() : ''
|
||||
@@ -332,12 +339,20 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
|
||||
// An audiobook's or album's tracks without a name around them: by their name without its numbers, as an
|
||||
// image set ("… Seeing A Large Cat - D11.16-22.mp3", "Candy Dulfer - Candy Store - 11 - everytime.mp3").
|
||||
if (AUDIO.test(input.name)) {
|
||||
// Scene tracks under a bare counter ("[06/14] - "02-repiet_-_dont_think_twice_(extended_mix).mp3""): the
|
||||
// artist's album, bounded by the counter's total (each track was a release of its own).
|
||||
const scene = postTotal ? SCENE_TRACK.exec(stem) : null
|
||||
if (scene) return { key: hash('stem', poster, scene[1], postTotal), name: scene[1], fileTotal, source: 'stem' }
|
||||
const tracks = stem.replace(/\d+\D*$/, '').replace(/\d+/g, ' ').replace(/[\s._-]+/g, ' ').trim()
|
||||
if (tracks) return { key: hash('stem', poster, tracks), name: tracks, fileTotal, source: 'stem' }
|
||||
}
|
||||
// The [i/M] total is informational, not part of the key: posters often
|
||||
// upload the par2 index without a counter, and it belongs to the same post.
|
||||
return { key: hash('stem', poster, stem), name: stem, fileTotal, source: 'stem' }
|
||||
// A numbered volume as the extension, within the counter's total ("(????) [0659/2195] - "DN.VPL PCK.1508"",
|
||||
// "TKY.ASN PCK.691."): the volumes are one upload.
|
||||
const volume = VOLUME_NUMBER.exec(stem)
|
||||
const base = volume && postTotal && Number(volume[2]) <= postTotal ? volume[1] : stem
|
||||
return { key: hash('stem', poster, base), name: stem, fileTotal, source: 'stem' }
|
||||
}
|
||||
|
||||
/** Release of a par2 recovery set; every file listed in it belongs to it. */
|
||||
|
||||
@@ -276,6 +276,22 @@ test.group('release / par2 names', () => {
|
||||
assert.notEqual(vlad('185', 77, 'P8250001.JPG').key, vlad('234', 77, 'P5300085.JPG').key)
|
||||
})
|
||||
|
||||
test('hashed stems under a tag, volume numbers as extensions, scene tracks under a bare counter', ({ assert }) => {
|
||||
const id = (subject: string, name: string) => releaseIdentity({ poster: 'p', subject, name, weak: false })!
|
||||
const u4e = (n: string) => id(`Seit 1753 Tagen - usenet4ever.info - "${n}" yEnc`, n)
|
||||
assert.equal(u4e('055631a1_b6c820bc_a856086c_85112a5-u4e.part01.rar').key, u4e('055631a1_b6c820bc_a856086c_85112a5-u4e.par2').key)
|
||||
assert.notEqual(u4e('055631a1_b6c820bc_a856086c_85112a5-u4e.part01.rar').key, u4e('4cc7c4c6_d3c2fecf_0bff51d6_d10df86-u4e.part01.rar').key)
|
||||
const pck = (i: string, n: string) => id(`(????) [${i}/2195] - "${n}" yEnc`, n)
|
||||
assert.equal(pck('0659', 'DN.VPL PCK.1508').key, pck('0660', 'DN.VPL PCK.1509').key)
|
||||
assert.equal(pck('0659', 'DN.VPL PCK.1508').name, 'DN.VPL PCK.1508')
|
||||
const movie = (i: string, n: string) => id(`[${i}/30] - "${n}" yEnc`, n)
|
||||
assert.notEqual(movie('01', 'Movie.2008').key, movie('02', 'Movie.2009').key)
|
||||
const track = (i: string, n: string) => id(`[${i}/14] - "${n}" yEnc`, n)
|
||||
assert.equal(track('06', '02-repiet_-_dont_think_twice_(extended_mix).mp3').key, track('07', '03-repiet_-_other_song.mp3').key)
|
||||
assert.equal(track('06', '02-repiet_-_dont_think_twice_(extended_mix).mp3').name, 'repiet')
|
||||
assert.equal(track('01', '01-2bm_mq-real_rap_(intro).mp3').name, '2bm_mq')
|
||||
})
|
||||
|
||||
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