2026-02-13 09:44:51 +08:00
|
|
|
"""Integration tests for duplicate resolution with embedded video metadata."""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from vlm.analysis import detect_duplicates
|
2026-06-01 15:32:03 +08:00
|
|
|
from vlm.io import identities_to_analysis_input, load_identities_json
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestDuplicateQualityWithMetadata:
|
|
|
|
|
"""Test that embedded metadata enables accurate quality comparison."""
|
|
|
|
|
|
|
|
|
|
def test_duplicate_quality_comparison_with_v2_metadata(self, tmp_path):
|
|
|
|
|
"""Test that v2 identities with embedded metadata produce accurate quality comparison."""
|
|
|
|
|
# Create v2 identities.json with two duplicates having different quality
|
|
|
|
|
identities_file = tmp_path / "identities.json"
|
|
|
|
|
data = {
|
|
|
|
|
"vlm_schema_version": "2.0",
|
|
|
|
|
"metadata": {
|
|
|
|
|
"generated": "2024-02-13T12:00:00",
|
|
|
|
|
"source_inventory": "inventory.csv",
|
|
|
|
|
"total_files": 2,
|
|
|
|
|
},
|
|
|
|
|
"movies": [
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/movie/Matrix (1999) 1080p.mkv",
|
|
|
|
|
"filename": "Matrix (1999) 1080p.mkv",
|
|
|
|
|
"category": "movie",
|
|
|
|
|
"title": "The Matrix",
|
|
|
|
|
"year": 1999,
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
"video_metadata": {
|
|
|
|
|
"size_bytes": 2000000000,
|
|
|
|
|
"modified_timestamp": "2024-01-01T10:00:00+00:00",
|
|
|
|
|
"resolution": "1920x1080",
|
|
|
|
|
"codec": "h264",
|
|
|
|
|
"duration_seconds": 7200.0,
|
|
|
|
|
"bitrate_kbps": 5000,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/movie/Matrix (1999) 720p.mkv",
|
|
|
|
|
"filename": "Matrix (1999) 720p.mkv",
|
|
|
|
|
"category": "movie",
|
|
|
|
|
"title": "The Matrix",
|
|
|
|
|
"year": 1999,
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
"video_metadata": {
|
|
|
|
|
"size_bytes": 1000000000,
|
|
|
|
|
"modified_timestamp": "2024-01-02T10:00:00+00:00",
|
|
|
|
|
"resolution": "1280x720",
|
|
|
|
|
"codec": "h264",
|
|
|
|
|
"duration_seconds": 7200.0,
|
|
|
|
|
"bitrate_kbps": 2500,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
"series": [],
|
|
|
|
|
"anime": [],
|
|
|
|
|
"other": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Save identities
|
|
|
|
|
with open(identities_file, "w") as f:
|
|
|
|
|
json.dump(data, f, indent=2)
|
|
|
|
|
|
|
|
|
|
# Load identities
|
|
|
|
|
loaded = load_identities_json(identities_file)
|
|
|
|
|
|
|
|
|
|
# Convert to analysis input
|
2026-04-07 08:07:18 +08:00
|
|
|
movie_pairs, series_pairs = identities_to_analysis_input(loaded)
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Create identity-file pairs
|
2026-04-07 08:07:18 +08:00
|
|
|
identity_file_pairs = movie_pairs
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Detect duplicates
|
|
|
|
|
duplicates = detect_duplicates(identity_file_pairs)
|
|
|
|
|
|
|
|
|
|
# Verify we found 1 duplicate group
|
|
|
|
|
assert len(duplicates) == 1
|
|
|
|
|
duplicate_group = duplicates[0]
|
|
|
|
|
|
|
|
|
|
# Verify duplicate group has 2 files
|
|
|
|
|
assert len(duplicate_group.files) == 2
|
|
|
|
|
|
|
|
|
|
# Verify quality comparison has actual metadata (not zeros/nulls)
|
|
|
|
|
quality_comparison = duplicate_group.quality_comparison
|
|
|
|
|
assert len(quality_comparison) == 2
|
|
|
|
|
|
|
|
|
|
# First file (1080p) should have correct metadata
|
|
|
|
|
file1 = quality_comparison[0]
|
|
|
|
|
assert file1["size_bytes"] == 2000000000
|
|
|
|
|
assert file1["resolution"] == "1920x1080"
|
|
|
|
|
assert file1["codec"] == "h264"
|
|
|
|
|
assert file1["bitrate_kbps"] == 5000
|
|
|
|
|
|
|
|
|
|
# Second file (720p) should have correct metadata
|
|
|
|
|
file2 = quality_comparison[1]
|
|
|
|
|
assert file2["size_bytes"] == 1000000000
|
|
|
|
|
assert file2["resolution"] == "1280x720"
|
|
|
|
|
assert file2["codec"] == "h264"
|
|
|
|
|
assert file2["bitrate_kbps"] == 2500
|
|
|
|
|
|
|
|
|
|
def test_duplicate_quality_comparison_with_v1_no_metadata(self, tmp_path):
|
|
|
|
|
"""Test that v1 identities without metadata produce limited quality comparison."""
|
|
|
|
|
# Create v1 identities.json without embedded metadata (backward compatibility)
|
|
|
|
|
identities_file = tmp_path / "identities.json"
|
|
|
|
|
data = {
|
|
|
|
|
# No vlm_schema_version (v1)
|
|
|
|
|
"metadata": {
|
|
|
|
|
"generated": "2024-02-13T12:00:00",
|
|
|
|
|
"source_inventory": "inventory.csv",
|
|
|
|
|
"total_files": 2,
|
|
|
|
|
},
|
|
|
|
|
"movies": [
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/movie/Inception (2010) 1080p.mkv",
|
|
|
|
|
"filename": "Inception (2010) 1080p.mkv",
|
|
|
|
|
"category": "movie",
|
|
|
|
|
"title": "Inception",
|
|
|
|
|
"year": 2010,
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
# No video_metadata
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/movie/Inception (2010) 720p.mkv",
|
|
|
|
|
"filename": "Inception (2010) 720p.mkv",
|
|
|
|
|
"category": "movie",
|
|
|
|
|
"title": "Inception",
|
|
|
|
|
"year": 2010,
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
# No video_metadata
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
"series": [],
|
|
|
|
|
"anime": [],
|
|
|
|
|
"other": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Save identities
|
|
|
|
|
with open(identities_file, "w") as f:
|
|
|
|
|
json.dump(data, f, indent=2)
|
|
|
|
|
|
|
|
|
|
# Load identities
|
|
|
|
|
loaded = load_identities_json(identities_file)
|
|
|
|
|
|
|
|
|
|
# Convert to analysis input
|
2026-04-07 08:07:18 +08:00
|
|
|
movie_pairs, series_pairs = identities_to_analysis_input(loaded)
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Create identity-file pairs
|
2026-04-07 08:07:18 +08:00
|
|
|
identity_file_pairs = movie_pairs
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Detect duplicates
|
|
|
|
|
duplicates = detect_duplicates(identity_file_pairs)
|
|
|
|
|
|
|
|
|
|
# Verify we found 1 duplicate group
|
|
|
|
|
assert len(duplicates) == 1
|
|
|
|
|
duplicate_group = duplicates[0]
|
|
|
|
|
|
|
|
|
|
# Verify quality comparison exists but has limited data (v1 defaults)
|
|
|
|
|
quality_comparison = duplicate_group.quality_comparison
|
|
|
|
|
assert len(quality_comparison) == 2
|
|
|
|
|
|
|
|
|
|
# v1 files have size_bytes=0 and no resolution/codec
|
|
|
|
|
for quality_info in quality_comparison:
|
|
|
|
|
assert quality_info["size_bytes"] == 0
|
|
|
|
|
assert "resolution" not in quality_info # None values are not included
|
|
|
|
|
assert "codec" not in quality_info
|
|
|
|
|
|
|
|
|
|
def test_series_duplicate_quality_comparison_with_v2_metadata(self, tmp_path):
|
|
|
|
|
"""Test series duplicates with embedded metadata."""
|
|
|
|
|
identities_file = tmp_path / "identities.json"
|
|
|
|
|
data = {
|
|
|
|
|
"vlm_schema_version": "2.0",
|
|
|
|
|
"metadata": {
|
|
|
|
|
"generated": "2024-02-13T12:00:00",
|
|
|
|
|
"source_inventory": "inventory.csv",
|
|
|
|
|
"total_files": 2,
|
|
|
|
|
},
|
|
|
|
|
"movies": [],
|
|
|
|
|
"series": [
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/series/Breaking Bad S01E01 1080p.mkv",
|
|
|
|
|
"filename": "Breaking Bad S01E01 1080p.mkv",
|
|
|
|
|
"category": "series",
|
|
|
|
|
"title": "Breaking Bad",
|
|
|
|
|
"season": 1,
|
|
|
|
|
"episodes": [1],
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
"video_metadata": {
|
|
|
|
|
"size_bytes": 800000000,
|
|
|
|
|
"modified_timestamp": "2024-01-01T10:00:00+00:00",
|
|
|
|
|
"resolution": "1920x1080",
|
|
|
|
|
"codec": "hevc",
|
|
|
|
|
"duration_seconds": 2700.0,
|
|
|
|
|
"bitrate_kbps": 4000,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"path": "/library/series/Breaking Bad S01E01 720p.mkv",
|
|
|
|
|
"filename": "Breaking Bad S01E01 720p.mkv",
|
|
|
|
|
"category": "series",
|
|
|
|
|
"title": "Breaking Bad",
|
|
|
|
|
"season": 1,
|
|
|
|
|
"episodes": [1],
|
|
|
|
|
"confidence": 0.9,
|
|
|
|
|
"needs_review": False,
|
|
|
|
|
"video_metadata": {
|
|
|
|
|
"size_bytes": 400000000,
|
|
|
|
|
"modified_timestamp": "2024-01-02T10:00:00+00:00",
|
|
|
|
|
"resolution": "1280x720",
|
|
|
|
|
"codec": "h264",
|
|
|
|
|
"duration_seconds": 2700.0,
|
|
|
|
|
"bitrate_kbps": 2000,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
"anime": [],
|
|
|
|
|
"other": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Save and load
|
|
|
|
|
with open(identities_file, "w") as f:
|
|
|
|
|
json.dump(data, f, indent=2)
|
|
|
|
|
|
|
|
|
|
loaded = load_identities_json(identities_file)
|
2026-04-07 08:07:18 +08:00
|
|
|
movie_pairs, series_pairs = identities_to_analysis_input(loaded)
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Create identity-file pairs for series
|
2026-04-07 08:07:18 +08:00
|
|
|
identity_file_pairs = series_pairs
|
2026-02-13 09:44:51 +08:00
|
|
|
|
|
|
|
|
# Detect duplicates
|
|
|
|
|
duplicates = detect_duplicates(identity_file_pairs)
|
|
|
|
|
|
|
|
|
|
# Verify duplicate detected
|
|
|
|
|
assert len(duplicates) == 1
|
|
|
|
|
duplicate_group = duplicates[0]
|
|
|
|
|
assert len(duplicate_group.files) == 2
|
|
|
|
|
|
|
|
|
|
# Verify quality comparison has metadata
|
|
|
|
|
quality_comparison = duplicate_group.quality_comparison
|
|
|
|
|
assert quality_comparison[0]["resolution"] == "1920x1080"
|
|
|
|
|
assert quality_comparison[0]["codec"] == "hevc"
|
|
|
|
|
assert quality_comparison[1]["resolution"] == "1280x720"
|
|
|
|
|
assert quality_comparison[1]["codec"] == "h264"
|