use log::trace;
use crate::matcher::span::{MatchSpan, Property};
use crate::tokenizer::TokenStream;
use crate::zone_map::ZoneMap;
use super::token_context;
pub fn apply_zone_rules(
input: &str,
_zone_map: &ZoneMap,
token_stream: &TokenStream,
matches: &mut Vec<MatchSpan>,
) {
let fn_start = crate::filename_start(input);
let initial_count = matches.len();
{
let drop_positions: Vec<usize> = matches
.iter()
.filter(|m| m.property == Property::Language)
.filter(|m| {
token_context::is_in_title_context(m, matches, token_stream)
|| token_context::has_duplicate_in_tech_context(m, matches, token_stream)
})
.map(|m| m.start)
.collect();
matches.retain(|m| m.property != Property::Language || !drop_positions.contains(&m.start));
}
trace!(
"zone rules: {} match(es) after language filtering (was {})",
matches.len(),
initial_count
);
let source_count = matches
.iter()
.filter(|m| m.property == Property::Source && m.start >= fn_start)
.count();
if source_count > 1 {
let drop_positions: Vec<usize> = matches
.iter()
.filter(|m| m.property == Property::Source && m.start >= fn_start)
.filter(|m| token_context::is_in_title_context(m, matches, token_stream))
.map(|m| m.start)
.collect();
matches.retain(|m| m.property != Property::Source || !drop_positions.contains(&m.start));
}
{
let has_filename_source = matches
.iter()
.any(|m| m.property == Property::Source && m.start >= fn_start);
if has_filename_source {
let before = matches.len();
matches.retain(|m| !(m.property == Property::Source && m.start < fn_start));
if matches.len() < before {
trace!(
"zone rules: dropped {} ancestor-path Source match(es) in favor of filename source",
before - matches.len()
);
}
}
}
let has_uhd_signal = matches.iter().any(|m| {
m.start >= fn_start
&& ((m.property == Property::Other && m.value == "Ultra HD")
|| (m.property == Property::ScreenSize && m.value == "2160p"))
});
if has_uhd_signal {
for m in matches.iter_mut() {
if m.start >= fn_start && m.property == Property::Source && m.value == "Blu-ray" {
m.value = "Ultra HD Blu-ray".into();
}
}
}
let source_has_uhd = matches
.iter()
.any(|m| m.property == Property::Source && m.value.contains("Ultra HD"));
if source_has_uhd {
matches.retain(|m| !(m.property == Property::Other && m.value == "Ultra HD"));
}
{
let has_sub_lang = matches
.iter()
.any(|m| m.property == Property::SubtitleLanguage);
let sub_spans: Vec<(usize, usize, i32)> = matches
.iter()
.filter(|m| m.property == Property::SubtitleLanguage)
.map(|m| (m.start, m.end, m.priority))
.collect();
matches.retain(|m| {
if m.property != Property::Source {
return true;
}
if m.value == "Telecine" && has_sub_lang {
return false;
}
!sub_spans
.iter()
.any(|(ss, se, sp)| m.start == *ss && m.end == *se && *sp >= m.priority)
});
}
{
let sub_spans: Vec<(usize, usize)> = matches
.iter()
.filter(|m| m.property == Property::SubtitleLanguage)
.map(|m| (m.start, m.end))
.collect();
if !sub_spans.is_empty() {
matches.retain(|m| {
if m.property != Property::Language {
return true;
}
!sub_spans
.iter()
.any(|(ss, se)| m.start >= *ss && m.end <= *se)
});
}
}
{
let source_values: Vec<(usize, String)> = matches
.iter()
.filter(|m| m.property == Property::Source)
.map(|m| (m.start, m.value.to_string()))
.collect();
if source_values.len() > 1 {
const SUBSUMPTIONS: &[(&str, &str)] = &[
("TV", "HDTV"),
("TV", "Ultra HDTV"),
("TV", "Digital TV"),
("HD", "HD-DVD"),
("HD", "HD Camera"),
];
let values: Vec<&str> = source_values.iter().map(|(_, v)| v.as_str()).collect();
let to_drop: Vec<&str> = SUBSUMPTIONS
.iter()
.filter(|(_, specific)| values.contains(specific))
.map(|(generic, _)| *generic)
.collect();
if !to_drop.is_empty() {
matches.retain(|m| {
!(m.property == Property::Source && to_drop.contains(&m.value.as_ref()))
});
}
}
}
let tech_spans: Vec<(usize, usize)> = matches
.iter()
.filter(|m| {
matches!(
m.property,
Property::Source
| Property::VideoCodec
| Property::AudioCodec
| Property::ScreenSize
| Property::StreamingService
)
})
.map(|m| (m.start, m.end))
.collect();
if !tech_spans.is_empty() {
matches.retain(|m| {
if !matches!(m.property, Property::Language | Property::SubtitleLanguage) {
return true;
}
!tech_spans
.iter()
.any(|(ts, te)| m.start >= *ts && m.end <= *te)
});
}
}
pub fn apply_post_release_group_rules(matches: &mut Vec<MatchSpan>) {
let rg_spans: Vec<(usize, usize)> = matches
.iter()
.filter(|m| m.property == Property::ReleaseGroup)
.map(|m| (m.start, m.end))
.collect();
if !rg_spans.is_empty() {
const AMBIGUOUS_OTHER: &[&str] = &["High Quality", "High Resolution", "Fan Subtitled"];
const ADJACENCY_GAP: usize = 2;
matches.retain(|m| {
if m.property != Property::Other || !AMBIGUOUS_OTHER.contains(&m.value.as_ref()) {
return true;
}
!rg_spans.iter().any(|(rs, re)| {
m.start < re.saturating_add(ADJACENCY_GAP)
&& m.end.saturating_add(ADJACENCY_GAP) > *rs
})
});
}
}