Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
11 KiB
HTML
243 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Encode Success - Stego{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-6">
|
|
<div class="card">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-check-circle me-2"></i>Encoding Successful!
|
|
</h5>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
{% if carrier_type == 'audio' %}
|
|
<!-- Audio Preview -->
|
|
<div class="my-4">
|
|
<div class="text-center">
|
|
<i class="bi bi-music-note-beamed text-success" style="font-size: 4rem;"></i>
|
|
<div class="mt-2">
|
|
<audio controls src="{{ url_for('encode_file_route', file_id=file_id) }}" class="w-100" style="max-width: 400px;"></audio>
|
|
</div>
|
|
<div class="mt-2 small text-muted">
|
|
<i class="bi bi-music-note-beamed me-1"></i>Encoded Audio Preview
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="my-4">
|
|
{% if thumbnail_url %}
|
|
<!-- Thumbnail of the actual encoded image -->
|
|
<div class="encoded-image-thumbnail">
|
|
<img src="{{ thumbnail_url }}"
|
|
alt="Encoded image thumbnail"
|
|
class="img-thumbnail rounded"
|
|
style="max-width: 250px; max-height: 250px; object-fit: contain;">
|
|
<div class="mt-2 small text-muted">
|
|
<i class="bi bi-image me-1"></i>Encoded Image Preview
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<!-- Fallback to icon if thumbnail not available -->
|
|
<i class="bi bi-file-earmark-image text-success" style="font-size: 4rem;"></i>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<p class="lead mb-4">Your secret has been hidden in the {{ 'audio file' if carrier_type == 'audio' else 'image' }}.</p>
|
|
|
|
<div class="mb-3">
|
|
<code class="fs-5">{{ filename }}</code>
|
|
</div>
|
|
|
|
<!-- Mode and format badges -->
|
|
<div class="mb-4">
|
|
{% if carrier_type == 'audio' %}
|
|
<!-- Audio mode badges -->
|
|
{% if embed_mode == 'audio_spread' %}
|
|
<span class="badge bg-warning text-dark fs-6">
|
|
<i class="bi bi-broadcast me-1"></i>Spread Spectrum
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-primary fs-6">
|
|
<i class="bi bi-grid-3x3-gap me-1"></i>Audio LSB
|
|
</span>
|
|
{% endif %}
|
|
<span class="badge bg-info fs-6 ms-1">
|
|
<i class="bi bi-file-earmark-music me-1"></i>WAV
|
|
</span>
|
|
<div class="small text-muted mt-2">
|
|
{% if embed_mode == 'audio_spread' %}
|
|
Spread spectrum embedding in audio samples
|
|
{% else %}
|
|
LSB embedding in audio samples, WAV output
|
|
{% endif %}
|
|
</div>
|
|
{% elif embed_mode == 'dct' %}
|
|
<span class="badge bg-info fs-6">
|
|
<i class="bi bi-soundwave me-1"></i>DCT Mode
|
|
</span>
|
|
|
|
<!-- Color mode badge (v3.0.1) -->
|
|
{% if color_mode == 'color' %}
|
|
<span class="badge bg-success fs-6 ms-1">
|
|
<i class="bi bi-palette-fill me-1"></i>Color
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary fs-6 ms-1">
|
|
<i class="bi bi-circle-half me-1"></i>Grayscale
|
|
</span>
|
|
{% endif %}
|
|
|
|
<!-- Output format badge -->
|
|
{% if output_format == 'jpeg' %}
|
|
<span class="badge bg-warning text-dark fs-6 ms-1">
|
|
<i class="bi bi-file-earmark-richtext me-1"></i>JPEG
|
|
</span>
|
|
<div class="small text-muted mt-2">
|
|
{% if color_mode == 'color' %}
|
|
Color JPEG, frequency domain embedding (Q=95)
|
|
{% else %}
|
|
Grayscale JPEG, frequency domain embedding (Q=95)
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<span class="badge bg-primary fs-6 ms-1">
|
|
<i class="bi bi-file-earmark-image me-1"></i>PNG
|
|
</span>
|
|
<div class="small text-muted mt-2">
|
|
{% if color_mode == 'color' %}
|
|
Color PNG, frequency domain embedding (lossless)
|
|
{% else %}
|
|
Grayscale PNG, frequency domain embedding (lossless)
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<span class="badge bg-primary fs-6">
|
|
<i class="bi bi-grid-3x3-gap me-1"></i>LSB Mode
|
|
</span>
|
|
<span class="badge bg-success fs-6 ms-1">
|
|
<i class="bi bi-palette-fill me-1"></i>Full Color
|
|
</span>
|
|
<span class="badge bg-primary fs-6 ms-1">
|
|
<i class="bi bi-file-earmark-image me-1"></i>PNG
|
|
</span>
|
|
<div class="small text-muted mt-2">Full color PNG, spatial LSB embedding</div>
|
|
{% endif %}
|
|
|
|
<!-- Channel info (v4.0.0) -->
|
|
<div class="mt-3">
|
|
{% if channel_mode == 'private' %}
|
|
<span class="badge bg-warning text-dark fs-6">
|
|
<i class="bi bi-shield-lock me-1"></i>Private Channel
|
|
</span>
|
|
{% if channel_fingerprint %}
|
|
<div class="small text-muted mt-1">
|
|
<code>{{ channel_fingerprint }}</code>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="badge bg-info fs-6">
|
|
<i class="bi bi-globe me-1"></i>Public Channel
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<a href="{{ url_for('encode_download', file_id=file_id) }}"
|
|
class="btn btn-primary btn-lg" id="downloadBtn">
|
|
<i class="bi bi-download me-2"></i>Download {{ 'Audio' if carrier_type == 'audio' else 'Image' }}
|
|
</a>
|
|
|
|
<button type="button" class="btn btn-outline-primary" id="shareBtn" style="display: none;">
|
|
<i class="bi bi-share me-2"></i>Share
|
|
</button>
|
|
</div>
|
|
|
|
<hr class="my-4">
|
|
|
|
<div class="alert alert-warning small text-start">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>
|
|
<strong>Important:</strong>
|
|
<ul class="mb-0 mt-2">
|
|
<li>This file expires in <strong>10 minutes</strong></li>
|
|
{% if carrier_type == 'audio' %}
|
|
<li>Do <strong>not</strong> re-encode or convert the audio file</li>
|
|
<li>WAV format preserves your hidden data losslessly</li>
|
|
<li>Sharing via platforms that re-encode audio will destroy the hidden data</li>
|
|
{% else %}
|
|
<li>Do <strong>not</strong> resize or recompress the image</li>
|
|
{% if embed_mode == 'dct' and output_format == 'jpeg' %}
|
|
<li>JPEG format is lossy - avoid re-saving or editing</li>
|
|
{% else %}
|
|
<li>PNG format preserves your hidden data</li>
|
|
{% endif %}
|
|
{% if embed_mode == 'dct' %}
|
|
<li>Recipient needs <strong>DCT mode</strong> or <strong>Auto</strong> detection to decode</li>
|
|
{% if color_mode == 'color' %}
|
|
<li>Color preserved - extraction works on both color and grayscale</li>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if channel_mode == 'private' %}
|
|
<li><i class="bi bi-shield-lock text-warning me-1"></i>Recipient needs the <strong>same channel key</strong> to decode</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
|
|
<a href="{{ url_for('encode') }}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-repeat me-2"></i>Encode Another
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Web Share API support
|
|
const shareBtn = document.getElementById('shareBtn');
|
|
const fileUrl = "{{ url_for('encode_file_route', file_id=file_id, _external=True) }}";
|
|
const fileName = "{{ filename }}";
|
|
const mimeType = "{{ 'audio/wav' if carrier_type == 'audio' else ('image/jpeg' if embed_mode == 'dct' and output_format == 'jpeg' else 'image/png') }}";
|
|
|
|
if (navigator.share && navigator.canShare) {
|
|
// Check if we can share files
|
|
fetch(fileUrl)
|
|
.then(response => response.blob())
|
|
.then(blob => {
|
|
const file = new File([blob], fileName, { type: mimeType });
|
|
if (navigator.canShare({ files: [file] })) {
|
|
shareBtn.style.display = 'block';
|
|
shareBtn.addEventListener('click', async () => {
|
|
try {
|
|
await navigator.share({
|
|
files: [file],
|
|
title: 'Stego Image',
|
|
});
|
|
} catch (err) {
|
|
if (err.name !== 'AbortError') {
|
|
console.error('Share failed:', err);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
})
|
|
.catch(err => console.log('Could not load file for sharing'));
|
|
}
|
|
|
|
// Auto-cleanup after download
|
|
document.getElementById('downloadBtn').addEventListener('click', function() {
|
|
// Give time for download to start, then cleanup
|
|
setTimeout(() => {
|
|
fetch("{{ url_for('encode_cleanup', file_id=file_id) }}", { method: 'POST' });
|
|
}, 2000);
|
|
});
|
|
</script>
|
|
{% endblock %}
|