API Reference
Complete guide to using FLAC Detective as a Python library.
Table of Contents
Installation
pip install flac-detective
Quick Start
Analyze a Single File
from flac_detective import FLACAnalyzer
from pathlib import Path
# Create analyzer
analyzer = FLACAnalyzer()
# Analyze a file
result = analyzer.analyze_file(Path('song.flac'))
# Check verdict
print(f"Verdict: {result['verdict']}")
print(f"Score: {result['score']}/150")
print(f"Reason: {result['reason']}")
Analyze Multiple Files
from flac_detective import FLACAnalyzer
from pathlib import Path
analyzer = FLACAnalyzer()
music_dir = Path('/path/to/music')
# Find all FLAC files
flac_files = list(music_dir.rglob('*.flac'))
# Analyze each
results = []
for flac_file in flac_files:
result = analyzer.analyze_file(flac_file)
results.append(result)
print(f"{result['filename']}: {result['verdict']}")
# Filter suspicious files
suspicious = [r for r in results if r['score'] >= 55]
print(f"Found {len(suspicious)} suspicious files")
Core API
FLACAnalyzer
Main analysis class.
Constructor
FLACAnalyzer(sample_duration: float = 30.0)
Parameters:
sample_duration(float): Seconds of audio to analyze (default: 30.0)Lower values = faster but less accurate
Higher values = slower but more accurate
Recommended: 15-60 seconds
Example:
# Fast analysis (15 seconds)
analyzer = FLACAnalyzer(sample_duration=15.0)
# Standard analysis (30 seconds) - default
analyzer = FLACAnalyzer()
# Thorough analysis (60 seconds)
analyzer = FLACAnalyzer(sample_duration=60.0)
Methods
analyze_file()
analyze_file(filepath: Path) -> dict
Analyzes a single FLAC file.
Parameters:
filepath(Path): Path to FLAC file
Returns: Dictionary with analysis results (see Result Objects)
Example:
from pathlib import Path
result = analyzer.analyze_file(Path('song.flac'))
Raises:
FileNotFoundError: File doesn’t existValueError: Not a valid FLAC fileRuntimeError: Analysis failed
Batch Analysis
Using find_flac_files()
from flac_detective.utils import find_flac_files
from pathlib import Path
# Find all FLAC files recursively
flac_files = find_flac_files(Path('/music'))
# Returns list of Path objects
print(f"Found {len(flac_files)} files")
Progress Tracking with ProgressTracker
from flac_detective import FLACAnalyzer, ProgressTracker
from flac_detective.utils import find_flac_files
from pathlib import Path
# Setup
analyzer = FLACAnalyzer()
tracker = ProgressTracker(progress_file=Path('progress.json'))
# Find files
flac_files = find_flac_files(Path('/music'))
tracker.set_total(len(flac_files))
# Analyze with resume capability
for flac_file in flac_files:
if not tracker.is_processed(str(flac_file)):
result = analyzer.analyze_file(flac_file)
tracker.add_result(result)
# Save every 10 files
if len(tracker.get_results()) % 10 == 0:
tracker.save()
# Final save
tracker.save()
# Get results
results = tracker.get_results()
processed, total = tracker.get_progress()
print(f"Processed {processed}/{total} files")
# Cleanup when done
tracker.cleanup()
Parallel Processing
from concurrent.futures import ProcessPoolExecutor, as_completed
from flac_detective import FLACAnalyzer
from flac_detective.utils import find_flac_files
from pathlib import Path
def analyze_file(filepath):
"""Worker function for parallel processing."""
analyzer = FLACAnalyzer()
return analyzer.analyze_file(filepath)
# Find files
flac_files = find_flac_files(Path('/music'))
# Parallel analysis with 4 workers
results = []
with ProcessPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(analyze_file, f): f for f in flac_files}
for future in as_completed(futures):
result = future.result()
results.append(result)
print(f"Processed: {result['filename']}")
print(f"Complete: {len(results)} files analyzed")
Result Objects
Analysis Result Dictionary
Each analyze_file() call returns a dictionary:
{
# File information
'filepath': str, # Full path: "/music/song.flac"
'filename': str, # Name only: "song.flac"
# Verdict
'score': int, # 0-150 (higher = more likely fake)
'verdict': str, # "AUTHENTIC" | "WARNING" | "SUSPICIOUS" | "FAKE_CERTAIN"
'confidence': str, # Confidence level description
'reason': str, # Human-readable explanation
# Audio metadata
'cutoff_freq': int, # Frequency cutoff in Hz (e.g., 22050)
'sample_rate': int, # Sample rate (e.g., 44100, 48000)
'bit_depth': int, # Bit depth (16, 24, etc.)
'channels': int, # Number of channels (1, 2)
'duration': float, # Duration in seconds
'encoder': str, # Encoder info from metadata
# Quality indicators
'has_clipping': bool, # Audio clipping detected
'clipping_severity': str, # "light" | "moderate" | "severe"
'has_dc_offset': bool, # DC offset detected
'dc_offset_severity': str, # "light" | "moderate" | "severe"
# Corruption detection
'is_corrupted': bool, # File integrity issues
'corruption_error': str, # Error message if corrupted
# Transcode detection
'is_fake_high_res': bool, # Fake high-resolution file
'is_upsampled': bool, # Upsampled from lower quality
'estimated_mp3_bitrate': int, # Estimated source bitrate (0 if unknown)
# Rule details (verbose mode)
'rule_scores': dict, # Individual rule contributions
}
Verdict Values
# Possible verdict values
VERDICTS = [
"AUTHENTIC", # Score ≤ 30
"WARNING", # Score 31-54
"SUSPICIOUS", # Score 55-85
"FAKE_CERTAIN", # Score ≥ 86
]
Working with Results
result = analyzer.analyze_file(Path('song.flac'))
# Check verdict
if result['verdict'] == 'AUTHENTIC':
print("File is genuine!")
elif result['verdict'] in ['SUSPICIOUS', 'FAKE_CERTAIN']:
print(f"Likely fake: {result['reason']}")
# Check score threshold
if result['score'] >= 86:
print("High confidence fake")
# Check specific indicators
if result['is_upsampled']:
print(f"Upsampled from {result['estimated_mp3_bitrate']} kbps")
# Access metadata
print(f"Sample rate: {result['sample_rate']} Hz")
print(f"Bit depth: {result['bit_depth']} bit")
Advanced Usage
Custom Filtering
from flac_detective import FLACAnalyzer
from flac_detective.utils import find_flac_files
from pathlib import Path
analyzer = FLACAnalyzer()
results = [analyzer.analyze_file(f) for f in find_flac_files(Path('/music'))]
# Find all fake files (score >= 86)
fakes = [r for r in results if r['score'] >= 86]
# Find upsampled files
upsampled = [r for r in results if r['is_upsampled']]
# Find 24-bit suspicious files
suspicious_24bit = [
r for r in results
if r['bit_depth'] == 24 and r['score'] >= 50
]
# Find corrupted files
corrupted = [r for r in results if r['is_corrupted']]
# Find authentic high-quality files
authentic_hq = [
r for r in results
if r['verdict'] == 'AUTHENTIC' and r['sample_rate'] >= 48000
]
Statistics Calculation
from flac_detective.reporting.statistics import calculate_statistics
# Analyze files
results = [analyzer.analyze_file(f) for f in flac_files]
# Calculate statistics
stats = calculate_statistics(results)
print(f"Total files: {stats['total_files']}")
print(f"Authentic: {stats['authentic_count']} ({stats['authentic_percentage']:.1f}%)")
print(f"Suspicious: {stats['suspicious_count']} ({stats['suspicious_percentage']:.1f}%)")
print(f"Fake: {stats['fake_count']} ({stats['fake_percentage']:.1f}%)")
print(f"Average score: {stats['average_score']:.1f}")
Report Generation
from flac_detective.reporting import TextReporter
from pathlib import Path
# Analyze files
analyzer = FLACAnalyzer()
results = [analyzer.analyze_file(f) for f in flac_files]
# Generate report
reporter = TextReporter()
report_file = Path('analysis_report.txt')
reporter.generate_report(
results=results,
output_file=report_file,
scan_paths=[Path('/music')]
)
print(f"Report saved to {report_file}")
FLAC Repair
Repair is enabled by default inside the analysis pipeline. When FLACAnalyzer
encounters a file it cannot read, it routes through repair_flac_file automatically,
preserves metadata, and re-analyses the repaired copy transparently. You do not need
to call the repair API in normal use.
For advanced cases where you want to run the duration-fix repair pass directly
(e.g. on a known-bad file outside the analyser), use the FLACDurationFixer class:
from flac_detective.repair import FLACDurationFixer
from pathlib import Path
fixer = FLACDurationFixer()
fixed = fixer.fix_duration(Path('corrupted.flac'), output_path=Path('repaired.flac'))
print('Repair attempted, output at:', fixed)
Or, to access the lower-level repair function used by the analyser:
from flac_detective.analysis.new_scoring.audio_loader import repair_flac_file
repaired_path = repair_flac_file(corrupted_path=Path('corrupted.flac'))
# repaired_path is None on failure
Integration Examples
With Pandas
import pandas as pd
from flac_detective import FLACAnalyzer
from flac_detective.utils import find_flac_files
from pathlib import Path
# Analyze
analyzer = FLACAnalyzer()
flac_files = find_flac_files(Path('/music'))
results = [analyzer.analyze_file(f) for f in flac_files]
# Create DataFrame
df = pd.DataFrame(results)
# Analysis
print(df['verdict'].value_counts())
print(df.groupby('verdict')['score'].mean())
# Filter and sort
suspicious = df[df['score'] >= 55].sort_values('score', ascending=False)
print(suspicious[['filename', 'score', 'verdict', 'reason']])
# Export
df.to_csv('analysis.csv', index=False)
suspicious.to_csv('suspicious.csv', index=False)
With tqdm Progress Bar
from tqdm import tqdm
from flac_detective import FLACAnalyzer
from flac_detective.utils import find_flac_files
from pathlib import Path
analyzer = FLACAnalyzer()
flac_files = find_flac_files(Path('/music'))
results = []
for flac_file in tqdm(flac_files, desc="Analyzing FLAC files"):
result = analyzer.analyze_file(flac_file)
results.append(result)
Database Integration (SQLite)
import sqlite3
from flac_detective import FLACAnalyzer
from flac_detective.utils import find_flac_files
from pathlib import Path
# Create database
conn = sqlite3.connect('flac_analysis.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS analysis (
filepath TEXT PRIMARY KEY,
filename TEXT,
score INTEGER,
verdict TEXT,
sample_rate INTEGER,
bit_depth INTEGER,
is_upsampled BOOLEAN,
analyzed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Analyze and store
analyzer = FLACAnalyzer()
flac_files = find_flac_files(Path('/music'))
for flac_file in flac_files:
result = analyzer.analyze_file(flac_file)
cursor.execute('''
INSERT OR REPLACE INTO analysis
(filepath, filename, score, verdict, sample_rate, bit_depth, is_upsampled)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
result['filepath'],
result['filename'],
result['score'],
result['verdict'],
result['sample_rate'],
result['bit_depth'],
result['is_upsampled']
))
conn.commit()
conn.close()
# Query later
conn = sqlite3.connect('flac_analysis.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM analysis WHERE verdict = 'FAKE_CERTAIN'")
fakes = cursor.fetchall()
print(f"Found {len(fakes)} certain fakes")
Web API (Flask Example)
from flask import Flask, request, jsonify
from flac_detective import FLACAnalyzer
from pathlib import Path
import tempfile
app = Flask(__name__)
analyzer = FLACAnalyzer()
@app.route('/analyze', methods=['POST'])
def analyze():
"""Analyze uploaded FLAC file."""
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}), 400
file = request.files['file']
# Save temporarily
with tempfile.NamedTemporaryFile(suffix='.flac', delete=False) as tmp:
file.save(tmp.name)
tmp_path = Path(tmp.name)
try:
# Analyze
result = analyzer.analyze_file(tmp_path)
return jsonify(result)
finally:
# Cleanup
tmp_path.unlink()
if __name__ == '__main__':
app.run(debug=True)
Error Handling
Basic Error Handling
from flac_detective import FLACAnalyzer
from pathlib import Path
analyzer = FLACAnalyzer()
try:
result = analyzer.analyze_file(Path('song.flac'))
print(f"Score: {result['score']}")
except FileNotFoundError:
print("File not found")
except ValueError as e:
print(f"Invalid file: {e}")
except RuntimeError as e:
print(f"Analysis failed: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
Graceful Degradation
def safe_analyze(filepath, analyzer):
"""Analyze with error handling."""
try:
return analyzer.analyze_file(filepath)
except Exception as e:
return {
'filepath': str(filepath),
'filename': filepath.name,
'score': 0,
'verdict': 'ERROR',
'reason': f'Analysis failed: {str(e)}',
'error': str(e)
}
# Use in batch processing
analyzer = FLACAnalyzer()
results = [safe_analyze(f, analyzer) for f in flac_files]
# Check for errors
errors = [r for r in results if r['verdict'] == 'ERROR']
print(f"Errors: {len(errors)}")
Corrupted File Handling
result = analyzer.analyze_file(Path('song.flac'))
if result['is_corrupted']:
print(f"File is corrupted: {result['corruption_error']}")
# FLACAnalyzer already attempted auto-repair internally before reporting the
# corruption — if you see is_corrupted=True, repair failed too. For a manual
# second pass you can call the lower-level repair directly:
from flac_detective.analysis.new_scoring.audio_loader import repair_flac_file
repaired = repair_flac_file(corrupted_path=Path(result['filepath']))
if repaired:
new_result = analyzer.analyze_file(repaired)
print(f"Repaired file score: {new_result['score']}")
Complete Example
Full pipeline with all features:
#!/usr/bin/env python3
"""Complete FLAC analysis pipeline with all features."""
from flac_detective import FLACAnalyzer, ProgressTracker
from flac_detective.utils import find_flac_files
from flac_detective.reporting import TextReporter
from flac_detective.reporting.statistics import calculate_statistics
from pathlib import Path
import sys
def main():
# Configuration
music_dir = Path(sys.argv[1] if len(sys.argv) > 1 else '.')
output_dir = Path('flac_analysis')
output_dir.mkdir(exist_ok=True)
# Initialize
analyzer = FLACAnalyzer(sample_duration=30.0)
tracker = ProgressTracker(progress_file=output_dir / 'progress.json')
# Find files
print(f"Scanning {music_dir}...")
flac_files = find_flac_files(music_dir)
print(f"Found {len(flac_files)} FLAC files\n")
# Analyze
tracker.set_total(len(flac_files))
for flac_file in flac_files:
if not tracker.is_processed(str(flac_file)):
try:
result = analyzer.analyze_file(flac_file)
tracker.add_result(result)
# Progress
processed, total = tracker.get_progress()
print(f"[{processed}/{total}] {result['filename']}: "
f"{result['score']}/150 ({result['verdict']})")
# Save periodically
if processed % 10 == 0:
tracker.save()
except Exception as e:
print(f"Error analyzing {flac_file}: {e}")
# Save results
tracker.save()
results = tracker.get_results()
# Generate report
report_file = output_dir / 'analysis_report.txt'
reporter = TextReporter()
reporter.generate_report(results, report_file, scan_paths=[music_dir])
# Statistics
stats = calculate_statistics(results)
# Summary
print(f"\n{'='*60}")
print("Analysis Complete")
print(f"{'='*60}")
print(f"Total files: {stats['total_files']}")
print(f"Authentic: {stats['authentic_count']} ({stats['authentic_percentage']:.1f}%)")
print(f"Suspicious: {stats['suspicious_count']} ({stats['suspicious_percentage']:.1f}%)")
print(f"Fake (certain): {stats['fake_count']} ({stats['fake_percentage']:.1f}%)")
print(f"Average score: {stats['average_score']:.1f}")
print(f"\nReport saved to: {report_file}")
print(f"{'='*60}\n")
# Cleanup
tracker.cleanup()
if __name__ == '__main__':
main()
Next Steps
User guide: User Guide
Technical details: Technical Details
Contributing: Contributing
For questions or issues, visit GitHub Discussions.