432 lines
15 KiB
JavaScript
432 lines
15 KiB
JavaScript
class YouTubeUploader {
|
|
constructor() {
|
|
this.CLIENT_ID = '728787049181-iq4lnrcks0fifee7r6h57h7h71berii6.apps.googleusercontent.com';
|
|
this.SCOPES = ['https://www.googleapis.com/auth/youtube.upload'];
|
|
this.initializeUploadButton();
|
|
}
|
|
|
|
initializeUploadButton() {
|
|
const uploadButton = document.getElementById('upload-youtube');
|
|
console.log('Upload button found:', !!uploadButton);
|
|
|
|
if (uploadButton) {
|
|
uploadButton.addEventListener('click', async (e) => {
|
|
console.log('Upload button clicked');
|
|
e.preventDefault();
|
|
try {
|
|
await this.prepareAndUploadVideo();
|
|
} catch (error) {
|
|
console.error('Complete upload error:', error);
|
|
this.showError(`Upload failed: ${error.message}`);
|
|
}
|
|
});
|
|
} else {
|
|
console.error('YouTube upload button not found in DOM');
|
|
}
|
|
}
|
|
|
|
async prepareAndUploadVideo() {
|
|
try {
|
|
// Detailed logging for video retrieval
|
|
const videoData = await this.getRecordedVideoFromStorage();
|
|
console.log('Video data retrieved:', !!videoData);
|
|
|
|
if (!videoData) {
|
|
this.showError('No video available to upload');
|
|
return;
|
|
}
|
|
|
|
// Convert base64 to blob with detailed logging
|
|
const videoBlob = this.base64ToBlob(videoData);
|
|
console.log('Video blob created, size:', videoBlob.size);
|
|
|
|
// Get authentication token
|
|
const token = await this.getAuthToken();
|
|
console.log('Authentication token obtained');
|
|
|
|
// Prepare metadata for the video
|
|
const metadata = {
|
|
snippet: {
|
|
title: `Screen Recording ${new Date().toLocaleString()}`,
|
|
description: 'Screen recording uploaded from Chrome Extension',
|
|
tags: ['screen recording'],
|
|
categoryId: '22' // Category for 'People & Blogs'
|
|
},
|
|
status: {
|
|
privacyStatus: 'private'
|
|
}
|
|
};
|
|
|
|
// Perform the upload
|
|
const uploadResult = await this.uploadVideo(token, videoBlob, metadata);
|
|
console.log('Upload result:', uploadResult);
|
|
|
|
this.showSuccess('Video uploaded to YouTube successfully!');
|
|
} catch (error) {
|
|
console.error('Complete YouTube Upload Error:', error);
|
|
this.showError(`Upload failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
getRecordedVideoFromStorage() {
|
|
return new Promise((resolve, reject) => {
|
|
chrome.storage.local.get(['recordedVideoData'], (result) => {
|
|
console.log('Storage retrieval:', result.recordedVideoData ? 'Video found' : 'No video');
|
|
if (chrome.runtime.lastError) {
|
|
reject(chrome.runtime.lastError);
|
|
} else {
|
|
resolve(result.recordedVideoData);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
getAuthToken() {
|
|
return new Promise((resolve, reject) => {
|
|
console.log('Attempting to get auth token');
|
|
// Fallback authentication method
|
|
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({
|
|
client_id: this.CLIENT_ID,
|
|
redirect_uri: chrome.identity.getRedirectURL(),
|
|
response_type: 'token',
|
|
scope: this.SCOPES.join(' '),
|
|
prompt: 'consent'
|
|
})}`;
|
|
|
|
chrome.identity.launchWebAuthFlow(
|
|
{ url: authUrl, interactive: true },
|
|
(redirectUrl) => {
|
|
console.log('Auth flow redirect received');
|
|
if (chrome.runtime.lastError) {
|
|
console.error('Auth flow error:', chrome.runtime.lastError);
|
|
reject(chrome.runtime.lastError);
|
|
return;
|
|
}
|
|
|
|
const urlParams = new URLSearchParams(new URL(redirectUrl).hash.slice(1));
|
|
const accessToken = urlParams.get('access_token');
|
|
|
|
if (!accessToken) {
|
|
console.error('No access token retrieved');
|
|
reject(new Error('Failed to retrieve access token'));
|
|
return;
|
|
}
|
|
|
|
console.log('Access token successfully retrieved');
|
|
resolve(accessToken);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
base64ToBlob(base64Data) {
|
|
// Remove the data URL prefix if it exists
|
|
const base64String = base64Data.replace(/^data:video\/\w+;base64,/, '');
|
|
|
|
// Decode base64
|
|
const byteCharacters = atob(base64String);
|
|
const byteArrays = [];
|
|
|
|
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
|
const slice = byteCharacters.slice(offset, offset + 512);
|
|
const byteNumbers = new Array(slice.length);
|
|
|
|
for (let i = 0; i < slice.length; i++) {
|
|
byteNumbers[i] = slice.charCodeAt(i);
|
|
}
|
|
|
|
const byteArray = new Uint8Array(byteNumbers);
|
|
byteArrays.push(byteArray);
|
|
}
|
|
|
|
return new Blob(byteArrays, { type: 'video/webm' });
|
|
}
|
|
|
|
async uploadVideo(token, videoBlob, metadata) {
|
|
const formData = new FormData();
|
|
|
|
const metadataBlob = new Blob([JSON.stringify(metadata)], {
|
|
type: 'application/json; charset=UTF-8'
|
|
});
|
|
formData.append('metadata', metadataBlob, 'metadata.json');
|
|
formData.append('file', videoBlob, 'screen_recording.webm');
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`https://www.googleapis.com/upload/youtube/v3/videos?uploadType=multipart&part=snippet,status`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: formData
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
console.error('Upload response error:', errorBody);
|
|
throw new Error(`Upload failed: ${errorBody}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('YouTube Upload Success:', result);
|
|
return result;
|
|
} catch (error) {
|
|
console.error('Upload Error Details:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
showSuccess(message) {
|
|
const notification = document.createElement('div');
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background-color: green;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
z-index: 1000;
|
|
`;
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
|
|
showError(message) {
|
|
const notification = document.createElement('div');
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background-color: red;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
z-index: 1000;
|
|
`;
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
}
|
|
|
|
// Initialize the uploader when the page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('DOM loaded, initializing YouTube Uploader');
|
|
new YouTubeUploader();
|
|
});
|
|
|
|
// Add global error logging
|
|
window.addEventListener('error', (event) => {
|
|
console.error('Unhandled error:', event.error);
|
|
});
|
|
|
|
|
|
class VimeoUploader {
|
|
constructor() {
|
|
// Replace with your Vimeo API access token
|
|
this.ACCESS_TOKEN = 'fad52305c371058da84097cefb9a95a3';
|
|
this.initializeUploadButton();
|
|
}
|
|
|
|
initializeUploadButton() {
|
|
const uploadButton = document.getElementById('upload-vimeo');
|
|
console.log('Vimeo upload button found:', !!uploadButton);
|
|
|
|
if (uploadButton) {
|
|
uploadButton.addEventListener('click', async (e) => {
|
|
console.log('Vimeo upload button clicked');
|
|
e.preventDefault();
|
|
try {
|
|
await this.prepareAndUploadVideo();
|
|
} catch (error) {
|
|
console.error('Vimeo upload error:', error);
|
|
this.showError(`Upload failed: ${error.message}`);
|
|
}
|
|
});
|
|
} else {
|
|
console.error('Vimeo upload button not found');
|
|
}
|
|
}
|
|
|
|
async prepareAndUploadVideo() {
|
|
try {
|
|
// Retrieve video from storage
|
|
const videoData = await this.getRecordedVideoFromStorage();
|
|
console.log('Video data retrieved:', !!videoData);
|
|
|
|
if (!videoData) {
|
|
this.showError('No video available to upload');
|
|
return;
|
|
}
|
|
|
|
// Convert base64 to blob
|
|
const videoBlob = this.base64ToBlob(videoData);
|
|
console.log('Video blob created, size:', videoBlob.size);
|
|
|
|
// Initiate upload and get upload link
|
|
const uploadTicket = await this.createUploadTicket(videoBlob.size);
|
|
|
|
// Upload the video
|
|
await this.uploadVideoToVimeo(uploadTicket.upload_link, videoBlob);
|
|
|
|
this.showSuccess('Video uploaded to Vimeo successfully!');
|
|
} catch (error) {
|
|
console.error('Vimeo Upload Error:', error);
|
|
this.showError(`Upload failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async createUploadTicket(fileSize) {
|
|
try {
|
|
const response = await fetch('https://api.vimeo.com/me/videos', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `bearer ${this.ACCESS_TOKEN}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/vnd.vimeo.*+json;version=3.4'
|
|
},
|
|
body: JSON.stringify({
|
|
upload: {
|
|
approach: 'tus',
|
|
size: fileSize
|
|
},
|
|
name: `Screen Recording ${new Date().toLocaleString()}`,
|
|
description: 'Screen recording uploaded from Chrome Extension'
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('Vimeo API Response:', errorText);
|
|
throw new Error(`Failed to create upload ticket: ${errorText}`);
|
|
}
|
|
|
|
const uploadTicket = await response.json();
|
|
console.log('Complete Upload Ticket:', uploadTicket);
|
|
|
|
// Explicitly log the upload link
|
|
const uploadLink = uploadTicket.upload?.upload_link;
|
|
console.log('Extracted Upload Link:', uploadLink);
|
|
|
|
if (!uploadLink) {
|
|
throw new Error('No upload link found in Vimeo response');
|
|
}
|
|
|
|
return uploadTicket;
|
|
} catch (error) {
|
|
console.error('Upload Ticket Creation Error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async uploadVideoToVimeo(uploadTicket, videoBlob) {
|
|
const uploadLink = uploadTicket.upload
|
|
? uploadTicket.upload.upload_link
|
|
: uploadTicket.upload_link || uploadTicket.uri;
|
|
|
|
console.log('Actual Upload Link:', uploadLink);
|
|
|
|
if (!uploadLink) {
|
|
throw new Error('No upload link found');
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(uploadLink, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/offset+octet-stream',
|
|
'Upload-Offset': '0',
|
|
'Tus-Resumable': '1.0.0',
|
|
'Authorization': `bearer ${this.ACCESS_TOKEN}`
|
|
},
|
|
body: videoBlob
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`Vimeo upload failed: ${errorText}`);
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Upload detailed error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
getRecordedVideoFromStorage() {
|
|
return new Promise((resolve, reject) => {
|
|
chrome.storage.local.get(['recordedVideoData'], (result) => {
|
|
console.log('Storage retrieval:', result.recordedVideoData ? 'Video found' : 'No video');
|
|
if (chrome.runtime.lastError) {
|
|
reject(chrome.runtime.lastError);
|
|
} else {
|
|
resolve(result.recordedVideoData);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
base64ToBlob(base64Data) {
|
|
const base64String = base64Data.replace(/^data:video\/\w+;base64,/, '');
|
|
const byteCharacters = atob(base64String);
|
|
const byteArrays = [];
|
|
|
|
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
|
const slice = byteCharacters.slice(offset, offset + 512);
|
|
const byteNumbers = new Array(slice.length);
|
|
|
|
for (let i = 0; i < slice.length; i++) {
|
|
byteNumbers[i] = slice.charCodeAt(i);
|
|
}
|
|
|
|
const byteArray = new Uint8Array(byteNumbers);
|
|
byteArrays.push(byteArray);
|
|
}
|
|
|
|
return new Blob(byteArrays, { type: 'video/webm' });
|
|
}
|
|
|
|
showSuccess(message) {
|
|
const notification = document.createElement('div');
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background-color: green;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
z-index: 1000;
|
|
`;
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
|
|
showError(message) {
|
|
const notification = document.createElement('div');
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background-color: red;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
z-index: 1000;
|
|
`;
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
}
|
|
|
|
// Initialize the uploader when the page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('DOM loaded, initializing Vimeo Uploader');
|
|
new VimeoUploader();
|
|
}); |