creatorplus / static /js /uploader.js
nitubhai's picture
Upload 3 files
1fbeebb verified
raw
history blame
24.9 kB
class YouTubeUploader {
constructor() {
this.isAuthenticated = false;
this.currentTaskId = null;
this.statusCheckInterval = null;
this.init();
}
init() {
this.cacheElements();
this.attachEventListeners();
this.checkAuthentication();
}
cacheElements() {
this.authSection = document.getElementById('authSection');
this.authBtn = document.getElementById('authBtn');
this.channelInfo = document.getElementById('channelInfo');
this.channelAvatar = document.getElementById('channelAvatar');
this.channelName = document.getElementById('channelName');
this.channelStats = document.getElementById('channelStats');
this.uploadSection = document.getElementById('uploadSection');
this.reelUrl = document.getElementById('reelUrl');
this.uploadBtn = document.getElementById('uploadBtn');
this.progressSection = document.getElementById('progressSection');
this.progressTitle = document.getElementById('progressTitle');
this.progressPercent = document.getElementById('progressPercent');
this.progressFill = document.getElementById('progressFill');
this.progressMessage = document.getElementById('progressMessage');
this.metadataPreview = document.getElementById('metadataPreview');
this.previewTitle = document.getElementById('previewTitle');
this.previewDescription = document.getElementById('previewDescription');
this.previewTags = document.getElementById('previewTags');
this.previewHashtags = document.getElementById('previewHashtags');
this.successResult = document.getElementById('successResult');
this.errorResult = document.getElementById('errorResult');
this.watchBtn = document.getElementById('watchBtn');
this.errorMessage = document.getElementById('errorMessage');
this.retryBtn = document.getElementById('retryBtn');
this.uploadAnotherBtn = document.getElementById('uploadAnotherBtn');
this.loadingOverlay = document.getElementById('loadingOverlay');
this.toast = document.getElementById('toast');
// Navbar elements
this.navAuthButtons = document.getElementById('navAuthButtons');
this.navSignInBtn = document.getElementById('navSignInBtn');
this.navUserMenu = document.getElementById('navUserMenu');
this.navUserAvatarImg = document.getElementById('navUserAvatarImg');
this.navUserName = document.getElementById('navUserName');
this.navUserStats = document.getElementById('navUserStats');
this.navLogoutBtn = document.getElementById('navLogoutBtn');
// Mobile menu elements
this.mobileSignInBtn = document.getElementById('mobileSignInBtn');
this.mobileUserInfo = document.getElementById('mobileUserInfo');
this.mobileUserName = document.getElementById('mobileUserName');
this.mobileUserStats = document.getElementById('mobileUserStats');
this.mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
// Music upload elements
this.musicUrl = document.getElementById('musicUrl');
this.musicVolume = document.getElementById('musicVolume');
this.enableEditingToggle = document.getElementById('enableEditingToggle');
// Music Source Tab elements
this.musicTabUrl = document.getElementById('musicTabUrl');
this.musicTabFile = document.getElementById('musicTabFile');
this.musicUrlSection = document.getElementById('musicUrlSection');
this.musicFileSection = document.getElementById('musicFileSection');
this.musicFileInput = document.getElementById('musicFileInput');
this.musicFileName = document.getElementById('musicFileName');
}
attachEventListeners() {
this.authBtn.addEventListener('click', () => this.handleAuthentication());
this.uploadBtn.addEventListener('click', () => this.handleUpload());
this.retryBtn.addEventListener('click', () => this.resetForm());
this.uploadAnotherBtn.addEventListener('click', () => this.resetForm());
this.navLogoutBtn.addEventListener('click', () => this.handleLogout());
this.reelUrl.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.handleUpload();
});
// Navbar sign in button
if (this.navSignInBtn) {
this.navSignInBtn.addEventListener('click', () => this.handleAuthentication());
}
// Mobile sign in and logout
if (this.mobileSignInBtn) {
this.mobileSignInBtn.addEventListener('click', () => {
document.getElementById('mobileMenu').classList.remove('active');
this.handleAuthentication();
});
}
if (this.mobileLogoutBtn) {
this.mobileLogoutBtn.addEventListener('click', () => {
document.getElementById('mobileMenu').classList.remove('active');
this.handleLogout();
});
}
// Music Source Tab Switching
if (this.musicTabUrl && this.musicTabFile) {
this.musicTabUrl.addEventListener('click', () => {
this.currentMusicSource = 'url';
this.musicTabUrl.style.opacity = '1';
this.musicTabFile.style.opacity = '0.6';
this.musicUrlSection.style.display = 'block';
this.musicFileSection.style.display = 'none';
this.uploadedMusicFile = null;
});
this.musicTabFile.addEventListener('click', () => {
this.currentMusicSource = 'file';
this.musicTabFile.style.opacity = '1';
this.musicTabUrl.style.opacity = '0.6';
this.musicFileSection.style.display = 'block';
this.musicUrlSection.style.display = 'none';
document.getElementById('musicUrl').value = '';
});
}
// Handle music file selection
if (this.musicFileInput) {
this.musicFileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
// Validate file type
const validTypes = ['audio/mpeg', 'audio/wav', 'audio/mp4', 'audio/x-m4a', 'audio/aac'];
if (!validTypes.includes(file.type) && !file.name.match(/\.(mp3|wav|m4a|aac)$/i)) {
this.showToast('Please select a valid audio file (MP3, WAV, M4A, AAC)', 'error');
return;
}
// Check file size (max 50MB)
if (file.size > 50 * 1024 * 1024) {
this.showToast('File size too large. Maximum 50MB allowed.', 'error');
return;
}
// Update UI
this.musicFileName.value = file.name;
this.showToast('Audio file selected. It will be uploaded when you start the upload process.', 'success');
// Store file for later upload
this.uploadedMusicFile = file;
});
}
}
async checkAuthentication() {
try {
const response = await fetch('/check-auth');
const data = await response.json();
if (data.authenticated) {
this.isAuthenticated = true;
if (data.channel) {
this.displayChannelInfo(data.channel);
}
this.showUploadSection();
} else {
this.showAuthSection();
}
} catch (error) {
console.error('Auth check failed:', error);
this.showAuthSection();
}
}
displayChannelInfo(channel) {
this.channelAvatar.src = channel.thumbnail || 'https://via.placeholder.com/80';
this.channelName.textContent = channel.title;
this.channelStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers • ${this.formatNumber(channel.videoCount)} videos`;
this.channelInfo.style.display = 'block';
// Update navbar user menu
this.navUserAvatarImg.src = channel.thumbnail || 'https://via.placeholder.com/40';
this.navUserName.textContent = channel.title;
this.navUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`;
this.navUserMenu.style.display = 'block';
this.navAuthButtons.style.display = 'none';
// Update mobile menu
this.mobileUserName.textContent = channel.title;
this.mobileUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`;
this.mobileUserInfo.style.display = 'block';
this.mobileLogoutBtn.style.display = 'block';
this.mobileSignInBtn.style.display = 'none';
}
async handleAuthentication() {
this.showLoading();
try {
const response = await fetch('/auth/start');
const data = await response.json();
if (data.auth_url) {
const authWindow = window.open(data.auth_url, 'YouTube Authentication', 'width=600,height=700');
const checkAuth = setInterval(async () => {
if (authWindow.closed) {
clearInterval(checkAuth);
this.hideLoading();
await this.checkAuthentication();
}
}, 1000);
}
} catch (error) {
this.showToast('Authentication failed: ' + error.message, 'error');
} finally {
this.hideLoading();
}
}
async handleLogout() {
if (!confirm('Are you sure you want to logout?')) return;
try {
const response = await fetch('/logout', { method: 'POST' });
const data = await response.json();
if (data.success) {
this.showToast('Logged out successfully', 'success');
this.isAuthenticated = false;
this.showAuthSection();
}
} catch (error) {
this.showToast('Logout failed: ' + error.message, 'error');
}
}
async handleUpload() {
if (!this.isAuthenticated) {
this.showToast('Please sign in with YouTube first', 'error');
return;
}
const url = this.reelUrl.value.trim();
if (!url) {
this.showToast('Please enter a valid Instagram Reel URL', 'error');
return;
}
// Get video editing preferences
const enableEditing = document.getElementById('enableEditingToggle').checked;
let editingOptions = null;
if (enableEditing) {
let musicSource = null;
// ✅ NEW: Handle local music file upload
const currentMusicSource = this.musicTabUrl.style.opacity === '1' ? 'url' : 'file';
if (currentMusicSource === 'file' && this.uploadedMusicFile) {
this.showLoading();
try {
// Upload music file to server
const formData = new FormData();
formData.append('music', this.uploadedMusicFile);
const uploadResponse = await fetch('/upload-music', {
method: 'POST',
body: formData
});
const uploadResult = await uploadResponse.json();
if (!uploadResult.success) {
throw new Error(uploadResult.error || 'Music upload failed');
}
musicSource = uploadResult.filepath;
this.showToast('Music file uploaded successfully', 'success');
} catch (error) {
this.hideLoading();
this.showToast('Failed to upload music: ' + error.message, 'error');
return;
} finally {
this.hideLoading();
}
} else if (currentMusicSource === 'url') {
musicSource = document.getElementById('musicUrl').value.trim();
}
const musicVolume = parseInt(document.getElementById('musicVolume').value) / 100;
// Get all text overlays
const textOverlays = [];
document.querySelectorAll('.text-overlay-item').forEach(item => {
const text = item.querySelector('.overlay-text').value.trim();
const position = item.querySelector('.overlay-position').value;
const duration = parseInt(item.querySelector('.overlay-duration').value);
if (text) {
textOverlays.push({ text, position, duration });
}
});
editingOptions = {
enabled: true,
music_url: currentMusicSource === 'url' ? musicSource : null,
music_file: currentMusicSource === 'file' ? musicSource : null,
music_volume: musicVolume,
text_overlays: textOverlays.length > 0 ? textOverlays : null
};
}
this.hideResults();
this.showProgress();
try {
const response = await fetch('/auto-upload-async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: url,
editing: editingOptions
})
});
const data = await response.json();
if (data.success) {
this.currentTaskId = data.task_id;
this.startStatusPolling();
} else {
throw new Error(data.error || 'Upload failed');
}
} catch (error) {
this.showError(error.message);
}
}
startStatusPolling() {
this.statusCheckInterval = setInterval(async () => {
await this.checkTaskStatus();
}, 2000);
}
async checkTaskStatus() {
if (!this.currentTaskId) return;
try {
const response = await fetch(`/task-status/${this.currentTaskId}`);
const data = await response.json();
if (data.success) {
const task = data.task;
this.updateProgress(task);
if (task.status === 'completed') {
this.stopStatusPolling();
this.showSuccess(task);
} else if (task.status === 'failed') {
this.stopStatusPolling();
this.showError(task.error || 'Upload failed');
}
}
} catch (error) {
console.error('Status check failed:', error);
}
}
stopStatusPolling() {
if (this.statusCheckInterval) {
clearInterval(this.statusCheckInterval);
this.statusCheckInterval = null;
}
}
updateProgress(task) {
this.progressTitle.textContent = this.getStatusTitle(task.status);
this.progressPercent.textContent = `${task.progress}%`;
this.progressFill.style.width = `${task.progress}%`;
this.progressMessage.textContent = task.message;
if (task.metadata && task.status === 'uploading') {
this.displayMetadata(task.metadata);
}
}
getStatusTitle(status) {
const titles = {
'started': 'Starting...',
'downloading': 'Downloading Reel',
'editing': 'Editing Video', // ✅ NEW: Add editing status
'generating_metadata': 'AI Analyzing Video',
'uploading': 'Uploading to YouTube',
'completed': 'Upload Complete',
'failed': 'Upload Failed'
};
return titles[status] || 'Processing...';
}
displayMetadata(metadata) {
this.previewTitle.textContent = metadata.title || '-';
this.previewDescription.textContent = metadata.description || '-';
this.previewTags.innerHTML = '';
if (metadata.tags && metadata.tags.length > 0) {
metadata.tags.slice(0, 15).forEach(tag => {
const span = document.createElement('span');
span.textContent = tag;
this.previewTags.appendChild(span);
});
}
this.previewHashtags.innerHTML = '';
if (metadata.hashtags && metadata.hashtags.length > 0) {
metadata.hashtags.slice(0, 20).forEach(hashtag => {
const span = document.createElement('span');
span.textContent = hashtag;
this.previewHashtags.appendChild(span);
});
}
this.metadataPreview.style.display = 'block';
}
showProgress() {
this.progressSection.style.display = 'block';
this.metadataPreview.style.display = 'none';
this.successResult.style.display = 'none';
this.errorResult.style.display = 'none';
}
showSuccess(task) {
this.progressSection.style.display = 'none';
this.successResult.style.display = 'block';
if (task.youtube_url) {
this.watchBtn.href = task.youtube_url;
}
this.showToast('Video uploaded successfully! 🎉', 'success');
}
showError(message) {
this.progressSection.style.display = 'none';
this.errorResult.style.display = 'block';
this.errorMessage.textContent = message;
this.showToast('Upload failed: ' + message, 'error');
}
hideResults() {
this.successResult.style.display = 'none';
this.errorResult.style.display = 'none';
this.metadataPreview.style.display = 'none';
}
resetForm() {
this.reelUrl.value = '';
this.hideResults();
this.progressSection.style.display = 'none';
this.currentTaskId = null;
this.stopStatusPolling();
}
showUploadSection() {
this.authSection.style.display = 'none';
this.uploadSection.style.display = 'block';
}
showAuthSection() {
this.authSection.style.display = 'block';
this.uploadSection.style.display = 'none';
this.channelInfo.style.display = 'none';
// Update navbar
this.navUserMenu.style.display = 'none';
this.navAuthButtons.style.display = 'flex';
// Update mobile menu
this.mobileUserInfo.style.display = 'none';
this.mobileLogoutBtn.style.display = 'none';
this.mobileSignInBtn.style.display = 'block';
}
showLoading() {
this.loadingOverlay.style.display = 'flex';
}
hideLoading() {
this.loadingOverlay.style.display = 'none';
}
showToast(message, type = 'info') {
this.toast.textContent = message;
this.toast.className = 'toast show';
if (type === 'success') {
this.toast.style.borderLeft = '4px solid var(--success)';
} else if (type === 'error') {
this.toast.style.borderLeft = '4px solid var(--error)';
}
setTimeout(() => {
this.toast.classList.remove('show');
}, 4000);
}
formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num;
}
}
document.addEventListener('DOMContentLoaded', () => {
new YouTubeUploader();
});
// Helper functions
function showLoadingOverlay(message) {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.querySelector('p').textContent = message;
overlay.style.display = 'flex';
}
}
function hideLoadingOverlay() {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.style.display = 'none';
}
}
function showToast(message, type = 'info') {
const toast = document.getElementById('toast');
if (toast) {
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
}
// Modified upload function to handle music file
async function startUpload() {
const reelUrl = document.getElementById('reelUrl').value.trim();
if (!reelUrl) {
showToast('Please enter an Instagram Reel URL', 'error');
return;
}
// Check if editing is enabled
const editingEnabled = document.getElementById('enableEditingToggle').checked;
let editingOptions = null;
if (editingEnabled) {
let musicPath = null;
// Handle music upload if local file is selected
if (currentMusicSource === 'file' && uploadedMusicFile) {
showLoadingOverlay('Uploading background music...');
try {
const formData = new FormData();
formData.append('music', uploadedMusicFile);
const uploadResponse = await fetch('/upload-music', {
method: 'POST',
body: formData
});
const uploadResult = await uploadResponse.json();
if (!uploadResult.success) {
throw new Error(uploadResult.error || 'Music upload failed');
}
musicPath = uploadResult.filepath;
hideLoadingOverlay();
} catch (error) {
hideLoadingOverlay();
showToast('Failed to upload music file: ' + error.message, 'error');
return;
}
} else if (currentMusicSource === 'url') {
musicPath = document.getElementById('musicUrl').value.trim();
}
// Collect text overlays
const textOverlays = [];
document.querySelectorAll('.text-overlay-item').forEach(item => {
const text = item.querySelector('.overlay-text').value.trim();
if (text) {
textOverlays.push({
text: text,
position: item.querySelector('.overlay-position').value,
duration: parseInt(item.querySelector('.overlay-duration').value) || 5
});
}
});
editingOptions = {
enabled: true,
music_url: currentMusicSource === 'url' ? musicPath : null,
music_file: currentMusicSource === 'file' ? musicPath : null,
music_volume: parseInt(document.getElementById('musicVolume').value) / 100,
text_overlays: textOverlays
};
}
// Start async upload
try {
showLoadingOverlay('Starting upload process...');
const response = await fetch('/auto-upload-async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: reelUrl,
editing: editingOptions
})
});
const result = await response.json();
hideLoadingOverlay();
if (result.success) {
// Start polling for status
pollTaskStatus(result.task_id);
} else {
showToast(result.error || 'Failed to start upload', 'error');
}
} catch (error) {
hideLoadingOverlay();
showToast('Error: ' + error.message, 'error');
}
}