nitubhai commited on
Commit
1fbeebb
·
verified ·
1 Parent(s): afbd1df

Upload 3 files

Browse files
static/js/downloader.js ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class InstagramDownloader {
2
+ constructor() {
3
+ this.currentFilename = null;
4
+ this.init();
5
+ }
6
+
7
+ init() {
8
+ this.cacheElements();
9
+ this.attachEventListeners();
10
+ }
11
+
12
+ cacheElements() {
13
+ this.downloadUrl = document.getElementById('downloadUrl');
14
+ this.downloadBtn = document.getElementById('downloadBtn');
15
+ this.progressSection = document.getElementById('progressSection');
16
+ this.progressTitle = document.getElementById('progressTitle');
17
+ this.progressPercent = document.getElementById('progressPercent');
18
+ this.progressFill = document.getElementById('progressFill');
19
+ this.progressMessage = document.getElementById('progressMessage');
20
+ this.successResult = document.getElementById('successResult');
21
+ this.errorResult = document.getElementById('errorResult');
22
+ this.errorMessage = document.getElementById('errorMessage');
23
+ this.retryBtn = document.getElementById('retryBtn');
24
+ this.downloadAgainBtn = document.getElementById('downloadAgainBtn');
25
+ this.loadingOverlay = document.getElementById('loadingOverlay');
26
+ this.toast = document.getElementById('toast');
27
+ }
28
+
29
+ attachEventListeners() {
30
+ this.downloadBtn.addEventListener('click', () => this.handleDownload());
31
+ this.retryBtn.addEventListener('click', () => this.resetForm());
32
+ this.downloadAgainBtn.addEventListener('click', () => this.resetForm());
33
+ this.downloadUrl.addEventListener('keypress', (e) => {
34
+ if (e.key === 'Enter') this.handleDownload();
35
+ });
36
+ }
37
+
38
+ async handleDownload() {
39
+ const url = this.downloadUrl.value.trim();
40
+
41
+ if (!url) {
42
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
43
+ return;
44
+ }
45
+
46
+ this.hideResults();
47
+ this.showProgress('Downloading...', 30, 'Preparing to download reel...');
48
+
49
+ try {
50
+ // Step 1: Request download from server
51
+ this.updateProgress('Downloading...', 40, 'Downloading from Instagram...');
52
+
53
+ const response = await fetch('/download', {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ url })
57
+ });
58
+
59
+ const data = await response.json();
60
+
61
+ if (!data.success) {
62
+ throw new Error(data.error || data.details || 'Download failed');
63
+ }
64
+
65
+ this.currentFilename = data.filename;
66
+
67
+ // Step 2: Download the file using fetch and blob
68
+ this.updateProgress('Downloading...', 70, 'Preparing file for download...');
69
+
70
+ try {
71
+ const videoResponse = await fetch(`/get-video/${data.filename}`);
72
+
73
+ if (!videoResponse.ok) {
74
+ throw new Error('Failed to fetch video file from server');
75
+ }
76
+
77
+ // Get the video as a blob
78
+ const blob = await videoResponse.blob();
79
+
80
+ // Create a download URL from the blob
81
+ const downloadUrl = window.URL.createObjectURL(blob);
82
+
83
+ // Create and trigger download
84
+ const link = document.createElement('a');
85
+ link.href = downloadUrl;
86
+ link.download = data.filename;
87
+ link.style.display = 'none';
88
+ document.body.appendChild(link);
89
+ link.click();
90
+
91
+ // Cleanup
92
+ setTimeout(() => {
93
+ document.body.removeChild(link);
94
+ window.URL.revokeObjectURL(downloadUrl);
95
+ }, 100);
96
+
97
+ // Step 3: Show success
98
+ this.updateProgress('Complete!', 100, 'Download completed successfully!');
99
+
100
+ setTimeout(() => {
101
+ this.showSuccess();
102
+ this.showToast('Download completed! Check your downloads folder.', 'success');
103
+
104
+ // Clean up file after 30 seconds
105
+ setTimeout(() => this.cleanupFile(data.filename), 30000);
106
+ }, 500);
107
+
108
+ } catch (downloadError) {
109
+ console.error('Download error:', downloadError);
110
+ throw new Error('Failed to download video file. Please try again.');
111
+ }
112
+
113
+ } catch (error) {
114
+ console.error('Download error:', error);
115
+ this.showError(error.message);
116
+ }
117
+ }
118
+
119
+ async cleanupFile(filename) {
120
+ try {
121
+ await fetch(`/cleanup/${filename}`, { method: 'POST' });
122
+ } catch (error) {
123
+ console.log('Cleanup error (non-critical):', error);
124
+ }
125
+ }
126
+
127
+ updateProgress(title, percent, message) {
128
+ this.progressTitle.textContent = title;
129
+ this.progressPercent.textContent = `${percent}%`;
130
+ this.progressFill.style.width = `${percent}%`;
131
+ this.progressMessage.textContent = message;
132
+ }
133
+
134
+ showProgress(title = 'Downloading...', percent = 0, message = 'Starting...') {
135
+ this.progressSection.style.display = 'block';
136
+ this.updateProgress(title, percent, message);
137
+ }
138
+
139
+ showSuccess() {
140
+ this.progressSection.style.display = 'none';
141
+ this.successResult.style.display = 'block';
142
+ }
143
+
144
+ showError(message) {
145
+ this.progressSection.style.display = 'none';
146
+ this.errorResult.style.display = 'block';
147
+ this.errorMessage.textContent = message;
148
+ this.showToast('Download failed: ' + message, 'error');
149
+ }
150
+
151
+ hideResults() {
152
+ this.successResult.style.display = 'none';
153
+ this.errorResult.style.display = 'none';
154
+ this.progressSection.style.display = 'none';
155
+ }
156
+
157
+ resetForm() {
158
+ this.downloadUrl.value = '';
159
+ this.hideResults();
160
+ this.currentFilename = null;
161
+ }
162
+
163
+ showToast(message, type = 'info') {
164
+ this.toast.textContent = message;
165
+ this.toast.className = 'toast show';
166
+
167
+ if (type === 'success') {
168
+ this.toast.style.borderLeft = '4px solid var(--success)';
169
+ } else if (type === 'error') {
170
+ this.toast.style.borderLeft = '4px solid var(--error)';
171
+ }
172
+
173
+ setTimeout(() => {
174
+ this.toast.classList.remove('show');
175
+ }, 4000);
176
+ }
177
+ }
178
+
179
+ document.addEventListener('DOMContentLoaded', () => {
180
+ new InstagramDownloader();
181
+ });
static/js/metadata.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class MetadataGenerator {
2
+ constructor() {
3
+ this.init();
4
+ }
5
+
6
+ init() {
7
+ this.cacheElements();
8
+ this.attachEventListeners();
9
+ }
10
+
11
+ cacheElements() {
12
+ this.metadataUrl = document.getElementById('metadataUrl');
13
+ this.generateBtn = document.getElementById('generateBtn');
14
+ this.metadataPreview = document.getElementById('metadataPreview');
15
+ this.previewTitle = document.getElementById('previewTitle');
16
+ this.previewDescription = document.getElementById('previewDescription');
17
+ this.previewTags = document.getElementById('previewTags');
18
+ this.previewHashtags = document.getElementById('previewHashtags');
19
+ this.loadingOverlay = document.getElementById('loadingOverlay');
20
+ this.toast = document.getElementById('toast');
21
+ }
22
+
23
+ attachEventListeners() {
24
+ this.generateBtn.addEventListener('click', () => this.handleGenerate());
25
+ this.metadataUrl.addEventListener('keypress', (e) => {
26
+ if (e.key === 'Enter') this.handleGenerate();
27
+ });
28
+ }
29
+
30
+ async handleGenerate() {
31
+ const url = this.metadataUrl.value.trim();
32
+
33
+ if (!url) {
34
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
35
+ return;
36
+ }
37
+
38
+ this.showLoading();
39
+
40
+ try {
41
+ const response = await fetch('/generate-preview', {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ url })
45
+ });
46
+
47
+ const data = await response.json();
48
+
49
+ if (data.success) {
50
+ this.displayMetadata(data);
51
+ this.showToast('AI metadata generated successfully!', 'success');
52
+ } else {
53
+ throw new Error(data.error || 'Generation failed');
54
+ }
55
+ } catch (error) {
56
+ this.showToast('Generation failed: ' + error.message, 'error');
57
+ } finally {
58
+ this.hideLoading();
59
+ }
60
+ }
61
+
62
+ displayMetadata(data) {
63
+ this.previewTitle.textContent = data.title || '-';
64
+ this.previewDescription.textContent = data.description || '-';
65
+
66
+ // Display tags
67
+ this.previewTags.innerHTML = '';
68
+ if (data.tags && data.tags.length > 0) {
69
+ data.tags.slice(0, 15).forEach(tag => {
70
+ const span = document.createElement('span');
71
+ span.textContent = tag;
72
+ this.previewTags.appendChild(span);
73
+ });
74
+ }
75
+
76
+ // Display hashtags
77
+ this.previewHashtags.innerHTML = '';
78
+ if (data.hashtags && data.hashtags.length > 0) {
79
+ data.hashtags.slice(0, 20).forEach(hashtag => {
80
+ const span = document.createElement('span');
81
+ span.textContent = hashtag;
82
+ this.previewHashtags.appendChild(span);
83
+ });
84
+ }
85
+
86
+ this.metadataPreview.style.display = 'block';
87
+ }
88
+
89
+ showLoading() {
90
+ this.loadingOverlay.style.display = 'flex';
91
+ }
92
+
93
+ hideLoading() {
94
+ this.loadingOverlay.style.display = 'none';
95
+ }
96
+
97
+ showToast(message, type = 'info') {
98
+ this.toast.textContent = message;
99
+ this.toast.className = 'toast show';
100
+
101
+ if (type === 'success') {
102
+ this.toast.style.borderLeft = '4px solid var(--success)';
103
+ } else if (type === 'error') {
104
+ this.toast.style.borderLeft = '4px solid var(--error)';
105
+ }
106
+
107
+ setTimeout(() => {
108
+ this.toast.classList.remove('show');
109
+ }, 4000);
110
+ }
111
+ }
112
+
113
+ document.addEventListener('DOMContentLoaded', () => {
114
+ new MetadataGenerator();
115
+ });
static/js/uploader.js ADDED
@@ -0,0 +1,643 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class YouTubeUploader {
2
+ constructor() {
3
+ this.isAuthenticated = false;
4
+ this.currentTaskId = null;
5
+ this.statusCheckInterval = null;
6
+ this.init();
7
+ }
8
+
9
+ init() {
10
+ this.cacheElements();
11
+ this.attachEventListeners();
12
+ this.checkAuthentication();
13
+ }
14
+
15
+ cacheElements() {
16
+ this.authSection = document.getElementById('authSection');
17
+ this.authBtn = document.getElementById('authBtn');
18
+ this.channelInfo = document.getElementById('channelInfo');
19
+ this.channelAvatar = document.getElementById('channelAvatar');
20
+ this.channelName = document.getElementById('channelName');
21
+ this.channelStats = document.getElementById('channelStats');
22
+ this.uploadSection = document.getElementById('uploadSection');
23
+ this.reelUrl = document.getElementById('reelUrl');
24
+ this.uploadBtn = document.getElementById('uploadBtn');
25
+ this.progressSection = document.getElementById('progressSection');
26
+ this.progressTitle = document.getElementById('progressTitle');
27
+ this.progressPercent = document.getElementById('progressPercent');
28
+ this.progressFill = document.getElementById('progressFill');
29
+ this.progressMessage = document.getElementById('progressMessage');
30
+ this.metadataPreview = document.getElementById('metadataPreview');
31
+ this.previewTitle = document.getElementById('previewTitle');
32
+ this.previewDescription = document.getElementById('previewDescription');
33
+ this.previewTags = document.getElementById('previewTags');
34
+ this.previewHashtags = document.getElementById('previewHashtags');
35
+ this.successResult = document.getElementById('successResult');
36
+ this.errorResult = document.getElementById('errorResult');
37
+ this.watchBtn = document.getElementById('watchBtn');
38
+ this.errorMessage = document.getElementById('errorMessage');
39
+ this.retryBtn = document.getElementById('retryBtn');
40
+ this.uploadAnotherBtn = document.getElementById('uploadAnotherBtn');
41
+ this.loadingOverlay = document.getElementById('loadingOverlay');
42
+ this.toast = document.getElementById('toast');
43
+
44
+ // Navbar elements
45
+ this.navAuthButtons = document.getElementById('navAuthButtons');
46
+ this.navSignInBtn = document.getElementById('navSignInBtn');
47
+ this.navUserMenu = document.getElementById('navUserMenu');
48
+ this.navUserAvatarImg = document.getElementById('navUserAvatarImg');
49
+ this.navUserName = document.getElementById('navUserName');
50
+ this.navUserStats = document.getElementById('navUserStats');
51
+ this.navLogoutBtn = document.getElementById('navLogoutBtn');
52
+
53
+ // Mobile menu elements
54
+ this.mobileSignInBtn = document.getElementById('mobileSignInBtn');
55
+ this.mobileUserInfo = document.getElementById('mobileUserInfo');
56
+ this.mobileUserName = document.getElementById('mobileUserName');
57
+ this.mobileUserStats = document.getElementById('mobileUserStats');
58
+ this.mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
59
+
60
+ // Music upload elements
61
+ this.musicUrl = document.getElementById('musicUrl');
62
+ this.musicVolume = document.getElementById('musicVolume');
63
+ this.enableEditingToggle = document.getElementById('enableEditingToggle');
64
+
65
+ // Music Source Tab elements
66
+ this.musicTabUrl = document.getElementById('musicTabUrl');
67
+ this.musicTabFile = document.getElementById('musicTabFile');
68
+ this.musicUrlSection = document.getElementById('musicUrlSection');
69
+ this.musicFileSection = document.getElementById('musicFileSection');
70
+ this.musicFileInput = document.getElementById('musicFileInput');
71
+ this.musicFileName = document.getElementById('musicFileName');
72
+ }
73
+
74
+ attachEventListeners() {
75
+ this.authBtn.addEventListener('click', () => this.handleAuthentication());
76
+ this.uploadBtn.addEventListener('click', () => this.handleUpload());
77
+ this.retryBtn.addEventListener('click', () => this.resetForm());
78
+ this.uploadAnotherBtn.addEventListener('click', () => this.resetForm());
79
+ this.navLogoutBtn.addEventListener('click', () => this.handleLogout());
80
+ this.reelUrl.addEventListener('keypress', (e) => {
81
+ if (e.key === 'Enter') this.handleUpload();
82
+ });
83
+
84
+ // Navbar sign in button
85
+ if (this.navSignInBtn) {
86
+ this.navSignInBtn.addEventListener('click', () => this.handleAuthentication());
87
+ }
88
+
89
+ // Mobile sign in and logout
90
+ if (this.mobileSignInBtn) {
91
+ this.mobileSignInBtn.addEventListener('click', () => {
92
+ document.getElementById('mobileMenu').classList.remove('active');
93
+ this.handleAuthentication();
94
+ });
95
+ }
96
+ if (this.mobileLogoutBtn) {
97
+ this.mobileLogoutBtn.addEventListener('click', () => {
98
+ document.getElementById('mobileMenu').classList.remove('active');
99
+ this.handleLogout();
100
+ });
101
+ }
102
+
103
+ // Music Source Tab Switching
104
+ if (this.musicTabUrl && this.musicTabFile) {
105
+ this.musicTabUrl.addEventListener('click', () => {
106
+ this.currentMusicSource = 'url';
107
+ this.musicTabUrl.style.opacity = '1';
108
+ this.musicTabFile.style.opacity = '0.6';
109
+ this.musicUrlSection.style.display = 'block';
110
+ this.musicFileSection.style.display = 'none';
111
+ this.uploadedMusicFile = null;
112
+ });
113
+
114
+ this.musicTabFile.addEventListener('click', () => {
115
+ this.currentMusicSource = 'file';
116
+ this.musicTabFile.style.opacity = '1';
117
+ this.musicTabUrl.style.opacity = '0.6';
118
+ this.musicFileSection.style.display = 'block';
119
+ this.musicUrlSection.style.display = 'none';
120
+ document.getElementById('musicUrl').value = '';
121
+ });
122
+ }
123
+
124
+ // Handle music file selection
125
+ if (this.musicFileInput) {
126
+ this.musicFileInput.addEventListener('change', async (e) => {
127
+ const file = e.target.files[0];
128
+ if (!file) return;
129
+
130
+ // Validate file type
131
+ const validTypes = ['audio/mpeg', 'audio/wav', 'audio/mp4', 'audio/x-m4a', 'audio/aac'];
132
+ if (!validTypes.includes(file.type) && !file.name.match(/\.(mp3|wav|m4a|aac)$/i)) {
133
+ this.showToast('Please select a valid audio file (MP3, WAV, M4A, AAC)', 'error');
134
+ return;
135
+ }
136
+
137
+ // Check file size (max 50MB)
138
+ if (file.size > 50 * 1024 * 1024) {
139
+ this.showToast('File size too large. Maximum 50MB allowed.', 'error');
140
+ return;
141
+ }
142
+
143
+ // Update UI
144
+ this.musicFileName.value = file.name;
145
+ this.showToast('Audio file selected. It will be uploaded when you start the upload process.', 'success');
146
+
147
+ // Store file for later upload
148
+ this.uploadedMusicFile = file;
149
+ });
150
+ }
151
+ }
152
+
153
+ async checkAuthentication() {
154
+ try {
155
+ const response = await fetch('/check-auth');
156
+ const data = await response.json();
157
+
158
+ if (data.authenticated) {
159
+ this.isAuthenticated = true;
160
+ if (data.channel) {
161
+ this.displayChannelInfo(data.channel);
162
+ }
163
+ this.showUploadSection();
164
+ } else {
165
+ this.showAuthSection();
166
+ }
167
+ } catch (error) {
168
+ console.error('Auth check failed:', error);
169
+ this.showAuthSection();
170
+ }
171
+ }
172
+
173
+ displayChannelInfo(channel) {
174
+ this.channelAvatar.src = channel.thumbnail || 'https://via.placeholder.com/80';
175
+ this.channelName.textContent = channel.title;
176
+ this.channelStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers • ${this.formatNumber(channel.videoCount)} videos`;
177
+ this.channelInfo.style.display = 'block';
178
+
179
+ // Update navbar user menu
180
+ this.navUserAvatarImg.src = channel.thumbnail || 'https://via.placeholder.com/40';
181
+ this.navUserName.textContent = channel.title;
182
+ this.navUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`;
183
+ this.navUserMenu.style.display = 'block';
184
+ this.navAuthButtons.style.display = 'none';
185
+
186
+ // Update mobile menu
187
+ this.mobileUserName.textContent = channel.title;
188
+ this.mobileUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`;
189
+ this.mobileUserInfo.style.display = 'block';
190
+ this.mobileLogoutBtn.style.display = 'block';
191
+ this.mobileSignInBtn.style.display = 'none';
192
+ }
193
+
194
+ async handleAuthentication() {
195
+ this.showLoading();
196
+
197
+ try {
198
+ const response = await fetch('/auth/start');
199
+ const data = await response.json();
200
+
201
+ if (data.auth_url) {
202
+ const authWindow = window.open(data.auth_url, 'YouTube Authentication', 'width=600,height=700');
203
+
204
+ const checkAuth = setInterval(async () => {
205
+ if (authWindow.closed) {
206
+ clearInterval(checkAuth);
207
+ this.hideLoading();
208
+ await this.checkAuthentication();
209
+ }
210
+ }, 1000);
211
+ }
212
+ } catch (error) {
213
+ this.showToast('Authentication failed: ' + error.message, 'error');
214
+ } finally {
215
+ this.hideLoading();
216
+ }
217
+ }
218
+
219
+ async handleLogout() {
220
+ if (!confirm('Are you sure you want to logout?')) return;
221
+
222
+ try {
223
+ const response = await fetch('/logout', { method: 'POST' });
224
+ const data = await response.json();
225
+
226
+ if (data.success) {
227
+ this.showToast('Logged out successfully', 'success');
228
+ this.isAuthenticated = false;
229
+ this.showAuthSection();
230
+ }
231
+ } catch (error) {
232
+ this.showToast('Logout failed: ' + error.message, 'error');
233
+ }
234
+ }
235
+
236
+ async handleUpload() {
237
+ if (!this.isAuthenticated) {
238
+ this.showToast('Please sign in with YouTube first', 'error');
239
+ return;
240
+ }
241
+
242
+ const url = this.reelUrl.value.trim();
243
+
244
+ if (!url) {
245
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
246
+ return;
247
+ }
248
+
249
+ // Get video editing preferences
250
+ const enableEditing = document.getElementById('enableEditingToggle').checked;
251
+ let editingOptions = null;
252
+
253
+ if (enableEditing) {
254
+ let musicSource = null;
255
+
256
+ // ✅ NEW: Handle local music file upload
257
+ const currentMusicSource = this.musicTabUrl.style.opacity === '1' ? 'url' : 'file';
258
+
259
+ if (currentMusicSource === 'file' && this.uploadedMusicFile) {
260
+ this.showLoading();
261
+ try {
262
+ // Upload music file to server
263
+ const formData = new FormData();
264
+ formData.append('music', this.uploadedMusicFile);
265
+
266
+ const uploadResponse = await fetch('/upload-music', {
267
+ method: 'POST',
268
+ body: formData
269
+ });
270
+
271
+ const uploadResult = await uploadResponse.json();
272
+
273
+ if (!uploadResult.success) {
274
+ throw new Error(uploadResult.error || 'Music upload failed');
275
+ }
276
+
277
+ musicSource = uploadResult.filepath;
278
+ this.showToast('Music file uploaded successfully', 'success');
279
+
280
+ } catch (error) {
281
+ this.hideLoading();
282
+ this.showToast('Failed to upload music: ' + error.message, 'error');
283
+ return;
284
+ } finally {
285
+ this.hideLoading();
286
+ }
287
+ } else if (currentMusicSource === 'url') {
288
+ musicSource = document.getElementById('musicUrl').value.trim();
289
+ }
290
+
291
+ const musicVolume = parseInt(document.getElementById('musicVolume').value) / 100;
292
+
293
+ // Get all text overlays
294
+ const textOverlays = [];
295
+ document.querySelectorAll('.text-overlay-item').forEach(item => {
296
+ const text = item.querySelector('.overlay-text').value.trim();
297
+ const position = item.querySelector('.overlay-position').value;
298
+ const duration = parseInt(item.querySelector('.overlay-duration').value);
299
+
300
+ if (text) {
301
+ textOverlays.push({ text, position, duration });
302
+ }
303
+ });
304
+
305
+ editingOptions = {
306
+ enabled: true,
307
+ music_url: currentMusicSource === 'url' ? musicSource : null,
308
+ music_file: currentMusicSource === 'file' ? musicSource : null,
309
+ music_volume: musicVolume,
310
+ text_overlays: textOverlays.length > 0 ? textOverlays : null
311
+ };
312
+ }
313
+
314
+ this.hideResults();
315
+ this.showProgress();
316
+
317
+ try {
318
+ const response = await fetch('/auto-upload-async', {
319
+ method: 'POST',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: JSON.stringify({
322
+ url: url,
323
+ editing: editingOptions
324
+ })
325
+ });
326
+
327
+ const data = await response.json();
328
+
329
+ if (data.success) {
330
+ this.currentTaskId = data.task_id;
331
+ this.startStatusPolling();
332
+ } else {
333
+ throw new Error(data.error || 'Upload failed');
334
+ }
335
+ } catch (error) {
336
+ this.showError(error.message);
337
+ }
338
+ }
339
+
340
+ startStatusPolling() {
341
+ this.statusCheckInterval = setInterval(async () => {
342
+ await this.checkTaskStatus();
343
+ }, 2000);
344
+ }
345
+
346
+ async checkTaskStatus() {
347
+ if (!this.currentTaskId) return;
348
+
349
+ try {
350
+ const response = await fetch(`/task-status/${this.currentTaskId}`);
351
+ const data = await response.json();
352
+
353
+ if (data.success) {
354
+ const task = data.task;
355
+ this.updateProgress(task);
356
+
357
+ if (task.status === 'completed') {
358
+ this.stopStatusPolling();
359
+ this.showSuccess(task);
360
+ } else if (task.status === 'failed') {
361
+ this.stopStatusPolling();
362
+ this.showError(task.error || 'Upload failed');
363
+ }
364
+ }
365
+ } catch (error) {
366
+ console.error('Status check failed:', error);
367
+ }
368
+ }
369
+
370
+ stopStatusPolling() {
371
+ if (this.statusCheckInterval) {
372
+ clearInterval(this.statusCheckInterval);
373
+ this.statusCheckInterval = null;
374
+ }
375
+ }
376
+
377
+ updateProgress(task) {
378
+ this.progressTitle.textContent = this.getStatusTitle(task.status);
379
+ this.progressPercent.textContent = `${task.progress}%`;
380
+ this.progressFill.style.width = `${task.progress}%`;
381
+ this.progressMessage.textContent = task.message;
382
+
383
+ if (task.metadata && task.status === 'uploading') {
384
+ this.displayMetadata(task.metadata);
385
+ }
386
+ }
387
+
388
+ getStatusTitle(status) {
389
+ const titles = {
390
+ 'started': 'Starting...',
391
+ 'downloading': 'Downloading Reel',
392
+ 'editing': 'Editing Video', // ✅ NEW: Add editing status
393
+ 'generating_metadata': 'AI Analyzing Video',
394
+ 'uploading': 'Uploading to YouTube',
395
+ 'completed': 'Upload Complete',
396
+ 'failed': 'Upload Failed'
397
+ };
398
+ return titles[status] || 'Processing...';
399
+ }
400
+
401
+ displayMetadata(metadata) {
402
+ this.previewTitle.textContent = metadata.title || '-';
403
+ this.previewDescription.textContent = metadata.description || '-';
404
+
405
+ this.previewTags.innerHTML = '';
406
+ if (metadata.tags && metadata.tags.length > 0) {
407
+ metadata.tags.slice(0, 15).forEach(tag => {
408
+ const span = document.createElement('span');
409
+ span.textContent = tag;
410
+ this.previewTags.appendChild(span);
411
+ });
412
+ }
413
+
414
+ this.previewHashtags.innerHTML = '';
415
+ if (metadata.hashtags && metadata.hashtags.length > 0) {
416
+ metadata.hashtags.slice(0, 20).forEach(hashtag => {
417
+ const span = document.createElement('span');
418
+ span.textContent = hashtag;
419
+ this.previewHashtags.appendChild(span);
420
+ });
421
+ }
422
+
423
+ this.metadataPreview.style.display = 'block';
424
+ }
425
+
426
+ showProgress() {
427
+ this.progressSection.style.display = 'block';
428
+ this.metadataPreview.style.display = 'none';
429
+ this.successResult.style.display = 'none';
430
+ this.errorResult.style.display = 'none';
431
+ }
432
+
433
+ showSuccess(task) {
434
+ this.progressSection.style.display = 'none';
435
+ this.successResult.style.display = 'block';
436
+
437
+ if (task.youtube_url) {
438
+ this.watchBtn.href = task.youtube_url;
439
+ }
440
+
441
+ this.showToast('Video uploaded successfully! 🎉', 'success');
442
+ }
443
+
444
+ showError(message) {
445
+ this.progressSection.style.display = 'none';
446
+ this.errorResult.style.display = 'block';
447
+ this.errorMessage.textContent = message;
448
+ this.showToast('Upload failed: ' + message, 'error');
449
+ }
450
+
451
+ hideResults() {
452
+ this.successResult.style.display = 'none';
453
+ this.errorResult.style.display = 'none';
454
+ this.metadataPreview.style.display = 'none';
455
+ }
456
+
457
+ resetForm() {
458
+ this.reelUrl.value = '';
459
+ this.hideResults();
460
+ this.progressSection.style.display = 'none';
461
+ this.currentTaskId = null;
462
+ this.stopStatusPolling();
463
+ }
464
+
465
+ showUploadSection() {
466
+ this.authSection.style.display = 'none';
467
+ this.uploadSection.style.display = 'block';
468
+ }
469
+
470
+ showAuthSection() {
471
+ this.authSection.style.display = 'block';
472
+ this.uploadSection.style.display = 'none';
473
+ this.channelInfo.style.display = 'none';
474
+
475
+ // Update navbar
476
+ this.navUserMenu.style.display = 'none';
477
+ this.navAuthButtons.style.display = 'flex';
478
+
479
+ // Update mobile menu
480
+ this.mobileUserInfo.style.display = 'none';
481
+ this.mobileLogoutBtn.style.display = 'none';
482
+ this.mobileSignInBtn.style.display = 'block';
483
+ }
484
+
485
+ showLoading() {
486
+ this.loadingOverlay.style.display = 'flex';
487
+ }
488
+
489
+ hideLoading() {
490
+ this.loadingOverlay.style.display = 'none';
491
+ }
492
+
493
+ showToast(message, type = 'info') {
494
+ this.toast.textContent = message;
495
+ this.toast.className = 'toast show';
496
+
497
+ if (type === 'success') {
498
+ this.toast.style.borderLeft = '4px solid var(--success)';
499
+ } else if (type === 'error') {
500
+ this.toast.style.borderLeft = '4px solid var(--error)';
501
+ }
502
+
503
+ setTimeout(() => {
504
+ this.toast.classList.remove('show');
505
+ }, 4000);
506
+ }
507
+
508
+ formatNumber(num) {
509
+ if (num >= 1000000) {
510
+ return (num / 1000000).toFixed(1) + 'M';
511
+ } else if (num >= 1000) {
512
+ return (num / 1000).toFixed(1) + 'K';
513
+ }
514
+ return num;
515
+ }
516
+ }
517
+
518
+ document.addEventListener('DOMContentLoaded', () => {
519
+ new YouTubeUploader();
520
+ });
521
+
522
+ // Helper functions
523
+ function showLoadingOverlay(message) {
524
+ const overlay = document.getElementById('loadingOverlay');
525
+ if (overlay) {
526
+ overlay.querySelector('p').textContent = message;
527
+ overlay.style.display = 'flex';
528
+ }
529
+ }
530
+
531
+ function hideLoadingOverlay() {
532
+ const overlay = document.getElementById('loadingOverlay');
533
+ if (overlay) {
534
+ overlay.style.display = 'none';
535
+ }
536
+ }
537
+
538
+ function showToast(message, type = 'info') {
539
+ const toast = document.getElementById('toast');
540
+ if (toast) {
541
+ toast.textContent = message;
542
+ toast.classList.add('show');
543
+ setTimeout(() => toast.classList.remove('show'), 3000);
544
+ }
545
+ }
546
+
547
+ // Modified upload function to handle music file
548
+ async function startUpload() {
549
+ const reelUrl = document.getElementById('reelUrl').value.trim();
550
+
551
+ if (!reelUrl) {
552
+ showToast('Please enter an Instagram Reel URL', 'error');
553
+ return;
554
+ }
555
+
556
+ // Check if editing is enabled
557
+ const editingEnabled = document.getElementById('enableEditingToggle').checked;
558
+ let editingOptions = null;
559
+
560
+ if (editingEnabled) {
561
+ let musicPath = null;
562
+
563
+ // Handle music upload if local file is selected
564
+ if (currentMusicSource === 'file' && uploadedMusicFile) {
565
+ showLoadingOverlay('Uploading background music...');
566
+
567
+ try {
568
+ const formData = new FormData();
569
+ formData.append('music', uploadedMusicFile);
570
+
571
+ const uploadResponse = await fetch('/upload-music', {
572
+ method: 'POST',
573
+ body: formData
574
+ });
575
+
576
+ const uploadResult = await uploadResponse.json();
577
+
578
+ if (!uploadResult.success) {
579
+ throw new Error(uploadResult.error || 'Music upload failed');
580
+ }
581
+
582
+ musicPath = uploadResult.filepath;
583
+ hideLoadingOverlay();
584
+
585
+ } catch (error) {
586
+ hideLoadingOverlay();
587
+ showToast('Failed to upload music file: ' + error.message, 'error');
588
+ return;
589
+ }
590
+ } else if (currentMusicSource === 'url') {
591
+ musicPath = document.getElementById('musicUrl').value.trim();
592
+ }
593
+
594
+ // Collect text overlays
595
+ const textOverlays = [];
596
+ document.querySelectorAll('.text-overlay-item').forEach(item => {
597
+ const text = item.querySelector('.overlay-text').value.trim();
598
+ if (text) {
599
+ textOverlays.push({
600
+ text: text,
601
+ position: item.querySelector('.overlay-position').value,
602
+ duration: parseInt(item.querySelector('.overlay-duration').value) || 5
603
+ });
604
+ }
605
+ });
606
+
607
+ editingOptions = {
608
+ enabled: true,
609
+ music_url: currentMusicSource === 'url' ? musicPath : null,
610
+ music_file: currentMusicSource === 'file' ? musicPath : null,
611
+ music_volume: parseInt(document.getElementById('musicVolume').value) / 100,
612
+ text_overlays: textOverlays
613
+ };
614
+ }
615
+
616
+ // Start async upload
617
+ try {
618
+ showLoadingOverlay('Starting upload process...');
619
+
620
+ const response = await fetch('/auto-upload-async', {
621
+ method: 'POST',
622
+ headers: { 'Content-Type': 'application/json' },
623
+ body: JSON.stringify({
624
+ url: reelUrl,
625
+ editing: editingOptions
626
+ })
627
+ });
628
+
629
+ const result = await response.json();
630
+ hideLoadingOverlay();
631
+
632
+ if (result.success) {
633
+ // Start polling for status
634
+ pollTaskStatus(result.task_id);
635
+ } else {
636
+ showToast(result.error || 'Failed to start upload', 'error');
637
+ }
638
+
639
+ } catch (error) {
640
+ hideLoadingOverlay();
641
+ showToast('Error: ' + error.message, 'error');
642
+ }
643
+ }