diff --git a/src/MediaManager.html b/src/MediaManager.html index 3d9af71..cd11c51 100644 --- a/src/MediaManager.html +++ b/src/MediaManager.html @@ -1364,7 +1364,7 @@ }) .withFailureHandler(e => ui.logStatus('error', `Ticket failed: ${e.message}`, 'error')) - .getUploadUrl(state.sku, filename, mimeType); + .getUploadUrl(state.sku, filename, mimeType, fetchUrl); }) .withFailureHandler(e => { ui.logStatus('error', `Cannot transfer ${filename}: ${e.message}`, 'error'); diff --git a/src/mediaHandlers.ts b/src/mediaHandlers.ts index ca7c34d..b015337 100644 --- a/src/mediaHandlers.ts +++ b/src/mediaHandlers.ts @@ -128,7 +128,7 @@ export function linkDriveFileToShopifyMedia(sku: string, driveId: string, shopif } // NEW: Resumable Upload Ticket -export function getUploadUrl(sku: string, filename: string, mimeType: string) { +export function getUploadUrl(sku: string, filename: string, mimeType: string, sourceUrl?: string) { const config = new Config() const driveService = new GASDriveService() @@ -136,7 +136,7 @@ export function getUploadUrl(sku: string, filename: string, mimeType: string) { const folder = driveService.getOrCreateFolder(sku, config.productPhotosFolderId) // Generate Ticket - return driveService.getResumableUploadUrl(filename, mimeType, folder.getId()) + return driveService.getResumableUploadUrl(filename, mimeType, folder.getId(), sourceUrl) } // Deprecated (but kept for fallback/legacy small files if needed) diff --git a/src/services/GASDriveService.ts b/src/services/GASDriveService.ts index cf93081..303b5b9 100644 --- a/src/services/GASDriveService.ts +++ b/src/services/GASDriveService.ts @@ -100,16 +100,59 @@ export class GASDriveService implements IDriveService { } } - getResumableUploadUrl(filename: string, mimeType: string, folderId: string): string { + getResumableUploadUrl(filename: string, mimeType: string, folderId: string, sourceUrl?: string): string { const token = ScriptApp.getOAuthToken(); // Metadata for the file to be created - const metadata = { + const metadata: any = { name: filename, mimeType: mimeType, parents: [folderId] }; + // feature: video-thumbnails-for-processing + // If this is a video from Google Photos, fetch a thumbnail and set as contentHint. + if (sourceUrl && sourceUrl.includes('googleusercontent.com') && mimeType.startsWith('video/')) { + try { + console.log(`[GASDriveService] Fetching thumbnail for ${filename}...`); + // =w800-h600 gives a decent sized jpeg thumbnail + // Use same auth token as we use for the video fetch + let thumbUrl = sourceUrl; + if (!thumbUrl.includes('=')) { + thumbUrl += '=w320-h320'; + } else { + thumbUrl = thumbUrl.replace(/=.*$/, '') + '=w320-h320'; + } + + const thumbResp = UrlFetchApp.fetch(thumbUrl, { + headers: { Authorization: `Bearer ${token}` }, + muteHttpExceptions: true + }); + + if (thumbResp.getResponseCode() === 200) { + const thumbBlob = thumbResp.getBlob(); + const base64Thumb = Utilities.base64EncodeWebSafe(thumbBlob.getBytes()); + + // Drive API Limit check (2MB) + if (base64Thumb.length < 2 * 1024 * 1024) { + metadata.contentHints = { + thumbnail: { + image: base64Thumb, + mimeType: 'image/jpeg' + } + }; + console.log(`[GASDriveService] Custom thumbnail injected (${base64Thumb.length} chars).`); + } else { + console.warn(`[GASDriveService] Thumbnail too large (${base64Thumb.length} chars). Skipping injection.`); + } + } else { + console.warn(`[GASDriveService] Thumbnail fetch failed: ${thumbResp.getResponseCode()}`); + } + } catch (e) { + console.warn(`[GASDriveService] Thumbnail generation failed: ${e.message}`); + } + } + const params = { method: 'post' as const, contentType: 'application/json',