Add Naming Convention management and remove L'Oréal branding
Major Features: - Complete Naming Convention editor in Admin Panel - Define custom filename patterns for platform detection - Define custom filename patterns for aspect ratio detection - Patterns saved to naming_conventions.json (editable) - Test pattern functionality built-in - Auto-loads patterns from JSON on server startup - Factory reset now restores original patterns too UI Changes: - Remove L'Oréal references from app (now generic tool) - Changed title to "Social Media Platform Optimization Tool" - Renamed "Reload from Server" to "Refresh Display" - Added "Reset to Factory Defaults" button (red, double-confirm) - New Naming Conventions section in admin panel - Pattern editor with add/remove functionality Backend Enhancements: - Save/load naming conventions to JSON - GET /api/admin/naming-conventions (retrieve patterns) - POST /api/admin/naming-conventions (save patterns) - Factory defaults for patterns stored at startup - Patterns persist across server restarts - Detection logic now uses editable patterns Naming Convention Features: - Platform patterns: Map platform key to filename patterns - Aspect ratio patterns: Map ratio to filename patterns - Multiple patterns per platform/ratio supported - Test functionality to verify detection - Immediate application to main app Example patterns: - TikTok: _tiktok_, _tt_ - 16:9: _16x9_, _landscape_ - Meta: _meta_, _fb_, _ig_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
300a67d247
commit
bc05da3314
6 changed files with 444 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -24,6 +24,7 @@ backend/outputs/*
|
|||
|
||||
# Generated platform specifications
|
||||
backend/platform_specs.json
|
||||
backend/naming_conventions.json
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ from datetime import datetime
|
|||
from video_processor import VideoProcessor
|
||||
from platform_specs import (
|
||||
PLATFORM_SPECS,
|
||||
FILENAME_PATTERNS,
|
||||
ASPECT_RATIO_PATTERNS,
|
||||
detect_platform_from_filename,
|
||||
detect_aspect_ratio_from_filename,
|
||||
get_all_platforms,
|
||||
|
|
@ -25,6 +27,8 @@ CORS(app)
|
|||
# Store factory defaults (original specs from platform_specs.py)
|
||||
import copy
|
||||
FACTORY_DEFAULTS = copy.deepcopy(PLATFORM_SPECS)
|
||||
FACTORY_FILENAME_PATTERNS = copy.deepcopy(FILENAME_PATTERNS)
|
||||
FACTORY_ASPECT_RATIO_PATTERNS = copy.deepcopy(ASPECT_RATIO_PATTERNS)
|
||||
|
||||
# Configuration
|
||||
UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), 'uploads')
|
||||
|
|
@ -278,6 +282,7 @@ def cleanup_files(file_id):
|
|||
# ============================================================================
|
||||
|
||||
SPECS_FILE = os.path.join(os.path.dirname(__file__), 'platform_specs.json')
|
||||
NAMING_FILE = os.path.join(os.path.dirname(__file__), 'naming_conventions.json')
|
||||
|
||||
|
||||
def save_specs_to_file(specs):
|
||||
|
|
@ -303,6 +308,33 @@ def load_specs_from_file():
|
|||
return None
|
||||
|
||||
|
||||
def save_naming_conventions(platform_patterns, aspect_ratio_patterns):
|
||||
"""Save naming conventions to JSON file"""
|
||||
try:
|
||||
data = {
|
||||
'platform_patterns': platform_patterns,
|
||||
'aspect_ratio_patterns': aspect_ratio_patterns
|
||||
}
|
||||
with open(NAMING_FILE, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving naming conventions: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def load_naming_conventions():
|
||||
"""Load naming conventions from JSON file"""
|
||||
try:
|
||||
if os.path.exists(NAMING_FILE):
|
||||
with open(NAMING_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error loading naming conventions: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@app.route('/api/admin/platforms', methods=['POST'])
|
||||
def admin_add_platform():
|
||||
"""Add a new platform configuration"""
|
||||
|
|
@ -432,13 +464,21 @@ def admin_reset_factory():
|
|||
PLATFORM_SPECS.clear()
|
||||
PLATFORM_SPECS.update(copy.deepcopy(FACTORY_DEFAULTS))
|
||||
|
||||
# Delete the saved JSON file if it exists
|
||||
FILENAME_PATTERNS.clear()
|
||||
FILENAME_PATTERNS.update(copy.deepcopy(FACTORY_FILENAME_PATTERNS))
|
||||
|
||||
ASPECT_RATIO_PATTERNS.clear()
|
||||
ASPECT_RATIO_PATTERNS.update(copy.deepcopy(FACTORY_ASPECT_RATIO_PATTERNS))
|
||||
|
||||
# Delete the saved JSON files if they exist
|
||||
if os.path.exists(SPECS_FILE):
|
||||
os.remove(SPECS_FILE)
|
||||
if os.path.exists(NAMING_FILE):
|
||||
os.remove(NAMING_FILE)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Platform specifications reset to factory defaults',
|
||||
'message': 'Platform specifications and naming conventions reset to factory defaults',
|
||||
'platforms_count': len(PLATFORM_SPECS)
|
||||
})
|
||||
|
||||
|
|
@ -446,6 +486,47 @@ def admin_reset_factory():
|
|||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/naming-conventions', methods=['GET'])
|
||||
def admin_get_naming_conventions():
|
||||
"""Get current naming conventions"""
|
||||
try:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'platform_patterns': FILENAME_PATTERNS,
|
||||
'aspect_ratio_patterns': ASPECT_RATIO_PATTERNS
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/naming-conventions', methods=['POST'])
|
||||
def admin_save_naming_conventions():
|
||||
"""Save naming conventions"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
platform_patterns = data.get('platform_patterns', {})
|
||||
aspect_ratio_patterns = data.get('aspect_ratio_patterns', {})
|
||||
|
||||
# Update in-memory patterns
|
||||
FILENAME_PATTERNS.clear()
|
||||
FILENAME_PATTERNS.update(platform_patterns)
|
||||
|
||||
ASPECT_RATIO_PATTERNS.clear()
|
||||
ASPECT_RATIO_PATTERNS.update(aspect_ratio_patterns)
|
||||
|
||||
# Save to file
|
||||
save_naming_conventions(platform_patterns, aspect_ratio_patterns)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Naming conventions saved successfully'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Load specs from file if exists
|
||||
saved_specs = load_specs_from_file()
|
||||
|
|
@ -454,6 +535,15 @@ if __name__ == '__main__':
|
|||
PLATFORM_SPECS.update(saved_specs)
|
||||
print(f"Loaded {len(saved_specs)} platform configurations from file")
|
||||
|
||||
# Load naming conventions from file if exists
|
||||
saved_naming = load_naming_conventions()
|
||||
if saved_naming:
|
||||
FILENAME_PATTERNS.clear()
|
||||
FILENAME_PATTERNS.update(saved_naming.get('platform_patterns', {}))
|
||||
ASPECT_RATIO_PATTERNS.clear()
|
||||
ASPECT_RATIO_PATTERNS.update(saved_naming.get('aspect_ratio_patterns', {}))
|
||||
print(f"Loaded naming conventions from file")
|
||||
|
||||
# Check FFmpeg installation
|
||||
if not VideoProcessor.check_ffmpeg_installed():
|
||||
print("WARNING: FFmpeg not found. Please install FFmpeg to use video conversion features.")
|
||||
|
|
|
|||
|
|
@ -227,6 +227,107 @@
|
|||
background-color: rgba(255, 196, 7, 0.05);
|
||||
}
|
||||
|
||||
/* Naming Conventions Section */
|
||||
.naming-section {
|
||||
background-color: var(--secondary-black);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.naming-section h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-yellow);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.naming-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.naming-panel {
|
||||
background-color: var(--primary-black);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.naming-panel h3 {
|
||||
color: var(--primary-yellow);
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.naming-panel .hint {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8125rem;
|
||||
margin-bottom: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
background-color: var(--secondary-black);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.pattern-item:hover {
|
||||
border-color: var(--primary-yellow);
|
||||
}
|
||||
|
||||
.pattern-select {
|
||||
flex: 0 0 180px;
|
||||
}
|
||||
|
||||
.pattern-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pattern-remove {
|
||||
flex: 0 0 auto;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ff0000;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.pattern-remove:hover {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.naming-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.naming-actions .btn-primary,
|
||||
.naming-actions .btn-secondary {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,39 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Naming Conventions Section -->
|
||||
<section class="naming-section">
|
||||
<h2>Naming Conventions</h2>
|
||||
<p class="section-description">Define filename patterns for automatic platform and aspect ratio detection. Use regular expressions or simple text patterns.</p>
|
||||
|
||||
<div class="naming-grid">
|
||||
<!-- Platform Patterns -->
|
||||
<div class="naming-panel">
|
||||
<h3>Platform Detection Patterns</h3>
|
||||
<p class="hint">Patterns to identify platform from filename (e.g., "_tiktok_", "_meta_")</p>
|
||||
<div id="platformPatternsList">
|
||||
<!-- Platform patterns will be loaded here -->
|
||||
</div>
|
||||
<button class="btn-secondary btn-sm" id="addPlatformPatternBtn">+ Add Platform Pattern</button>
|
||||
</div>
|
||||
|
||||
<!-- Aspect Ratio Patterns -->
|
||||
<div class="naming-panel">
|
||||
<h3>Aspect Ratio Detection Patterns</h3>
|
||||
<p class="hint">Patterns to identify aspect ratio from filename (e.g., "_16x9_", "_1x1_")</p>
|
||||
<div id="aspectRatioPatternsList">
|
||||
<!-- Aspect ratio patterns will be loaded here -->
|
||||
</div>
|
||||
<button class="btn-secondary btn-sm" id="addAspectRatioPatternBtn">+ Add Aspect Ratio Pattern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="naming-actions">
|
||||
<button class="btn-primary" id="saveNamingBtn">Save Naming Conventions</button>
|
||||
<button class="btn-secondary" id="testNamingBtn">Test Pattern</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Add/Edit Platform Modal -->
|
||||
<div class="modal" id="platformModal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
|
|
@ -120,7 +153,7 @@
|
|||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<p>L'Oréal Video Optimizer - Admin Panel</p>
|
||||
<p>Video Optimizer - Admin Panel</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ const API_BASE = CONFIG ? CONFIG.API_BASE : 'http://localhost:5000/api';
|
|||
let platforms = [];
|
||||
let editingPlatformKey = null;
|
||||
let formatCounter = 0;
|
||||
let platformPatterns = {};
|
||||
let aspectRatioPatterns = {};
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadPlatforms();
|
||||
loadNamingConventions();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
|
|
@ -25,6 +28,12 @@ function setupEventListeners() {
|
|||
document.getElementById('cancelBtn').addEventListener('click', closeModal);
|
||||
document.getElementById('addFormatBtn').addEventListener('click', addFormatConfig);
|
||||
document.getElementById('platformForm').addEventListener('submit', savePlatform);
|
||||
|
||||
// Naming conventions
|
||||
document.getElementById('addPlatformPatternBtn').addEventListener('click', addPlatformPattern);
|
||||
document.getElementById('addAspectRatioPatternBtn').addEventListener('click', addAspectRatioPattern);
|
||||
document.getElementById('saveNamingBtn').addEventListener('click', saveNamingConventions);
|
||||
document.getElementById('testNamingBtn').addEventListener('click', testNaming);
|
||||
}
|
||||
|
||||
// Load Platforms
|
||||
|
|
@ -443,3 +452,207 @@ function showMessage(message, type = 'success') {
|
|||
messageDiv.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NAMING CONVENTIONS
|
||||
// ============================================================================
|
||||
|
||||
// Load Naming Conventions
|
||||
async function loadNamingConventions() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/naming-conventions`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
platformPatterns = data.platform_patterns || {};
|
||||
aspectRatioPatterns = data.aspect_ratio_patterns || {};
|
||||
|
||||
renderPlatformPatterns();
|
||||
renderAspectRatioPatterns();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading naming conventions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Render Platform Patterns
|
||||
function renderPlatformPatterns() {
|
||||
const container = document.getElementById('platformPatternsList');
|
||||
container.innerHTML = '';
|
||||
|
||||
Object.keys(platformPatterns).forEach(platformKey => {
|
||||
const patterns = platformPatterns[platformKey];
|
||||
patterns.forEach((pattern, index) => {
|
||||
addPlatformPatternRow(platformKey, pattern, index);
|
||||
});
|
||||
});
|
||||
|
||||
// Add empty row for new pattern
|
||||
if (platforms.length > 0 && Object.keys(platformPatterns).length === 0) {
|
||||
addPlatformPatternRow();
|
||||
}
|
||||
}
|
||||
|
||||
// Render Aspect Ratio Patterns
|
||||
function renderAspectRatioPatterns() {
|
||||
const container = document.getElementById('aspectRatioPatternsList');
|
||||
container.innerHTML = '';
|
||||
|
||||
Object.keys(aspectRatioPatterns).forEach(ratio => {
|
||||
const patterns = aspectRatioPatterns[ratio];
|
||||
patterns.forEach((pattern, index) => {
|
||||
addAspectRatioPatternRow(ratio, pattern, index);
|
||||
});
|
||||
});
|
||||
|
||||
// Add empty row for new pattern
|
||||
if (Object.keys(aspectRatioPatterns).length === 0) {
|
||||
addAspectRatioPatternRow();
|
||||
}
|
||||
}
|
||||
|
||||
// Add Platform Pattern Row
|
||||
function addPlatformPatternRow(platformKey = '', pattern = '', index = 0) {
|
||||
const container = document.getElementById('platformPatternsList');
|
||||
const itemId = `platform-pattern-${Date.now()}-${index}`;
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'pattern-item';
|
||||
itemDiv.id = itemId;
|
||||
itemDiv.innerHTML = `
|
||||
<select class="select-input pattern-select platform-select-pattern">
|
||||
<option value="">Select Platform...</option>
|
||||
${platforms.map(p => `<option value="${p.key}" ${p.key === platformKey ? 'selected' : ''}>${p.name}</option>`).join('')}
|
||||
</select>
|
||||
<input type="text" class="text-input pattern-input platform-pattern-value" placeholder="e.g., _tiktok_, _tt_" value="${pattern}">
|
||||
<button type="button" class="pattern-remove" onclick="removePatternItem('${itemId}')">×</button>
|
||||
`;
|
||||
container.appendChild(itemDiv);
|
||||
}
|
||||
|
||||
// Add Aspect Ratio Pattern Row
|
||||
function addAspectRatioPatternRow(ratio = '', pattern = '', index = 0) {
|
||||
const container = document.getElementById('aspectRatioPatternsList');
|
||||
const itemId = `ratio-pattern-${Date.now()}-${index}`;
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'pattern-item';
|
||||
itemDiv.id = itemId;
|
||||
itemDiv.innerHTML = `
|
||||
<input type="text" class="text-input pattern-select aspect-ratio-select" placeholder="e.g., 16:9" value="${ratio}">
|
||||
<input type="text" class="text-input pattern-input aspect-ratio-pattern-value" placeholder="e.g., _16x9_, _landscape_" value="${pattern}">
|
||||
<button type="button" class="pattern-remove" onclick="removePatternItem('${itemId}')">×</button>
|
||||
`;
|
||||
container.appendChild(itemDiv);
|
||||
}
|
||||
|
||||
// Add Pattern Button Handlers
|
||||
function addPlatformPattern() {
|
||||
addPlatformPatternRow();
|
||||
}
|
||||
|
||||
function addAspectRatioPattern() {
|
||||
addAspectRatioPatternRow();
|
||||
}
|
||||
|
||||
// Remove Pattern Item
|
||||
function removePatternItem(itemId) {
|
||||
const item = document.getElementById(itemId);
|
||||
if (item) {
|
||||
item.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Save Naming Conventions
|
||||
async function saveNamingConventions() {
|
||||
try {
|
||||
// Collect platform patterns
|
||||
const newPlatformPatterns = {};
|
||||
const platformItems = document.querySelectorAll('#platformPatternsList .pattern-item');
|
||||
|
||||
platformItems.forEach(item => {
|
||||
const platformKey = item.querySelector('.platform-select-pattern').value;
|
||||
const pattern = item.querySelector('.platform-pattern-value').value.trim();
|
||||
|
||||
if (platformKey && pattern) {
|
||||
if (!newPlatformPatterns[platformKey]) {
|
||||
newPlatformPatterns[platformKey] = [];
|
||||
}
|
||||
newPlatformPatterns[platformKey].push(pattern);
|
||||
}
|
||||
});
|
||||
|
||||
// Collect aspect ratio patterns
|
||||
const newAspectRatioPatterns = {};
|
||||
const ratioItems = document.querySelectorAll('#aspectRatioPatternsList .pattern-item');
|
||||
|
||||
ratioItems.forEach(item => {
|
||||
const ratio = item.querySelector('.aspect-ratio-select').value.trim();
|
||||
const pattern = item.querySelector('.aspect-ratio-pattern-value').value.trim();
|
||||
|
||||
if (ratio && pattern) {
|
||||
if (!newAspectRatioPatterns[ratio]) {
|
||||
newAspectRatioPatterns[ratio] = [];
|
||||
}
|
||||
newAspectRatioPatterns[ratio].push(pattern);
|
||||
}
|
||||
});
|
||||
|
||||
// Save to backend
|
||||
const response = await fetch(`${API_BASE}/admin/naming-conventions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
platform_patterns: newPlatformPatterns,
|
||||
aspect_ratio_patterns: newAspectRatioPatterns
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
platformPatterns = newPlatformPatterns;
|
||||
aspectRatioPatterns = newAspectRatioPatterns;
|
||||
showMessage('Naming conventions saved successfully!', 'success');
|
||||
} else {
|
||||
showMessage('Error saving: ' + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('Error saving naming conventions: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Test Naming Patterns
|
||||
function testNaming() {
|
||||
const filename = prompt('Enter a filename to test pattern detection:');
|
||||
if (!filename) return;
|
||||
|
||||
let detectedPlatform = null;
|
||||
let detectedRatio = null;
|
||||
|
||||
// Test platform patterns
|
||||
Object.keys(platformPatterns).forEach(platformKey => {
|
||||
platformPatterns[platformKey].forEach(pattern => {
|
||||
if (filename.toLowerCase().includes(pattern.toLowerCase())) {
|
||||
detectedPlatform = platformKey;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Test aspect ratio patterns
|
||||
Object.keys(aspectRatioPatterns).forEach(ratio => {
|
||||
aspectRatioPatterns[ratio].forEach(pattern => {
|
||||
if (filename.toLowerCase().includes(pattern.toLowerCase())) {
|
||||
detectedRatio = ratio;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const message = `Filename: "${filename}"\n\n` +
|
||||
`Detected Platform: ${detectedPlatform || 'None'}\n` +
|
||||
`Detected Aspect Ratio: ${detectedRatio || 'None'}`;
|
||||
|
||||
alert(message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Video Optimizer - L'Oréal Creative Optimization</title>
|
||||
<title>Video Optimizer - Social Media Platform Tool</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<!-- Header -->
|
||||
<header class="header">
|
||||
<h1>Video Optimizer</h1>
|
||||
<p class="subtitle">L'Oréal Creative Optimization Tool</p>
|
||||
<p class="subtitle">Social Media Platform Optimization Tool</p>
|
||||
</header>
|
||||
|
||||
<!-- Upload Section -->
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<p>Based on L'Oréal CDMO Creative Optimization Documentation v1.1</p>
|
||||
<p>Video Optimization Tool for Social Media Platforms</p>
|
||||
<p><a href="admin.html" style="color: #FFC407; text-decoration: none;">Admin Panel →</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue